| /* |
| * Copyright 2013 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.google.common.jimfs; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; |
| import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; |
| import static java.nio.file.StandardOpenOption.CREATE; |
| import static java.nio.file.StandardOpenOption.CREATE_NEW; |
| import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; |
| import static java.nio.file.StandardOpenOption.WRITE; |
| |
| import com.google.common.base.Supplier; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSortedSet; |
| import com.google.common.collect.Lists; |
| import java.io.IOException; |
| import java.nio.file.CopyOption; |
| import java.nio.file.DirectoryNotEmptyException; |
| import java.nio.file.DirectoryStream; |
| import java.nio.file.FileAlreadyExistsException; |
| import java.nio.file.FileSystemException; |
| import java.nio.file.LinkOption; |
| import java.nio.file.NoSuchFileException; |
| import java.nio.file.OpenOption; |
| import java.nio.file.Path; |
| import java.nio.file.SecureDirectoryStream; |
| import java.nio.file.attribute.BasicFileAttributes; |
| import java.nio.file.attribute.FileAttribute; |
| import java.nio.file.attribute.FileAttributeView; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.locks.Lock; |
| import java.util.concurrent.locks.ReadWriteLock; |
| import org.checkerframework.checker.nullness.compatqual.NullableDecl; |
| |
| /** |
| * View of a file system with a specific working directory. As all file system operations need to |
| * work when given either relative or absolute paths, this class contains the implementation of most |
| * file system operations, with relative path operations resolving against the working directory. |
| * |
| * <p>A file system has one default view using the file system's working directory. Additional views |
| * may be created for use in {@link SecureDirectoryStream} instances, which each have a different |
| * working directory they use. |
| * |
| * @author Colin Decker |
| */ |
| final class FileSystemView { |
| |
| private final JimfsFileStore store; |
| |
| private final Directory workingDirectory; |
| private final JimfsPath workingDirectoryPath; |
| |
| /** Creates a new file system view. */ |
| public FileSystemView( |
| JimfsFileStore store, Directory workingDirectory, JimfsPath workingDirectoryPath) { |
| this.store = checkNotNull(store); |
| this.workingDirectory = checkNotNull(workingDirectory); |
| this.workingDirectoryPath = checkNotNull(workingDirectoryPath); |
| } |
| |
| /** Returns whether or not this view and the given view belong to the same file system. */ |
| private boolean isSameFileSystem(FileSystemView other) { |
| return store == other.store; |
| } |
| |
| /** Returns the file system state. */ |
| public FileSystemState state() { |
| return store.state(); |
| } |
| |
| /** |
| * Returns the path of the working directory at the time this view was created. Does not reflect |
| * changes to the path caused by the directory being moved. |
| */ |
| public JimfsPath getWorkingDirectoryPath() { |
| return workingDirectoryPath; |
| } |
| |
| /** Attempt to look up the file at the given path. */ |
| DirectoryEntry lookUpWithLock(JimfsPath path, Set<? super LinkOption> options) |
| throws IOException { |
| store.readLock().lock(); |
| try { |
| return lookUp(path, options); |
| } finally { |
| store.readLock().unlock(); |
| } |
| } |
| |
| /** Looks up the file at the given path without locking. */ |
| private DirectoryEntry lookUp(JimfsPath path, Set<? super LinkOption> options) |
| throws IOException { |
| return store.lookUp(workingDirectory, path, options); |
| } |
| |
| /** |
| * Creates a new directory stream for the directory located by the given path. The given {@code |
| * basePathForStream} is that base path that the returned stream will use. This will be the same |
| * as {@code dir} except for streams created relative to another secure stream. |
| */ |
| public DirectoryStream<Path> newDirectoryStream( |
| JimfsPath dir, |
| DirectoryStream.Filter<? super Path> filter, |
| Set<? super LinkOption> options, |
| JimfsPath basePathForStream) |
| throws IOException { |
| Directory file = (Directory) lookUpWithLock(dir, options).requireDirectory(dir).file(); |
| FileSystemView view = new FileSystemView(store, file, basePathForStream); |
| JimfsSecureDirectoryStream stream = new JimfsSecureDirectoryStream(view, filter, state()); |
| return store.supportsFeature(Feature.SECURE_DIRECTORY_STREAM) |
| ? stream |
| : new DowngradedDirectoryStream(stream); |
| } |
| |
| /** Snapshots the entries of the working directory of this view. */ |
| public ImmutableSortedSet<Name> snapshotWorkingDirectoryEntries() { |
| store.readLock().lock(); |
| try { |
| ImmutableSortedSet<Name> names = workingDirectory.snapshot(); |
| workingDirectory.updateAccessTime(); |
| return names; |
| } finally { |
| store.readLock().unlock(); |
| } |
| } |
| |
| /** |
| * Returns a snapshot mapping the names of each file in the directory at the given path to the |
| * last modified time of that file. |
| */ |
| public ImmutableMap<Name, Long> snapshotModifiedTimes(JimfsPath path) throws IOException { |
| ImmutableMap.Builder<Name, Long> modifiedTimes = ImmutableMap.builder(); |
| |
| store.readLock().lock(); |
| try { |
| Directory dir = (Directory) lookUp(path, Options.FOLLOW_LINKS).requireDirectory(path).file(); |
| // TODO(cgdecker): Investigate whether WatchServices should keep a reference to the actual |
| // directory when SecureDirectoryStream is supported rather than looking up the directory |
| // each time the WatchService polls |
| |
| for (DirectoryEntry entry : dir) { |
| if (!entry.name().equals(Name.SELF) && !entry.name().equals(Name.PARENT)) { |
| modifiedTimes.put(entry.name(), entry.file().getLastModifiedTime()); |
| } |
| } |
| |
| return modifiedTimes.build(); |
| } finally { |
| store.readLock().unlock(); |
| } |
| } |
| |
| /** |
| * Returns whether or not the two given paths locate the same file. The second path is located |
| * using the given view rather than this file view. |
| */ |
| public boolean isSameFile(JimfsPath path, FileSystemView view2, JimfsPath path2) |
| throws IOException { |
| if (!isSameFileSystem(view2)) { |
| return false; |
| } |
| |
| store.readLock().lock(); |
| try { |
| File file = lookUp(path, Options.FOLLOW_LINKS).fileOrNull(); |
| File file2 = view2.lookUp(path2, Options.FOLLOW_LINKS).fileOrNull(); |
| return file != null && Objects.equals(file, file2); |
| } finally { |
| store.readLock().unlock(); |
| } |
| } |
| |
| /** |
| * Gets the {@linkplain Path#toRealPath(LinkOption...) real path} to the file located by the given |
| * path. |
| */ |
| public JimfsPath toRealPath( |
| JimfsPath path, PathService pathService, Set<? super LinkOption> options) throws IOException { |
| checkNotNull(path); |
| checkNotNull(options); |
| |
| store.readLock().lock(); |
| try { |
| DirectoryEntry entry = lookUp(path, options).requireExists(path); |
| |
| List<Name> names = new ArrayList<>(); |
| names.add(entry.name()); |
| while (!entry.file().isRootDirectory()) { |
| entry = entry.directory().entryInParent(); |
| names.add(entry.name()); |
| } |
| |
| // names are ordered last to first in the list, so get the reverse view |
| List<Name> reversed = Lists.reverse(names); |
| Name root = reversed.remove(0); |
| return pathService.createPath(root, reversed); |
| } finally { |
| store.readLock().unlock(); |
| } |
| } |
| |
| /** |
| * Creates a new directory at the given path. The given attributes will be set on the new file if |
| * possible. |
| */ |
| public Directory createDirectory(JimfsPath path, FileAttribute<?>... attrs) throws IOException { |
| return (Directory) createFile(path, store.directoryCreator(), true, attrs); |
| } |
| |
| /** |
| * Creates a new symbolic link at the given path with the given target. The given attributes will |
| * be set on the new file if possible. |
| */ |
| public SymbolicLink createSymbolicLink( |
| JimfsPath path, JimfsPath target, FileAttribute<?>... attrs) throws IOException { |
| if (!store.supportsFeature(Feature.SYMBOLIC_LINKS)) { |
| throw new UnsupportedOperationException(); |
| } |
| return (SymbolicLink) createFile(path, store.symbolicLinkCreator(target), true, attrs); |
| } |
| |
| /** |
| * Creates a new file at the given path if possible, using the given supplier to create the file. |
| * Returns the new file. If {@code allowExisting} is {@code true} and a file already exists at the |
| * given path, returns that file. Otherwise, throws {@link FileAlreadyExistsException}. |
| */ |
| private File createFile( |
| JimfsPath path, |
| Supplier<? extends File> fileCreator, |
| boolean failIfExists, |
| FileAttribute<?>... attrs) |
| throws IOException { |
| checkNotNull(path); |
| checkNotNull(fileCreator); |
| |
| store.writeLock().lock(); |
| try { |
| DirectoryEntry entry = lookUp(path, Options.NOFOLLOW_LINKS); |
| |
| if (entry.exists()) { |
| if (failIfExists) { |
| throw new FileAlreadyExistsException(path.toString()); |
| } |
| |
| // currently can only happen if getOrCreateFile doesn't find the file with the read lock |
| // and then the file is created between when it releases the read lock and when it |
| // acquires the write lock; so, very unlikely |
| return entry.file(); |
| } |
| |
| Directory parent = entry.directory(); |
| |
| File newFile = fileCreator.get(); |
| store.setInitialAttributes(newFile, attrs); |
| parent.link(path.name(), newFile); |
| parent.updateModifiedTime(); |
| return newFile; |
| } finally { |
| store.writeLock().unlock(); |
| } |
| } |
| |
| /** |
| * Gets the regular file at the given path, creating it if it doesn't exist and the given options |
| * specify that it should be created. |
| */ |
| public RegularFile getOrCreateRegularFile( |
| JimfsPath path, Set<OpenOption> options, FileAttribute<?>... attrs) throws IOException { |
| checkNotNull(path); |
| |
| if (!options.contains(CREATE_NEW)) { |
| // assume file exists unless we're explicitly trying to create a new file |
| RegularFile file = lookUpRegularFile(path, options); |
| if (file != null) { |
| return file; |
| } |
| } |
| |
| if (options.contains(CREATE) || options.contains(CREATE_NEW)) { |
| return getOrCreateRegularFileWithWriteLock(path, options, attrs); |
| } else { |
| throw new NoSuchFileException(path.toString()); |
| } |
| } |
| |
| /** |
| * Looks up the regular file at the given path, throwing an exception if the file isn't a regular |
| * file. Returns null if the file did not exist. |
| */ |
| @NullableDecl |
| private RegularFile lookUpRegularFile(JimfsPath path, Set<OpenOption> options) |
| throws IOException { |
| store.readLock().lock(); |
| try { |
| DirectoryEntry entry = lookUp(path, options); |
| if (entry.exists()) { |
| File file = entry.file(); |
| if (!file.isRegularFile()) { |
| throw new FileSystemException(path.toString(), null, "not a regular file"); |
| } |
| return open((RegularFile) file, options); |
| } else { |
| return null; |
| } |
| } finally { |
| store.readLock().unlock(); |
| } |
| } |
| |
| /** Gets or creates a new regular file with a write lock (assuming the file does not exist). */ |
| private RegularFile getOrCreateRegularFileWithWriteLock( |
| JimfsPath path, Set<OpenOption> options, FileAttribute<?>[] attrs) throws IOException { |
| store.writeLock().lock(); |
| try { |
| File file = createFile(path, store.regularFileCreator(), options.contains(CREATE_NEW), attrs); |
| // the file already existed but was not a regular file |
| if (!file.isRegularFile()) { |
| throw new FileSystemException(path.toString(), null, "not a regular file"); |
| } |
| return open((RegularFile) file, options); |
| } finally { |
| store.writeLock().unlock(); |
| } |
| } |
| |
| /** |
| * Opens the given regular file with the given options, truncating it if necessary and |
| * incrementing its open count. Returns the given file. |
| */ |
| private static RegularFile open(RegularFile file, Set<OpenOption> options) { |
| if (options.contains(TRUNCATE_EXISTING) && options.contains(WRITE)) { |
| file.writeLock().lock(); |
| try { |
| file.truncate(0); |
| } finally { |
| file.writeLock().unlock(); |
| } |
| } |
| |
| // must be opened while holding a file store lock to ensure no race between opening and |
| // deleting the file |
| file.opened(); |
| |
| return file; |
| } |
| |
| /** Returns the target of the symbolic link at the given path. */ |
| public JimfsPath readSymbolicLink(JimfsPath path) throws IOException { |
| if (!store.supportsFeature(Feature.SYMBOLIC_LINKS)) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| SymbolicLink symbolicLink = |
| (SymbolicLink) |
| lookUpWithLock(path, Options.NOFOLLOW_LINKS).requireSymbolicLink(path).file(); |
| |
| return symbolicLink.target(); |
| } |
| |
| /** |
| * Checks access to the file at the given path for the given modes. Since access controls are not |
| * implemented for this file system, this just checks that the file exists. |
| */ |
| public void checkAccess(JimfsPath path) throws IOException { |
| // just check that the file exists |
| lookUpWithLock(path, Options.FOLLOW_LINKS).requireExists(path); |
| } |
| |
| /** |
| * Creates a hard link at the given link path to the regular file at the given path. The existing |
| * file must exist and must be a regular file. The given file system view must belong to the same |
| * file system as this view. |
| */ |
| public void link(JimfsPath link, FileSystemView existingView, JimfsPath existing) |
| throws IOException { |
| checkNotNull(link); |
| checkNotNull(existingView); |
| checkNotNull(existing); |
| |
| if (!store.supportsFeature(Feature.LINKS)) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| if (!isSameFileSystem(existingView)) { |
| throw new FileSystemException( |
| link.toString(), |
| existing.toString(), |
| "can't link: source and target are in different file system instances"); |
| } |
| |
| Name linkName = link.name(); |
| |
| // existingView is in the same file system, so just one lock is needed |
| store.writeLock().lock(); |
| try { |
| // we do want to follow links when finding the existing file |
| File existingFile = |
| existingView.lookUp(existing, Options.FOLLOW_LINKS).requireExists(existing).file(); |
| if (!existingFile.isRegularFile()) { |
| throw new FileSystemException( |
| link.toString(), existing.toString(), "can't link: not a regular file"); |
| } |
| |
| Directory linkParent = |
| lookUp(link, Options.NOFOLLOW_LINKS).requireDoesNotExist(link).directory(); |
| |
| linkParent.link(linkName, existingFile); |
| linkParent.updateModifiedTime(); |
| } finally { |
| store.writeLock().unlock(); |
| } |
| } |
| |
| /** Deletes the file at the given absolute path. */ |
| public void deleteFile(JimfsPath path, DeleteMode deleteMode) throws IOException { |
| store.writeLock().lock(); |
| try { |
| DirectoryEntry entry = lookUp(path, Options.NOFOLLOW_LINKS).requireExists(path); |
| delete(entry, deleteMode, path); |
| } finally { |
| store.writeLock().unlock(); |
| } |
| } |
| |
| /** Deletes the given directory entry from its parent directory. */ |
| private void delete(DirectoryEntry entry, DeleteMode deleteMode, JimfsPath pathForException) |
| throws IOException { |
| Directory parent = entry.directory(); |
| File file = entry.file(); |
| |
| checkDeletable(file, deleteMode, pathForException); |
| parent.unlink(entry.name()); |
| parent.updateModifiedTime(); |
| |
| file.deleted(); |
| } |
| |
| /** Mode for deleting. Determines what types of files can be deleted. */ |
| public enum DeleteMode { |
| /** Delete any file. */ |
| ANY, |
| /** Only delete non-directory files. */ |
| NON_DIRECTORY_ONLY, |
| /** Only delete directory files. */ |
| DIRECTORY_ONLY |
| } |
| |
| /** Checks that the given file can be deleted, throwing an exception if it can't. */ |
| private void checkDeletable(File file, DeleteMode mode, Path path) throws IOException { |
| if (file.isRootDirectory()) { |
| throw new FileSystemException(path.toString(), null, "can't delete root directory"); |
| } |
| |
| if (file.isDirectory()) { |
| if (mode == DeleteMode.NON_DIRECTORY_ONLY) { |
| throw new FileSystemException(path.toString(), null, "can't delete: is a directory"); |
| } |
| |
| checkEmpty(((Directory) file), path); |
| } else if (mode == DeleteMode.DIRECTORY_ONLY) { |
| throw new FileSystemException(path.toString(), null, "can't delete: is not a directory"); |
| } |
| |
| if (file == workingDirectory && !path.isAbsolute()) { |
| // this is weird, but on Unix at least, the file system seems to be happy to delete the |
| // working directory if you give the absolute path to it but fail if you use a relative path |
| // that resolves to the working directory (e.g. "" or ".") |
| throw new FileSystemException(path.toString(), null, "invalid argument"); |
| } |
| } |
| |
| /** Checks that given directory is empty, throwing {@link DirectoryNotEmptyException} if not. */ |
| private void checkEmpty(Directory dir, Path pathForException) throws FileSystemException { |
| if (!dir.isEmpty()) { |
| throw new DirectoryNotEmptyException(pathForException.toString()); |
| } |
| } |
| |
| /** Copies or moves the file at the given source path to the given dest path. */ |
| public void copy( |
| JimfsPath source, |
| FileSystemView destView, |
| JimfsPath dest, |
| Set<CopyOption> options, |
| boolean move) |
| throws IOException { |
| checkNotNull(source); |
| checkNotNull(destView); |
| checkNotNull(dest); |
| checkNotNull(options); |
| |
| boolean sameFileSystem = isSameFileSystem(destView); |
| |
| File sourceFile; |
| File copyFile = null; // non-null after block completes iff source file was copied |
| lockBoth(store.writeLock(), destView.store.writeLock()); |
| try { |
| DirectoryEntry sourceEntry = lookUp(source, options).requireExists(source); |
| DirectoryEntry destEntry = destView.lookUp(dest, Options.NOFOLLOW_LINKS); |
| |
| Directory sourceParent = sourceEntry.directory(); |
| sourceFile = sourceEntry.file(); |
| |
| Directory destParent = destEntry.directory(); |
| |
| if (move && sourceFile.isDirectory()) { |
| if (sameFileSystem) { |
| checkMovable(sourceFile, source); |
| checkNotAncestor(sourceFile, destParent, destView); |
| } else { |
| // move to another file system is accomplished by copy-then-delete, so the source file |
| // must be deletable to be moved |
| checkDeletable(sourceFile, DeleteMode.ANY, source); |
| } |
| } |
| |
| if (destEntry.exists()) { |
| if (destEntry.file().equals(sourceFile)) { |
| return; |
| } else if (options.contains(REPLACE_EXISTING)) { |
| destView.delete(destEntry, DeleteMode.ANY, dest); |
| } else { |
| throw new FileAlreadyExistsException(dest.toString()); |
| } |
| } |
| |
| if (move && sameFileSystem) { |
| // Real move on the same file system. |
| sourceParent.unlink(source.name()); |
| sourceParent.updateModifiedTime(); |
| |
| destParent.link(dest.name(), sourceFile); |
| destParent.updateModifiedTime(); |
| } else { |
| // Doing a copy OR a move to a different file system, which must be implemented by copy and |
| // delete. |
| |
| // By default, don't copy attributes. |
| AttributeCopyOption attributeCopyOption = AttributeCopyOption.NONE; |
| if (move) { |
| // Copy only the basic attributes of the file to the other file system, as it may not |
| // support all the attribute views that this file system does. This also matches the |
| // behavior of moving a file to a foreign file system with a different |
| // FileSystemProvider. |
| attributeCopyOption = AttributeCopyOption.BASIC; |
| } else if (options.contains(COPY_ATTRIBUTES)) { |
| // As with move, if we're copying the file to a different file system, only copy its |
| // basic attributes. |
| attributeCopyOption = |
| sameFileSystem ? AttributeCopyOption.ALL : AttributeCopyOption.BASIC; |
| } |
| |
| // Copy the file, but don't copy its content while we're holding the file store locks. |
| copyFile = destView.store.copyWithoutContent(sourceFile, attributeCopyOption); |
| destParent.link(dest.name(), copyFile); |
| destParent.updateModifiedTime(); |
| |
| // In order for the copy to be atomic (not strictly necessary, but seems preferable since |
| // we can) lock both source and copy files before leaving the file store locks. This |
| // ensures that users cannot observe the copy's content until the content has been copied. |
| // This also marks the source file as opened, preventing its content from being deleted |
| // until after it's copied if the source file itself is deleted in the next step. |
| lockSourceAndCopy(sourceFile, copyFile); |
| |
| if (move) { |
| // It should not be possible for delete to throw an exception here, because we already |
| // checked that the file was deletable above. |
| delete(sourceEntry, DeleteMode.ANY, source); |
| } |
| } |
| } finally { |
| destView.store.writeLock().unlock(); |
| store.writeLock().unlock(); |
| } |
| |
| if (copyFile != null) { |
| // Copy the content. This is done outside the above block to minimize the time spent holding |
| // file store locks, since copying the content of a regular file could take a (relatively) |
| // long time. If done inside the above block, copying using Files.copy can be slower than |
| // copying with an InputStream and an OutputStream if many files are being copied on |
| // different threads. |
| try { |
| sourceFile.copyContentTo(copyFile); |
| } finally { |
| // Unlock the files, allowing the content of the copy to be observed by the user. This also |
| // closes the source file, allowing its content to be deleted if it was deleted. |
| unlockSourceAndCopy(sourceFile, copyFile); |
| } |
| } |
| } |
| |
| private void checkMovable(File file, JimfsPath path) throws FileSystemException { |
| if (file.isRootDirectory()) { |
| throw new FileSystemException(path.toString(), null, "can't move root directory"); |
| } |
| } |
| |
| /** |
| * Acquires both write locks in a way that attempts to avoid the possibility of deadlock. Note |
| * that typically (when only one file system instance is involved), both locks will be the same |
| * lock and there will be no issue at all. |
| */ |
| private static void lockBoth(Lock sourceWriteLock, Lock destWriteLock) { |
| while (true) { |
| sourceWriteLock.lock(); |
| if (destWriteLock.tryLock()) { |
| return; |
| } else { |
| sourceWriteLock.unlock(); |
| } |
| |
| destWriteLock.lock(); |
| if (sourceWriteLock.tryLock()) { |
| return; |
| } else { |
| destWriteLock.unlock(); |
| } |
| } |
| } |
| |
| /** Checks that source is not an ancestor of dest, throwing an exception if it is. */ |
| private void checkNotAncestor(File source, Directory destParent, FileSystemView destView) |
| throws IOException { |
| // if dest is not in the same file system, it couldn't be in source's subdirectories |
| if (!isSameFileSystem(destView)) { |
| return; |
| } |
| |
| Directory current = destParent; |
| while (true) { |
| if (current.equals(source)) { |
| throw new IOException( |
| "invalid argument: can't move directory into a subdirectory of itself"); |
| } |
| |
| if (current.isRootDirectory()) { |
| return; |
| } else { |
| current = current.parent(); |
| } |
| } |
| } |
| |
| /** |
| * Locks source and copy files before copying content. Also marks the source file as opened so |
| * that its content won't be deleted until after the copy if it is deleted. |
| */ |
| private void lockSourceAndCopy(File sourceFile, File copyFile) { |
| sourceFile.opened(); |
| ReadWriteLock sourceLock = sourceFile.contentLock(); |
| if (sourceLock != null) { |
| sourceLock.readLock().lock(); |
| } |
| ReadWriteLock copyLock = copyFile.contentLock(); |
| if (copyLock != null) { |
| copyLock.writeLock().lock(); |
| } |
| } |
| |
| /** |
| * Unlocks source and copy files after copying content. Also closes the source file so its content |
| * can be deleted if it was deleted. |
| */ |
| private void unlockSourceAndCopy(File sourceFile, File copyFile) { |
| ReadWriteLock sourceLock = sourceFile.contentLock(); |
| if (sourceLock != null) { |
| sourceLock.readLock().unlock(); |
| } |
| ReadWriteLock copyLock = copyFile.contentLock(); |
| if (copyLock != null) { |
| copyLock.writeLock().unlock(); |
| } |
| sourceFile.closed(); |
| } |
| |
| /** Returns a file attribute view using the given lookup callback. */ |
| @NullableDecl |
| public <V extends FileAttributeView> V getFileAttributeView(FileLookup lookup, Class<V> type) { |
| return store.getFileAttributeView(lookup, type); |
| } |
| |
| /** Returns a file attribute view for the given path in this view. */ |
| @NullableDecl |
| public <V extends FileAttributeView> V getFileAttributeView( |
| final JimfsPath path, Class<V> type, final Set<? super LinkOption> options) { |
| return store.getFileAttributeView( |
| new FileLookup() { |
| @Override |
| public File lookup() throws IOException { |
| return lookUpWithLock(path, options).requireExists(path).file(); |
| } |
| }, |
| type); |
| } |
| |
| /** Reads attributes of the file located by the given path in this view as an object. */ |
| public <A extends BasicFileAttributes> A readAttributes( |
| JimfsPath path, Class<A> type, Set<? super LinkOption> options) throws IOException { |
| File file = lookUpWithLock(path, options).requireExists(path).file(); |
| return store.readAttributes(file, type); |
| } |
| |
| /** Reads attributes of the file located by the given path in this view as a map. */ |
| public ImmutableMap<String, Object> readAttributes( |
| JimfsPath path, String attributes, Set<? super LinkOption> options) throws IOException { |
| File file = lookUpWithLock(path, options).requireExists(path).file(); |
| return store.readAttributes(file, attributes); |
| } |
| |
| /** |
| * Sets the given attribute to the given value on the file located by the given path in this view. |
| */ |
| public void setAttribute( |
| JimfsPath path, String attribute, Object value, Set<? super LinkOption> options) |
| throws IOException { |
| File file = lookUpWithLock(path, options).requireExists(path).file(); |
| store.setAttribute(file, attribute, value); |
| } |
| } |