Поставщик файловой системы Zip Java: ReadOnly на удаленном диске [Windows]
У меня проблема с провайдером файловой системы Zip: если zip-файл находится на удаленном диске (отображается или нет, кажется, неактуальным), виртуальная файловая система доступна только для чтения, хотя сам файл - нет. Я написал минимальный пример кода:
public static void main(String[] args) throws IOException {
File workingDir = new File(args[0]);
File source = new File(workingDir, "in.zip");
File target = new File(workingDir, "out.zip");
Files.copy(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
try (FileSystem zipfs = FileSystems.newFileSystem(target.toPath(), null)) {
Path pathInZipfile = zipfs.getPath("test.xml");
System.out.println("zipfile writable: " + target.canWrite());
System.out.println("zipFS writable: " + !zipfs.isReadOnly());
Files.delete(pathInZipfile);
System.out.println("File successfully deleted");
} catch (IOException e) {
e.printStackTrace();
}
}
Если workingDir - локальный каталог, все работает нормально. Однако, если это (сопоставленный) удаленный диск, я получаю:
zipfile writable: true
zipFS writable: false
Exception in thread "main" java.nio.file.ReadOnlyFileSystemException
at com.sun.nio.zipfs.ZipFileSystem.checkWritable(ZipFileSystem.java:155)
at com.sun.nio.zipfs.ZipFileSystem.deleteFile(ZipFileSystem.java:1335)
at com.sun.nio.zipfs.ZipPath.delete(ZipPath.java:655)
at com.sun.nio.zipfs.ZipFileSystemProvider.delete(ZipFileSystemProvider.java:206)
at java.nio.file.Files.delete(Unknown Source)
at zipfs.ZipFS.main(ZipFS.java:23)
Я делаю что-то неправильно? Это невозможно? Есть ли обходной путь?
3 ответа
Я столкнулся с тем же и посмотрел код JDK.
Выводы
В ZipFileSystem.java есть три соответствующие строки:
zfpath.getFileSystem().provider().checkAccess(zfpath, AccessMode.READ);
if (!Files.isWritable(zfpath))
this.readOnly = true;
zfPath - это объект Path. Что-то в поставщике файловой системы Windows блокирует доступ на запись к пути zip-архива. Не похоже, что с этим многое можно сделать.
Временное решение
То, что я использовал в качестве обходного пути, было:
- Создайте zip-архив во временной папке
- Заполните zip-архив
- Скопируйте временный файл в исходное расположение подключенного диска
Пока подключенный диск доступен для записи в контекстах вне zip-файловой системы, этот метод работает.
Мы столкнулись с точно такой же проблемой и придумали несколько варварское решение.
FileSystems.newFileSystem(Path)
принимает интерфейс, поэтому его можно удовлетворить, обернув реальный
Path
пример.
Вот решение, которое реализует требуемый
FileSysteProvider.checkAccess()
:
private static FileSystem createZip(Path zipPath)
throws Exception
{
var fileSystem = zipPath.getFileSystem();
var provider = fileSystem.provider();
return FileSystems.newFileSystem(
new Path()
{
private Path path(Path path)
{
return this == path ? zipPath : path;
}
@Override
public FileSystem getFileSystem()
{
return new FileSystem()
{
public Set<String> supportedFileAttributeViews()
{
return fileSystem.supportedFileAttributeViews();
}
@Override
public FileSystemProvider provider()
{
return new FileSystemProvider()
{
@Override
public void setAttribute(
Path path,
String attribute,
Object value,
LinkOption... options)
throws IOException
{
provider.setAttribute(path(path), attribute, value, options);
}
@Override
public Map<String, Object> readAttributes(
Path path,
String attributes,
LinkOption... options)
throws IOException
{
return provider.
readAttributes(path(path), attributes, options);
}
@Override
public <A extends BasicFileAttributes> A readAttributes(
Path path,
Class<A> type,
LinkOption... options)
throws IOException
{
return provider.readAttributes(path(path), type, options);
}
@Override
public FileSystem newFileSystem(URI uri, Map<String, ?> env)
throws IOException
{
return provider.newFileSystem(uri, env);
}
@Override
public DirectoryStream<Path> newDirectoryStream(
Path dir,
Filter<? super Path> filter)
throws IOException
{
return provider.newDirectoryStream(dir, filter);
}
@Override
public SeekableByteChannel newByteChannel(
Path path,
Set<? extends OpenOption> options,
FileAttribute<?>... attrs)
throws IOException
{
return provider.newByteChannel(path(path), options, attrs);
}
@Override
public void move(
Path source,
Path target,
CopyOption... options)
throws IOException
{
provider.move(path(source), path(target), options);
}
@Override
public boolean isSameFile(Path path, Path path2)
throws IOException
{
return provider.isSameFile(path(path), path(path2));
}
@Override
public boolean isHidden(Path path)
throws IOException
{
return provider.isHidden(path(path));
}
@Override
public String getScheme()
{
return provider.getScheme();
}
@Override
public Path getPath(URI uri)
{
return provider.getPath(uri);
}
@Override
public FileSystem getFileSystem(URI uri)
{
return provider.getFileSystem(uri);
}
@Override
public FileStore getFileStore(Path path)
throws IOException
{
return provider.getFileStore(path(path));
}
@Override
public <V extends FileAttributeView> V getFileAttributeView(
Path path,
Class<V> type,
LinkOption... options)
{
return provider.
getFileAttributeView(path(path), type, options);
}
@Override
public void delete(Path path)
throws IOException
{
provider.delete(path(path));
}
@Override
public void createDirectory(Path dir, FileAttribute<?>... attrs)
throws IOException
{
provider.createDirectory(path(dir), attrs);
}
@Override
public void copy(
Path source,
Path target,
CopyOption... options)
throws IOException
{
provider.copy(path(source), path(target), options);
}
@Override
public void checkAccess(Path path, AccessMode... modes)
throws IOException
{
if ((modes != null) &&
(modes.length == 1) &&
(modes[0] == AccessMode.WRITE))
{
return;
}
provider.checkAccess(path(path), modes);
}
};
}
@Override
public WatchService newWatchService()
throws IOException
{
return fileSystem.newWatchService();
}
@Override
public boolean isReadOnly()
{
return false;
}
@Override
public boolean isOpen()
{
return fileSystem.isOpen();
}
@Override
public UserPrincipalLookupService getUserPrincipalLookupService()
{
return fileSystem.getUserPrincipalLookupService();
}
@Override
public String getSeparator()
{
return fileSystem.getSeparator();
}
@Override
public Iterable<Path> getRootDirectories()
{
return fileSystem.getRootDirectories();
}
@Override
public PathMatcher getPathMatcher(String syntaxAndPattern)
{
return fileSystem.getPathMatcher(syntaxAndPattern);
}
@Override
public Path getPath(String first, String... more)
{
return fileSystem.getPath(first, more);
}
@Override
public Iterable<FileStore> getFileStores()
{
return fileSystem.getFileStores();
}
@Override
public void close() throws IOException
{
fileSystem.close();
}
};
}
@Override
public boolean isAbsolute()
{
return zipPath.isAbsolute();
}
@Override
public Path getRoot()
{
return zipPath.getRoot();
}
@Override
public Path getFileName()
{
return zipPath.getFileName();
}
@Override
public Path getParent()
{
return zipPath.getParent();
}
@Override
public int getNameCount()
{
return zipPath.getNameCount();
}
@Override
public Path getName(int index)
{
return zipPath.getName(index);
}
@Override
public Path subpath(int beginIndex, int endIndex)
{
return zipPath.subpath(beginIndex, endIndex);
}
@Override
public boolean startsWith(Path other)
{
return zipPath.startsWith(other);
}
@Override
public boolean endsWith(Path other)
{
return zipPath.endsWith(other);
}
@Override
public Path normalize()
{
return zipPath.normalize();
}
@Override
public Path resolve(Path other)
{
return zipPath.relativize(other);
}
@Override
public Path relativize(Path other)
{
return zipPath.relativize(other);
}
@Override
public URI toUri()
{
return zipPath.toUri();
}
@Override
public Path toAbsolutePath()
{
return zipPath.toAbsolutePath();
}
@Override
public Path toRealPath(LinkOption... options)
throws IOException
{
return zipPath.toRealPath(options);
}
@Override
public WatchKey register(
WatchService watcher,
Kind<?>[] events,
Modifier... modifiers)
throws IOException
{
return zipPath.register(watcher, events, modifiers);
}
@Override
public int compareTo(Path other)
{
return zipPath.compareTo(other);
}
},
Map.of("create", "true"));
}
Это больше похоже на взлом, но мы думаем, что он решает ошибку в оригинале.
ZipFileSystem
Попробуйте установить приватное поле readOnly
в ZipFileSystem
в false
с отражением.