
de.schlichtherle.truezip.nio.file.TFileSystem Maven / Gradle / Ivy
Show all versions of truezip-path Show documentation
/*
* Copyright (C) 2005-2015 Schlichtherle IT Services.
* All rights reserved. Use is subject to license terms.
*/
package de.schlichtherle.truezip.nio.file;
import de.schlichtherle.truezip.entry.Entry;
import de.schlichtherle.truezip.entry.Entry.Access;
import static de.schlichtherle.truezip.entry.Entry.Access.*;
import static de.schlichtherle.truezip.entry.Entry.Size.DATA;
import static de.schlichtherle.truezip.entry.Entry.Type.*;
import static de.schlichtherle.truezip.entry.Entry.UNKNOWN;
import static de.schlichtherle.truezip.entry.EntryName.SEPARATOR;
import de.schlichtherle.truezip.file.TConfig;
import de.schlichtherle.truezip.file.TVFS;
import static de.schlichtherle.truezip.fs.FsOutputOption.EXCLUSIVE;
import static de.schlichtherle.truezip.fs.FsSyncOptions.UMOUNT;
import de.schlichtherle.truezip.fs.*;
import de.schlichtherle.truezip.socket.InputSocket;
import de.schlichtherle.truezip.socket.OutputSocket;
import de.schlichtherle.truezip.util.BitField;
import de.schlichtherle.truezip.util.FilteringIterator;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.util.*;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import javax.annotation.concurrent.ThreadSafe;
/**
* A {@link FileSystem} implementation based on the TrueZIP Kernel module.
*
* @since TrueZIP 7.2
* @author Christian Schlichtherle
*/
@ThreadSafe
public final class TFileSystem extends FileSystem {
private final FsController> controller;
private final TFileSystemProvider provider;
@SuppressWarnings("deprecation")
TFileSystem(final TPath path) {
assert null != path;
this.controller = TConfig.get().getFsManager().getController(
path.getMountPoint(), path.getArchiveDetector());
this.provider = TFileSystemProvider.get(path.getName());
assert invariants();
}
private boolean invariants() {
assert null != getController();
assert null != provider();
return true;
}
/**
* Equivalent to
* {@link TConfig#isLenient TConfig.get().isLenient()}.
*/
public static boolean isLenient() {
return TConfig.get().isLenient();
}
/**
* Equivalent to
* {@link TConfig#setLenient TConfig.get().setLenient(lenient)}.
*/
public static void setLenient(boolean lenient) {
TConfig.get().setLenient(lenient);
}
private FsController> getController() {
return controller;
}
FsMountPoint getMountPoint() {
return getController().getModel().getMountPoint();
}
@Override
public TFileSystemProvider provider() {
return provider;
}
/**
* Commits all pending changes for this (federated) file system and all its
* federated child file systems to their respective parent file system,
* closes their associated target (archive) file in order to allow access
* by third parties (e.g. other processes), cleans up any temporary
* allocated resources (e.g. temporary files) and purges any cached
* data.
*
* Calling this method is equivalent to
* {@link #sync(BitField) sync(FsSyncOptions.UMOUNT)}.
*
* @throws FsSyncWarningException if only warning conditions
* apply.
* This implies that the respective parent file system has been
* synchronized with constraints, e.g. if an unclosed archive entry
* stream gets forcibly closed.
* @throws FsSyncException if any error conditions apply.
* This implies some loss of data!
* @see #sync(BitField)
*/
@Override
public void close() throws FsSyncException {
sync(UMOUNT);
}
/**
* Commits all pending changes for this (federated) file system and all its
* federated child file systems to their respective parent file system with
* respect to the given options.
*
* @param options a bit field of options for the synchronization operation.
* @throws IllegalArgumentException if the combination of synchronization
* options is illegal, e.g. if
* {@code FsSyncOption.FORCE_CLOSE_INPUT} is cleared and
* {@code FsSyncOption.FORCE_CLOSE_OUTPUT} is set or if
* {@code FsSyncOption.ABORT_CHANGES} is set.
* @throws FsSyncWarningException if only warning conditions
* apply.
* This implies that the respective parent file system has been
* synchronized with constraints, e.g. if
* {@code FsSyncOption.FORCE_CLOSE_INPUT} or
* {@code FsSyncOption.FORCE_CLOSE_OUTPUT} is set and an unclosed
* archive entry stream gets forcibly closed.
* @throws FsSyncException if any error conditions apply.
* This implies some loss of data!
*/
@SuppressWarnings("deprecation")
public void sync(BitField options) throws FsSyncException {
TVFS.sync(getMountPoint(), options);
}
/**
* Returns {@code true}.
*
* @return {@code true}.
*/
@Override
public boolean isOpen() {
return true;
}
/**
* Returns {@code false}.
*
* @return {@code false}.
*/
@Override
public boolean isReadOnly() {
return false;
}
/**
* Returns {@link File#separator}.
*
* @return {@link File#separator}.
*/
@Override
public String getSeparator() {
return File.separator;
}
@Override
public Iterable getRootDirectories() {
return Collections.singleton((Path)
new TPath(getMountPoint().toHierarchicalUri().resolve(SEPARATOR)));
}
/** @throws UnsupportedOperationException always */
@Override
public Iterable getFileStores() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Set supportedFileAttributeViews() {
return Collections.singleton("basic");
}
/**
* Constructs a new path from the given sub path strings.
*
* This method scans the {@link TPath#toString() path name} resulting
* from the segment parameters to detect prospective archive files using
* the {@link TPath#getDefaultArchiveDetector() default archive detector}.
*
* The supported path name separators are "{@link File#separator}" and
* "{@code /}".
* Any leading and trailing separators in the resulting path name get
* discarded.
*
* @param first the first sub path string.
* @param more optional sub path strings.
*/
@Override
public TPath getPath(String first, String... more) {
return new TPath(this, first, more);
}
/** @throws UnsupportedOperationException always */
@Override
public PathMatcher getPathMatcher(String syntaxAndPattern) {
throw new UnsupportedOperationException("Not supported yet.");
}
/** @throws UnsupportedOperationException always */
@Override
public UserPrincipalLookupService getUserPrincipalLookupService() {
throw new UnsupportedOperationException("Not supported yet.");
}
/** @throws UnsupportedOperationException always */
@Override
public WatchService newWatchService() throws IOException {
throw new UnsupportedOperationException("Not supported yet.");
}
SeekableByteChannel newByteChannel(
final TPath path,
final Set extends OpenOption> options,
final FileAttribute>... attrs)
throws IOException {
final FsEntryName name = path.getEntryName();
final FsController> controller = getController();
if (options.isEmpty() || options.contains(StandardOpenOption.READ)) {
final BitField
o = path.mapInput(options).set(FsInputOption.CACHE);
return controller
.getInputSocket(name, o)
.newSeekableByteChannel();
} else {
final BitField
o = path.mapOutput(options).set(FsOutputOption.CACHE);
try {
return controller
.getOutputSocket(name, o, null)
.newSeekableByteChannel();
} catch (final IOException ex) {
if (o.get(EXCLUSIVE) && null != controller.getEntry(name))
throw (IOException) new FileAlreadyExistsException(path.toString())
.initCause(ex);
throw ex;
}
}
}
InputStream newInputStream(TPath path, OpenOption... options)
throws IOException {
return getController()
.getInputSocket(
path.getEntryName(),
path.mapInput(options))
.newInputStream();
}
OutputStream newOutputStream(TPath path, OpenOption... options)
throws IOException {
return getController()
.getOutputSocket(
path.getEntryName(),
path.mapOutput(options),
null)
.newOutputStream();
}
DirectoryStream newDirectoryStream(
final TPath path,
final Filter super Path> filter)
throws IOException {
final FsEntry entry = getEntry(path);
final Set set;
if (null == entry || null == (set = entry.getMembers()))
throw new NotDirectoryException(path.toString());
@NotThreadSafe
class Adapter implements Iterator {
final Iterator it = set.iterator();
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public Path next() {
return path.resolve(it.next());
}
@Override
public void remove() {
throw new UnsupportedOperationException("Not supported yet.");
}
} // Adapter
@NotThreadSafe
class FilterIterator extends FilteringIterator {
FilterIterator() { super(new Adapter()); }
@Override
protected boolean accept(Path element) {
try {
return filter.accept(element);
} catch (IOException ex) {
throw new DirectoryIteratorException(ex);
}
}
} // FilterIterator
return new Stream(new FilterIterator());
}
@NotThreadSafe
private static final class Stream implements DirectoryStream {
final Iterator it;
boolean consumed;
Stream(final Iterator it) {
this.it = it;
}
@Override
public Iterator iterator() {
if (consumed)
throw new IllegalStateException();
consumed = true;
return it;
}
@Override
public void close() {
consumed = true;
}
} // Stream
void createDirectory(final TPath path, final FileAttribute>... attrs)
throws IOException {
if (0 < attrs.length)
throw new UnsupportedOperationException();
final FsController> controller = getController();
final FsEntryName name = path.getEntryName();
try {
controller.mknod(
name,
DIRECTORY,
path.getOutputPreferences(),
null);
} catch (IOException ex) {
if (null != controller.getEntry(name))
throw (IOException) new FileAlreadyExistsException(path.toString())
.initCause(ex);
throw ex;
}
}
void delete(TPath path) throws IOException {
getController().unlink(path.getEntryName(), path.getOutputPreferences());
}
FsEntry getEntry(TPath path) throws IOException {
return getController().getEntry(path.getEntryName());
}
InputSocket> getInputSocket( TPath path,
BitField options) {
return getController()
.getInputSocket(path.getEntryName(), options);
}
OutputSocket> getOutputSocket(TPath path,
BitField options,
@CheckForNull Entry template) {
return getController()
.getOutputSocket(path.getEntryName(), options, template);
}
void checkAccess(final TPath path, final AccessMode... modes)
throws IOException {
final FsEntryName name = path.getEntryName();
final FsController> controller = getController();
if (null == controller.getEntry(name))
throw new NoSuchFileException(path.toString());
for (final AccessMode m : modes) {
switch (m) {
case READ:
if (!controller.isReadable(name))
throw new AccessDeniedException(path.toString());
break;
case WRITE:
if (!controller.isWritable(name))
throw new AccessDeniedException(path.toString());
break;
case EXECUTE:
if (!controller.isExecutable(name))
throw new AccessDeniedException(path.toString());
break;
default:
throw new UnsupportedOperationException();
}
}
}
@Nullable
V getFileAttributeView(
TPath path,
Class type,
LinkOption... options) {
if (type.isAssignableFrom(BasicFileAttributeView.class))
return type.cast(new FsEntryAttributeView(path));
return null;
}
A readAttributes(
TPath path,
Class type,
LinkOption... options)
throws IOException {
if (type.isAssignableFrom(BasicFileAttributes.class))
return type.cast(new FsEntryAttributes(path));
throw new UnsupportedOperationException();
}
private final class FsEntryAttributeView
implements BasicFileAttributeView {
private final TPath path;
FsEntryAttributeView(final TPath path) {
this.path = path;
}
@Override
public String name() {
return "basic";
}
@Override
public BasicFileAttributes readAttributes() throws IOException {
return new FsEntryAttributes(path);
}
@Override
public void setTimes( final FileTime lastModifiedTime,
final FileTime lastAccessTime,
final FileTime createTime)
throws IOException {
final FsController> controller = getController();
final Map times = new EnumMap(
Access.class);
if (null != lastModifiedTime)
times.put(WRITE, lastModifiedTime.toMillis());
if (null != lastAccessTime)
times.put(READ, lastAccessTime.toMillis());
if (null != createTime)
times.put(CREATE, createTime.toMillis());
controller.setTime(
path.getEntryName(),
times,
path.getOutputPreferences());
}
} // FsEntryAttributeView
private final class FsEntryAttributes
implements BasicFileAttributes {
private final FsEntry entry;
FsEntryAttributes(final TPath path) throws IOException {
if (null == (entry = getController().getEntry(path.getEntryName())))
throw new NoSuchFileException(path.toString());
}
@Override
public FileTime lastModifiedTime() {
return FileTime.fromMillis(entry.getTime(WRITE));
}
@Override
public FileTime lastAccessTime() {
return FileTime.fromMillis(entry.getTime(READ));
}
@Override
public FileTime creationTime() {
return FileTime.fromMillis(entry.getTime(CREATE));
}
@Override
public boolean isRegularFile() {
return entry.isType(FILE);
}
@Override
public boolean isDirectory() {
return entry.isType(DIRECTORY);
}
@Override
public boolean isSymbolicLink() {
return entry.isType(SYMLINK);
}
@Override
public boolean isOther() {
return entry.isType(SPECIAL);
}
@Override
public long size() {
final long size = entry.getSize(DATA);
return UNKNOWN == size ? 0 : size;
}
/** @throws UnsupportedOperationException always */
@Override
public Object fileKey() {
throw new UnsupportedOperationException("Not supported yet.");
}
} // FsEntryAttributes
}