@@ -85,44 +85,65 @@ Stream<Node> resolveConflict(Node conflicting, Path canonicalPath) throws IOExce
8585 resolved .extractedCiphertext = conflicting .extractedCiphertext ;
8686 return Stream .of (resolved );
8787 } else {
88- return Stream . of ( renameConflictingFile (canonicalPath , conflictingPath , conflicting . cleartextName ) );
88+ return renameConflictingFile (canonicalPath , conflicting );
8989 }
9090 }
9191
9292 /**
9393 * Resolves a conflict by renaming the conflicting file.
9494 *
9595 * @param canonicalPath The path to the original (conflict-free) file.
96- * @param conflictingPath The path to the potentially conflicting file.
97- * @param cleartext The cleartext name of the conflicting file.
98- * @return The newly created Node after renaming the conflicting file.
99- * @throws IOException
96+ * @param conflicting The conflicting file.
97+ * @return The newly created Node if rename succeeded or an empty stream otherwise.
98+ * @throws IOException If an unexpected I/O exception occurs during rename
10099 */
101- private Node renameConflictingFile (Path canonicalPath , Path conflictingPath , String cleartext ) throws IOException {
100+ private Stream < Node > renameConflictingFile (Path canonicalPath , Node conflicting ) throws IOException {
102101 assert Files .exists (canonicalPath );
103- final int beginOfFileExtension = cleartext .lastIndexOf ('.' );
104- final String fileExtension = (beginOfFileExtension > 0 ) ? cleartext .substring (beginOfFileExtension ) : "" ;
105- final String basename = (beginOfFileExtension > 0 ) ? cleartext .substring (0 , beginOfFileExtension ) : cleartext ;
106- final String lengthRestrictedBasename = basename .substring (0 , Math .min (basename .length (), maxCleartextFileNameLength - fileExtension .length () - 5 )); // 5 chars for conflict suffix " (42)"
107- String alternativeCleartext ;
108- String alternativeCiphertext ;
109- String alternativeCiphertextName ;
110- Path alternativePath ;
111- int i = 1 ;
112- do {
113- alternativeCleartext = lengthRestrictedBasename + " (" + i ++ + ")" + fileExtension ;
102+ assert conflicting .fullCiphertextFileName .endsWith (Constants .CRYPTOMATOR_FILE_SUFFIX );
103+ assert conflicting .fullCiphertextFileName .contains (conflicting .extractedCiphertext );
104+
105+ final String cleartext = conflicting .cleartextName ;
106+ final int beginOfCleartextExt = cleartext .lastIndexOf ('.' );
107+ final String cleartextFileExt = (beginOfCleartextExt > 0 ) ? cleartext .substring (beginOfCleartextExt ) : "" ;
108+ final String cleartextBasename = (beginOfCleartextExt > 0 ) ? cleartext .substring (0 , beginOfCleartextExt ) : cleartext ;
109+
110+ // let's assume that some the sync conflict string is added at the end of the file name, but before .c9r:
111+ final int endOfCiphertext = conflicting .fullCiphertextFileName .indexOf (conflicting .extractedCiphertext ) + conflicting .extractedCiphertext .length ();
112+ final String originalConflictSuffix = conflicting .fullCiphertextFileName .substring (endOfCiphertext , conflicting .fullCiphertextFileName .length () - Constants .CRYPTOMATOR_FILE_SUFFIX .length ());
113+
114+ // split available maxCleartextFileNameLength between basename, conflict suffix, and file extension:
115+ final int netCleartext = maxCleartextFileNameLength - cleartextFileExt .length (); // file extension must be preserved
116+ final String conflictSuffix = originalConflictSuffix .substring (0 , Math .min (originalConflictSuffix .length (), netCleartext / 2 )); // max 50% of available space
117+ final int conflictSuffixLen = Math .max (4 , conflictSuffix .length ()); // prefer to use original conflict suffix, but reserver at least 4 chars for numerical fallback: " (9)"
118+ final String lengthRestrictedBasename = cleartextBasename .substring (0 , Math .min (cleartextBasename .length (), netCleartext - conflictSuffixLen )); // remaining space for basename
119+
120+ // attempt to use original conflict suffix:
121+ String alternativeCleartext = lengthRestrictedBasename + conflictSuffix + cleartextFileExt ;
122+ String alternativeCiphertext = cryptor .fileNameCryptor ().encryptFilename (BaseEncoding .base64Url (), alternativeCleartext , dirId );
123+ String alternativeCiphertextName = alternativeCiphertext + Constants .CRYPTOMATOR_FILE_SUFFIX ;
124+ Path alternativePath = canonicalPath .resolveSibling (alternativeCiphertextName );
125+
126+ // fallback to number conflic suffix, if file with alternative path already exists:
127+ for (int i = 1 ; i < 10 && Files .exists (alternativePath ); i ++) {
128+ alternativeCleartext = lengthRestrictedBasename + " (" + i + ")" + cleartextFileExt ;
114129 alternativeCiphertext = cryptor .fileNameCryptor ().encryptFilename (BaseEncoding .base64Url (), alternativeCleartext , dirId );
115130 alternativeCiphertextName = alternativeCiphertext + Constants .CRYPTOMATOR_FILE_SUFFIX ;
116131 alternativePath = canonicalPath .resolveSibling (alternativeCiphertextName );
117- } while (Files .exists (alternativePath ));
132+ }
133+
118134 assert alternativeCiphertextName .length () <= maxC9rFileNameLength ;
119- LOG .info ("Moving conflicting file {} to {}" , conflictingPath , alternativePath );
120- Files .move (conflictingPath , alternativePath , StandardCopyOption .ATOMIC_MOVE );
135+ if (Files .exists (alternativePath )) {
136+ LOG .warn ("Failed finding alternative name for {}. Keeping original name." , conflicting .ciphertextPath );
137+ return Stream .empty ();
138+ }
139+
140+ Files .move (conflicting .ciphertextPath , alternativePath , StandardCopyOption .ATOMIC_MOVE );
141+ LOG .info ("Renamed conflicting file {} to {}..." , conflicting .ciphertextPath , alternativePath );
121142 Node node = new Node (alternativePath );
122143 node .cleartextName = alternativeCleartext ;
123144 node .extractedCiphertext = alternativeCiphertext ;
124- eventConsumer .accept (new ConflictResolvedEvent (cleartextPath .resolve (cleartext ), canonicalPath , cleartextPath .resolve (alternativeCleartext ), alternativePath ));
125- return node ;
145+ eventConsumer .accept (new ConflictResolvedEvent (cleartextPath .resolve (cleartext ), conflicting . ciphertextPath , cleartextPath .resolve (alternativeCleartext ), alternativePath ));
146+ return Stream . of ( node ) ;
126147 }
127148
128149
0 commit comments