diff --git a/src/main/java/org/cryptomator/frontend/fuse/FuseNioAdapter.java b/src/main/java/org/cryptomator/frontend/fuse/FuseNioAdapter.java index f651ee8..5d727c1 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/FuseNioAdapter.java +++ b/src/main/java/org/cryptomator/frontend/fuse/FuseNioAdapter.java @@ -11,6 +11,13 @@ public interface FuseNioAdapter extends FuseOperations, AutoCloseable { */ int DEFAULT_MAX_FILENAMELENGTH = 254; // 255 is preferred, but nautilus checks for this value + 1 + /** + * The default value until an {@link OpenFile} is considered active. + * + * @see OpenFileFactory#hasActiveFiles(long) + */ + long DEFAULT_ACTIVE_THRESHOLD = Long.MAX_VALUE; + /** * Checks if the filesystem is in use (and therefore an unmount attempt should be avoided). *

diff --git a/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java b/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java index 5fc4dd9..26b0875 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java +++ b/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java @@ -10,7 +10,9 @@ import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.attribute.FileAttribute; +import java.time.Instant; import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; public class OpenFile implements Closeable { @@ -18,12 +20,13 @@ public class OpenFile implements Closeable { private final Path path; private final FileChannel channel; + private final AtomicLong lastUsed = new AtomicLong(Instant.now().toEpochMilli()); private OpenFile(Path path, FileChannel channel) { this.path = path; this.channel = channel; } - + static OpenFile create(Path path, Set options, FileAttribute... attrs) throws IOException { FileChannel ch = FileChannel.open(path, options, attrs); return new OpenFile(path, ch); @@ -39,6 +42,7 @@ static OpenFile create(Path path, Set options, FileAttribu * @throws IOException If an exception occurs during read. */ public int read(ByteBuffer buf, long num, long offset) throws IOException { + lastUsed.set(Instant.now().toEpochMilli()); long size = channel.size(); if (offset >= size) { return 0; @@ -69,6 +73,7 @@ public int read(ByteBuffer buf, long num, long offset) throws IOException { * @throws IOException If an exception occurs during write. */ public int write(ByteBuffer buf, long num, long offset) throws IOException { + lastUsed.set(Instant.now().toEpochMilli()); if (num > Integer.MAX_VALUE) { throw new IOException("Requested too many bytes"); } @@ -80,6 +85,10 @@ public int write(ByteBuffer buf, long num, long offset) throws IOException { return written; } + public Instant lastUsed() { + return Instant.ofEpochMilli(lastUsed.get()); + } + @Override public void close() throws IOException { channel.close(); diff --git a/src/main/java/org/cryptomator/frontend/fuse/OpenFileFactory.java b/src/main/java/org/cryptomator/frontend/fuse/OpenFileFactory.java index f1471cb..8b0fee6 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/OpenFileFactory.java +++ b/src/main/java/org/cryptomator/frontend/fuse/OpenFileFactory.java @@ -8,6 +8,7 @@ import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.attribute.FileAttribute; +import java.time.Instant; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -67,8 +68,18 @@ public void close(long fileHandle) throws ClosedChannelException, IOException { } } - public int getOpenFileCount(){ - return openFiles.size(); + /** + * Checks, if any active {@link OpenFile} exists. + *

+ * An OpenFile is active, if its read or write methods were called in the last threshold given seconds. + * To get atomicity guarantees this method must be externally synchronized. + * + * @param activeThreshold number of seconds defining the threshold an OpenFile is considered active + * @return {@code true} if at least one active OpenFile exists, otherwise {@code false} + */ + public boolean hasActiveFiles(long activeThreshold) { + var comparsion = Instant.now().minusSeconds(activeThreshold); + return openFiles.values().stream().anyMatch(openFile -> openFile.lastUsed().isAfter(comparsion)); } /** diff --git a/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java b/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java index a4c8064..5a6ffe9 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java +++ b/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java @@ -43,39 +43,42 @@ public sealed class ReadOnlyAdapter implements FuseNioAdapter permits ReadWriteA private final int maxFileNameLength; protected final FileStore fileStore; protected final LockManager lockManager; - protected final OpenFileFactory openFiles; protected final FileNameTranscoder fileNameTranscoder; private final ReadOnlyDirectoryHandler dirHandler; private final ReadOnlyFileHandler fileHandler; private final ReadOnlyLinkHandler linkHandler; - private final BooleanSupplier hasOpenFiles; + private final BooleanSupplier inUseCheck; - protected ReadOnlyAdapter(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, FileStore fileStore, OpenFileFactory openFiles, ReadOnlyDirectoryHandler dirHandler, ReadOnlyFileHandler fileHandler) { + protected ReadOnlyAdapter(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, FileStore fileStore, BooleanSupplier inUseCheck, ReadOnlyDirectoryHandler dirHandler, ReadOnlyFileHandler fileHandler) { this.errno = errno; this.root = root; this.maxFileNameLength = maxFileNameLength; this.fileNameTranscoder = fileNameTranscoder; this.fileStore = fileStore; this.lockManager = new LockManager(); - this.openFiles = openFiles; this.dirHandler = dirHandler; this.fileHandler = fileHandler; this.linkHandler = new ReadOnlyLinkHandler(fileNameTranscoder); - this.hasOpenFiles = () -> openFiles.getOpenFileCount() != 0; + this.inUseCheck = inUseCheck; } - public static ReadOnlyAdapter create(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder) { + public static ReadOnlyAdapter create(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, long activeOpenFileThreshold) { try { var fileStore = Files.getFileStore(root); var openFiles = new OpenFileFactory(); var dirHandler = new ReadOnlyDirectoryHandler(fileNameTranscoder); var fileHandler = new ReadOnlyFileHandler(openFiles); - return new ReadOnlyAdapter(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, openFiles, dirHandler, fileHandler); + BooleanSupplier inUseCheck = () -> openFiles.hasActiveFiles(activeOpenFileThreshold); + return new ReadOnlyAdapter(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, inUseCheck, dirHandler, fileHandler); } catch (IOException e) { throw new UncheckedIOException(e); } } + public static ReadOnlyAdapter create(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder) { + return create(errno, root, maxFileNameLength, fileNameTranscoder, DEFAULT_ACTIVE_THRESHOLD); + } + @Override public Errno errno() { return errno; @@ -305,7 +308,7 @@ public void destroy() { @Override public boolean isInUse() { try (PathLock pLock = lockManager.tryLockForWriting("/")) { - return hasOpenFiles.getAsBoolean(); + return inUseCheck.getAsBoolean(); } catch (AlreadyLockedException e) { return true; } diff --git a/src/main/java/org/cryptomator/frontend/fuse/ReadWriteAdapter.java b/src/main/java/org/cryptomator/frontend/fuse/ReadWriteAdapter.java index 810c312..c555908 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/ReadWriteAdapter.java +++ b/src/main/java/org/cryptomator/frontend/fuse/ReadWriteAdapter.java @@ -29,6 +29,7 @@ import java.nio.file.attribute.PosixFilePermissions; import java.util.EnumSet; import java.util.Set; +import java.util.function.BooleanSupplier; /** * @@ -38,23 +39,28 @@ public final class ReadWriteAdapter extends ReadOnlyAdapter { private static final Logger LOG = LoggerFactory.getLogger(ReadWriteAdapter.class); private final ReadWriteFileHandler fileHandler; - private ReadWriteAdapter(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, FileStore fileStore, OpenFileFactory openFiles, ReadWriteDirectoryHandler dirHandler, ReadWriteFileHandler fileHandler) { - super(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, openFiles, dirHandler, fileHandler); + private ReadWriteAdapter(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, FileStore fileStore, BooleanSupplier inUseCheck, ReadWriteDirectoryHandler dirHandler, ReadWriteFileHandler fileHandler) { + super(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, inUseCheck, dirHandler, fileHandler); this.fileHandler = fileHandler; } - public static ReadWriteAdapter create(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder) { + public static ReadWriteAdapter create(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, long activeOpenFileThreshold) { try { var fileStore = Files.getFileStore(root); var openFiles = new OpenFileFactory(); var dirHandler = new ReadWriteDirectoryHandler(fileNameTranscoder); var fileHandler = new ReadWriteFileHandler(openFiles); - return new ReadWriteAdapter(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, openFiles, dirHandler, fileHandler); + BooleanSupplier inUseCheck = () -> openFiles.hasActiveFiles(activeOpenFileThreshold); + return new ReadWriteAdapter(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, inUseCheck, dirHandler, fileHandler); } catch (IOException e) { throw new UncheckedIOException(e); } } + public static ReadWriteAdapter create(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder) { + return create(errno, root, maxFileNameLength, fileNameTranscoder, DEFAULT_ACTIVE_THRESHOLD); + } + @Override public Set supportedOperations() { var ops = EnumSet.copyOf(super.supportedOperations());