// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
//
// ----------------------------------------------------------------------------
//
// Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// - Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// - Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// - Neither the name of Oracle nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package org.infinity.util.io.zip;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AccessMode;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemAlreadyExistsException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.ProviderMismatchException;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.spi.FileSystemProvider;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* FileSystemProvider implementation for DLC archives in zip format.
*/
public class DlcFileSystemProvider extends FileSystemProvider
{
/** Returns the URI scheme that identifies this provider. */
public static final String SCHEME = "dlc";
private final Map<Path, DlcFileSystem> filesystems = new HashMap<>();
public DlcFileSystemProvider() {}
@Override
public String getScheme()
{
return SCHEME;
}
@Override
public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException
{
return newFileSystem(uriToPath(uri), env);
}
@Override
public FileSystem newFileSystem(Path path, Map<String, ?> env) throws IOException
{
if (path.getFileSystem() != FileSystems.getDefault()) {
throw new UnsupportedOperationException();
}
synchronized (filesystems) {
Path realPath = null;
if (ensureFile(path)) {
// XXX: Using LinkOption.NOFOLLOW_LINKS to prevent issues with Windows junctions
realPath = path.toRealPath(LinkOption.NOFOLLOW_LINKS);
if (filesystems.containsKey(realPath)) {
throw new FileSystemAlreadyExistsException();
}
}
DlcFileSystem dlcfs = null;
try {
dlcfs = new DlcFileSystem(this, path, env);
} catch (DlcError de) {
String pname = path.toString();
if(pname.endsWith(".zip") || pname.endsWith(".mod")) {
throw de;
}
// assume NOT a zip file
throw new UnsupportedOperationException();
}
filesystems.put(realPath, dlcfs);
return dlcfs;
}
}
@Override
public FileSystem getFileSystem(URI uri)
{
synchronized (filesystems) {
DlcFileSystem dlcfs = null;
try {
// XXX: Using LinkOption.NOFOLLOW_LINKS to prevent issues with Windows junctions
dlcfs = filesystems.get(uriToPath(uri).toRealPath(LinkOption.NOFOLLOW_LINKS));
} catch (IOException ioe) {
// ignore the ioe from toRealPath(), return FSNFE
}
if (dlcfs == null) {
throw new FileSystemNotFoundException();
}
return dlcfs;
}
}
@Override
public Path getPath(URI uri)
{
String spec = uri.getSchemeSpecificPart();
int sep = spec.indexOf("!/");
if (sep == -1) {
throw new IllegalArgumentException("URI: " + uri +
" does not contain path info ex. dlc:file:/c:/foo.zip!/BAR");
}
return getFileSystem(uri).getPath(spec.substring(sep + 1));
}
@Override
public InputStream newInputStream(Path path, OpenOption... options) throws IOException
{
return toDlcPath(path).newInputStream(options);
}
@Override
public OutputStream newOutputStream(Path path, OpenOption... options) throws IOException
{
return toDlcPath(path).newOutputStream(options);
}
@Override
public FileChannel newFileChannel(Path path, Set<? extends OpenOption> options,
FileAttribute<?>... attrs) throws IOException
{
return toDlcPath(path).newFileChannel(options, attrs);
}
@Override
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options,
FileAttribute<?>... attrs) throws IOException
{
return toDlcPath(path).newByteChannel(options, attrs);
}
@Override
public DirectoryStream<Path> newDirectoryStream(Path dir, Filter<? super Path> filter)
throws IOException
{
return toDlcPath(dir).newDirectoryStream(filter);
}
@Override
public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException
{
toDlcPath(dir).createDirectory(attrs);
}
@Override
public void delete(Path path) throws IOException
{
toDlcPath(path).delete();
}
@Override
public void copy(Path source, Path target, CopyOption... options) throws IOException
{
toDlcPath(source).copy(toDlcPath(target), options);
}
@Override
public void move(Path source, Path target, CopyOption... options) throws IOException
{
toDlcPath(source).move(toDlcPath(target), options);
}
@Override
public boolean isSameFile(Path path, Path path2) throws IOException
{
return toDlcPath(path).isSameFile(path2);
}
@Override
public boolean isHidden(Path path) throws IOException
{
return toDlcPath(path).isHidden();
}
@Override
public FileStore getFileStore(Path path) throws IOException
{
return toDlcPath(path).getFileStore();
}
@Override
public void checkAccess(Path path, AccessMode... modes) throws IOException
{
toDlcPath(path).checkAccess(modes);
}
@Override
public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type,
LinkOption... options)
{
return DlcFileAttributeView.get(toDlcPath(path), type);
}
@SuppressWarnings("unchecked")
@Override
public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type,
LinkOption... options) throws IOException
{
if (type == BasicFileAttributes.class || type == DlcFileAttributes.class) {
return (A)toDlcPath(path).getAttributes();
}
return null;
}
@Override
public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options)
throws IOException
{
return toDlcPath(path).readAttributes(attributes, options);
}
@Override
public void setAttribute(Path path, String attribute, Object value, LinkOption... options)
throws IOException
{
toDlcPath(path).setAttribute(attribute, value, options);
}
protected Path uriToPath(URI uri)
{
String scheme = uri.getScheme();
if ((scheme == null) || !scheme.equalsIgnoreCase(getScheme())) {
throw new IllegalArgumentException("URI scheme is not '" + getScheme() + "'");
}
try {
// only support legacy JAR URL syntax dlc:{uri}!/{entry} for now
String spec = uri.getRawSchemeSpecificPart();
int sep = spec.indexOf("!/");
if (sep != -1) {
spec = spec.substring(0, sep);
}
return Paths.get(new URI(spec)).toAbsolutePath();
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
private boolean ensureFile(Path path)
{
try {
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
if (!attrs.isRegularFile()) {
throw new UnsupportedOperationException();
}
return true;
} catch (IOException ioe) {
return false;
}
}
// Checks that the given file is a UnixPath
static final DlcPath toDlcPath(Path path)
{
if (path == null) {
throw new NullPointerException();
}
if (!(path instanceof DlcPath)) {
throw new ProviderMismatchException();
}
return (DlcPath) path;
}
//////////////////////////////////////////////////////////////
void removeFileSystem(Path dfpath, DlcFileSystem dfs) throws IOException
{
synchronized (filesystems) {
dfpath = dfpath.toRealPath();
if (filesystems.get(dfpath) == dfs) {
filesystems.remove(dfpath);
}
}
}
}