77import ch .cyberduck .core .*;
88import ch .cyberduck .core .exception .BackgroundException ;
99import ch .cyberduck .core .features .Bulk ;
10+ import ch .cyberduck .core .features .Delete ;
1011import ch .cyberduck .core .features .Directory ;
1112import ch .cyberduck .core .features .Home ;
13+ import ch .cyberduck .core .features .Move ;
1214import ch .cyberduck .core .features .Read ;
1315import ch .cyberduck .core .features .Vault ;
1416import ch .cyberduck .core .features .Write ;
3537
3638import java .io .ByteArrayInputStream ;
3739import java .io .IOException ;
40+ import java .io .InputStream ;
3841import java .util .Arrays ;
3942import java .util .Collections ;
4043import java .util .EnumSet ;
@@ -74,8 +77,6 @@ public abstract class AbstractHubSynchronizeTest extends AbstractHubTest {
7477
7578 /**
7679 * Start with unattended setup (e.g. UnattendedMinio) and then run tests with corresponding attended setup (e.g. AttendedMinio) to save startup times at every test execution.
77- *
78- * @throws InterruptedException
7980 */
8081 @ Test
8182 @ Disabled
@@ -285,9 +286,11 @@ public void test03AddVault(final HubTestConfig config) throws Exception {
285286 assertEquals (file .getName (), list .get (0 ).getName ());
286287
287288 byte [] actual = new byte [300 ];
288- int l = session .getFeature (Read .class ).read (file , new TransferStatus (), new DisabledConnectionCallback ()).read (actual );
289- assert l == 234 ;
290- assertArrayEquals (content , Arrays .copyOfRange (actual , 0 , l ));
289+ try (final InputStream inputStream = session .getFeature (Read .class ).read (file , new TransferStatus (), new DisabledConnectionCallback ())) {
290+ int l = inputStream .read (actual );
291+ assert l == 234 ;
292+ assertArrayEquals (content , Arrays .copyOfRange (actual , 0 , l ));
293+ }
291294 }
292295 {
293296 // encrypted directory creation and listing
@@ -296,7 +299,7 @@ public void test03AddVault(final HubTestConfig config) throws Exception {
296299
297300 session .getFeature (Directory .class ).mkdir (folder , new TransferStatus ());
298301 final AttributedList <Path > list = session .getFeature (ListService .class ).list (bucket , new DisabledListProgressListener ());
299- assertEquals (2 , list .size ());
302+ assertEquals (2 , list .size ()); // a file and a folder
300303
301304 {
302305 // encrypted file upload in subfolder
@@ -307,22 +310,77 @@ public void test03AddVault(final HubTestConfig config) throws Exception {
307310 assertEquals (file .getName (), sublist .get (0 ).getName ());
308311
309312 byte [] actual = new byte [600 ];
310- int l = session .getFeature (Read .class ).read (file , new TransferStatus (), new DisabledConnectionCallback ()).read (actual );
311- assert l == 555 ;
312- assertArrayEquals (content , Arrays .copyOfRange (actual , 0 , l ));
313+ try (final InputStream inputStream = session .getFeature (Read .class ).read (file , new TransferStatus (), new DisabledConnectionCallback ())) {
314+ int l = inputStream .read (actual );
315+ assert l == 555 ;
316+ assertArrayEquals (content , Arrays .copyOfRange (actual , 0 , l ));
317+ }
318+
319+ // move operation to root folder and read again
320+ session .getFeature (Move .class ).move (file , new Path (home , file .getName (), EnumSet .of (AbstractPath .Type .file )), new TransferStatus (), new Delete .DisabledCallback (), new DisabledConnectionCallback ());
321+
322+ final AttributedList <Path > list2 = session .getFeature (ListService .class ).list (home , new DisabledListProgressListener ());
323+ assertEquals (3 , list2 .size ()); // 1 subfolder and 2 files
324+
325+ assertEquals (1 , list2 .toStream ().map (Path ::isDirectory ).filter (Boolean ::booleanValue ).count ());
326+ assertEquals (2 , list2 .toStream ().map (Path ::isFile ).filter (Boolean ::booleanValue ).count ());
313327 }
314328 }
315329 {
316330 // raw listing encrypted file names
331+ // aka. ciphertext directory structure
332+ // see https://github.com/encryption-alliance/unified-vault-format/blob/develop/file%20name%20encryption/AES-SIV-512-B64URL.md#ciphertext-directory-structure
317333 vaultRegistry .close (bucket );
318334 assertSame (Vault .DISABLED , vaultRegistry .find (session , bucket ));
319335 assertTrue (vaultRegistry .isEmpty ());
320336
321- final AttributedList <Path > list = session .getFeature (ListService .class ).list (bucket , new DisabledListProgressListener ());
322- assertFalse (list .isEmpty ());
323- assertEquals (2 , list .size ());
324- assertNotNull (list .find (new SimplePathPredicate (new Path (bucket , "d" , EnumSet .of (Path .Type .directory , AbstractPath .Type .placeholder )))));
325- assertNotNull (list .find (new SimplePathPredicate (new Path (bucket , PreferencesFactory .get ().getProperty ("cryptomator.vault.config.filename" ), EnumSet .of (Path .Type .file )))));
337+ {
338+ final AttributedList <Path > list = session .getFeature (ListService .class ).list (bucket , new DisabledListProgressListener ());
339+ assertFalse (list .isEmpty ());
340+ assertEquals (2 , list .size ());
341+ // /<bucket>/d/
342+ assertNotNull (list .find (new SimplePathPredicate (new Path (bucket , "d" , EnumSet .of (Path .Type .directory , AbstractPath .Type .placeholder )))));
343+ // /<bucket>/vault.uvf
344+ assertNotNull (list .find (new SimplePathPredicate (new Path (bucket , PreferencesFactory .get ().getProperty ("cryptomator.vault.config.filename" ), EnumSet .of (Path .Type .file )))));
345+ }
346+ {
347+ // level 2: /<bucket>/d/
348+ final AttributedList <Path > level2List = session .getFeature (ListService .class ).list (new Path (bucket , "d" , EnumSet .of (Path .Type .directory , AbstractPath .Type .placeholder )), new DisabledListProgressListener ());
349+ assertFalse (level2List .isEmpty ());
350+ assertEquals (2 , level2List .size ());
351+ for (final Path level3 : level2List ) {
352+ // level 3: /<bucket>/d/<2-letter-folder>/
353+ final AttributedList <Path > level3List = session .getFeature (ListService .class ).list (level3 , new DisabledListProgressListener ());
354+ // by hashing, only 1 sub-folder expected
355+ assertEquals (1 , level3List .size ());
356+ for (final Path level4 : level3List ) {
357+ // level 4: /<bucket>/d/<2-letter-folder>/<30-letter-folder/
358+ final AttributedList <Path > level4List = session .getFeature (ListService .class ).list (level4 , new DisabledListProgressListener ());
359+ assertTrue (level4List .toStream ().map (Path ::getName ).allMatch (n -> n .endsWith (".uvf" )));
360+ // empty sub-folder
361+ log .info ("level4List.size()={}" , level4List .size ());
362+ assert (level4List .size () >= 2 );
363+ // root folder contains two files and a sub-folder
364+ assertTrue (level4List .size () <= 3 );
365+ if (level4List .size () == 2 ) {
366+ // MiniO versioned API returns a first version with the file content and a second empty version upon deletion
367+ assertTrue (level4List .toStream ().allMatch (p -> p .attributes ().isDuplicate ()));
368+ }
369+ else if (level4List .size () == 3 ) {
370+ // the root directory -> contains two files...
371+ assertEquals (2 , level4List .toStream ().map (p -> p .isFile () && p .getName ().endsWith (".uvf" )).filter (Boolean ::booleanValue ).count ());
372+ assertEquals (1 , level4List .toStream ().map (p -> p .isDirectory () && p .getName ().endsWith (".uvf" )).filter (Boolean ::booleanValue ).count ());
373+ // ... and a subfolder with a dir.uvf in it
374+ final Path level5 = level4List .toStream ().filter (Path ::isDirectory ).findFirst ().get ();
375+ final AttributedList <Path > level5list = session .getFeature (ListService .class ).list (level5 , new DisabledListProgressListener ());
376+ assertEquals (1 , level5list .size ());
377+ final Path level6 = level5list .get (0 );
378+ assertEquals ("dir.uvf" , level6 .getName ());
379+ assertTrue (level6 .isFile ());
380+ }
381+ }
382+ }
383+ }
326384 }
327385 }
328386 finally {
0 commit comments