Skip to content

Conversation

@infeo
Copy link
Member

@infeo infeo commented Oct 10, 2025

This PR implements the Files-in-use feature.

Problem

Cryptofs encrypts each file by its own, in order to be easily used for often synced files (i.e. synced with a cloud storage provider). A common scenario nowadays is, that multiple people work on the same synced files at the same time but at different locations. This leads to conflicts, which are normally resolved by the sync algorithms (and Cryptomator. But some applications simply overwrite newer versions without asking. Also, if the files reside on a network shared location, you have direct access and hence conflicts will not be detected.

Solution

Every time a file channel is opened or an existing file is overwritten/deleted, we first check if the file is currently used by another party. "Used" means, that an encrypted helper file with the same cipher file name and file extension .c9u exists, containing an owner string and a lastUpdated timestamp. If

  • the owner is not the same as this filesystem and
  • last updated time is recent enough (see below)

the file is considered as in use. If the file is used, io operations throw a special exception (FileAlreadyInUseException). Additionally, an event is triggered.

An in-use file is created, if a file channel with WRITE option is successfully opened to a file. The owner is set to the string handed over in the CryptoFileSystemProperties. The inUseFile has the same lifetime as the OpenCryptoFile and if the latter is closed, also the inUseFile is closed (and deleted). To mitigate performance drops, the first 5 seconds the inUseFile only exists in memory and afterwards it is persisted on disk. If during the "virtual" time the file channel is closed again , no file is created.

Every 5min, all open inUseFiles are refreshed, i.e. rewritten with an updated "lastUpdated" timestamp. If the lastUpdated time is 10min ago, it is considered stale and the file can be stolen by other users.

If a file is in-use , the consumer can "unblock" this state for the next accesses over a short amount of time. If during this time a file channel is openened, any existing inUseFile will be stolen, i.e. after successful opening the file is overwritten with this cryptofilesystem owner.

The whole inUse system is deactivated if no filesystem owner is specified in the CryptoFileSystemProperties. This is done by using a StubInUseManager.

Todos

  • Health check removing all in-use files
  • Conflict resolution of in-use-files

infeo added 30 commits July 30, 2025 13:41
* first read in-use-file
* on failure create/own it
* more static methods
* renaming methods
* rename FileIsInUseException to FileAlreadyInUseException
* rename InUse:tryMarkInUse to acquire()
* add ignore flag for inUse check
* close token on failed file creation
* add logging
* only move file if file is already created
* delete file on close
* add factory method to create InvalidToken
* add instance variable owner
* define in-use-file-creation threshold as a constant
* create interface
* sublass interface by "fake" impl and real impl
* decide on cryptofilesystem proeprties what impl to use
* create interface UseToken
* subclass it with real and stub impl
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java (1)

6-13: Clarify "in use" definition in interface Javadoc.

The bullets describe what InUseManager does but don't define when a file is considered "in use by others." Per the implementation, a file is in use when: (1) an in-use file exists, (2) it's owned by a different owner than this filesystem, and (3) lastUpdated is within the staleness threshold. Rephrase the first bullet to make this explicit.

Based on past review comments.

src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (2)

108-118: Add defensive size check for Properties payload.

If the serialized Properties exceeds INUSE_CLEARTEXT_SIZE (e.g., long owner string), readers will silently truncate, causing validation failures. Add a check after line 115 to log a warning or throw if rawInfo.size() > Constants.INUSE_CLEARTEXT_SIZE.

Apply this diff:

 int writeInUseFile() throws IOException {
     try (var nonClosingWrapper = new NonClosingByteChannel(channel); //
          var encChannel = encWrapper.wrapWithEncryption(nonClosingWrapper, cryptor)) {
         var rawInfo = new ByteArrayOutputStream(Constants.INUSE_CLEARTEXT_SIZE);
         var prop = new Properties();
         prop.put(UseToken.OWNER_KEY, owner);
         prop.put(UseToken.LASTUPDATED_KEY, Instant.now().toString());
         prop.store(rawInfo, null);
+        var data = rawInfo.toByteArray();
+        if (data.length > Constants.INUSE_CLEARTEXT_SIZE) {
+            throw new IOException("In-use file payload (%d bytes) exceeds buffer size (%d bytes). Shorten owner string.".formatted(data.length, Constants.INUSE_CLEARTEXT_SIZE));
+        }
-        return encChannel.write(ByteBuffer.wrap(rawInfo.toByteArray()));
+        return encChannel.write(ByteBuffer.wrap(data));
     }
 }

Based on past review comments.


93-106: Critical: refresh() positions channel AFTER write, leaving trailing bytes.

Line 100 calls channel.position(0) AFTER writeInUseFile() completes, which doesn't prevent stale data. If the new Properties payload is shorter than the previous write, trailing bytes will remain and corrupt the file. Position and truncate the channel BEFORE writing.

Apply this diff:

 void refresh() {
     fileCreationSync.lock();
     try {
         if (closed || channel == null) {
             return;
         }
+        channel.position(0);
+        channel.truncate(0);
         writeInUseFile();
-        channel.position(0);
     } catch (IOException e) {
         LOG.debug("Failed to refresh in-use file {}.", filePath, e);
     } finally {
         fileCreationSync.unlock();
     }
 }
🧹 Nitpick comments (1)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)

132-132: Remove stale TODO comment.

The validate() method is already tested via RealInUseManagerTest (nested class IsInUseUseInfo and other test methods that call readInUseFile() or isInUse() which invoke validate()). Remove the TODO.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb283b and b65c0ea.

📒 Files selected for processing (7)
  • src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java (9 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java (7 hunks)
  • src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/inuse/RealUseTokenTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
📚 Learning: 2025-10-14T16:55:45.287Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java
  • src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java
  • src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java
🧬 Code graph analysis (4)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (3)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (39-233)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (4)
src/main/java/org/cryptomator/cryptofs/common/CacheUtils.java (1)
  • CacheUtils (8-57)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)
  • RealUseToken (36-211)
src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java (2)
src/main/java/org/cryptomator/cryptofs/inuse/FileAlreadyInUseException.java (1)
  • FileAlreadyInUseException (6-11)
src/main/java/org/cryptomator/cryptofs/EffectiveOpenOptions.java (1)
  • EffectiveOpenOptions (29-189)
src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (3)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (39-233)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)
  • RealUseToken (36-211)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyse
  • GitHub Check: Build and Test
🔇 Additional comments (3)
src/test/java/org/cryptomator/cryptofs/inuse/RealUseTokenTest.java (1)

1-257: LGTM! Comprehensive test coverage for RealUseToken.

The test suite thoroughly exercises token lifecycle, including delayed persistence, file operations, move semantics, refresh behavior, and error handling. The use of Awaitility for timing assertions and extensive mocking is appropriate for this component.

src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java (1)

68-271: LGTM! Proper integration testing for in-use feature.

The updated tests correctly verify InUseManager integration with OpenCryptoFile, covering token lifecycle scenarios, read-only bypass, token acquisition on closed state, exception handling, and path update operations.

src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (1)

1-367: LGTM! Thorough test coverage for RealInUseManager.

The test suite comprehensively exercises all RealInUseManager paths, including cache interactions, validation logic, token creation strategies, error handling, and edge cases. The nested test structure and extensive mocking appropriately test the component in isolation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
pom.xml (1)

162-167: Dependency addition is well‑justified for async testing.

Awaitility is an appropriate choice for testing the asynchronous state transitions in the in‑use feature (delayed persistence, refresh intervals, token lifecycle). The dependency is properly scoped and pinned.

For consistency with other test dependencies (junit, mockito, hamcrest, jimfs), consider defining the version as a property variable:

<awaitility.version>4.3.0</awaitility.version>

Then reference it in the dependency. This is a minor style preference and can be addressed now or deferred.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b65c0ea and 5eb9191.

📒 Files selected for processing (1)
  • pom.xml (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyse
  • GitHub Check: Build and Test

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (2)

654-668: Remove unused mock variable.

The inUsePath mock declared on line 656 is never wired into the test setup (no when() stubs) and the verification on line 667 doesn't add meaningful coverage since it's not connected to any code path.

Apply this diff:

 	@Test
 	public void testDeleteShortenedExistingFile() throws IOException {
-		var inUsePath = mock(Path.class, "in use file");
 		when(cryptoPathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.FILE);
 		when(physicalFsProv.deleteIfExists(ciphertextRawPath)).thenReturn(true);
 		doNothing().when(openCryptoFiles).delete(Mockito.any());
 		when(ciphertextPath.isShortened()).thenReturn(true);
 
 		inTest.delete(cleartextPath);
 
 		verify(readonlyFlag).assertWritable();
 		verify(openCryptoFiles).delete(ciphertextFilePath);
 		verify(physicalFsProv).deleteIfExists(ciphertextRawPath);
-		verify(physicalFsProv, never()).deleteIfExists(inUsePath);
 	}

670-685: Remove unused mock variable.

The inUsePath mock declared on line 672 is never wired into the test setup and the verification on line 684 doesn't add meaningful coverage.

Apply this diff:

 	@Test
 	public void testDeleteInUseFileThrows() throws IOException {
-		var inUsePath = mock(Path.class, "in use file");
 		when(cryptoPathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.FILE);
 		when(physicalFsProv.deleteIfExists(ciphertextRawPath)).thenReturn(true);
 		doNothing().when(openCryptoFiles).delete(Mockito.any());
 		when(ciphertextPath.isShortened()).thenReturn(false);
 		var inTestSpy = spy(inTest);
 		doThrow(FileAlreadyInUseException.class).when(inTestSpy).checkUsage(cleartextPath, ciphertextPath);
 
 		Assertions.assertThrows(FileAlreadyInUseException.class, () -> inTestSpy.delete(cleartextPath));
 
 		verify(openCryptoFiles, never()).delete(ciphertextFilePath);
 		verify(physicalFsProv, never()).deleteIfExists(ciphertextRawPath);
-		verify(physicalFsProv, never()).deleteIfExists(inUsePath);
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5eb9191 and 7a2b548.

📒 Files selected for processing (2)
  • src/main/java/org/cryptomator/cryptofs/event/FileIsInUseEvent.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (9 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
📚 Learning: 2025-10-17T13:54:22.498Z
Learnt from: infeo
PR: cryptomator/cryptofs#319
File: src/main/java/org/cryptomator/cryptofs/event/BrokenFileNodeEvent.java:14-18
Timestamp: 2025-10-17T13:54:22.498Z
Learning: In the cryptofs event package (e.g., BrokenFileNodeEvent, ConflictResolutionFailedEvent, ConflictResolvedEvent), the event constructors are intended to be called only from within the filesystem implementation, so null-safety guards like Objects.requireNonNull() are not needed because the call sites are controlled and paths are guaranteed to be non-null.

Applied to files:

  • src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
  • src/main/java/org/cryptomator/cryptofs/event/FileIsInUseEvent.java
🧬 Code graph analysis (1)
src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (2)
src/main/java/org/cryptomator/cryptofs/inuse/FileAlreadyInUseException.java (1)
  • FileAlreadyInUseException (6-11)
src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFiles.java (1)
  • TwoPhaseMove (124-174)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyse
  • GitHub Check: Build and Test
🔇 Additional comments (3)
src/main/java/org/cryptomator/cryptofs/event/FileIsInUseEvent.java (1)

1-25: LGTM!

The event record is well-structured. The secondary constructor conveniently accepts a Path for cleartextPath and converts it to String for the canonical constructor, which is a good ergonomic choice for call sites.

src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (2)

589-597: LGTM!

The test correctly verifies that when a file channel cannot be opened due to in-use status, both the exception is propagated and the corresponding event is emitted.


850-902: LGTM!

The move tests properly verify in-use checking for both source and destination paths. The refactoring to use a helper method moveFileWithXInUse eliminates duplication between the two in-use test cases.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (1)

475-477: Fix the incorrect @DisplayName annotation.

The @DisplayName says "throws exception when file is in-use" but the test name testCheckUsageForNotInUseFiles and body (using assertDoesNotThrow) clearly test the opposite case where the file is NOT in use.

Apply this diff:

 @Test
-@DisplayName("checkUsage throws exception when file is in-use")
+@DisplayName("checkUsage succeeds when file is not in-use")
 public void testCheckUsageForNotInUseFiles() throws FileAlreadyInUseException {
🧹 Nitpick comments (3)
src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java (1)

638-640: Consider addressing the TODO optimization.

The TODO suggests skipping usage checks when no owner is configured. Since StubInUseManager provides no-op behavior, the current implementation is safe but could be optimized by checking if the owner is set before calling checkUsage.

Consider:

// In CryptoFileSystemModule or properties check:
if (fileSystemProperties.owner().isPresent()) {
    checkUsage(cleartextSource, ciphertextSource);
    checkUsage(cleartextTarget, ciphertextTarget);
}
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (2)

67-67: Remove stale TODO comment.

The TODO "never closed -> resource leak" is no longer accurate since the close() method at lines 216-233 properly shuts down both executors with appropriate timeout and interrupt handling.

Apply this diff:

-		this.tokenRefresher = Executors.newSingleThreadScheduledExecutor(); //TODO: never closed -> resource leak
+		this.tokenRefresher = Executors.newSingleThreadScheduledExecutor();

134-134: Remove outdated TODO comment.

The TODO "test" is outdated since RealInUseManagerTest already includes comprehensive tests for the validate() method through the isInUse tests and ReadInUseFile nested class.

Apply this diff:

-	//TODO: test
 	UseInfo validate(Properties content) throws IllegalArgumentException {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a2b548 and cd7bf09.

📒 Files selected for processing (6)
  • src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java (12 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/StubInUseManager.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (12 hunks)
  • src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
📚 Learning: 2025-10-17T13:54:22.510Z
Learnt from: infeo
PR: cryptomator/cryptofs#319
File: src/main/java/org/cryptomator/cryptofs/event/BrokenFileNodeEvent.java:14-18
Timestamp: 2025-10-17T13:54:22.510Z
Learning: In the cryptofs event package (e.g., BrokenFileNodeEvent, ConflictResolutionFailedEvent, ConflictResolvedEvent), the event constructors are intended to be called only from within the filesystem implementation, so null-safety guards like Objects.requireNonNull() are not needed because the call sites are controlled and paths are guaranteed to be non-null.

Applied to files:

  • src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
📚 Learning: 2025-10-14T16:55:45.287Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/StubInUseManager.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java
  • src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java
  • src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java
  • src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java
📚 Learning: 2025-10-17T13:49:30.221Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java:108-118
Timestamp: 2025-10-17T13:49:30.221Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java`, the owner field is validated in `CryptoFileSystemProperties.withOwner(String)` to enforce a maximum length of 100 characters. This validation ensures that the serialized Properties payload (owner + lastUpdated timestamp + Properties overhead) never exceeds the 1000-byte INUSE_CLEARTEXT_SIZE buffer, making additional size checks in writeInUseFile() unnecessary.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java
🧬 Code graph analysis (4)
src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (2)
src/main/java/org/cryptomator/cryptofs/inuse/FileAlreadyInUseException.java (1)
  • FileAlreadyInUseException (6-11)
src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFiles.java (1)
  • TwoPhaseMove (124-174)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (4)
src/main/java/org/cryptomator/cryptofs/common/CacheUtils.java (1)
  • CacheUtils (8-57)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)
  • RealUseToken (36-211)
src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java (2)
src/main/java/org/cryptomator/cryptofs/inuse/FileAlreadyInUseException.java (1)
  • FileAlreadyInUseException (6-11)
src/main/java/org/cryptomator/cryptofs/common/DeletingFileVisitor.java (1)
  • DeletingFileVisitor (31-91)
src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (3)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (40-255)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)
  • RealUseToken (36-211)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyse
🔇 Additional comments (13)
src/main/java/org/cryptomator/cryptofs/inuse/StubInUseManager.java (1)

5-11: LGTM! Stub implementation is appropriate.

The minimal stub implementation correctly relies on the InUseManager interface defaults and provides an empty close() method, which is appropriate for scenarios where in-use tracking is disabled (readonly or no owner configured). Based on learnings.

src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java (5)

102-144: LGTM! Proper dependency injection for in-use workflow.

The InUseManager and event consumer are correctly wired through the constructor and stored as final fields, enabling the in-use tracking feature.


214-221: LGTM! InUseManager properly closed in shutdown sequence.

The close() method correctly includes inUseManager::close in the finallyUtil guarantee chain, ensuring clean resource cleanup.


429-442: LGTM! Proper error handling with event emission.

FileAlreadyInUseException is caught, an event is emitted with UseInfo, and the channel is closed safely with proper exception suppression. The error handling correctly addresses past review concerns.


459-468: LGTM! Proper usage check before file deletion.

The deleteFile method correctly checks for in-use status before deletion, preventing accidental deletion of files currently used by other filesystem instances.


736-744: LGTM! Centralized usage check with proper event signaling.

The checkUsage method correctly checks in-use status, emits FileIsInUseEvent with UseInfo data, and throws FileAlreadyInUseException. The fallback to "UNKNOWN" owner handles edge cases gracefully.

src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (4)

108-133: LGTM! Test setup properly includes in-use mocks.

The test setup correctly mocks InUseManager and event consumer, wiring them into the CryptoFileSystemImpl constructor for comprehensive testing.


305-310: LGTM! Test verifies InUseManager cleanup.

The test correctly verifies that inUseManager.close() is invoked during filesystem shutdown.


653-700: LGTM! Comprehensive tests for delete with in-use checks.

The tests properly verify that deleteFile calls checkUsage and that FileAlreadyInUseException prevents deletion, ensuring in-use files are protected.


874-917: LGTM! Thorough tests for move with in-use scenarios.

The tests cover both source and target in-use scenarios, verifying that usage checks prevent moves when files are in use and that the move operation is blocked appropriately.

src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (1)

36-371: LGTM! Comprehensive test suite for RealInUseManager.

The test suite provides excellent coverage of RealInUseManager functionality, including:

  • isInUseByOthers behavior with existing tokens and cache interactions
  • use() flow with token creation and exception handling
  • createInternal scenarios for existing, invalid, and missing in-use files
  • readInUseFile with encryption/decryption mocking
  • isInUse logic for owner matching and timestamp thresholds
  • Cache interaction verification

The tests are well-structured with proper mocking, clear display names, and good isolation.

src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (2)

76-161: LGTM! Well-structured in-use detection with proper caching.

The three-layered isInUse implementation is well-designed:

  1. isInUseByOthers checks own tokens first, then delegates
  2. isInUse(Path) uses ignored cache and useInfoCache with proper IO wrapping
  3. isInUse(UseInfo) validates owner and staleness threshold

The 10-minute staleness threshold (2×REFRESH_DELAY_MINUTES) correctly prevents false positives while allowing detection of abandoned in-use files.


177-208: LGTM! Robust token creation with comprehensive error handling.

The use() method properly:

  • Computes and caches tokens atomically
  • Invalidates ignored cache on successful use
  • Unwraps FileAlreadyInUseException from UncheckedIOException
  • Returns CLOSED_TOKEN for other IO errors (graceful degradation)

The createInternal logic correctly handles all scenarios:

  • Existing valid in-use file → throw FileAlreadyInUseException
  • Non-existing file → create new token
  • Existing invalid file → steal ownership
  • Other IO errors → wrapped as UncheckedIOException

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)

107-121: Consider truncating after repositioning to guarantee no trailing bytes.

While channel.position(0) resets the write position, the channel is not truncated. If a subsequent write is shorter than the previous content, trailing bytes will remain. Although the encryption layer may add padding that makes this unlikely, explicitly truncating provides stronger guarantees.

Consider adding channel.truncate(0) after channel.position(0):

 int writeInUseFile() throws IOException {
 	channel.position(0);
+	channel.truncate(0);
 	final int bytesWritten;
 	try (var nonClosingWrapper = new NonClosingByteChannel(channel); //

Alternatively, if the encryption always produces fixed-size output, document that assumption.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd7bf09 and 3448469.

📒 Files selected for processing (4)
  • pom.xml (2 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (12 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
📚 Learning: 2025-10-17T13:54:22.510Z
Learnt from: infeo
PR: cryptomator/cryptofs#319
File: src/main/java/org/cryptomator/cryptofs/event/BrokenFileNodeEvent.java:14-18
Timestamp: 2025-10-17T13:54:22.510Z
Learning: In the cryptofs event package (e.g., BrokenFileNodeEvent, ConflictResolutionFailedEvent, ConflictResolvedEvent), the event constructors are intended to be called only from within the filesystem implementation, so null-safety guards like Objects.requireNonNull() are not needed because the call sites are controlled and paths are guaranteed to be non-null.

Applied to files:

  • src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
📚 Learning: 2025-10-14T16:55:45.287Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java
📚 Learning: 2025-10-17T13:49:30.221Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java:108-118
Timestamp: 2025-10-17T13:49:30.221Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java`, the owner field is validated in `CryptoFileSystemProperties.withOwner(String)` to enforce a maximum length of 100 characters. This validation ensures that the serialized Properties payload (owner + lastUpdated timestamp + Properties overhead) never exceeds the 1000-byte INUSE_CLEARTEXT_SIZE buffer, making additional size checks in writeInUseFile() unnecessary.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java
🧬 Code graph analysis (3)
src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (2)
src/main/java/org/cryptomator/cryptofs/inuse/FileAlreadyInUseException.java (1)
  • FileAlreadyInUseException (6-11)
src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFiles.java (1)
  • TwoPhaseMove (124-174)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (3)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (40-255)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (4)
src/main/java/org/cryptomator/cryptofs/common/CacheUtils.java (1)
  • CacheUtils (8-57)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)
  • RealUseToken (36-214)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyse
  • GitHub Check: Build and Test
🔇 Additional comments (18)
pom.xml (1)

28-28: Properly integrated Awaitility test dependency.

The addition of Awaitility follows project conventions: the version is properly managed via Maven properties, the dependency is correctly scoped to test, and the placement is consistent with other test dependencies. Verification confirms that version 4.3.0 is the current release with no known vulnerabilities. The configuration aligns well with the async/timing assertions needed for the in-use token lifecycle tests.

src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (7)

291-295: LGTM: finallyUtil parameter count updated correctly.

The addition of a fifth RunnableThrowingException parameter aligns with the new inUseManager.close() invocation in the filesystem shutdown sequence (verified by the test at lines 305-310).

Also applies to: 367-371


305-310: LGTM: Good test coverage for InUseManager cleanup.

The test correctly verifies that inUseManager.close() is invoked as part of the filesystem shutdown sequence.


457-473: LGTM: Test correctly verifies FileAlreadyInUseException and event emission.

The test properly mocks the in-use condition, verifies the exception is thrown, and confirms that a FileIsInUseEvent is emitted via the event consumer.


475-488: LGTM: Test correctly verifies no-op behavior when file is not in-use.

The test properly verifies that checkUsage does not throw and does not emit events when the file is not in use. The @DisplayName now correctly describes the test behavior.


604-612: LGTM: Test correctly verifies newFileChannel failure when file is in-use.

The test properly verifies that when openCryptoFile.newFileChannel() throws FileAlreadyInUseException, the exception is propagated and a FileIsInUseEvent is emitted.


652-700: LGTM: Tests correctly verify delete operation with in-use checks.

The tests properly cover:

  • Regular file deletion with successful checkUsage
  • Shortened file deletion (skips checkUsage per design)
  • In-use file deletion throwing exception and preventing the delete

The spy pattern correctly isolates the checkUsage behavior for testing.


874-918: LGTM: Tests correctly verify move operation with in-use checks.

The tests properly cover move operations with in-use detection for both source and target files. The helper method moveFileWithXInUse avoids duplication while testing both scenarios.

src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (4)

59-74: LGTM: Constructor correctly implements delayed persistence.

The delayed executor pattern using the injected tokenPersistor correctly schedules file creation after INUSE_DELAY_MILLIS, allowing the token to be canceled before persistence if the channel closes early.


93-105: LGTM: Refresh correctly updates the in-use file timestamp.

The method properly synchronizes with the write lock, guards against closed/not-yet-created states, and handles errors gracefully. The channel.position(0) in writeInUseFile() ensures no trailing bytes remain from previous writes.


130-154: LGTM: Move operation correctly maintains consistency.

The atomic update of the useTokens map combined with the file move under a write lock ensures consistency. The defensive close() on error prevents invalid states where the map and filesystem are out of sync.


162-185: LGTM: Close correctly cleans up all resources.

The method properly:

  • Cancels the delayed creation task to prevent late file creation
  • Atomically removes the token from the map
  • Closes the channel and deletes the file if it was created
  • Handles cleanup errors gracefully with logging
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (6)

53-72: LGTM: Constructor correctly initializes caches and executors.

The cache configurations are appropriate:

  • ignoredInUseFiles: 2-minute TTL prevents lingering ignores
  • useInfoCache: 5-second TTL reduces disk reads while staying fresh
  • Periodic refresh every 5 minutes with matching initial delay maintains active ownership

75-87: LGTM: Fast path optimization for self-owned files.

The method correctly short-circuits when we own the token and gracefully handles errors by assuming the file is not in use by others, allowing operations to proceed.


134-151: Address the TODO comment for test coverage.

The validate() method correctly parses the owner from the file (not the local owner field) and validates both required keys. However, the TODO comment indicates test coverage is needed.

Consider adding unit tests to cover:

  • Valid Properties with owner and lastUpdated
  • Missing owner key
  • Blank owner value
  • Missing lastUpdated key
  • Invalid lastUpdated format (DateTimeParseException)

Based on learnings


115-132: LGTM: File reading and decryption correctly implemented.

The method safely reads up to INUSE_CLEARTEXT_SIZE bytes, decrypts the content, validates non-empty, and loads Properties. The fixed buffer size prevents overflow while accommodating the validated owner length.

Based on learnings


177-191: LGTM: Token acquisition correctly handles concurrency and errors.

The atomic computeIfAbsent ensures only one token is created per path, the ignore cache is properly invalidated, and errors are handled defensively by returning CLOSED_TOKEN (except for FileAlreadyInUseException which is correctly propagated).


217-233: LGTM: Executor cleanup correctly prevents resource leaks.

The close method properly shuts down both executors with appropriate timeouts and forced termination, and correctly handles InterruptedException by re-interrupting and forcing shutdown.

Based on learnings

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (3)

28-29: Use ReentrantLock instead of WriteLock (read lock unused).

A plain ReentrantLock is simpler and clearer since only exclusive access is used.

-import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.concurrent.locks.ReentrantLock;
@@
-	private final ReentrantReadWriteLock.WriteLock fileCreationSync = new ReentrantReadWriteLock().writeLock();
+	private final ReentrantLock fileCreationSync = new ReentrantLock();

Also applies to: 53-54


64-74: Rename ctor parameter for clarity (it’s a delayed executor).

The second constructor’s parameter named tokenPersistor is actually the executor used by runAsync (already delayed by the first ctor). Rename to avoid confusion.

-	RealUseToken(Path filePath, String owner, Cryptor cryptor, ConcurrentMap<Path, RealUseToken> useTokens, Executor tokenPersistor, OpenOption openMode, EncryptionDecorator encWrapper) {
+	RealUseToken(Path filePath, String owner, Cryptor cryptor, ConcurrentMap<Path, RealUseToken> useTokens, Executor executor, OpenOption openMode, EncryptionDecorator encWrapper) {
@@
-		this.creationTask = CompletableFuture.runAsync(() -> createInUseFile(openOptions), tokenPersistor);
+		this.creationTask = CompletableFuture.runAsync(() -> createInUseFile(openOptions), executor);

175-178: Log the exception cause on delete failure.

Currently the warn drops the IOException; include it for diagnostics.

-						//ignore
-						LOG.warn("Failed to delete inUse File {}. Must be deleted manually.", path);
+						LOG.warn("Failed to delete inUse File {}. Must be deleted manually.", path, e);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3448469 and ebe8c6e.

📒 Files selected for processing (1)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
📚 Learning: 2025-10-14T16:55:45.287Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
📚 Learning: 2025-10-17T13:49:30.221Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java:108-118
Timestamp: 2025-10-17T13:49:30.221Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java`, the owner field is validated in `CryptoFileSystemProperties.withOwner(String)` to enforce a maximum length of 100 characters. This validation ensures that the serialized Properties payload (owner + lastUpdated timestamp + Properties overhead) never exceeds the 1000-byte INUSE_CLEARTEXT_SIZE buffer, making additional size checks in writeInUseFile() unnecessary.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
🧬 Code graph analysis (1)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (3)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (40-255)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyse
  • GitHub Check: Build and Test

Comment on lines 136 to 151
useTokens.compute(newFilePath, (_, _) -> {
try {
if (channel != null) {
Files.move(filePath, newFilePath, StandardCopyOption.REPLACE_EXISTING);
}
return this;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
useTokens.remove(filePath);
this.filePath = newFilePath;
} catch (UncheckedIOException e) {
LOG.debug("Failed to move in-use file {} to {}.", filePath, newFilePath, e.getCause());
close(); //To prevent invalid states
} finally {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Moving an open file is fragile on Windows; add a fallback (close/recreate) path.

Files.move on an open FileChannel often fails on Windows (sharing/locking). Today you close the token on failure. Prefer keeping the token by falling back to recreating the in-use file at the new path.

-			useTokens.compute(newFilePath, (k, v) -> {
-				try {
-					if (channel != null) {
-						Files.move(filePath, newFilePath, StandardCopyOption.REPLACE_EXISTING);
-					}
-					return this;
-				} catch (IOException e) {
-					throw new UncheckedIOException(e);
-				}
-			});
+			useTokens.compute(newFilePath, (k, v) -> {
+				try {
+					if (channel != null) {
+						try {
+							Files.move(filePath, newFilePath, StandardCopyOption.REPLACE_EXISTING);
+							return this;
+						} catch (java.nio.file.AccessDeniedException winMoveDenied) {
+							// Windows fallback: close old channel, recreate at new path, then delete old file.
+							var oldPath = filePath;
+							var oldCh = channel;
+							channel = null; // prevent refresh from writing to stale handle
+							this.filePath = newFilePath;
+							this.channel = FileChannel.open(newFilePath, java.util.Set.of(
+									StandardOpenOption.WRITE,
+									StandardOpenOption.CREATE,
+									StandardOpenOption.TRUNCATE_EXISTING));
+							writeInUseFile();
+							try {
+								if (oldCh != null && oldCh.isOpen()) oldCh.close();
+							} catch (IOException ignore) { /* best-effort */ }
+							try {
+								Files.deleteIfExists(oldPath);
+							} catch (IOException ignore) { /* best-effort */ }
+							return this;
+						}
+					}
+					return this;
+				} catch (IOException e) {
+					throw new UncheckedIOException(e);
+				}
+			});

If you prefer not to special-case Windows, consider always closing/recreating instead of moving when channel != null. I can provide that variant too.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java around lines
136-151, Files.move of an open file can fail on Windows; instead of closing the
token on UncheckedIOException, implement a fallback that preserves the token by
(1) closing the open channel/stream safely, (2) creating or opening a new in-use
file at newFilePath (with the same semantics as the original, e.g.,
createNewFile or replace existing), (3) updating the useTokens map atomically to
point to the new path and removing the old key, and (4) reinitializing
this.filePath to newFilePath and reopening the channel if needed — ensure all
IOExceptions are handled/wrapped consistently, log the fallback action, and keep
the finally cleanup unchanged so no invalid state is left if the fallback also
fails.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)

178-203: Add exception to the log statement for better diagnostics.

The warning on line 195 should include the exception to aid troubleshooting.

Apply this diff:

 					} catch (IOException e) {
 						//ignore
-						LOG.warn("Failed to delete inUse File {}. Must be deleted manually.", path);
+						LOG.warn("Failed to delete inUse File {}. Must be deleted manually.", path, e);
 					}

Otherwise, the close logic is solid: the lock ensures atomicity, creationTask.cancel(false) is appropriate, and the differentiated deleteFile flag correctly supports both cleanup scenarios.

🧹 Nitpick comments (2)
src/main/java/org/cryptomator/cryptofs/dir/C9uProcessor.java (1)

14-18: Consider renaming field to match type.

The field is named conflictRemover but the type is C9uConflictResolver. For consistency and clarity, consider renaming the field to conflictResolver.

Apply this diff:

-	private final C9uConflictResolver conflictRemover;
+	private final C9uConflictResolver conflictResolver;
 
 	@Inject
-	public C9uProcessor(C9uConflictResolver conflictRemover) {
-		this.conflictRemover = conflictRemover;
+	public C9uProcessor(C9uConflictResolver conflictResolver) {
+		this.conflictResolver = conflictResolver;
 	}

And update line 22:

 	public Stream<Node> process(Node node) {
-		return conflictRemover.process(node);
+		return conflictResolver.process(node);
 	}
src/main/java/org/cryptomator/cryptofs/dir/C9uConflictResolver.java (1)

46-46: Track or implement TODO for closing UseToken.

The TODO indicates that orphaned use tokens should be closed when their corresponding in-use files are deleted. This is related to the unused InUseManager field.

Do you want me to:

  1. Generate an implementation that uses InUseManager to close orphaned tokens?
  2. Open a new issue to track this task for future implementation?
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ebe8c6e and fb7ccb9.

📒 Files selected for processing (9)
  • src/main/java/org/cryptomator/cryptofs/common/Constants.java (3 hunks)
  • src/main/java/org/cryptomator/cryptofs/dir/C9rDecryptor.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/dir/C9uConflictResolver.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/dir/C9uProcessor.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/dir/NodeProcessor.java (2 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/dir/C9rDecryptorTest.java (3 hunks)
  • src/test/java/org/cryptomator/cryptofs/dir/C9uConflictResolverTest.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/inuse/RealUseTokenTest.java (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/org/cryptomator/cryptofs/dir/C9rDecryptor.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/org/cryptomator/cryptofs/common/Constants.java
  • src/test/java/org/cryptomator/cryptofs/inuse/RealUseTokenTest.java
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
📚 Learning: 2025-10-14T16:55:45.287Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
📚 Learning: 2025-10-17T13:49:30.221Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java:108-118
Timestamp: 2025-10-17T13:49:30.221Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java`, the owner field is validated in `CryptoFileSystemProperties.withOwner(String)` to enforce a maximum length of 100 characters. This validation ensures that the serialized Properties payload (owner + lastUpdated timestamp + Properties overhead) never exceeds the 1000-byte INUSE_CLEARTEXT_SIZE buffer, making additional size checks in writeInUseFile() unnecessary.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
🧬 Code graph analysis (6)
src/main/java/org/cryptomator/cryptofs/dir/NodeProcessor.java (1)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (13-45)
src/main/java/org/cryptomator/cryptofs/dir/C9uConflictResolver.java (5)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (13-45)
src/main/java/org/cryptomator/cryptofs/common/StringUtils.java (1)
  • StringUtils (6-28)
src/main/java/org/cryptomator/cryptofs/dir/C9rDecryptor.java (1)
  • DirectoryStreamScoped (21-88)
src/main/java/org/cryptomator/cryptofs/dir/C9uProcessor.java (1)
  • DirectoryStreamScoped (11-24)
src/main/java/org/cryptomator/cryptofs/dir/NodeProcessor.java (1)
  • DirectoryStreamScoped (8-36)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (3)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (13-45)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (40-255)
src/test/java/org/cryptomator/cryptofs/dir/C9uConflictResolverTest.java (1)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (13-45)
src/test/java/org/cryptomator/cryptofs/dir/C9rDecryptorTest.java (1)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (13-45)
src/main/java/org/cryptomator/cryptofs/dir/C9uProcessor.java (3)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (13-45)
src/main/java/org/cryptomator/cryptofs/dir/C9uConflictResolver.java (1)
  • DirectoryStreamScoped (20-57)
src/main/java/org/cryptomator/cryptofs/dir/NodeProcessor.java (1)
  • DirectoryStreamScoped (8-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyse
  • GitHub Check: Build and Test
🔇 Additional comments (12)
src/test/java/org/cryptomator/cryptofs/dir/C9rDecryptorTest.java (1)

3-3: LGTM! Good refactoring to use centralized constant.

The changes correctly replace the pattern reference with Constants.BASE64_PATTERN, improving maintainability by centralizing the definition. The import is properly added and the test logic remains unchanged.

Also applies to: 42-42, 57-57

src/main/java/org/cryptomator/cryptofs/dir/C9uConflictResolver.java (1)

41-55: Base64 validation logic is correct.

The implementation properly validates the entire basename against the base64 pattern using matcher.matches(), appropriately handles deletion failures, and correctly returns an empty stream since in-use files should not appear in directory listings.

src/test/java/org/cryptomator/cryptofs/dir/C9uConflictResolverTest.java (2)

27-38: Test correctly validates valid base64 handling.

The test properly verifies that in-use files with valid base64-encoded basenames are preserved and the processor returns an empty stream.


40-51: Test correctly validates invalid base64 handling.

The test appropriately verifies that in-use files with invalid base64-encoded basenames (e.g., renamed with conflict markers) are deleted and the processor returns an empty stream.

src/main/java/org/cryptomator/cryptofs/dir/NodeProcessor.java (2)

13-22: Integration of C9uProcessor follows existing patterns.

The addition of C9uProcessor as a dependency is consistent with the existing architecture (similar to C9rProcessor and C9sProcessor). The constructor injection and field initialization are correct.


29-30: New processing branch correctly handles in-use files.

The implementation properly delegates in-use file processing to C9uProcessor and chains the result through brokenDirFilter, maintaining consistency with the existing processing branches for .c9r and .c9s files.

src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (6)

38-75: LGTM!

The factory methods and constructor are well-designed. The injected tokenPersistor executor properly addresses the resource leak from earlier reviews, and the delayed persistence mechanism is correctly implemented using CompletableFuture.delayedExecutor.


77-92: LGTM!

The synchronization and error handling are correct. The early return when closed is true prevents races with the close operation, and calling close() on IOException properly cleans up failed creation attempts.


94-115: Refresh logic is sound.

The modification detection (comparing lastModified timestamps) before refreshing is correct, and the differentiated close behavior makes sense:

  • ModifiedFileExceptionclose(false) preserves the file (someone else owns it)
  • IOExceptionclose() removes the file (can't maintain ownership)

The TODO on line 106 about event emission is reasonable given the lack of cleartext context at this layer.


117-132: LGTM!

The channel.truncate(0) at line 118 correctly addresses the trailing bytes concern from earlier reviews. The NonClosingByteChannel wrapper prevents premature closure, and channel.force(true) ensures durability. The lastModified update after the write properly tracks changes for subsequent refresh cycles.


168-171: LGTM!


205-237: LGTM!

The helper constructs are well-designed:

  • EncryptionDecorator provides a clean testability seam for mocking encryption
  • NonClosingByteChannel elegantly prevents premature closure of the long-lived FileChannel in try-with-resources blocks
  • ModifiedFileException serves as a clear marker for external modifications

Comment on lines +140 to +166
//visible for testing
void moveToInternal(Path newFilePath) {
fileCreationSync.lock();
try {
if (closed) {
return;
}
useTokens.compute(newFilePath, (_, _) -> {
try {
if (channel != null) {
//TODO: does this affect the lastModified file?
Files.move(filePath, newFilePath, StandardCopyOption.REPLACE_EXISTING);
}
return this;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
useTokens.remove(filePath);
this.filePath = newFilePath;
} catch (UncheckedIOException e) {
LOG.debug("Failed to move in-use file {} to {}.", filePath, newFilePath, e.getCause());
close(); //To prevent invalid states
} finally {
fileCreationSync.unlock();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Address the TODO: Files.move may invalidate lastModified tracking.

The TODO on line 150 highlights a real concern. If Files.move() changes the lastModified timestamp (behavior varies by platform/filesystem), the next refresh() call will detect a mismatch (line 100-102), throw ModifiedFileException, and call close(false), leaving an orphaned in-use file on disk.

Recommendation: After the move succeeds (line 151), update lastModified to match the new file's timestamp:

 					if (channel != null) {
-						//TODO: does this affect the lastModified file?
 						Files.move(filePath, newFilePath, StandardCopyOption.REPLACE_EXISTING);
+						// No need to update lastModified here; it's updated in the next writeInUseFile() call during refresh
 					}

Actually, on second thought: if the move changes the timestamp, you could explicitly re-read it after the move:

 					if (channel != null) {
-						//TODO: does this affect the lastModified file?
 						Files.move(filePath, newFilePath, StandardCopyOption.REPLACE_EXISTING);
+						lastModified = Files.getLastModifiedTime(newFilePath).toMillis();
 					}

This ensures the token's lastModified stays synchronized with the file after a move.


Operational note: Moving an open file can fail on Windows.

A past review noted that Files.move() on an open FileChannel often fails on Windows (ACCESS_DENIED due to locking). The current implementation handles failure by closing and deleting the token (line 162), which is safe but may surprise users who expect moves to succeed. If this becomes a frequent issue, consider a fallback: close the old channel, open a new one at newFilePath, rewrite the properties, then delete the old file.

🤖 Prompt for AI Agents
In src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java around lines
140-166, after the Files.move(filePath, newFilePath,
StandardCopyOption.REPLACE_EXISTING) call, re-read and set the token's
lastModified from the moved file (e.g.,
Files.getLastModifiedTime(newFilePath).toMillis()) so the in-memory timestamp
stays in sync and avoids a spurious ModifiedFileException on the next refresh;
keep the existing IOException handling (wrap in UncheckedIOException) and ensure
this update happens before updating useTokens/remove/filePath. Optionally (if
move fails on Windows), consider a fallback: close the old channel, open a new
channel on newFilePath and restore any needed state before removing the old
entry, but at minimum update lastModified after a successful move.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)

125-142: Add unit tests for validate() method.

The TODO comment and the corresponding note in RealInUseManagerTest.java (line 365) indicate that validate() lacks dedicated unit test coverage. This method performs critical validation (owner presence, blank check, timestamp parsing) and should be tested to ensure it correctly handles valid inputs, missing keys, blank owners, and malformed timestamps.

Would you like me to draft unit tests for this method?

src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (2)

187-192: Clarify or resolve the TODO regarding Files.move and lastModified.

If Files.move() alters the file's lastModified timestamp (behavior varies by platform/filesystem), the next refresh() call will detect the mismatch (line 141) and close the token without deleting the file (line 148 calls close(false)). This defensive behavior prevents orphaned files but may surprise users if moves frequently trigger closures.

Consider either:

  1. Documenting this behavior and removing the TODO, or
  2. Re-reading and updating lastModified after a successful move to keep the token synchronized:
 				if (channel != null) {
-					//TODO: does this affect the lastModified file?
 					Files.move(filePath, newFilePath, StandardCopyOption.REPLACE_EXISTING);
+					// Re-read lastModified to stay in sync after move
+					lastModified = Files.getLastModifiedTime(newFilePath).toMillis();
 				}

87-109: Consider adding explicit tests for scheduleRefresh logic.

The TODO comment on line 87 suggests missing test coverage. While testFileRefresh and related tests exercise refresh behavior, explicit unit tests for the exponential backoff scheduling (including edge cases like cancellation, closure during scheduling, and delay calculation) would improve coverage and confidence in this recursive scheduling logic.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb7ccb9 and 75b3b50.

📒 Files selected for processing (6)
  • src/main/java/org/cryptomator/cryptofs/common/Constants.java (3 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/UseToken.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/inuse/RealUseTokenTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/org/cryptomator/cryptofs/common/Constants.java
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
📚 Learning: 2025-10-14T16:55:45.287Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.

Applied to files:

  • src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
  • src/main/java/org/cryptomator/cryptofs/inuse/UseToken.java
📚 Learning: 2025-10-17T13:49:30.221Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java:108-118
Timestamp: 2025-10-17T13:49:30.221Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java`, the owner field is validated in `CryptoFileSystemProperties.withOwner(String)` to enforce a maximum length of 100 characters. This validation ensures that the serialized Properties payload (owner + lastUpdated timestamp + Properties overhead) never exceeds the 1000-byte INUSE_CLEARTEXT_SIZE buffer, making additional size checks in writeInUseFile() unnecessary.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
🧬 Code graph analysis (3)
src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (3)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (38-241)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)
  • RealUseToken (39-278)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (4)
src/main/java/org/cryptomator/cryptofs/common/CacheUtils.java (1)
  • CacheUtils (8-57)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (13-44)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)
  • RealUseToken (39-278)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (3)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (13-44)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (38-241)
🔇 Additional comments (3)
src/test/java/org/cryptomator/cryptofs/inuse/RealUseTokenTest.java (1)

1-276: LGTM! Comprehensive test coverage.

The test suite thoroughly exercises RealUseToken behavior including delayed file creation, content validation, stealing scenarios, movement before/after creation, refresh logic, and closure. The use of WatchService to verify filesystem events and Awaitility for timing-based assertions is appropriate. Good job!

src/main/java/org/cryptomator/cryptofs/inuse/UseToken.java (1)

1-35: LGTM! Clean interface design.

The sealed interface with default implementations, singleton tokens, and well-defined constants provides a clear contract for in-use token lifecycle management. The inline comment on line 34 explaining the MAX_CLEARTEXT_SIZE_BYTES calculation is helpful.

src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (1)

1-371: LGTM! Excellent test coverage.

The test suite comprehensively exercises RealInUseManager functionality including token creation/reuse, exception handling, file reading, owner/timestamp validation logic, and cache interactions. The TODO on line 365 for testing validate() is noted and addressed in the review of RealInUseManager.java. Well-organized test structure with nested classes and descriptive names.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/main/java/org/cryptomator/cryptofs/dir/C9uConflictResolver.java (1)

26-48: Finish UseToken cleanup before deleting invalid in-use files.

Right now the resolver deletes the renamed .c9u helper and returns, but the associated UseToken (if any) remains registered in InUseManager. That leaves a stale ownership entry behind, so subsequent open/overwrite attempts on the ciphertext still trip FileAlreadyInUseException until the entry times out. Please inject the InUseManager here and close/release the token before you delete the helper file so the in-use registry stays consistent, completing the TODO.

🧹 Nitpick comments (1)
src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java (1)

177-187: Consider clarifying the timing values.

The test correctly validates the in-use file lifecycle. The timing values (4900ms and 8000ms) correspond to the 5-second deferral mentioned in the PR objectives, but using magic numbers makes this less obvious.

Consider extracting timing values as constants or adding a comment:

+		// In-use files are deferred for 5 seconds, so we wait at least 4.9s for persistence
 		@Test
 		@DisplayName("Opening a file channel creates an in-use file and removes it on close")
 		public void testOpeningFCCreatesInUseFile() throws IOException {
 			try (var writer = FileChannel.open(file, CREATE, WRITE)) {
-				Awaitility.await().atLeast( 4900, TimeUnit.MILLISECONDS) //
-						.atMost(8000, TimeUnit.MILLISECONDS) //
+				Awaitility.await().atLeast(4900, TimeUnit.MILLISECONDS) //
+						.atMost(8000, TimeUnit.MILLISECONDS) //
 						.until(() -> numberOfInUseFiles() == 1);
 			}

Also note: the assertion on line 186 happens immediately after closing the channel, which assumes synchronous cleanup. This appears correct based on the PR description, but could be worth confirming if intermittent test failures occur in CI.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 75b3b50 and 415cb76.

📒 Files selected for processing (4)
  • src/main/java/org/cryptomator/cryptofs/dir/C9uConflictResolver.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/dir/C9uProcessor.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java (4 hunks)
  • src/test/java/org/cryptomator/cryptofs/dir/C9uConflictResolverTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/java/org/cryptomator/cryptofs/dir/C9uConflictResolverTest.java
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java:108-118
Timestamp: 2025-10-17T13:49:30.221Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java`, the owner field is validated in `CryptoFileSystemProperties.withOwner(String)` to enforce a maximum length of 100 characters. This validation ensures that the serialized Properties payload (owner + lastUpdated timestamp + Properties overhead) never exceeds the 1000-byte INUSE_CLEARTEXT_SIZE buffer, making additional size checks in writeInUseFile() unnecessary.
Learnt from: infeo
PR: cryptomator/cryptofs#319
File: src/main/java/org/cryptomator/cryptofs/event/BrokenFileNodeEvent.java:14-18
Timestamp: 2025-10-17T13:54:22.510Z
Learning: In the cryptofs event package (e.g., BrokenFileNodeEvent, ConflictResolutionFailedEvent, ConflictResolvedEvent), the event constructors are intended to be called only from within the filesystem implementation, so null-safety guards like Objects.requireNonNull() are not needed because the call sites are controlled and paths are guaranteed to be non-null.
📚 Learning: 2024-10-21T15:54:48.161Z
Learnt from: infeo
PR: cryptomator/cryptofs#253
File: src/test/java/org/cryptomator/cryptofs/ClearToCipherDirCacheTest.java:20-26
Timestamp: 2024-10-21T15:54:48.161Z
Learning: In the Java test file `ClearToCipherDirCacheTest.java`, the `beforeEach` method includes a call to `dirLoader.load()` which can throw an `IOException`, so the `throws IOException` declaration is necessary.

Applied to files:

  • src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java
📚 Learning: 2025-10-14T16:55:45.287Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.

Applied to files:

  • src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java
  • src/main/java/org/cryptomator/cryptofs/dir/C9uConflictResolver.java
📚 Learning: 2025-10-17T13:49:30.221Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java:108-118
Timestamp: 2025-10-17T13:49:30.221Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java`, the owner field is validated in `CryptoFileSystemProperties.withOwner(String)` to enforce a maximum length of 100 characters. This validation ensures that the serialized Properties payload (owner + lastUpdated timestamp + Properties overhead) never exceeds the 1000-byte INUSE_CLEARTEXT_SIZE buffer, making additional size checks in writeInUseFile() unnecessary.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/dir/C9uConflictResolver.java
🧬 Code graph analysis (3)
src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java (2)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (13-44)
src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java (1)
  • CryptoFileSystemProvider (86-322)
src/main/java/org/cryptomator/cryptofs/dir/C9uProcessor.java (3)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (13-44)
src/main/java/org/cryptomator/cryptofs/dir/C9uConflictResolver.java (1)
  • DirectoryStreamScoped (20-54)
src/main/java/org/cryptomator/cryptofs/dir/NodeProcessor.java (1)
  • DirectoryStreamScoped (8-36)
src/main/java/org/cryptomator/cryptofs/dir/C9uConflictResolver.java (4)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (13-44)
src/main/java/org/cryptomator/cryptofs/common/StringUtils.java (1)
  • StringUtils (6-28)
src/main/java/org/cryptomator/cryptofs/dir/C9uProcessor.java (1)
  • DirectoryStreamScoped (11-24)
src/main/java/org/cryptomator/cryptofs/dir/NodeProcessor.java (1)
  • DirectoryStreamScoped (8-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyse
  • GitHub Check: Build and Test
🔇 Additional comments (5)
src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java (4)

12-13: LGTM!

The new imports are correctly added to support the in-use feature testing. Awaitility enables asynchronous waiting for the deferred in-use file creation, and Constants provides access to the in-use file suffix.


148-169: Well-structured test setup.

The setup correctly configures an in-memory filesystem with an owner property to activate the in-use feature. The use of Jimfs and mocked MasterkeyLoader aligns with other test classes in this file.


189-194: LGTM!

The helper method correctly walks the vault directory and counts in-use files. Proper resource management with try-with-resources ensures the stream is closed.


205-205: LGTM!

Refactoring vaultPath from a local variable to a field improves consistency with the new InUseFeatureActivated test class structure. The additional blank line enhances readability.

Also applies to: 210-210, 231-231

src/main/java/org/cryptomator/cryptofs/dir/C9uProcessor.java (1)

21-23: Delegating keeps the NodeProcessor flow consistent.

Routing .c9u handling straight through the conflict resolver mirrors the existing .c9r/.c9s paths and keeps the directory-processing pipeline tidy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants