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 extends OpenOption> 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 extends OpenOption> 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