diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSCopyFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSCopyFeature.java index cd95e48eb41..f78469f00c5 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSCopyFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSCopyFeature.java @@ -59,14 +59,14 @@ public SDSCopyFeature(final SDSSession session, final SDSNodeIdProvider nodeid) @Override public Path copy(final Path source, final Path target, final TransferStatus status, final ConnectionCallback callback, final StreamListener listener) throws BackgroundException { try { - new NodesApi(session.getClient()).copyNodes( + nodeid.retry(target.getParent(), () -> new NodesApi(session.getClient()).copyNodes( new CopyNodesRequest() .resolutionStrategy(CopyNodesRequest.ResolutionStrategyEnum.OVERWRITE) .addItemsItem(new CopyNode().id(Long.parseLong(nodeid.getVersionId(source)))) .keepShareLinks(new HostPreferences(session.getHost()).getBoolean("sds.upload.sharelinks.keep")), // Target Parent Node ID Long.parseLong(nodeid.getVersionId(target.getParent())), - StringUtils.EMPTY, null); + StringUtils.EMPTY, null)); listener.sent(status.getLength()); nodeid.cache(target, null); final PathAttributes attributes = new SDSAttributesFinderFeature(session, nodeid).find(target); diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectS3MultipartWriteFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectS3MultipartWriteFeature.java index 60a5c2ef389..682b2622361 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectS3MultipartWriteFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectS3MultipartWriteFeature.java @@ -97,12 +97,12 @@ public SDSDirectS3MultipartWriteFeature(final SDSSession session, final SDSNodeI @Override public HttpResponseOutputStream write(final Path file, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException { try { - final CreateFileUploadRequest createFileUploadRequest = new CreateFileUploadRequest() + final CreateFileUploadRequest createFileUploadRequest = nodeid.retry(file.getParent(), () -> new CreateFileUploadRequest() .directS3Upload(true) .timestampModification(status.getModified() != null ? new DateTime(status.getModified()) : null) .timestampCreation(status.getCreated() != null ? new DateTime(status.getCreated()) : null) .parentId(Long.parseLong(nodeid.getVersionId(file.getParent()))) - .name(file.getName()); + .name(file.getName())); final CreateFileUploadResponse createFileUploadResponse = new NodesApi(session.getClient()) .createFileUploadChannel(createFileUploadRequest, StringUtils.EMPTY); log.debug("upload started for {} with response {}", file, createFileUploadResponse); diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectS3UploadFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectS3UploadFeature.java index 0145fe5ab5f..b6addf5b1e4 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectS3UploadFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectS3UploadFeature.java @@ -121,13 +121,13 @@ public Node upload(final Path file, final Local local, final BandwidthThrottle t else { in = local.getInputStream(); } - final CreateFileUploadRequest createFileUploadRequest = new CreateFileUploadRequest() + final CreateFileUploadRequest createFileUploadRequest = nodeid.retry(file.getParent(), () -> new CreateFileUploadRequest() .directS3Upload(true) .timestampModification(status.getModified() != null ? new DateTime(status.getModified()) : null) .timestampCreation(status.getCreated() != null ? new DateTime(status.getCreated()) : null) .size(TransferStatus.UNKNOWN_LENGTH == status.getLength() ? null : status.getLength()) .parentId(Long.parseLong(nodeid.getVersionId(file.getParent()))) - .name(file.getName()); + .name(file.getName())); final CreateFileUploadResponse createFileUploadResponse = new NodesApi(session.getClient()) .createFileUploadChannel(createFileUploadRequest, StringUtils.EMPTY); log.debug("upload started for {} with response {}", file, createFileUploadResponse); diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectoryFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectoryFeature.java index b4f71adba4d..5820e7567a0 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectoryFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectoryFeature.java @@ -69,10 +69,9 @@ public Path mkdir(final Path folder, final TransferStatus status) throws Backgro } private Path createFolder(final Path folder) throws BackgroundException, ApiException { - final CreateFolderRequest folderRequest = new CreateFolderRequest(); - folderRequest.setParentId(Long.parseLong(nodeid.getVersionId(folder.getParent()))); - folderRequest.setName(folder.getName()); - final Node node = new NodesApi(session.getClient()).createFolder(folderRequest, StringUtils.EMPTY, null); + final Node node = nodeid.retry(folder.getParent(), () -> new NodesApi(session.getClient()).createFolder(new CreateFolderRequest() + .parentId(Long.parseLong(nodeid.getVersionId(folder.getParent()))) + .name(folder.getName()), StringUtils.EMPTY, null)); nodeid.cache(folder, String.valueOf(node.getId())); return folder.withAttributes(new SDSAttributesAdapter(session).toAttributes(node)); } diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMoveFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMoveFeature.java index 09d5b971c1a..133468ab0f8 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMoveFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMoveFeature.java @@ -91,13 +91,13 @@ public Path move(final Path file, final Path renamed, final TransferStatus statu } else { // Move to different parent - new NodesApi(session.getClient()).moveNodes( + nodeid.retry(renamed.getParent(), () -> new NodesApi(session.getClient()).moveNodes( new MoveNodesRequest() .resolutionStrategy(MoveNodesRequest.ResolutionStrategyEnum.OVERWRITE) .addItemsItem(new MoveNode().id(nodeId).name(renamed.getName())) .keepShareLinks(new HostPreferences(session.getHost()).getBoolean("sds.upload.sharelinks.keep")), Long.parseLong(nodeid.getVersionId(renamed.getParent())), - StringUtils.EMPTY, null); + StringUtils.EMPTY, null)); } nodeid.cache(renamed, file.attributes().getVersionId()); nodeid.cache(file, null); diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSNodeIdProvider.java b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSNodeIdProvider.java index 1563ea0bc58..e95a818fb35 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSNodeIdProvider.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSNodeIdProvider.java @@ -30,9 +30,12 @@ import ch.cyberduck.core.unicode.UnicodeNormalizer; import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpStatus; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.concurrent.Callable; + public class SDSNodeIdProvider extends CachingVersionIdProvider implements VersionIdProvider { private static final Logger log = LogManager.getLogger(SDSNodeIdProvider.class); @@ -107,4 +110,25 @@ protected String getNodeId(final Path file, final int chunksize) throws Backgrou throw new SDSExceptionMappingService(this).map("Failure to read attributes of {0}", e, file); } } + + public V retry(final Path file, final ApiExceptionCallable callable) throws ApiException, BackgroundException { + try { + return callable.call(); + } + catch(ApiException e) { + switch(e.getCode()) { + case HttpStatus.SC_NOT_FOUND: + // Parent directory not found. Try again with resetting node id cache + this.cache(file, null); + log.warn("Retry {}", callable); + return callable.call(); + } + throw e; + } + } + + public interface ApiExceptionCallable extends Callable { + @Override + T call() throws ApiException, BackgroundException; + } } diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSVersioningFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSVersioningFeature.java index 7b08bf34273..e6bf8545787 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSVersioningFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSVersioningFeature.java @@ -62,7 +62,7 @@ public void revert(final Path file) throws BackgroundException { .resolutionStrategy(RestoreDeletedNodesRequest.ResolutionStrategyEnum.OVERWRITE) .keepShareLinks(new HostPreferences(session.getHost()).getBoolean("sds.upload.sharelinks.keep")) .addDeletedNodeIdsItem(Long.parseLong(nodeid.getVersionId(file))) - .parentId(Long.parseLong(nodeid.getVersionId(file.getParent()))), StringUtils.EMPTY); + .parentId(Long.parseLong(nodeid.getVersionId(file.getParent()))), StringUtils.EMPTY);//todo } catch(ApiException e) { throw new SDSExceptionMappingService(nodeid).map("Failure to write attributes of {0}", e, file); @@ -81,10 +81,11 @@ public AttributedList list(final Path file, final ListProgressListener lis DeletedNodeVersionsList nodes; final AttributedList versions = new AttributedList<>(); do { - nodes = new NodesApi(session.getClient()).requestDeletedNodeVersions( + final int range = offset; + nodes = nodeid.retry(file.getParent(), () -> new NodesApi(session.getClient()).requestDeletedNodeVersions( Long.parseLong(nodeid.getVersionId(file.getParent())), file.isFile() ? "file" : "folder", file.getName(), StringUtils.EMPTY, "updatedAt:desc", - offset, chunksize, null); + range, chunksize, null)); for(DeletedNode item : nodes.getItems()) { versions.add(new Path(file.getParent(), file.getName(), file.getType(), new SDSAttributesAdapter(session).toAttributes(item))); diff --git a/dracoon/src/test/java/ch/cyberduck/core/sds/SDSDirectoryFeatureTest.java b/dracoon/src/test/java/ch/cyberduck/core/sds/SDSDirectoryFeatureTest.java index 46c54457b45..7ebe32120d9 100644 --- a/dracoon/src/test/java/ch/cyberduck/core/sds/SDSDirectoryFeatureTest.java +++ b/dracoon/src/test/java/ch/cyberduck/core/sds/SDSDirectoryFeatureTest.java @@ -18,13 +18,16 @@ import ch.cyberduck.core.AlphanumericRandomStringService; import ch.cyberduck.core.DisabledLoginCallback; import ch.cyberduck.core.Path; -import ch.cyberduck.core.RandomStringService; import ch.cyberduck.core.exception.ConflictException; import ch.cyberduck.core.features.Delete; +import ch.cyberduck.core.sds.io.swagger.client.api.NodesApi; +import ch.cyberduck.core.sds.io.swagger.client.model.CreateFolderRequest; +import ch.cyberduck.core.sds.io.swagger.client.model.Node; import ch.cyberduck.core.shared.DefaultFindFeature; import ch.cyberduck.core.transfer.TransferStatus; import ch.cyberduck.test.IntegrationTest; +import org.apache.commons.lang3.StringUtils; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -47,19 +50,30 @@ public void testCreateDirectory() throws Exception { assertThrows(ConflictException.class, () -> new SDSDirectoryFeature(session, nodeid).mkdir(test, new TransferStatus())); assertNotNull(test.attributes().getVersionId()); assertTrue(new DefaultFindFeature(session).find(test)); - new SDSDeleteFeature(session, nodeid).delete(Arrays.asList(test, room), new DisabledLoginCallback(), new Delete.DisabledCallback()); - assertFalse(new DefaultFindFeature(session).find(test)); + // Replace directory on server with same name + new NodesApi(session.getClient()).removeNode(Long.parseLong(test.attributes().getVersionId()), StringUtils.EMPTY); + final CreateFolderRequest folderRequest = new CreateFolderRequest(); + folderRequest.setParentId(Long.parseLong(room.attributes().getVersionId())); + folderRequest.setName(test.getName()); + // New node for directory with same nmae + final Node node = new NodesApi(session.getClient()).createFolder(folderRequest, StringUtils.EMPTY, null); + assertNotEquals(test.attributes().getVersionId(), node.getId().toString()); + // Attempt to create subdirectory referencing previous node id + final Path subdir = new SDSDirectoryFeature(session, nodeid).mkdir(new Path(test, + new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.directory)), new TransferStatus()); + assertEquals(node.getId().toString(), nodeid.getVersionId(test)); + assertEquals(test.attributes().getVersionId(), node.getId().toString()); + new SDSDeleteFeature(session, nodeid).delete(Arrays.asList(subdir, test, room), new DisabledLoginCallback(), new Delete.DisabledCallback()); } @Test public void testCreateDataRoom() throws Exception { - final RandomStringService randomStringService = new AlphanumericRandomStringService(); final SDSNodeIdProvider nodeid = new SDSNodeIdProvider(session); final Path room = new SDSDirectoryFeature(session, nodeid).mkdir( - new Path(new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.directory, Path.Type.volume)), new TransferStatus()); + new Path(new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.directory, Path.Type.volume)), new TransferStatus()); assertNotNull(room.attributes().getVersionId()); assertTrue(new DefaultFindFeature(session).find(room)); - new SDSDeleteFeature(session, nodeid).delete(Collections.singletonList(room), new DisabledLoginCallback(), new Delete.DisabledCallback()); + new SDSDeleteFeature(session, nodeid).delete(Collections.singletonList(room), new DisabledLoginCallback(), new Delete.DisabledCallback()); assertFalse(new DefaultFindFeature(session).find(room)); } }