From eff8910d4857b51959fa12769bfa2d305d9c8109 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Fri, 26 Apr 2024 11:12:42 +0200 Subject: [PATCH 1/3] Add default method to disable support for append. --- .../ch/cyberduck/core/b2/B2LargeUploadWriteFeature.java | 5 ----- .../ch/cyberduck/core/box/BoxChunkedWriteFeature.java | 5 ----- .../ch/cyberduck/core/box/BoxMultipartWriteFeature.java | 5 ----- .../main/java/ch/cyberduck/core/box/BoxWriteFeature.java | 5 ----- .../cyberduck/core/brick/BrickMultipartWriteFeature.java | 5 ----- .../java/ch/cyberduck/core/brick/BrickWriteFeature.java | 5 ----- core/src/main/java/ch/cyberduck/core/features/Write.java | 4 +++- .../ch/cyberduck/core/http/AbstractHttpWriteFeature.java | 3 +-- .../ch/cyberduck/core/shared/BufferWriteFeature.java | 5 ----- .../core/cryptomator/features/CryptoWriteFeature.java | 5 ----- .../core/sds/SDSDirectS3MultipartWriteFeature.java | 5 ----- .../ch/cyberduck/core/sds/SDSDirectS3WriteFeature.java | 5 ----- .../ch/cyberduck/core/sds/SDSMultipartWriteFeature.java | 5 ----- .../ch/cyberduck/core/dropbox/DropboxWriteFeature.java | 5 ----- .../ch/cyberduck/core/eue/EueMultipartWriteFeature.java | 5 ----- .../ch/cyberduck/core/eue/EueThresholdWriteFeature.java | 5 ----- .../main/java/ch/cyberduck/core/eue/EueWriteFeature.java | 5 ----- .../ch/cyberduck/core/googledrive/DriveWriteFeature.java | 5 ----- .../core/googlestorage/GoogleStorageWriteFeature.java | 5 ----- .../java/ch/cyberduck/core/manta/MantaWriteFeature.java | 9 --------- .../core/onedrive/features/GraphWriteFeature.java | 5 ----- .../core/openstack/SwiftLargeUploadWriteFeature.java | 5 ----- .../ch/cyberduck/core/s3/S3MultipartWriteFeature.java | 5 ----- .../main/java/ch/cyberduck/core/smb/SMBWriteFeature.java | 5 ----- .../core/storegate/StoregateMultipartWriteFeature.java | 5 ----- .../cyberduck/core/storegate/StoregateWriteFeature.java | 5 ----- 26 files changed, 4 insertions(+), 127 deletions(-) diff --git a/backblaze/src/main/java/ch/cyberduck/core/b2/B2LargeUploadWriteFeature.java b/backblaze/src/main/java/ch/cyberduck/core/b2/B2LargeUploadWriteFeature.java index 9799967f010..eed517a7c32 100644 --- a/backblaze/src/main/java/ch/cyberduck/core/b2/B2LargeUploadWriteFeature.java +++ b/backblaze/src/main/java/ch/cyberduck/core/b2/B2LargeUploadWriteFeature.java @@ -88,11 +88,6 @@ public BaseB2Response getStatus() { }; } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - private final class LargeUploadOutputStream extends OutputStream { final List completed = new ArrayList<>(); private final Path file; diff --git a/box/src/main/java/ch/cyberduck/core/box/BoxChunkedWriteFeature.java b/box/src/main/java/ch/cyberduck/core/box/BoxChunkedWriteFeature.java index c9f8f82f839..8c75152bb67 100644 --- a/box/src/main/java/ch/cyberduck/core/box/BoxChunkedWriteFeature.java +++ b/box/src/main/java/ch/cyberduck/core/box/BoxChunkedWriteFeature.java @@ -106,9 +106,4 @@ public long getContentLength() { public ChecksumCompute checksum(final Path file, final TransferStatus status) { return new SHA1ChecksumCompute(); } - - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } } diff --git a/box/src/main/java/ch/cyberduck/core/box/BoxMultipartWriteFeature.java b/box/src/main/java/ch/cyberduck/core/box/BoxMultipartWriteFeature.java index 76987170df3..edcdd2e5007 100644 --- a/box/src/main/java/ch/cyberduck/core/box/BoxMultipartWriteFeature.java +++ b/box/src/main/java/ch/cyberduck/core/box/BoxMultipartWriteFeature.java @@ -163,9 +163,4 @@ public File getResult() { public ChecksumCompute checksum(final Path file, final TransferStatus status) { return new SHA1ChecksumCompute(); } - - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } } diff --git a/box/src/main/java/ch/cyberduck/core/box/BoxWriteFeature.java b/box/src/main/java/ch/cyberduck/core/box/BoxWriteFeature.java index 73cdddeb198..af99f443b02 100644 --- a/box/src/main/java/ch/cyberduck/core/box/BoxWriteFeature.java +++ b/box/src/main/java/ch/cyberduck/core/box/BoxWriteFeature.java @@ -145,11 +145,6 @@ public ChecksumCompute checksum(final Path file, final TransferStatus status) { return new SHA1ChecksumCompute(); } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - @Override public EnumSet features(final Path file) { return EnumSet.of(Flags.timestamp); diff --git a/brick/src/main/java/ch/cyberduck/core/brick/BrickMultipartWriteFeature.java b/brick/src/main/java/ch/cyberduck/core/brick/BrickMultipartWriteFeature.java index cacae912289..7c3cdfef4a6 100644 --- a/brick/src/main/java/ch/cyberduck/core/brick/BrickMultipartWriteFeature.java +++ b/brick/src/main/java/ch/cyberduck/core/brick/BrickMultipartWriteFeature.java @@ -82,11 +82,6 @@ public FileEntity getStatus() { }; } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - private final class MultipartOutputStream extends OutputStream { private final BrickWriteFeature writer = new BrickWriteFeature(session); private final Path file; diff --git a/brick/src/main/java/ch/cyberduck/core/brick/BrickWriteFeature.java b/brick/src/main/java/ch/cyberduck/core/brick/BrickWriteFeature.java index 62ad3561104..2fb8ba72579 100644 --- a/brick/src/main/java/ch/cyberduck/core/brick/BrickWriteFeature.java +++ b/brick/src/main/java/ch/cyberduck/core/brick/BrickWriteFeature.java @@ -177,9 +177,4 @@ public ChecksumCompute checksum(final Path file, final TransferStatus status) { public EnumSet features(final Path file) { return EnumSet.of(Flags.timestamp); } - - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } } diff --git a/core/src/main/java/ch/cyberduck/core/features/Write.java b/core/src/main/java/ch/cyberduck/core/features/Write.java index e02d1909e81..1816c87e541 100644 --- a/core/src/main/java/ch/cyberduck/core/features/Write.java +++ b/core/src/main/java/ch/cyberduck/core/features/Write.java @@ -48,7 +48,9 @@ public interface Write { * @param status Transfer status including attributes of file on server and size of file to write * @return True if can append to existing file */ - Append append(Path file, TransferStatus status) throws BackgroundException; + default Append append(Path file, TransferStatus status) throws BackgroundException { + return new Append(false).withStatus(status); + } /** * @return True if supporting random writes with arbitrary offset and length diff --git a/core/src/main/java/ch/cyberduck/core/http/AbstractHttpWriteFeature.java b/core/src/main/java/ch/cyberduck/core/http/AbstractHttpWriteFeature.java index 86ce15da9a5..c5e4d2432d1 100644 --- a/core/src/main/java/ch/cyberduck/core/http/AbstractHttpWriteFeature.java +++ b/core/src/main/java/ch/cyberduck/core/http/AbstractHttpWriteFeature.java @@ -25,7 +25,6 @@ import ch.cyberduck.core.exception.ConnectionCanceledException; import ch.cyberduck.core.features.AttributesAdapter; import ch.cyberduck.core.preferences.PreferencesFactory; -import ch.cyberduck.core.shared.AppendWriteFeature; import ch.cyberduck.core.threading.NamedThreadFactory; import ch.cyberduck.core.transfer.TransferStatus; import ch.cyberduck.core.worker.DefaultExceptionMappingService; @@ -41,7 +40,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadFactory; -public abstract class AbstractHttpWriteFeature extends AppendWriteFeature implements HttpWriteFeature { +public abstract class AbstractHttpWriteFeature implements HttpWriteFeature { private static final Logger log = LogManager.getLogger(AbstractHttpWriteFeature.class); private final AttributesAdapter attributes; diff --git a/core/src/main/java/ch/cyberduck/core/shared/BufferWriteFeature.java b/core/src/main/java/ch/cyberduck/core/shared/BufferWriteFeature.java index 00523cafc93..d09aa038038 100644 --- a/core/src/main/java/ch/cyberduck/core/shared/BufferWriteFeature.java +++ b/core/src/main/java/ch/cyberduck/core/shared/BufferWriteFeature.java @@ -112,9 +112,4 @@ public boolean retry(final BackgroundException failure, final ProgressListener p } }); } - - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } } diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoWriteFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoWriteFeature.java index 2f5feba4178..8dbfa7f7000 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoWriteFeature.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoWriteFeature.java @@ -81,11 +81,6 @@ public Reply getStatus() throws BackgroundException { } } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - @Override public EnumSet features(final Path file) { return proxy.features(file); 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 fd1088e6f54..53a9feb944d 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectS3MultipartWriteFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectS3MultipartWriteFeature.java @@ -284,11 +284,6 @@ public String toString() { } } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - @Override public EnumSet features(final Path file) { return EnumSet.of(Flags.timestamp); diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectS3WriteFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectS3WriteFeature.java index fca5184f969..fb787ec0f77 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectS3WriteFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDirectS3WriteFeature.java @@ -106,11 +106,6 @@ public long getContentLength() { }); } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - @Override public EnumSet features(final Path file) { return EnumSet.of(Flags.timestamp); diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMultipartWriteFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMultipartWriteFeature.java index 627decb6957..919d73a2527 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMultipartWriteFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMultipartWriteFeature.java @@ -104,11 +104,6 @@ protected void handleIOException(final IOException e) throws IOException { }; } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - @Override public EnumSet features(final Path file) { return EnumSet.of(Flags.timestamp); diff --git a/dropbox/src/main/java/ch/cyberduck/core/dropbox/DropboxWriteFeature.java b/dropbox/src/main/java/ch/cyberduck/core/dropbox/DropboxWriteFeature.java index 32591c2fcc2..053d44caf47 100644 --- a/dropbox/src/main/java/ch/cyberduck/core/dropbox/DropboxWriteFeature.java +++ b/dropbox/src/main/java/ch/cyberduck/core/dropbox/DropboxWriteFeature.java @@ -62,11 +62,6 @@ public DropboxWriteFeature(final DropboxSession session, final Long chunksize) { this.containerService = new DropboxPathContainerService(session); } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - @Override public HttpResponseOutputStream write(final Path file, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException { try { diff --git a/eue/src/main/java/ch/cyberduck/core/eue/EueMultipartWriteFeature.java b/eue/src/main/java/ch/cyberduck/core/eue/EueMultipartWriteFeature.java index 8d26e8797e4..b6e5c12fa09 100644 --- a/eue/src/main/java/ch/cyberduck/core/eue/EueMultipartWriteFeature.java +++ b/eue/src/main/java/ch/cyberduck/core/eue/EueMultipartWriteFeature.java @@ -72,11 +72,6 @@ public EueMultipartWriteFeature(final EueSession session, final EueResourceIdPro this.fileid = fileid; } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - @Override public HttpResponseOutputStream write(final Path file, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException { String uploadUri; diff --git a/eue/src/main/java/ch/cyberduck/core/eue/EueThresholdWriteFeature.java b/eue/src/main/java/ch/cyberduck/core/eue/EueThresholdWriteFeature.java index 0903cee8586..893a2180d7c 100644 --- a/eue/src/main/java/ch/cyberduck/core/eue/EueThresholdWriteFeature.java +++ b/eue/src/main/java/ch/cyberduck/core/eue/EueThresholdWriteFeature.java @@ -56,11 +56,6 @@ public ChecksumCompute checksum(final Path file, final TransferStatus status) { return new SHA256ChecksumCompute(); } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - @Override public EnumSet features(final Path file) { return EnumSet.of(Flags.timestamp); diff --git a/eue/src/main/java/ch/cyberduck/core/eue/EueWriteFeature.java b/eue/src/main/java/ch/cyberduck/core/eue/EueWriteFeature.java index c95b3197f05..1a9ca14afbb 100644 --- a/eue/src/main/java/ch/cyberduck/core/eue/EueWriteFeature.java +++ b/eue/src/main/java/ch/cyberduck/core/eue/EueWriteFeature.java @@ -153,11 +153,6 @@ public ChecksumCompute checksum(final Path file, final TransferStatus status) { return new SHA256ChecksumCompute(); } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - @Override public EnumSet features(final Path file) { return EnumSet.of(Flags.timestamp); diff --git a/googledrive/src/main/java/ch/cyberduck/core/googledrive/DriveWriteFeature.java b/googledrive/src/main/java/ch/cyberduck/core/googledrive/DriveWriteFeature.java index 0616f1195f0..453a092fd8f 100644 --- a/googledrive/src/main/java/ch/cyberduck/core/googledrive/DriveWriteFeature.java +++ b/googledrive/src/main/java/ch/cyberduck/core/googledrive/DriveWriteFeature.java @@ -65,11 +65,6 @@ public DriveWriteFeature(final DriveSession session, final DriveFileIdProvider f this.fileid = fileid; } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - @Override public EnumSet features(final Path file) { return EnumSet.of(Flags.timestamp); diff --git a/googlestorage/src/main/java/ch/cyberduck/core/googlestorage/GoogleStorageWriteFeature.java b/googlestorage/src/main/java/ch/cyberduck/core/googlestorage/GoogleStorageWriteFeature.java index f4c24fca7f2..0efcef0726b 100644 --- a/googlestorage/src/main/java/ch/cyberduck/core/googlestorage/GoogleStorageWriteFeature.java +++ b/googlestorage/src/main/java/ch/cyberduck/core/googlestorage/GoogleStorageWriteFeature.java @@ -192,11 +192,6 @@ public long getContentLength() { return this.write(file, status, command); } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - @Override public EnumSet features(final Path file) { return EnumSet.of(Flags.timestamp); diff --git a/manta/src/main/java/ch/cyberduck/core/manta/MantaWriteFeature.java b/manta/src/main/java/ch/cyberduck/core/manta/MantaWriteFeature.java index 27476726098..8907f25e0ff 100644 --- a/manta/src/main/java/ch/cyberduck/core/manta/MantaWriteFeature.java +++ b/manta/src/main/java/ch/cyberduck/core/manta/MantaWriteFeature.java @@ -17,7 +17,6 @@ import ch.cyberduck.core.ConnectionCallback; import ch.cyberduck.core.Path; -import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.Write; import ch.cyberduck.core.io.StatusOutputStream; import ch.cyberduck.core.io.VoidStatusOutputStream; @@ -44,12 +43,4 @@ public StatusOutputStream write(final Path file, final TransferStatus stat final OutputStream putStream = session.getClient().putAsOutputStream(file.getAbsolute()); return new VoidStatusOutputStream(putStream); } - - /** - * Manta does not support raw append operations. - */ - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } } diff --git a/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphWriteFeature.java b/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphWriteFeature.java index 292f74180f1..62177c41039 100644 --- a/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphWriteFeature.java +++ b/onedrive/src/main/java/ch/cyberduck/core/onedrive/features/GraphWriteFeature.java @@ -91,11 +91,6 @@ public DriveItem.Metadata getStatus() { } } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - private final class ChunkedOutputStream extends OutputStream { private final UploadSession upload; private final Path file; diff --git a/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftLargeUploadWriteFeature.java b/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftLargeUploadWriteFeature.java index c0241dbbd02..8f8c8cdaac7 100644 --- a/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftLargeUploadWriteFeature.java +++ b/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftLargeUploadWriteFeature.java @@ -84,11 +84,6 @@ public StorageObject getStatus() { }; } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - private final class LargeUploadOutputStream extends OutputStream { private final List completed = new ArrayList<>(); private final Path file; diff --git a/s3/src/main/java/ch/cyberduck/core/s3/S3MultipartWriteFeature.java b/s3/src/main/java/ch/cyberduck/core/s3/S3MultipartWriteFeature.java index c956c4577e0..fe4ba910293 100644 --- a/s3/src/main/java/ch/cyberduck/core/s3/S3MultipartWriteFeature.java +++ b/s3/src/main/java/ch/cyberduck/core/s3/S3MultipartWriteFeature.java @@ -94,11 +94,6 @@ public StorageObject getStatus() { }; } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - @Override public ChecksumCompute checksum(final Path file, final TransferStatus status) { return ChecksumComputeFactory.get(HashAlgorithm.sha256); diff --git a/smb/src/main/java/ch/cyberduck/core/smb/SMBWriteFeature.java b/smb/src/main/java/ch/cyberduck/core/smb/SMBWriteFeature.java index 5a1717d89f3..3bb870b45c9 100644 --- a/smb/src/main/java/ch/cyberduck/core/smb/SMBWriteFeature.java +++ b/smb/src/main/java/ch/cyberduck/core/smb/SMBWriteFeature.java @@ -68,11 +68,6 @@ public StatusOutputStream write(final Path file, final TransferStatus stat } } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - private final class SMBOutputStream extends ProxyOutputStream { private final Path file; private final File handle; diff --git a/storegate/src/main/java/ch/cyberduck/core/storegate/StoregateMultipartWriteFeature.java b/storegate/src/main/java/ch/cyberduck/core/storegate/StoregateMultipartWriteFeature.java index f3085a29302..a472925d8e7 100644 --- a/storegate/src/main/java/ch/cyberduck/core/storegate/StoregateMultipartWriteFeature.java +++ b/storegate/src/main/java/ch/cyberduck/core/storegate/StoregateMultipartWriteFeature.java @@ -63,11 +63,6 @@ public StoregateMultipartWriteFeature(final StoregateSession session, final Stor this.fileid = fileid; } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - @Override public HttpResponseOutputStream write(final Path file, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException { final String location = new StoregateWriteFeature(session, fileid).start(file, status); diff --git a/storegate/src/main/java/ch/cyberduck/core/storegate/StoregateWriteFeature.java b/storegate/src/main/java/ch/cyberduck/core/storegate/StoregateWriteFeature.java index 1b322d5313d..2b448d552db 100644 --- a/storegate/src/main/java/ch/cyberduck/core/storegate/StoregateWriteFeature.java +++ b/storegate/src/main/java/ch/cyberduck/core/storegate/StoregateWriteFeature.java @@ -69,11 +69,6 @@ public StoregateWriteFeature(final StoregateSession session, final StoregateIdPr this.fileid = fileid; } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - @Override public EnumSet features(final Path file) { return EnumSet.of(Flags.timestamp); From aa9c5d941b999949af268c20959a37a890e04ab9 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Fri, 26 Apr 2024 18:16:49 +0200 Subject: [PATCH 2/3] Move append to Upload feature. --- .../ch/cyberduck/core/azure/AzureSession.java | 4 ++ .../core/azure/AzureUploadFeature.java | 50 ++++++++++++++++++ .../core/azure/AzureWriteFeature.java | 29 +++-------- .../core/azure/AzureWriteFeatureTest.java | 5 -- .../cryptomator/AzureWriteFeatureTest.java | 1 - .../core/b2/B2AttributesFinderFeature.java | 4 +- .../core/b2/B2LargeUploadService.java | 14 +++++ .../core/b2/B2ThresholdCopyFeature.java | 2 +- .../core/b2/B2ThresholdUploadService.java | 15 +++--- .../ch/cyberduck/core/b2/B2WriteFeature.java | 17 ------ .../core/b2/B2LargeUploadServiceTest.java | 4 +- .../cyberduck/core/b2/B2WriteFeatureTest.java | 4 -- .../core/cryptomator/B2WriteFeatureTest.java | 1 - .../core/box/BoxThresholdUploadService.java | 5 -- .../brick/BrickThresholdUploadFeature.java | 2 +- .../ch/cyberduck/core/features/Upload.java | 4 +- .../ch/cyberduck/core/features/Write.java | 25 +++------ .../core/http/HttpUploadFeature.java | 5 -- .../core/shared/AppendWriteFeature.java | 32 ------------ .../core/shared/DefaultUploadFeature.java | 5 -- .../core/transfer/upload/ResumeFilter.java | 6 +-- .../registry/VaultRegistryWriteFeature.java | 5 -- .../java/ch/cyberduck/core/NullSession.java | 6 ++- .../ch/cyberduck/core/NullUploadFeature.java | 40 ++++++++++++++ .../ch/cyberduck/core/NullWriteFeature.java | 7 +-- .../core/transfer/UploadTransferTest.java | 7 --- .../upload/RenameExistingFilterTest.java | 12 ----- .../transfer/upload/ResumeFilterTest.java | 7 +-- .../core/ctera/CteraWriteFeatureTest.java | 1 - .../core/sds/SDSDelegatingWriteFeature.java | 8 --- .../triplecrypt/TripleCryptWriteFeature.java | 5 -- .../cryptomator/DropboxWriteFeatureTest.java | 1 - .../core/dropbox/DropboxWriteFeatureTest.java | 2 - .../core/eue/EueThresholdUploadService.java | 5 -- .../core/cryptomator/EueWriteFeatureTest.java | 4 -- .../ch/cyberduck/core/ftp/FTPSession.java | 18 ++----- .../cyberduck/core/ftp/FTPUploadFeature.java | 38 ++++++++++++++ .../cyberduck/core/ftp/FTPWriteFeature.java | 4 +- .../core/cryptomator/FTPWriteFeatureTest.java | 1 - .../core/ftp/FTPUploadFeatureTest.java | 40 ++++++++++++++ .../core/ftp/FTPWriteFeatureTest.java | 9 ---- .../FTPConcurrentTransferWorkerTest.java | 7 +-- .../cryptomator/DriveWriteFeatureTest.java | 2 - .../googledrive/DriveUploadFeatureTest.java | 2 +- .../googledrive/DriveWriteFeatureTest.java | 4 -- .../GoogleStorageWriteFeatureTest.java | 4 -- .../core/irods/IRODSUploadFeature.java | 2 +- .../core/irods/IRODSWriteFeature.java | 4 +- .../core/irods/IRODSWriteFeatureTest.java | 12 ++--- .../core/nio/LocalUploadFeature.java | 38 ++++++++++++++ .../cyberduck/core/nio/LocalWriteFeature.java | 4 +- .../core/nio/LocalUploadFeatureTest.java | 48 +++++++++++++++++ .../core/nio/LocalWriteFeatureTest.java | 12 ----- .../cryptomator/GraphWriteFeatureTest.java | 1 - .../SwiftAttributesFinderFeature.java | 4 +- .../SwiftLargeObjectUploadFeature.java | 25 +++++++++ .../SwiftThresholdUploadService.java | 26 ++++++---- .../core/openstack/SwiftWriteFeature.java | 25 +-------- .../cryptomator/SwiftWriteFeatureTest.java | 2 - .../SwiftLargeObjectUploadFeatureTest.java | 13 +++++ .../core/openstack/SwiftWriteFeatureTest.java | 14 ----- .../core/s3/S3AttributesFinderFeature.java | 4 +- .../core/s3/S3MultipartUploadService.java | 19 +++++++ .../core/s3/S3ThresholdUploadService.java | 29 +++++++---- .../ch/cyberduck/core/s3/S3WriteFeature.java | 26 ---------- .../core/cryptomator/S3WriteFeatureTest.java | 1 - .../core/s3/S3MultipartUploadServiceTest.java | 52 ++++++++++++++----- .../cyberduck/core/s3/S3WriteFeatureTest.java | 24 --------- .../core/smb/SMBReadFeatureTest.java | 1 - .../ch/cyberduck/core/sftp/SFTPSession.java | 3 ++ .../core/sftp/SFTPUploadFeature.java | 38 ++++++++++++++ .../cyberduck/core/sftp/SFTPWriteFeature.java | 4 +- .../core/cryptomator/SFTPReadFeatureTest.java | 1 - .../cryptomator/SFTPWriteFeatureTest.java | 4 -- .../core/sftp/SFTPUploadFeatureTest.java | 42 +++++++++++++++ .../core/sftp/SFTPWriteFeatureTest.java | 10 ---- .../worker/SFTPSingleTransferWorkerTest.java | 32 +++++++----- .../cyberduck/core/tus/TusUploadFeature.java | 2 +- .../cyberduck/core/dav/DAVUploadFeature.java | 11 ++++ .../cyberduck/core/dav/DAVWriteFeature.java | 8 --- .../core/cryptomator/DAVReadFeatureTest.java | 4 +- .../core/cryptomator/DAVWriteFeatureTest.java | 6 ++- .../core/dav/DAVUploadFeatureTest.java | 11 +++- .../core/dav/DAVWriteFeatureTest.java | 12 +---- .../MicrosoftIISDAVLockFeatureTest.java | 3 +- 85 files changed, 614 insertions(+), 436 deletions(-) create mode 100644 azure/src/main/java/ch/cyberduck/core/azure/AzureUploadFeature.java delete mode 100644 core/src/main/java/ch/cyberduck/core/shared/AppendWriteFeature.java create mode 100644 core/src/test/java/ch/cyberduck/core/NullUploadFeature.java create mode 100644 ftp/src/main/java/ch/cyberduck/core/ftp/FTPUploadFeature.java create mode 100644 ftp/src/test/java/ch/cyberduck/core/ftp/FTPUploadFeatureTest.java create mode 100644 nio/src/main/java/ch/cyberduck/core/nio/LocalUploadFeature.java create mode 100644 nio/src/test/java/ch/cyberduck/core/nio/LocalUploadFeatureTest.java create mode 100644 ssh/src/main/java/ch/cyberduck/core/sftp/SFTPUploadFeature.java create mode 100644 ssh/src/test/java/ch/cyberduck/core/sftp/SFTPUploadFeatureTest.java diff --git a/azure/src/main/java/ch/cyberduck/core/azure/AzureSession.java b/azure/src/main/java/ch/cyberduck/core/azure/AzureSession.java index 0fe768a03ea..b4c3eb195c9 100644 --- a/azure/src/main/java/ch/cyberduck/core/azure/AzureSession.java +++ b/azure/src/main/java/ch/cyberduck/core/azure/AzureSession.java @@ -44,6 +44,7 @@ import ch.cyberduck.core.features.Move; import ch.cyberduck.core.features.Read; import ch.cyberduck.core.features.Touch; +import ch.cyberduck.core.features.Upload; import ch.cyberduck.core.features.Write; import ch.cyberduck.core.http.DisabledX509HostnameVerifier; import ch.cyberduck.core.proxy.Proxy; @@ -195,6 +196,9 @@ public T _getFeature(final Class type) { if(type == Read.class) { return (T) new AzureReadFeature(this, context); } + if(type == Upload.class) { + return (T) new AzureUploadFeature(this, context); + } if(type == Write.class) { return (T) new AzureWriteFeature(this, context); } diff --git a/azure/src/main/java/ch/cyberduck/core/azure/AzureUploadFeature.java b/azure/src/main/java/ch/cyberduck/core/azure/AzureUploadFeature.java new file mode 100644 index 00000000000..39d8f6af62a --- /dev/null +++ b/azure/src/main/java/ch/cyberduck/core/azure/AzureUploadFeature.java @@ -0,0 +1,50 @@ +package ch.cyberduck.core.azure; + +/* + * Copyright (c) 2002-2024 iterate GmbH. All rights reserved. + * https://cyberduck.io/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +import ch.cyberduck.core.Path; +import ch.cyberduck.core.PathAttributes; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.Write; +import ch.cyberduck.core.shared.DefaultUploadFeature; +import ch.cyberduck.core.transfer.TransferStatus; + +import com.microsoft.azure.storage.OperationContext; +import com.microsoft.azure.storage.blob.BlobType; + +public class AzureUploadFeature extends DefaultUploadFeature { + + private final AzureSession session; + private final OperationContext context; + + public AzureUploadFeature(final AzureSession session, final OperationContext context) { + super(new AzureWriteFeature(session, context)); + this.session = session; + this.context = context; + } + + @Override + public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { + final Write.Append append = new Write.Append(status.isExists()).withStatus(status); + if(append.append) { + final PathAttributes attr = new AzureAttributesFinderFeature(session, context).find(file); + if(BlobType.APPEND_BLOB == BlobType.valueOf(attr.getCustom().get(AzureAttributesFinderFeature.KEY_BLOB_TYPE))) { + return append; + } + } + return Write.override; + } +} diff --git a/azure/src/main/java/ch/cyberduck/core/azure/AzureWriteFeature.java b/azure/src/main/java/ch/cyberduck/core/azure/AzureWriteFeature.java index 0f274b6a83a..29a7386167a 100644 --- a/azure/src/main/java/ch/cyberduck/core/azure/AzureWriteFeature.java +++ b/azure/src/main/java/ch/cyberduck/core/azure/AzureWriteFeature.java @@ -33,7 +33,6 @@ import ch.cyberduck.core.io.StatusOutputStream; import ch.cyberduck.core.io.VoidStatusOutputStream; import ch.cyberduck.core.preferences.HostPreferences; -import ch.cyberduck.core.shared.AppendWriteFeature; import ch.cyberduck.core.transfer.TransferStatus; import org.apache.commons.lang3.StringUtils; @@ -57,13 +56,13 @@ import com.microsoft.azure.storage.blob.CloudBlockBlob; import com.microsoft.azure.storage.core.SR; -public class AzureWriteFeature extends AppendWriteFeature implements Write { +public class AzureWriteFeature implements Write { private static final Logger log = LogManager.getLogger(AzureWriteFeature.class); private final AzureSession session; private final OperationContext context; private final PathContainerService containerService - = new DirectoryDelimiterPathContainerService(); + = new DirectoryDelimiterPathContainerService(); private final BlobType blobType; public AzureWriteFeature(final AzureSession session, final OperationContext context) { @@ -81,18 +80,6 @@ public ChecksumCompute checksum(final Path file, final TransferStatus status) { return ChecksumComputeFactory.get(HashAlgorithm.md5); } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - final Append append = super.append(file, status); - if(append.append) { - final PathAttributes attr = new AzureAttributesFinderFeature(session, context).find(file); - if(BlobType.APPEND_BLOB == BlobType.valueOf(attr.getCustom().get(AzureAttributesFinderFeature.KEY_BLOB_TYPE))) { - return append; - } - } - return Write.override; - } - @Override public StatusOutputStream write(final Path file, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException { try { @@ -100,23 +87,23 @@ public StatusOutputStream write(final Path file, final TransferStatus stat if(status.isExists()) { if(new HostPreferences(session.getHost()).getBoolean("azure.upload.snapshot")) { session.getClient().getContainerReference(containerService.getContainer(file).getName()) - .getBlobReferenceFromServer(containerService.getKey(file)).createSnapshot(); + .getBlobReferenceFromServer(containerService.getKey(file)).createSnapshot(); } if(status.isAppend()) { // Existing append blob type blob = session.getClient().getContainerReference(containerService.getContainer(file).getName()) - .getAppendBlobReference(containerService.getKey(file)); + .getAppendBlobReference(containerService.getKey(file)); } else { // Existing block blob type final PathAttributes attr = new AzureAttributesFinderFeature(session, context).find(file); if(BlobType.APPEND_BLOB == BlobType.valueOf(attr.getCustom().get(AzureAttributesFinderFeature.KEY_BLOB_TYPE))) { blob = session.getClient().getContainerReference(containerService.getContainer(file).getName()) - .getAppendBlobReference(containerService.getKey(file)); + .getAppendBlobReference(containerService.getKey(file)); } else { blob = session.getClient().getContainerReference(containerService.getContainer(file).getName()) - .getBlockBlobReference(containerService.getKey(file)); + .getBlockBlobReference(containerService.getKey(file)); } } } @@ -125,11 +112,11 @@ public StatusOutputStream write(final Path file, final TransferStatus stat switch(blobType) { case APPEND_BLOB: blob = session.getClient().getContainerReference(containerService.getContainer(file).getName()) - .getAppendBlobReference(containerService.getKey(file)); + .getAppendBlobReference(containerService.getKey(file)); break; default: blob = session.getClient().getContainerReference(containerService.getContainer(file).getName()) - .getBlockBlobReference(containerService.getKey(file)); + .getBlockBlobReference(containerService.getKey(file)); } } if(StringUtils.isNotBlank(status.getMime())) { diff --git a/azure/src/test/java/ch/cyberduck/core/azure/AzureWriteFeatureTest.java b/azure/src/test/java/ch/cyberduck/core/azure/AzureWriteFeatureTest.java index 7b25b1ad7c4..a6b20877004 100644 --- a/azure/src/test/java/ch/cyberduck/core/azure/AzureWriteFeatureTest.java +++ b/azure/src/test/java/ch/cyberduck/core/azure/AzureWriteFeatureTest.java @@ -6,7 +6,6 @@ import ch.cyberduck.core.Path; import ch.cyberduck.core.PathAttributes; import ch.cyberduck.core.features.Delete; -import ch.cyberduck.core.features.Write; import ch.cyberduck.core.io.MD5ChecksumCompute; import ch.cyberduck.core.io.StreamCopier; import ch.cyberduck.core.transfer.TransferStatus; @@ -54,7 +53,6 @@ public void testWriteOverrideAppendBlob() throws Exception { final Map metadata = new AzureMetadataFeature(session, context).getMetadata(test); assertEquals("text/plain", metadata.get("Content-Type")); assertEquals("public,max-age=86400", metadata.get("Cache-Control")); - assertEquals(content.length, new AzureWriteFeature(session, context).append(test, status.exists(true).withRemote(attributes)).size, 0L); final byte[] buffer = new byte[content.length]; final InputStream in = new AzureReadFeature(session, context).read(test, new TransferStatus(), new DisabledConnectionCallback()); IOUtils.readFully(in, buffer); @@ -92,9 +90,6 @@ public void testWriteOverrideBlockBlob() throws Exception { final Map metadata = new AzureMetadataFeature(session, context).getMetadata(test); assertEquals("text/plain", metadata.get("Content-Type")); assertEquals("public,max-age=86400", metadata.get("Cache-Control")); - final Write.Append append = new AzureWriteFeature(session, context).append(test, status.withRemote(attributes)); - assertFalse(append.append); - assertEquals(0L, append.size, 0L); final byte[] buffer = new byte[content.length]; final InputStream in = new AzureReadFeature(session, context).read(test, new TransferStatus(), new DisabledConnectionCallback()); IOUtils.readFully(in, buffer); diff --git a/azure/src/test/java/ch/cyberduck/core/cryptomator/AzureWriteFeatureTest.java b/azure/src/test/java/ch/cyberduck/core/cryptomator/AzureWriteFeatureTest.java index 0ad3ed2310a..ab29a5bfde7 100644 --- a/azure/src/test/java/ch/cyberduck/core/cryptomator/AzureWriteFeatureTest.java +++ b/azure/src/test/java/ch/cyberduck/core/cryptomator/AzureWriteFeatureTest.java @@ -87,7 +87,6 @@ public void testWrite() throws Exception { assertTrue(cryptomator.getFeature(session, Find.class, new AzureFindFeature(session, null)).find(test)); final PathAttributes attributes = new CryptoListService(session, new AzureListService(session, context), cryptomator).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes(); assertEquals(content.length, attributes.getSize()); - assertEquals(content.length, new CryptoWriteFeature<>(session, new AzureWriteFeature(session, context), cryptomator).append(test, status.withRemote(attributes)).size, 0L); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); final InputStream in = new CryptoReadFeature(session, new AzureReadFeature(session, context), cryptomator).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()); new StreamCopier(status, status).transfer(in, buffer); diff --git a/backblaze/src/main/java/ch/cyberduck/core/b2/B2AttributesFinderFeature.java b/backblaze/src/main/java/ch/cyberduck/core/b2/B2AttributesFinderFeature.java index 014be3181b0..784dc9f00d7 100644 --- a/backblaze/src/main/java/ch/cyberduck/core/b2/B2AttributesFinderFeature.java +++ b/backblaze/src/main/java/ch/cyberduck/core/b2/B2AttributesFinderFeature.java @@ -68,9 +68,9 @@ public PathAttributes find(final Path file, final ListProgressListener listener) } if(file.getType().contains(Path.Type.upload)) { // Pending large file upload - final Write.Append append = new B2WriteFeature(session, fileid).append(file, new TransferStatus()); + final Write.Append append = new B2LargeUploadService(session, fileid, new B2WriteFeature(session, fileid)).append(file, new TransferStatus()); if(append.append) { - return new PathAttributes().withSize(append.size); + return new PathAttributes().withSize(append.offset); } return PathAttributes.EMPTY; } diff --git a/backblaze/src/main/java/ch/cyberduck/core/b2/B2LargeUploadService.java b/backblaze/src/main/java/ch/cyberduck/core/b2/B2LargeUploadService.java index 63665f04d7e..5f1084516f9 100644 --- a/backblaze/src/main/java/ch/cyberduck/core/b2/B2LargeUploadService.java +++ b/backblaze/src/main/java/ch/cyberduck/core/b2/B2LargeUploadService.java @@ -242,6 +242,20 @@ public B2UploadPartResponse call() throws BackgroundException { }, overall, counter)); } + @Override + public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { + final B2LargeUploadPartService partService = new B2LargeUploadPartService(session, fileid); + final List upload = partService.find(file); + if(!upload.isEmpty()) { + Long size = 0L; + for(B2UploadPartResponse completed : partService.list(upload.iterator().next().getFileId())) { + size += completed.getContentLength(); + } + return new Write.Append(true).withStatus(status).withOffset(size); + } + return new Write.Append(false).withStatus(status); + } + @Override public Upload withWriter(final Write writer) { this.writer = writer; diff --git a/backblaze/src/main/java/ch/cyberduck/core/b2/B2ThresholdCopyFeature.java b/backblaze/src/main/java/ch/cyberduck/core/b2/B2ThresholdCopyFeature.java index d36207b2b49..73cf73e076a 100644 --- a/backblaze/src/main/java/ch/cyberduck/core/b2/B2ThresholdCopyFeature.java +++ b/backblaze/src/main/java/ch/cyberduck/core/b2/B2ThresholdCopyFeature.java @@ -47,7 +47,7 @@ public B2ThresholdCopyFeature(final B2Session session, final B2VersionIdProvider @Override public Path copy(final Path source, final Path target, final TransferStatus status, final ConnectionCallback callback, final StreamListener listener) throws BackgroundException { - if(new B2ThresholdUploadService(session, fileid, threshold).threshold(status.getLength())) { + if(new B2ThresholdUploadService(session, fileid, threshold).threshold(status)) { return new B2LargeCopyFeature(session, fileid).copy(source, target, status, callback, listener); } else { diff --git a/backblaze/src/main/java/ch/cyberduck/core/b2/B2ThresholdUploadService.java b/backblaze/src/main/java/ch/cyberduck/core/b2/B2ThresholdUploadService.java index 45b69cbfccf..baf12517343 100644 --- a/backblaze/src/main/java/ch/cyberduck/core/b2/B2ThresholdUploadService.java +++ b/backblaze/src/main/java/ch/cyberduck/core/b2/B2ThresholdUploadService.java @@ -52,13 +52,16 @@ public B2ThresholdUploadService(final B2Session session, final B2VersionIdProvid @Override public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { - return writer.append(file, status); + if(this.threshold(status)) { + return new B2LargeUploadService(session, fileid, writer).append(file, status); + } + return new Write.Append(false).withStatus(status); } @Override public BaseB2Response upload(final Path file, final Local local, final BandwidthThrottle throttle, final StreamListener listener, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException { - if(this.threshold(status.getLength())) { + if(this.threshold(status)) { return new B2LargeUploadService(session, fileid, writer).upload(file, local, throttle, listener, status, callback); } else { @@ -72,12 +75,12 @@ public Upload withWriter(final Write writer) { return this; } - protected boolean threshold(final Long length) { - if(length > threshold) { - if(length > new HostPreferences(session.getHost()).getLong("b2.upload.largeobject.size")) { + protected boolean threshold(final TransferStatus status) { + if(status.getLength() > threshold) { + if(status.getLength() > new HostPreferences(session.getHost()).getLong("b2.upload.largeobject.size")) { if(!new HostPreferences(session.getHost()).getBoolean("b2.upload.largeobject")) { // Disabled by user - if(length < new HostPreferences(session.getHost()).getLong("b2.upload.largeobject.required.threshold")) { + if(status.getLength() < new HostPreferences(session.getHost()).getLong("b2.upload.largeobject.required.threshold")) { log.warn("Large upload is disabled with property b2.upload.largeobject.required.threshold"); return false; } diff --git a/backblaze/src/main/java/ch/cyberduck/core/b2/B2WriteFeature.java b/backblaze/src/main/java/ch/cyberduck/core/b2/B2WriteFeature.java index 60d8c18e2ef..377c2f793a4 100644 --- a/backblaze/src/main/java/ch/cyberduck/core/b2/B2WriteFeature.java +++ b/backblaze/src/main/java/ch/cyberduck/core/b2/B2WriteFeature.java @@ -37,15 +37,12 @@ import java.io.IOException; import java.util.EnumSet; import java.util.HashMap; -import java.util.List; import java.util.Map; import synapticloop.b2.exception.B2ApiException; -import synapticloop.b2.response.B2FileInfoResponse; import synapticloop.b2.response.B2FileResponse; import synapticloop.b2.response.B2GetUploadPartUrlResponse; import synapticloop.b2.response.B2GetUploadUrlResponse; -import synapticloop.b2.response.B2UploadPartResponse; import synapticloop.b2.response.BaseB2Response; import static ch.cyberduck.core.b2.B2MetadataFeature.X_BZ_INFO_SRC_CREATION_DATE_MILLIS; @@ -148,20 +145,6 @@ public ChecksumCompute checksum(final Path file, final TransferStatus status) { return ChecksumComputeFactory.get(HashAlgorithm.sha1); } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - final B2LargeUploadPartService partService = new B2LargeUploadPartService(session, fileid); - final List upload = partService.find(file); - if(!upload.isEmpty()) { - Long size = 0L; - for(B2UploadPartResponse completed : partService.list(upload.iterator().next().getFileId())) { - size += completed.getContentLength(); - } - return new Append(true).withStatus(status).withSize(size); - } - return new Append(false).withStatus(status); - } - @Override public EnumSet features(final Path file) { return EnumSet.of(Flags.timestamp); diff --git a/backblaze/src/test/java/ch/cyberduck/core/b2/B2LargeUploadServiceTest.java b/backblaze/src/test/java/ch/cyberduck/core/b2/B2LargeUploadServiceTest.java index ff6ea09588b..4f317994ffe 100644 --- a/backblaze/src/test/java/ch/cyberduck/core/b2/B2LargeUploadServiceTest.java +++ b/backblaze/src/test/java/ch/cyberduck/core/b2/B2LargeUploadServiceTest.java @@ -139,7 +139,7 @@ public BaseB2Response upload(final Path file, final Local local, final Bandwidth assertEquals(TransferStatus.UNKNOWN_LENGTH, status.getResponse().getSize()); final Write.Append resume = service.append(test, status); assertTrue(resume.append); - assertEquals(0L, resume.size, 0L); + assertEquals(0L, resume.offset, 0L); final TransferStatus append = new TransferStatus().append(true).withLength(content.length); service.upload(test, local, new BandwidthThrottle(BandwidthThrottle.UNLIMITED), new DisabledStreamListener(), append, @@ -196,7 +196,7 @@ public BaseB2Response upload(final Path file, final Local local, final Bandwidth assertEquals(TransferStatus.UNKNOWN_LENGTH, status.getResponse().getSize()); final Write.Append appendStatus = feature.append(test, status); assertTrue(appendStatus.append); - assertEquals(5 * 1000L * 1000L, appendStatus.size, 0L); + assertEquals(5 * 1000L * 1000L, appendStatus.offset, 0L); final Path upload = new Path(test).withType(EnumSet.of(Path.Type.file, Path.Type.upload)); assertTrue(new B2FindFeature(session, fileid).find(upload)); assertEquals(5 * 1000L * 1000L, new B2AttributesFinderFeature(session, fileid).find(upload).getSize(), 0L); diff --git a/backblaze/src/test/java/ch/cyberduck/core/b2/B2WriteFeatureTest.java b/backblaze/src/test/java/ch/cyberduck/core/b2/B2WriteFeatureTest.java index 0d47f8afa88..125327b8bcd 100644 --- a/backblaze/src/test/java/ch/cyberduck/core/b2/B2WriteFeatureTest.java +++ b/backblaze/src/test/java/ch/cyberduck/core/b2/B2WriteFeatureTest.java @@ -22,7 +22,6 @@ import ch.cyberduck.core.PathAttributes; import ch.cyberduck.core.exception.ChecksumException; import ch.cyberduck.core.features.Delete; -import ch.cyberduck.core.features.Write; import ch.cyberduck.core.http.HttpResponseOutputStream; import ch.cyberduck.core.io.Checksum; import ch.cyberduck.core.io.SHA1ChecksumCompute; @@ -94,9 +93,6 @@ public void testWrite() throws Exception { assertEquals(content.length, attributes.getSize()); assertEquals(new B2AttributesFinderFeature(session, fileid).toAttributes(response), attributes); assertEquals(bucketAttr, new B2AttributesFinderFeature(session, fileid).find(bucket)); - final Write.Append append = new B2WriteFeature(session, fileid).append(test, status.withRemote(attributes)); - assertFalse(append.append); - assertEquals(content.length, append.size, 0L); final byte[] buffer = new byte[content.length]; final InputStream in = new B2ReadFeature(session, fileid).read(test, new TransferStatus(), new DisabledConnectionCallback()); IOUtils.readFully(in, buffer); diff --git a/backblaze/src/test/java/ch/cyberduck/core/cryptomator/B2WriteFeatureTest.java b/backblaze/src/test/java/ch/cyberduck/core/cryptomator/B2WriteFeatureTest.java index a21394dd8c2..19b73fbfcc0 100644 --- a/backblaze/src/test/java/ch/cyberduck/core/cryptomator/B2WriteFeatureTest.java +++ b/backblaze/src/test/java/ch/cyberduck/core/cryptomator/B2WriteFeatureTest.java @@ -88,7 +88,6 @@ public void testWrite() throws Exception { assertTrue(cryptomator.getFeature(session, Find.class, new B2FindFeature(session, fileid)).find(test)); final PathAttributes attributes = cryptomator.getFeature(session, AttributesFinder.class, new B2AttributesFinderFeature(session, fileid)).find(test); assertEquals(content.length, attributes.getSize()); - assertEquals(content.length, writer.append(test, status.withRemote(attributes)).size, 0L); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); final InputStream in = new CryptoReadFeature(session, new B2ReadFeature(session, fileid), cryptomator).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()); new StreamCopier(status, status).transfer(in, buffer); diff --git a/box/src/main/java/ch/cyberduck/core/box/BoxThresholdUploadService.java b/box/src/main/java/ch/cyberduck/core/box/BoxThresholdUploadService.java index a9f5ef52417..028fb20c87b 100644 --- a/box/src/main/java/ch/cyberduck/core/box/BoxThresholdUploadService.java +++ b/box/src/main/java/ch/cyberduck/core/box/BoxThresholdUploadService.java @@ -56,11 +56,6 @@ public File upload(final Path file, final Local local, final BandwidthThrottle t return new BoxSmallUploadService(session, fileid, writer).upload(file, local, throttle, listener, status, callback); } - @Override - public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Write.Append(false).withStatus(status); - } - @Override public Upload withWriter(final Write writer) { this.writer = writer; diff --git a/brick/src/main/java/ch/cyberduck/core/brick/BrickThresholdUploadFeature.java b/brick/src/main/java/ch/cyberduck/core/brick/BrickThresholdUploadFeature.java index 61d5c2c2ec5..b2ed5ca2250 100644 --- a/brick/src/main/java/ch/cyberduck/core/brick/BrickThresholdUploadFeature.java +++ b/brick/src/main/java/ch/cyberduck/core/brick/BrickThresholdUploadFeature.java @@ -50,7 +50,7 @@ public FileEntity upload(final Path file, final Local local, final BandwidthThro @Override public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { - return writer.append(file, status); + return new BrickUploadFeature(session, writer).append(file, status); } @Override diff --git a/core/src/main/java/ch/cyberduck/core/features/Upload.java b/core/src/main/java/ch/cyberduck/core/features/Upload.java index 9a81bc54b67..3e6c74c122d 100644 --- a/core/src/main/java/ch/cyberduck/core/features/Upload.java +++ b/core/src/main/java/ch/cyberduck/core/features/Upload.java @@ -47,7 +47,9 @@ Reply upload(Path file, Local local, BandwidthThrottle throttle, StreamListener * @param status Transfer status including attributes of file on server and size of file to write * @return True if can append to existing file */ - Write.Append append(Path file, TransferStatus status) throws BackgroundException; + default Write.Append append(Path file, TransferStatus status) throws BackgroundException { + return new Write.Append(false).withStatus(status); + } Upload withWriter(Write writer); } diff --git a/core/src/main/java/ch/cyberduck/core/features/Write.java b/core/src/main/java/ch/cyberduck/core/features/Write.java index 1816c87e541..33547305feb 100644 --- a/core/src/main/java/ch/cyberduck/core/features/Write.java +++ b/core/src/main/java/ch/cyberduck/core/features/Write.java @@ -41,17 +41,6 @@ public interface Write { */ StatusOutputStream write(Path file, TransferStatus status, final ConnectionCallback callback) throws BackgroundException; - /** - * Determine if appending to file is supported - * - * @param file File - * @param status Transfer status including attributes of file on server and size of file to write - * @return True if can append to existing file - */ - default Append append(Path file, TransferStatus status) throws BackgroundException { - return new Append(false).withStatus(status); - } - /** * @return True if supporting random writes with arbitrary offset and length */ @@ -85,7 +74,7 @@ final class Append { /** * Remote file size */ - public Long size = 0L; + public Long offset = 0L; /** * Remote file checksum @@ -101,13 +90,13 @@ public Append withChecksum(final Checksum checksum) { return this; } - public Append withSize(final Long size) { - this.size = size; + public Append withOffset(final Long offset) { + this.offset = offset; return this; } public Append withStatus(final TransferStatus status) { - return this.withSize(TransferStatus.UNKNOWN_LENGTH == status.getRemote().getSize() ? 0L : status.getRemote().getSize()).withChecksum(status.getRemote().getChecksum()); + return this.withOffset(TransferStatus.UNKNOWN_LENGTH == status.getRemote().getSize() ? 0L : status.getRemote().getSize()).withChecksum(status.getRemote().getChecksum()); } @Override @@ -119,18 +108,18 @@ public boolean equals(final Object o) { return false; } final Append append1 = (Append) o; - return append == append1.append && Objects.equals(size, append1.size) && Objects.equals(checksum, append1.checksum); + return append == append1.append && Objects.equals(offset, append1.offset) && Objects.equals(checksum, append1.checksum); } @Override public int hashCode() { - return Objects.hash(append, size, checksum); + return Objects.hash(append, offset, checksum); } @Override public String toString() { final StringBuilder sb = new StringBuilder("Append{"); - sb.append("size=").append(size); + sb.append("size=").append(offset); sb.append(", checksum=").append(checksum); sb.append('}'); return sb.toString(); diff --git a/core/src/main/java/ch/cyberduck/core/http/HttpUploadFeature.java b/core/src/main/java/ch/cyberduck/core/http/HttpUploadFeature.java index bd86c9f0947..2865996af0a 100644 --- a/core/src/main/java/ch/cyberduck/core/http/HttpUploadFeature.java +++ b/core/src/main/java/ch/cyberduck/core/http/HttpUploadFeature.java @@ -55,11 +55,6 @@ public HttpUploadFeature(final Write writer) { this.writer = writer; } - @Override - public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { - return writer.append(file, status); - } - @Override public Reply upload(final Path file, final Local local, final BandwidthThrottle throttle, final StreamListener listener, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException { diff --git a/core/src/main/java/ch/cyberduck/core/shared/AppendWriteFeature.java b/core/src/main/java/ch/cyberduck/core/shared/AppendWriteFeature.java deleted file mode 100644 index abfd5017685..00000000000 --- a/core/src/main/java/ch/cyberduck/core/shared/AppendWriteFeature.java +++ /dev/null @@ -1,32 +0,0 @@ -package ch.cyberduck.core.shared; - -/* - * Copyright (c) 2002-2014 David Kocher. All rights reserved. - * http://cyberduck.io/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * Bug fixes, suggestions and comments should be sent to: - * feedback@cyberduck.io - */ - -import ch.cyberduck.core.Path; -import ch.cyberduck.core.exception.BackgroundException; -import ch.cyberduck.core.features.Write; -import ch.cyberduck.core.transfer.TransferStatus; - -public abstract class AppendWriteFeature implements Write { - - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return new Append(status.isExists()).withStatus(status); - } -} diff --git a/core/src/main/java/ch/cyberduck/core/shared/DefaultUploadFeature.java b/core/src/main/java/ch/cyberduck/core/shared/DefaultUploadFeature.java index 1476e8f6c85..02d48885c91 100644 --- a/core/src/main/java/ch/cyberduck/core/shared/DefaultUploadFeature.java +++ b/core/src/main/java/ch/cyberduck/core/shared/DefaultUploadFeature.java @@ -55,11 +55,6 @@ public Reply upload(final Path file, final Local local, final BandwidthThrottle return out.getStatus(); } - @Override - public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { - return writer.append(file, status); - } - @Override public Upload withWriter(final Write writer) { this.writer = writer; diff --git a/core/src/main/java/ch/cyberduck/core/transfer/upload/ResumeFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/upload/ResumeFilter.java index c8b501e0557..ca3a19156f5 100644 --- a/core/src/main/java/ch/cyberduck/core/transfer/upload/ResumeFilter.java +++ b/core/src/main/java/ch/cyberduck/core/transfer/upload/ResumeFilter.java @@ -93,11 +93,11 @@ public TransferStatus prepare(final Path file, final Local local, final Transfer final TransferStatus status = super.prepare(file, local, parent, progress); if(file.isFile()) { final Write.Append append = upload.append(file, status); - if(append.append && append.size <= status.getLength()) { + if(append.append && append.offset <= status.getLength()) { // Append to existing file status.withRename((Path) null).withDisplayname((Path) null).setAppend(true); - status.setLength(status.getLength() - append.size); - status.setOffset(append.size); + status.setLength(status.getLength() - append.offset); + status.setOffset(append.offset); if(log.isDebugEnabled()) { log.debug(String.format("Resume file %s at offset %d and remaining length %d", file, status.getOffset(), status.getLength())); } diff --git a/core/src/main/java/ch/cyberduck/core/vault/registry/VaultRegistryWriteFeature.java b/core/src/main/java/ch/cyberduck/core/vault/registry/VaultRegistryWriteFeature.java index 715e27d1b32..32e03824d1d 100644 --- a/core/src/main/java/ch/cyberduck/core/vault/registry/VaultRegistryWriteFeature.java +++ b/core/src/main/java/ch/cyberduck/core/vault/registry/VaultRegistryWriteFeature.java @@ -45,11 +45,6 @@ public StatusOutputStream write(final Path file, final TransferStatus status, return registry.find(session, file).getFeature(session, Write.class, proxy).write(file, status, callback); } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return registry.find(session, file).getFeature(session, Write.class, proxy).append(file, status); - } - @Override public EnumSet features(final Path file) { return proxy.features(file); diff --git a/core/src/test/java/ch/cyberduck/core/NullSession.java b/core/src/test/java/ch/cyberduck/core/NullSession.java index dfb1954f9f3..779eb55247b 100644 --- a/core/src/test/java/ch/cyberduck/core/NullSession.java +++ b/core/src/test/java/ch/cyberduck/core/NullSession.java @@ -6,6 +6,7 @@ import ch.cyberduck.core.features.Directory; import ch.cyberduck.core.features.Move; import ch.cyberduck.core.features.Read; +import ch.cyberduck.core.features.Upload; import ch.cyberduck.core.features.Write; import ch.cyberduck.core.proxy.Proxy; import ch.cyberduck.core.threading.CancelCallback; @@ -48,8 +49,11 @@ public T _getFeature(Class type) { if(type == ListService.class) { return (T) this; } + if(type == Upload.class) { + return (T) new NullUploadFeature(); + } if(type == Write.class) { - return (T) new NullWriteFeature(this); + return (T) new NullWriteFeature(); } if(type == Read.class) { return (T) new NullReadFeature(); diff --git a/core/src/test/java/ch/cyberduck/core/NullUploadFeature.java b/core/src/test/java/ch/cyberduck/core/NullUploadFeature.java new file mode 100644 index 00000000000..b48506153ee --- /dev/null +++ b/core/src/test/java/ch/cyberduck/core/NullUploadFeature.java @@ -0,0 +1,40 @@ +package ch.cyberduck.core;/* + * Copyright (c) 2002-2024 iterate GmbH. All rights reserved. + * https://cyberduck.io/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.Upload; +import ch.cyberduck.core.features.Write; +import ch.cyberduck.core.io.BandwidthThrottle; +import ch.cyberduck.core.io.StreamListener; +import ch.cyberduck.core.transfer.TransferStatus; + +public class NullUploadFeature implements Upload { + + @Override + public Void upload(final Path file, final Local local, final BandwidthThrottle throttle, final StreamListener listener, + final TransferStatus status, final ConnectionCallback callback) throws BackgroundException { + return null; + } + + @Override + public Upload withWriter(final Write writer) { + return this; + } + + @Override + public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { + return new Write.Append(true).withStatus(status); + } +} diff --git a/core/src/test/java/ch/cyberduck/core/NullWriteFeature.java b/core/src/test/java/ch/cyberduck/core/NullWriteFeature.java index b67d084230b..da26294946a 100644 --- a/core/src/test/java/ch/cyberduck/core/NullWriteFeature.java +++ b/core/src/test/java/ch/cyberduck/core/NullWriteFeature.java @@ -15,17 +15,14 @@ * GNU General Public License for more details. */ +import ch.cyberduck.core.features.Write; import ch.cyberduck.core.io.StatusOutputStream; import ch.cyberduck.core.io.VoidStatusOutputStream; -import ch.cyberduck.core.shared.AppendWriteFeature; import ch.cyberduck.core.transfer.TransferStatus; import org.apache.commons.io.output.NullOutputStream; -public class NullWriteFeature extends AppendWriteFeature { - public NullWriteFeature(final Session session) { - super(); - } +public class NullWriteFeature implements Write { @Override public StatusOutputStream write(final Path file, final TransferStatus status, final ConnectionCallback callback) { diff --git a/core/src/test/java/ch/cyberduck/core/transfer/UploadTransferTest.java b/core/src/test/java/ch/cyberduck/core/transfer/UploadTransferTest.java index 0157b1bf580..f796e9a9844 100755 --- a/core/src/test/java/ch/cyberduck/core/transfer/UploadTransferTest.java +++ b/core/src/test/java/ch/cyberduck/core/transfer/UploadTransferTest.java @@ -403,13 +403,6 @@ public StatusOutputStream write(final Path file, final TransferStatus status, fi fail(); return null; } - - @Override - public Append append(final Path file, final TransferStatus status) { - fail(); - return new Write.Append(false); - } - }; } return super._getFeature(type); diff --git a/core/src/test/java/ch/cyberduck/core/transfer/upload/RenameExistingFilterTest.java b/core/src/test/java/ch/cyberduck/core/transfer/upload/RenameExistingFilterTest.java index 6a911cf94bb..5129f124f9a 100755 --- a/core/src/test/java/ch/cyberduck/core/transfer/upload/RenameExistingFilterTest.java +++ b/core/src/test/java/ch/cyberduck/core/transfer/upload/RenameExistingFilterTest.java @@ -146,12 +146,6 @@ public StatusOutputStream write(final Path file, final TransferStatus status, fi fail(); return null; } - - @Override - public Append append(final Path file, final TransferStatus status) { - fail(); - return new Append(false); - } }; } return null; @@ -228,12 +222,6 @@ public StatusOutputStream write(final Path file, final TransferStatus status, fi fail(); return null; } - - @Override - public Append append(final Path file, final TransferStatus status) { - fail(); - return new Append(false); - } }; } return null; diff --git a/core/src/test/java/ch/cyberduck/core/transfer/upload/ResumeFilterTest.java b/core/src/test/java/ch/cyberduck/core/transfer/upload/ResumeFilterTest.java index 66308dfc9a5..5866b039683 100644 --- a/core/src/test/java/ch/cyberduck/core/transfer/upload/ResumeFilterTest.java +++ b/core/src/test/java/ch/cyberduck/core/transfer/upload/ResumeFilterTest.java @@ -7,6 +7,7 @@ import ch.cyberduck.core.LocalAttributes; import ch.cyberduck.core.NullLocal; import ch.cyberduck.core.NullSession; +import ch.cyberduck.core.NullUploadFeature; import ch.cyberduck.core.NullWriteFeature; import ch.cyberduck.core.Path; import ch.cyberduck.core.PathAttributes; @@ -152,7 +153,7 @@ public AttributedList list(final Path folder, final ListProgressListener l } }; final ResumeFilter f = new ResumeFilter(new DisabledUploadSymlinkResolver(), session, - new UploadFilterOptions(host).withTemporary(true), new DefaultUploadFeature<>(new NullWriteFeature(session))); + new UploadFilterOptions(host).withTemporary(true), new NullUploadFeature()); final long size = 3L; final Path t = new Path("t", EnumSet.of(Path.Type.file)); assertFalse(f.accept(t, new NullLocal("t") { @@ -191,7 +192,7 @@ public AttributedList list(final Path folder, final ListProgressListener l } }; final ResumeFilter f = new ResumeFilter(new DisabledUploadSymlinkResolver(), session, - new UploadFilterOptions(host).withTemporary(true), new DefaultUploadFeature<>(new NullWriteFeature(session))); + new UploadFilterOptions(host).withTemporary(true), new NullUploadFeature()); final long size = 3L; final Path t = new Path("t", EnumSet.of(Path.Type.file)); final NullLocal l = new NullLocal("t") { @@ -235,7 +236,7 @@ public AttributedList list(final Path folder, final ListProgressListener l } }; final ResumeFilter f = new ResumeFilter(new DisabledUploadSymlinkResolver(), session, - new UploadFilterOptions(host).withTemporary(true), new DefaultUploadFeature<>(new NullWriteFeature(session))); + new UploadFilterOptions(host).withTemporary(true), new DefaultUploadFeature<>(new NullWriteFeature())); final long size = 3L; final Path t = new Path("t", EnumSet.of(Path.Type.file)); final NullLocal l = new NullLocal("t") { diff --git a/ctera/src/test/java/ch/cyberduck/core/ctera/CteraWriteFeatureTest.java b/ctera/src/test/java/ch/cyberduck/core/ctera/CteraWriteFeatureTest.java index dd144929ea0..87c97676fbd 100644 --- a/ctera/src/test/java/ch/cyberduck/core/ctera/CteraWriteFeatureTest.java +++ b/ctera/src/test/java/ch/cyberduck/core/ctera/CteraWriteFeatureTest.java @@ -62,7 +62,6 @@ public void testReadWrite() throws Exception { assertTrue(session.getFeature(Find.class).find(test)); assertEquals(content.length, new CteraListService(session).list(test.getParent(), new DisabledListProgressListener()) .find(new SimplePathPredicate(test)).attributes().getSize(), 0L); - assertEquals(content.length, new CteraWriteFeature(session).append(test, status.withRemote(new CteraAttributesFinderFeature(session).find(test))).size, 0L); { final byte[] buffer = new byte[content.length]; IOUtils.readFully(new CteraReadFeature(session).read(test, new TransferStatus(), new DisabledConnectionCallback()), buffer); diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDelegatingWriteFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDelegatingWriteFeature.java index 52056d795d2..035a5c788bb 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDelegatingWriteFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSDelegatingWriteFeature.java @@ -60,14 +60,6 @@ public StatusOutputStream write(final Path file, final TransferStatus stat return proxy.write(file, status, callback); } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - if(SDSAttributesAdapter.isEncrypted(containerService.getContainer(file).attributes())) { - return new TripleCryptWriteFeature(session, nodeid, proxy).append(file, status); - } - return proxy.append(file, status); - } - @Override public ChecksumCompute checksum(final Path file, final TransferStatus status) { if(SDSAttributesAdapter.isEncrypted(containerService.getContainer(file).attributes())) { diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/triplecrypt/TripleCryptWriteFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/triplecrypt/TripleCryptWriteFeature.java index 905d3790730..42e0d8745d4 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/triplecrypt/TripleCryptWriteFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/triplecrypt/TripleCryptWriteFeature.java @@ -75,11 +75,6 @@ public StatusOutputStream write(final Path file, final TransferStatus stat } } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - return proxy.append(file, status); - } - @Override public EnumSet features(final Path file) { return proxy.features(file); diff --git a/dropbox/src/test/java/ch/cyberduck/core/cryptomator/DropboxWriteFeatureTest.java b/dropbox/src/test/java/ch/cyberduck/core/cryptomator/DropboxWriteFeatureTest.java index 5445329c702..998b46f1f86 100644 --- a/dropbox/src/test/java/ch/cyberduck/core/cryptomator/DropboxWriteFeatureTest.java +++ b/dropbox/src/test/java/ch/cyberduck/core/cryptomator/DropboxWriteFeatureTest.java @@ -89,7 +89,6 @@ public void testWrite() throws Exception { assertTrue(cryptomator.getFeature(session, Find.class, new DropboxFindFeature(session)).find(test)); final PathAttributes pathAttributes = new CryptoListService(session, new DropboxListService(session), cryptomator).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes(); assertEquals(content.length, pathAttributes.getSize()); - assertEquals(content.length, new CryptoWriteFeature<>(session, new DropboxWriteFeature(session, 150000000L), cryptomator).append(test, status.withRemote(pathAttributes)).size, 0L); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); final InputStream in = new CryptoReadFeature(session, new DropboxReadFeature(session), cryptomator).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()); new StreamCopier(status, status).transfer(in, buffer); diff --git a/dropbox/src/test/java/ch/cyberduck/core/dropbox/DropboxWriteFeatureTest.java b/dropbox/src/test/java/ch/cyberduck/core/dropbox/DropboxWriteFeatureTest.java index bb78dac1c7e..51c653ee8a1 100644 --- a/dropbox/src/test/java/ch/cyberduck/core/dropbox/DropboxWriteFeatureTest.java +++ b/dropbox/src/test/java/ch/cyberduck/core/dropbox/DropboxWriteFeatureTest.java @@ -68,7 +68,6 @@ public void testReadWrite() throws Exception { assertEquals(status.getResponse(), attributes); assertEquals(1700638960000L, attributes.getModificationDate()); assertEquals(content.length, attributes.getSize()); - assertEquals(content.length, write.append(test, status.withRemote(attributes)).size, 0L); { final InputStream in = new DropboxReadFeature(session).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); @@ -103,7 +102,6 @@ public void testWriteAppendChunks() throws Exception { assertTrue(new DropboxFindFeature(session).find(test)); final PathAttributes attributes = new DropboxListService(session).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes(); assertEquals(content.length, attributes.getSize()); - assertEquals(content.length, write.append(test, status.withRemote(attributes)).size, 0L); { final InputStream in = new DropboxReadFeature(session).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); diff --git a/eue/src/main/java/ch/cyberduck/core/eue/EueThresholdUploadService.java b/eue/src/main/java/ch/cyberduck/core/eue/EueThresholdUploadService.java index 0a21b806254..018ec8ea2cf 100644 --- a/eue/src/main/java/ch/cyberduck/core/eue/EueThresholdUploadService.java +++ b/eue/src/main/java/ch/cyberduck/core/eue/EueThresholdUploadService.java @@ -52,11 +52,6 @@ public EueThresholdUploadService(final EueSession session, final EueResourceIdPr this.writer = new EueWriteFeature(session, fileid); } - @Override - public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { - return writer.append(file, status); - } - @Override public EueWriteFeature.Chunk upload(final Path file, Local local, final BandwidthThrottle throttle, final StreamListener listener, final TransferStatus status, final ConnectionCallback prompt) throws BackgroundException { diff --git a/eue/src/test/java/ch/cyberduck/core/cryptomator/EueWriteFeatureTest.java b/eue/src/test/java/ch/cyberduck/core/cryptomator/EueWriteFeatureTest.java index d7b64fdff61..fe94fbf4dad 100644 --- a/eue/src/test/java/ch/cyberduck/core/cryptomator/EueWriteFeatureTest.java +++ b/eue/src/test/java/ch/cyberduck/core/cryptomator/EueWriteFeatureTest.java @@ -26,13 +26,11 @@ import ch.cyberduck.core.cryptomator.features.CryptoWriteFeature; import ch.cyberduck.core.cryptomator.random.RotatingNonceGenerator; import ch.cyberduck.core.eue.AbstractEueSessionTest; -import ch.cyberduck.core.eue.EueAttributesFinderFeature; import ch.cyberduck.core.eue.EueDeleteFeature; import ch.cyberduck.core.eue.EueFindFeature; import ch.cyberduck.core.eue.EueReadFeature; import ch.cyberduck.core.eue.EueResourceIdProvider; import ch.cyberduck.core.eue.EueWriteFeature; -import ch.cyberduck.core.features.AttributesFinder; import ch.cyberduck.core.features.Delete; import ch.cyberduck.core.features.Find; import ch.cyberduck.core.io.StreamCopier; @@ -91,8 +89,6 @@ public void testWrite() throws Exception { out.close(); assertEquals(PathAttributes.EMPTY, status.getResponse()); assertTrue(cryptomator.getFeature(session, Find.class, new EueFindFeature(session, fileid)).find(test)); - assertEquals(content.length, new CryptoWriteFeature<>(session, new EueWriteFeature(session, fileid), cryptomator).append(test, status - .withRemote(cryptomator.getFeature(session, AttributesFinder.class, new EueAttributesFinderFeature(session, fileid)).find(test))).size, 0L); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); final InputStream in = new CryptoReadFeature(session, new EueReadFeature(session, fileid), cryptomator).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()); new StreamCopier(status, status).transfer(in, buffer); diff --git a/ftp/src/main/java/ch/cyberduck/core/ftp/FTPSession.java b/ftp/src/main/java/ch/cyberduck/core/ftp/FTPSession.java index 8770d799266..3af2197220f 100644 --- a/ftp/src/main/java/ch/cyberduck/core/ftp/FTPSession.java +++ b/ftp/src/main/java/ch/cyberduck/core/ftp/FTPSession.java @@ -31,20 +31,7 @@ import ch.cyberduck.core.cloudfront.CustomOriginCloudFrontDistributionConfiguration; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.LoginCanceledException; -import ch.cyberduck.core.features.AttributesFinder; -import ch.cyberduck.core.features.Command; -import ch.cyberduck.core.features.Copy; -import ch.cyberduck.core.features.Delete; -import ch.cyberduck.core.features.Directory; -import ch.cyberduck.core.features.Find; -import ch.cyberduck.core.features.Home; -import ch.cyberduck.core.features.Move; -import ch.cyberduck.core.features.Read; -import ch.cyberduck.core.features.Symlink; -import ch.cyberduck.core.features.Timestamp; -import ch.cyberduck.core.features.Touch; -import ch.cyberduck.core.features.UnixPermission; -import ch.cyberduck.core.features.Write; +import ch.cyberduck.core.features.*; import ch.cyberduck.core.ftp.list.FTPListService; import ch.cyberduck.core.idna.PunycodeConverter; import ch.cyberduck.core.preferences.HostPreferences; @@ -332,6 +319,9 @@ public T _getFeature(final Class type) { if(type == Read.class) { return (T) new FTPReadFeature(this); } + if(type == Upload.class) { + return (T) new FTPUploadFeature(this); + } if(type == Write.class) { return (T) new FTPWriteFeature(this); } diff --git a/ftp/src/main/java/ch/cyberduck/core/ftp/FTPUploadFeature.java b/ftp/src/main/java/ch/cyberduck/core/ftp/FTPUploadFeature.java new file mode 100644 index 00000000000..4993846da8a --- /dev/null +++ b/ftp/src/main/java/ch/cyberduck/core/ftp/FTPUploadFeature.java @@ -0,0 +1,38 @@ +package ch.cyberduck.core.ftp; + +/* + * Copyright (c) 2002-2024 iterate GmbH. All rights reserved. + * https://cyberduck.io/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +import ch.cyberduck.core.Path; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.Write; +import ch.cyberduck.core.shared.DefaultUploadFeature; +import ch.cyberduck.core.transfer.TransferStatus; + +public class FTPUploadFeature extends DefaultUploadFeature { + + public FTPUploadFeature(final FTPSession session) { + super(new FTPWriteFeature(session)); + } + + public FTPUploadFeature(final Write writer) { + super(writer); + } + + @Override + public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { + return new Write.Append(status.isExists()).withStatus(status); + } +} diff --git a/ftp/src/main/java/ch/cyberduck/core/ftp/FTPWriteFeature.java b/ftp/src/main/java/ch/cyberduck/core/ftp/FTPWriteFeature.java index 91d9f8ac775..9753d9cfb43 100644 --- a/ftp/src/main/java/ch/cyberduck/core/ftp/FTPWriteFeature.java +++ b/ftp/src/main/java/ch/cyberduck/core/ftp/FTPWriteFeature.java @@ -20,9 +20,9 @@ import ch.cyberduck.core.ConnectionCallback; import ch.cyberduck.core.Path; import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.Write; import ch.cyberduck.core.io.StatusOutputStream; import ch.cyberduck.core.io.VoidStatusOutputStream; -import ch.cyberduck.core.shared.AppendWriteFeature; import ch.cyberduck.core.transfer.TransferStatus; import org.apache.commons.net.ftp.FTPReply; @@ -33,7 +33,7 @@ import java.io.OutputStream; import java.util.concurrent.atomic.AtomicBoolean; -public class FTPWriteFeature extends AppendWriteFeature { +public class FTPWriteFeature implements Write { private static final Logger log = LogManager.getLogger(FTPWriteFeature.class); private final FTPSession session; diff --git a/ftp/src/test/java/ch/cyberduck/core/cryptomator/FTPWriteFeatureTest.java b/ftp/src/test/java/ch/cyberduck/core/cryptomator/FTPWriteFeatureTest.java index b98fff0b42b..4fd57c694b4 100644 --- a/ftp/src/test/java/ch/cyberduck/core/cryptomator/FTPWriteFeatureTest.java +++ b/ftp/src/test/java/ch/cyberduck/core/cryptomator/FTPWriteFeatureTest.java @@ -91,7 +91,6 @@ public void testWrite() throws Exception { assertTrue(cryptomator.getFeature(session, Find.class, new DefaultFindFeature(session)).find(test)); final PathAttributes attr = cryptomator.getFeature(session, AttributesFinder.class, new FTPAttributesFinderFeature(session)).find(test); assertEquals(content.length, new CryptoListService(session, new FTPListService(session, null, TimeZone.getDefault()), cryptomator).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes().getSize()); - assertEquals(content.length, new CryptoWriteFeature<>(session, new FTPWriteFeature(session), cryptomator).append(test, status.withRemote(attr)).size, 0L); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); final InputStream in = new CryptoReadFeature(session, new FTPReadFeature(session), cryptomator).read(test, new TransferStatus(), new DisabledConnectionCallback()); new StreamCopier(status, status).transfer(in, buffer); diff --git a/ftp/src/test/java/ch/cyberduck/core/ftp/FTPUploadFeatureTest.java b/ftp/src/test/java/ch/cyberduck/core/ftp/FTPUploadFeatureTest.java new file mode 100644 index 00000000000..26ce87dcf5f --- /dev/null +++ b/ftp/src/test/java/ch/cyberduck/core/ftp/FTPUploadFeatureTest.java @@ -0,0 +1,40 @@ +package ch.cyberduck.core.ftp; + +/* + * Copyright (c) 2002-2024 iterate GmbH. All rights reserved. + * https://cyberduck.io/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +import ch.cyberduck.core.AlphanumericRandomStringService; +import ch.cyberduck.core.DisabledLoginCallback; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.features.Delete; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.junit.Test; + +import java.util.Collections; +import java.util.EnumSet; + +import static org.junit.Assert.assertTrue; + +public class FTPUploadFeatureTest extends AbstractFTPTest { + + @Test + public void testAppend() throws Exception { + final Path f = new Path(new FTPWorkdirService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)); + new FTPTouchFeature(session).touch(f, new TransferStatus()); + assertTrue(new FTPUploadFeature(session).append(f, new TransferStatus().exists(true).withLength(0L).withRemote(new FTPAttributesFinderFeature(session).find(f))).append); + new FTPDeleteFeature(session).delete(Collections.singletonList(f), new DisabledLoginCallback(), new Delete.DisabledCallback()); + } +} \ No newline at end of file diff --git a/ftp/src/test/java/ch/cyberduck/core/ftp/FTPWriteFeatureTest.java b/ftp/src/test/java/ch/cyberduck/core/ftp/FTPWriteFeatureTest.java index f8dbf03682a..d897f882ff9 100644 --- a/ftp/src/test/java/ch/cyberduck/core/ftp/FTPWriteFeatureTest.java +++ b/ftp/src/test/java/ch/cyberduck/core/ftp/FTPWriteFeatureTest.java @@ -50,7 +50,6 @@ public void testReadWrite() throws Exception { assertTrue(session.getFeature(Find.class).find(test)); final PathAttributes attributes = new FTPListService(session, null, TimeZone.getDefault()).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes(); assertEquals(content.length, attributes.getSize()); - assertEquals(content.length, new FTPWriteFeature(session).append(test, status.withRemote(attributes)).size, 0L); { final InputStream in = new FTPReadFeature(session).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); @@ -144,12 +143,4 @@ public void testWriteNotFound() throws Exception { final Path test = new Path(new FTPWorkdirService(session).find().getAbsolute() + "/nosuchdirectory/" + UUID.randomUUID(), EnumSet.of(Path.Type.file)); new FTPWriteFeature(session).write(test, new TransferStatus(), new DisabledConnectionCallback()); } - - @Test - public void testAppend() throws Exception { - final Path f = new Path(new FTPWorkdirService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)); - new FTPTouchFeature(session).touch(f, new TransferStatus()); - assertTrue(new FTPWriteFeature(session).append(f, new TransferStatus().exists(true).withLength(0L).withRemote(new FTPAttributesFinderFeature(session).find(f))).append); - new FTPDeleteFeature(session).delete(Collections.singletonList(f), new DisabledLoginCallback(), new Delete.DisabledCallback()); - } } diff --git a/ftp/src/test/java/ch/cyberduck/core/worker/FTPConcurrentTransferWorkerTest.java b/ftp/src/test/java/ch/cyberduck/core/worker/FTPConcurrentTransferWorkerTest.java index 2ff97b21bf0..f23497d7dfd 100644 --- a/ftp/src/test/java/ch/cyberduck/core/worker/FTPConcurrentTransferWorkerTest.java +++ b/ftp/src/test/java/ch/cyberduck/core/worker/FTPConcurrentTransferWorkerTest.java @@ -18,11 +18,12 @@ import ch.cyberduck.core.*; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.Delete; -import ch.cyberduck.core.features.Write; +import ch.cyberduck.core.features.Upload; import ch.cyberduck.core.ftp.AbstractFTPTest; import ch.cyberduck.core.ftp.FTPAttributesFinderFeature; import ch.cyberduck.core.ftp.FTPDeleteFeature; import ch.cyberduck.core.ftp.FTPSession; +import ch.cyberduck.core.ftp.FTPUploadFeature; import ch.cyberduck.core.ftp.FTPWriteFeature; import ch.cyberduck.core.io.DisabledStreamListener; import ch.cyberduck.core.io.StatusOutputStream; @@ -130,8 +131,8 @@ public Void getStatus() throws BackgroundException { @Override @SuppressWarnings("unchecked") public T _getFeature(final Class type) { - if(type == Write.class) { - return (T) write; + if(type == Upload.class) { + return (T) new FTPUploadFeature(write); } return super._getFeature(type); } diff --git a/googledrive/src/test/java/ch/cyberduck/core/cryptomator/DriveWriteFeatureTest.java b/googledrive/src/test/java/ch/cyberduck/core/cryptomator/DriveWriteFeatureTest.java index a3af6c3b036..9a0da05a379 100644 --- a/googledrive/src/test/java/ch/cyberduck/core/cryptomator/DriveWriteFeatureTest.java +++ b/googledrive/src/test/java/ch/cyberduck/core/cryptomator/DriveWriteFeatureTest.java @@ -101,7 +101,6 @@ public void testWrite() throws Exception { assertTrue(cryptomator.getFeature(session, Find.class, new DefaultFindFeature(session)).find(test)); final PathAttributes attributes = cryptomator.getFeature(session, AttributesFinder.class, new DefaultAttributesFinderFeature(session)).find(test); assertEquals(content.length, attributes.getSize()); - assertEquals(content.length, writer.append(test, status.withRemote(attributes)).size, 0L); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); final InputStream in = new CryptoReadFeature(session, new DriveReadFeature(session, fileid), cryptomator).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()); new StreamCopier(status, status).transfer(in, buffer); @@ -141,7 +140,6 @@ public void testWriteWithCache() throws Exception { cache.put(vault, list); assertEquals(content.length, cache.get(vault).get(0).attributes().getSize()); assertEquals(content.length, found.attributes().getSize()); - assertEquals(content.length, writer.append(test, status.withRemote(found.attributes())).size, 0L); { final PathAttributes attributes = cryptomator.getFeature(session, AttributesFinder.class, new DriveAttributesFinderFeature(session, fileid)).find(test); assertEquals(content.length, attributes.getSize()); diff --git a/googledrive/src/test/java/ch/cyberduck/core/googledrive/DriveUploadFeatureTest.java b/googledrive/src/test/java/ch/cyberduck/core/googledrive/DriveUploadFeatureTest.java index 33ac2de223b..a4ece92af10 100644 --- a/googledrive/src/test/java/ch/cyberduck/core/googledrive/DriveUploadFeatureTest.java +++ b/googledrive/src/test/java/ch/cyberduck/core/googledrive/DriveUploadFeatureTest.java @@ -61,7 +61,7 @@ public void testWrite() throws Exception { test.attributes().setFileId(fileid.getFileId(test)); assertTrue(session.getFeature(Find.class).find(test)); assertEquals(content.length, new DriveListService(session, fileid).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes().getSize(), 0L); - assertEquals(content.length, new DriveWriteFeature(session, fileid).append(test, status.withRemote(new DriveAttributesFinderFeature(session, fileid).find(test))).size, 0L); + assertEquals(content.length, upload.append(test, status.withRemote(new DriveAttributesFinderFeature(session, fileid).find(test))).offset, 0L); { final byte[] buffer = new byte[content.length]; IOUtils.readFully(new DriveReadFeature(session, fileid).read(test, new TransferStatus(), new DisabledConnectionCallback()), buffer); diff --git a/googledrive/src/test/java/ch/cyberduck/core/googledrive/DriveWriteFeatureTest.java b/googledrive/src/test/java/ch/cyberduck/core/googledrive/DriveWriteFeatureTest.java index b3505ee584e..bb5c0bec4bf 100644 --- a/googledrive/src/test/java/ch/cyberduck/core/googledrive/DriveWriteFeatureTest.java +++ b/googledrive/src/test/java/ch/cyberduck/core/googledrive/DriveWriteFeatureTest.java @@ -23,7 +23,6 @@ import ch.cyberduck.core.Path; import ch.cyberduck.core.PathAttributes; import ch.cyberduck.core.features.Delete; -import ch.cyberduck.core.features.Write; import ch.cyberduck.core.http.HttpResponseOutputStream; import ch.cyberduck.core.io.StreamCopier; import ch.cyberduck.core.shared.DefaultFindFeature; @@ -75,9 +74,6 @@ public void testWrite() throws Exception { assertEquals(1620113107725L, attributes.getModificationDate()); assertEquals(1695160857860L, attributes.getCreationDate()); assertEquals(content.length, attributes.getSize()); - final Write.Append append = new DriveWriteFeature(session, idProvider).append(test, status.withRemote(new DriveAttributesFinderFeature(session, idProvider).find(test))); - assertFalse(append.append); - assertEquals(content.length, append.size, 0L); final byte[] buffer = new byte[content.length]; final InputStream in = new DriveReadFeature(session, idProvider).read(test, new TransferStatus(), new DisabledConnectionCallback()); IOUtils.readFully(in, buffer); diff --git a/googlestorage/src/test/java/ch/cyberduck/core/googlestorage/GoogleStorageWriteFeatureTest.java b/googlestorage/src/test/java/ch/cyberduck/core/googlestorage/GoogleStorageWriteFeatureTest.java index 5251823480f..6133bc8128d 100644 --- a/googlestorage/src/test/java/ch/cyberduck/core/googlestorage/GoogleStorageWriteFeatureTest.java +++ b/googlestorage/src/test/java/ch/cyberduck/core/googlestorage/GoogleStorageWriteFeatureTest.java @@ -24,7 +24,6 @@ import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.InteroperabilityException; import ch.cyberduck.core.features.Delete; -import ch.cyberduck.core.features.Write; import ch.cyberduck.core.http.HttpResponseOutputStream; import ch.cyberduck.core.io.StreamCopier; import ch.cyberduck.core.transfer.TransferStatus; @@ -153,9 +152,6 @@ public void testWrite() throws Exception { out.close(); assertNotNull(out.getStatus().getGeneration()); assertTrue(new GoogleStorageFindFeature(session).find(test)); - final Write.Append append = new GoogleStorageWriteFeature(session).append(test, status.withRemote(new GoogleStorageAttributesFinderFeature(session).find(test))); - assertFalse(append.append); - assertEquals(content.length, append.size, 0L); final byte[] buffer = new byte[content.length]; final InputStream in = new GoogleStorageReadFeature(session).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()); IOUtils.readFully(in, buffer); diff --git a/irods/src/main/java/ch/cyberduck/core/irods/IRODSUploadFeature.java b/irods/src/main/java/ch/cyberduck/core/irods/IRODSUploadFeature.java index 5dec6440723..875983a73d4 100644 --- a/irods/src/main/java/ch/cyberduck/core/irods/IRODSUploadFeature.java +++ b/irods/src/main/java/ch/cyberduck/core/irods/IRODSUploadFeature.java @@ -113,7 +113,7 @@ public Checksum upload(final Path file, final Local local, final BandwidthThrott @Override public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { - return Write.override; + return new Write.Append(status.isExists()).withStatus(status); } @Override diff --git a/irods/src/main/java/ch/cyberduck/core/irods/IRODSWriteFeature.java b/irods/src/main/java/ch/cyberduck/core/irods/IRODSWriteFeature.java index e96e453ae49..20e323035d0 100644 --- a/irods/src/main/java/ch/cyberduck/core/irods/IRODSWriteFeature.java +++ b/irods/src/main/java/ch/cyberduck/core/irods/IRODSWriteFeature.java @@ -20,8 +20,8 @@ import ch.cyberduck.core.ConnectionCallback; import ch.cyberduck.core.Path; import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.Write; import ch.cyberduck.core.io.StatusOutputStream; -import ch.cyberduck.core.shared.AppendWriteFeature; import ch.cyberduck.core.transfer.TransferStatus; import ch.cyberduck.core.worker.DefaultExceptionMappingService; @@ -33,7 +33,7 @@ import org.irods.jargon.core.pub.io.IRODSFileOutputStream; import org.irods.jargon.core.pub.io.PackingIrodsOutputStream; -public class IRODSWriteFeature extends AppendWriteFeature { +public class IRODSWriteFeature implements Write { private final IRODSSession session; diff --git a/irods/src/test/java/ch/cyberduck/core/irods/IRODSWriteFeatureTest.java b/irods/src/test/java/ch/cyberduck/core/irods/IRODSWriteFeatureTest.java index 845a54544a7..c477dd969d5 100644 --- a/irods/src/test/java/ch/cyberduck/core/irods/IRODSWriteFeatureTest.java +++ b/irods/src/test/java/ch/cyberduck/core/irods/IRODSWriteFeatureTest.java @@ -234,7 +234,7 @@ public void testWrite() throws Exception { status.setAppend(false); status.setLength(content.length); - assertEquals(0L, feature.append(test, status).size, 0L); + assertEquals(0L, new IRODSUploadFeature(session).append(test, status).offset, 0L); final StatusOutputStream out = feature.write(test, status, new DisabledConnectionCallback()); assertNotNull(out); @@ -259,8 +259,8 @@ public void testWrite() throws Exception { status.setLength(newcontent.length); status.setRemote(new IRODSAttributesFinderFeature(session).find(test)); - assertTrue(feature.append(test, status).append); - assertEquals(content.length, feature.append(test, status).size, 0L); + assertTrue(new IRODSUploadFeature(session).append(test, status).append); + assertEquals(content.length, new IRODSUploadFeature(session).append(test, status).offset, 0L); final StatusOutputStream out = feature.write(test, status, new DisabledConnectionCallback()); assertNotNull(out); @@ -307,7 +307,7 @@ public void testWriteAppend() throws Exception { status.setLength(content.length); final IRODSWriteFeature feature = new IRODSWriteFeature(session); - assertEquals(0L, feature.append(test, status).size, 0L); + assertEquals(0L, new IRODSUploadFeature(session).append(test, status).offset, 0L); final OutputStream out = feature.write(test, status, new DisabledConnectionCallback()); assertNotNull(out); @@ -333,8 +333,8 @@ public void testWriteAppend() throws Exception { status_append.setLength(content_append.length); status_append.setRemote(new IRODSAttributesFinderFeature(session).find(test)); - assertTrue(feature.append(test, status_append).append); - assertEquals(status.getLength(), feature.append(test, status_append).size, 0L); + assertTrue(new IRODSUploadFeature(session).append(test, status_append).append); + assertEquals(status.getLength(), new IRODSUploadFeature(session).append(test, status_append).offset, 0L); final OutputStream out_append = feature.write(test, status_append, new DisabledConnectionCallback()); assertNotNull(out_append); diff --git a/nio/src/main/java/ch/cyberduck/core/nio/LocalUploadFeature.java b/nio/src/main/java/ch/cyberduck/core/nio/LocalUploadFeature.java new file mode 100644 index 00000000000..bfd4c34c0f5 --- /dev/null +++ b/nio/src/main/java/ch/cyberduck/core/nio/LocalUploadFeature.java @@ -0,0 +1,38 @@ +package ch.cyberduck.core.nio; + +/* + * Copyright (c) 2002-2024 iterate GmbH. All rights reserved. + * https://cyberduck.io/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +import ch.cyberduck.core.Path; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.Write; +import ch.cyberduck.core.shared.DefaultUploadFeature; +import ch.cyberduck.core.transfer.TransferStatus; + +public class LocalUploadFeature extends DefaultUploadFeature { + + public LocalUploadFeature(final LocalSession session) { + super(new LocalWriteFeature(session)); + } + + public LocalUploadFeature(final Write writer) { + super(writer); + } + + @Override + public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { + return new Write.Append(status.isExists()).withStatus(status); + } +} diff --git a/nio/src/main/java/ch/cyberduck/core/nio/LocalWriteFeature.java b/nio/src/main/java/ch/cyberduck/core/nio/LocalWriteFeature.java index fcfa15e29d9..752f2c26ab1 100644 --- a/nio/src/main/java/ch/cyberduck/core/nio/LocalWriteFeature.java +++ b/nio/src/main/java/ch/cyberduck/core/nio/LocalWriteFeature.java @@ -18,9 +18,9 @@ import ch.cyberduck.core.ConnectionCallback; import ch.cyberduck.core.Path; import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.Write; import ch.cyberduck.core.io.StatusOutputStream; import ch.cyberduck.core.io.VoidStatusOutputStream; -import ch.cyberduck.core.shared.AppendWriteFeature; import ch.cyberduck.core.transfer.TransferStatus; import java.io.IOException; @@ -33,7 +33,7 @@ import java.util.HashSet; import java.util.Set; -public class LocalWriteFeature extends AppendWriteFeature { +public class LocalWriteFeature implements Write { private final LocalSession session; diff --git a/nio/src/test/java/ch/cyberduck/core/nio/LocalUploadFeatureTest.java b/nio/src/test/java/ch/cyberduck/core/nio/LocalUploadFeatureTest.java new file mode 100644 index 00000000000..2d24e1db781 --- /dev/null +++ b/nio/src/test/java/ch/cyberduck/core/nio/LocalUploadFeatureTest.java @@ -0,0 +1,48 @@ +package ch.cyberduck.core.nio; + +/* + * Copyright (c) 2002-2024 iterate GmbH. All rights reserved. + * https://cyberduck.io/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +import ch.cyberduck.core.DisabledCancelCallback; +import ch.cyberduck.core.DisabledHostKeyCallback; +import ch.cyberduck.core.DisabledLoginCallback; +import ch.cyberduck.core.Host; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.features.Delete; +import ch.cyberduck.core.proxy.Proxy; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.junit.Test; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.UUID; + +import static org.junit.Assert.assertTrue; + +public class LocalUploadFeatureTest { + + @Test + public void testAppend() throws Exception { + final LocalSession session = new LocalSession(new Host(new LocalProtocol(), new LocalProtocol().getDefaultHostname())); + session.open(Proxy.DIRECT, new DisabledHostKeyCallback(), new DisabledLoginCallback(), new DisabledCancelCallback()); + session.login(Proxy.DIRECT, new DisabledLoginCallback(), new DisabledCancelCallback()); + final Path workdir = new LocalHomeFinderFeature().find(); + final Path test = new Path(workdir, UUID.randomUUID().toString(), EnumSet.of(Path.Type.file)); + new LocalTouchFeature(session).touch(test, new TransferStatus()); + assertTrue(new LocalUploadFeature(session).append(test, new TransferStatus().exists(true).withLength(0L).withRemote(new LocalAttributesFinderFeature(session).find(test))).append); + new LocalDeleteFeature(session).delete(Collections.singletonList(test), new DisabledLoginCallback(), new Delete.DisabledCallback()); + } +} \ No newline at end of file diff --git a/nio/src/test/java/ch/cyberduck/core/nio/LocalWriteFeatureTest.java b/nio/src/test/java/ch/cyberduck/core/nio/LocalWriteFeatureTest.java index a73fd81d218..8141763b16d 100644 --- a/nio/src/test/java/ch/cyberduck/core/nio/LocalWriteFeatureTest.java +++ b/nio/src/test/java/ch/cyberduck/core/nio/LocalWriteFeatureTest.java @@ -102,18 +102,6 @@ public void testWriteNotFound() throws Exception { new LocalWriteFeature(session).write(test, new TransferStatus(), new DisabledConnectionCallback()); } - @Test - public void testAppend() throws Exception { - final LocalSession session = new LocalSession(new Host(new LocalProtocol(), new LocalProtocol().getDefaultHostname())); - session.open(Proxy.DIRECT, new DisabledHostKeyCallback(), new DisabledLoginCallback(), new DisabledCancelCallback()); - session.login(Proxy.DIRECT, new DisabledLoginCallback(), new DisabledCancelCallback()); - final Path workdir = new LocalHomeFinderFeature().find(); - final Path test = new Path(workdir, UUID.randomUUID().toString(), EnumSet.of(Path.Type.file)); - new LocalTouchFeature(session).touch(test, new TransferStatus()); - assertTrue(new LocalWriteFeature(session).append(test, new TransferStatus().exists(true).withLength(0L).withRemote(new LocalAttributesFinderFeature(session).find(test))).append); - new LocalDeleteFeature(session).delete(Collections.singletonList(test), new DisabledLoginCallback(), new Delete.DisabledCallback()); - } - @Test public void testWriteContentRange() throws Exception { final LocalSession session = new LocalSession(new Host(new LocalProtocol(), new LocalProtocol().getDefaultHostname())); diff --git a/onedrive/src/test/java/ch/cyberduck/core/cryptomator/GraphWriteFeatureTest.java b/onedrive/src/test/java/ch/cyberduck/core/cryptomator/GraphWriteFeatureTest.java index d025270a999..a9041c3d140 100644 --- a/onedrive/src/test/java/ch/cyberduck/core/cryptomator/GraphWriteFeatureTest.java +++ b/onedrive/src/test/java/ch/cyberduck/core/cryptomator/GraphWriteFeatureTest.java @@ -88,7 +88,6 @@ public void testWrite() throws Exception { assertTrue(cryptomator.getFeature(session, Find.class, new GraphFindFeature(session, fileid)).find(test)); final PathAttributes attributes = new CryptoListService(session, new GraphItemListService(session, fileid), cryptomator).list(test.getParent(), new DisabledListProgressListener()).find(new SimplePathPredicate(test)).attributes(); assertEquals(content.length, attributes.getSize()); - assertEquals(content.length, new CryptoWriteFeature<>(session, new GraphWriteFeature(session, fileid), cryptomator).append(test, status.withRemote(attributes)).size, 0L); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); final InputStream in = new CryptoReadFeature(session, new GraphReadFeature(session, fileid), cryptomator).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()); new StreamCopier(status, status).transfer(in, buffer); diff --git a/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftAttributesFinderFeature.java b/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftAttributesFinderFeature.java index a1e090ad3da..c75a56455c3 100644 --- a/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftAttributesFinderFeature.java +++ b/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftAttributesFinderFeature.java @@ -110,9 +110,9 @@ public PathAttributes find(final Path file, final ListProgressListener listener) return PathAttributes.EMPTY; } // Try to find pending large file upload - final Write.Append append = new SwiftWriteFeature(session, regionService).append(file, new TransferStatus()); + final Write.Append append = new SwiftLargeObjectUploadFeature(session, regionService, new SwiftWriteFeature(session, regionService)).append(file, new TransferStatus()); if(append.append) { - return new PathAttributes().withSize(append.size); + return new PathAttributes().withSize(append.offset); } throw e; } diff --git a/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftLargeObjectUploadFeature.java b/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftLargeObjectUploadFeature.java index 94816714952..01d45de2056 100644 --- a/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftLargeObjectUploadFeature.java +++ b/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftLargeObjectUploadFeature.java @@ -69,6 +69,11 @@ public class SwiftLargeObjectUploadFeature extends HttpUploadFeature writer; + public SwiftLargeObjectUploadFeature(final SwiftSession session, final SwiftRegionService regionService, final Write writer) { + this(session, regionService, writer, new HostPreferences(session.getHost()).getLong("openstack.upload.largeobject.size"), + new HostPreferences(session.getHost()).getInteger("openstack.upload.largeobject.concurrency")); + } + public SwiftLargeObjectUploadFeature(final SwiftSession session, final SwiftRegionService regionService, final Write writer, final Long segmentSize, final Integer concurrency) { this(session, regionService, new SwiftObjectListService(session, regionService), new SwiftSegmentService(session, regionService), writer, @@ -213,6 +218,26 @@ public StorageObject call() throws BackgroundException { }, overall, counter)); } + @Override + public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { + final List segments; + long size = 0L; + try { + segments = new SwiftObjectListService(session, regionService).list(new SwiftSegmentService(session, regionService) + .getSegmentsDirectory(file), new DisabledListProgressListener()).toList(); + if(segments.isEmpty()) { + return Write.override; + } + } + catch(NotfoundException e) { + return Write.override; + } + for(Path segment : segments) { + size += segment.attributes().getSize(); + } + return new Write.Append(true).withStatus(status).withOffset(size); + } + @Override public Upload withWriter(final Write writer) { this.writer = writer; diff --git a/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftThresholdUploadService.java b/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftThresholdUploadService.java index 92d4b4516a1..1a0e6c61242 100644 --- a/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftThresholdUploadService.java +++ b/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftThresholdUploadService.java @@ -59,29 +59,37 @@ public SwiftThresholdUploadService(final SwiftSession session, final SwiftRegion @Override public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { - return writer.append(file, status); + if(this.threshold(status)) { + return new SwiftLargeObjectUploadFeature(session, regionService, writer).append(file, status); + } + return new Write.Append(false).withStatus(status); } @Override public StorageObject upload(final Path file, final Local local, final BandwidthThrottle throttle, final StreamListener listener, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException { final Upload feature; + if(this.threshold(status)) { + feature = new SwiftLargeObjectUploadFeature(session, regionService, writer); + } + else { + feature = new SwiftSmallObjectUploadFeature(session, writer); + } + return feature.upload(file, local, throttle, listener, status, callback); + } + + protected boolean threshold(final TransferStatus status) { if(status.getLength() > threshold) { if(!new HostPreferences(session.getHost()).getBoolean("openstack.upload.largeobject")) { // Disabled by user if(status.getLength() < new HostPreferences(session.getHost()).getLong("openstack.upload.largeobject.required.threshold")) { log.warn("Large upload is disabled with property openstack.upload.largeobject"); - return new SwiftSmallObjectUploadFeature(session, writer).upload(file, local, throttle, listener, status, callback); + return false; } } - feature = new SwiftLargeObjectUploadFeature(session, regionService, writer, - new HostPreferences(session.getHost()).getLong("openstack.upload.largeobject.size"), - new HostPreferences(session.getHost()).getInteger("openstack.upload.largeobject.concurrency")); - } - else { - feature = new SwiftSmallObjectUploadFeature(session, writer); + return true; } - return feature.upload(file, local, throttle, listener, status, callback); + return false; } @Override diff --git a/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftWriteFeature.java b/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftWriteFeature.java index 14d44c52ea0..719ba08229c 100644 --- a/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftWriteFeature.java +++ b/openstack/src/main/java/ch/cyberduck/core/openstack/SwiftWriteFeature.java @@ -21,11 +21,9 @@ import ch.cyberduck.core.ConnectionCallback; import ch.cyberduck.core.DefaultIOExceptionMappingService; import ch.cyberduck.core.DefaultPathContainerService; -import ch.cyberduck.core.DisabledListProgressListener; import ch.cyberduck.core.Path; import ch.cyberduck.core.PathContainerService; import ch.cyberduck.core.exception.BackgroundException; -import ch.cyberduck.core.exception.NotfoundException; import ch.cyberduck.core.features.Write; import ch.cyberduck.core.http.AbstractHttpWriteFeature; import ch.cyberduck.core.http.DelayedHttpEntityCallable; @@ -42,7 +40,6 @@ import java.io.IOException; import java.util.HashMap; -import java.util.List; import ch.iterate.openstack.swift.Constants; import ch.iterate.openstack.swift.exception.GenericException; @@ -52,7 +49,7 @@ public class SwiftWriteFeature extends AbstractHttpWriteFeature i private static final Logger log = LogManager.getLogger(SwiftSession.class); private final PathContainerService containerService - = new DefaultPathContainerService(); + = new DefaultPathContainerService(); private final SwiftSession session; private final SwiftRegionService regionService; @@ -108,26 +105,6 @@ public long getContentLength() { return this.write(file, status, command); } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - final List segments; - long size = 0L; - try { - segments = new SwiftObjectListService(session, regionService).list(new SwiftSegmentService(session, regionService) - .getSegmentsDirectory(file), new DisabledListProgressListener()).toList(); - if(segments.isEmpty()) { - return Write.override; - } - } - catch(NotfoundException e) { - return Write.override; - } - for(Path segment : segments) { - size += segment.attributes().getSize(); - } - return new Append(true).withStatus(status).withSize(size); - } - @Override public ChecksumCompute checksum(final Path file, final TransferStatus status) { return ChecksumComputeFactory.get(HashAlgorithm.md5); diff --git a/openstack/src/test/java/ch/cyberduck/core/cryptomator/SwiftWriteFeatureTest.java b/openstack/src/test/java/ch/cyberduck/core/cryptomator/SwiftWriteFeatureTest.java index c5377c966e4..d7c95ab0e96 100644 --- a/openstack/src/test/java/ch/cyberduck/core/cryptomator/SwiftWriteFeatureTest.java +++ b/openstack/src/test/java/ch/cyberduck/core/cryptomator/SwiftWriteFeatureTest.java @@ -22,7 +22,6 @@ import ch.cyberduck.core.DisabledPasswordCallback; import ch.cyberduck.core.DisabledPasswordStore; import ch.cyberduck.core.Path; -import ch.cyberduck.core.PathAttributes; import ch.cyberduck.core.cryptomator.features.CryptoListService; import ch.cyberduck.core.cryptomator.features.CryptoReadFeature; import ch.cyberduck.core.cryptomator.features.CryptoWriteFeature; @@ -90,7 +89,6 @@ public void testWrite() throws Exception { assertEquals(content.length, status.getResponse().getSize()); assertTrue(cryptomator.getFeature(session, Find.class, new SwiftFindFeature(session)).find(test)); assertEquals(content.length, new CryptoListService(session, new SwiftListService(session, regionService), cryptomator).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes().getSize()); - assertEquals(content.length, writer.append(test, status.withRemote(new PathAttributes().withSize(content.length))).size, 0L); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); final InputStream in = new CryptoReadFeature(session, new SwiftReadFeature(session, regionService), cryptomator).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()); new StreamCopier(status, status).transfer(in, buffer); diff --git a/openstack/src/test/java/ch/cyberduck/core/openstack/SwiftLargeObjectUploadFeatureTest.java b/openstack/src/test/java/ch/cyberduck/core/openstack/SwiftLargeObjectUploadFeatureTest.java index 4a0175a8e19..75f995bffa1 100644 --- a/openstack/src/test/java/ch/cyberduck/core/openstack/SwiftLargeObjectUploadFeatureTest.java +++ b/openstack/src/test/java/ch/cyberduck/core/openstack/SwiftLargeObjectUploadFeatureTest.java @@ -12,6 +12,7 @@ import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.ConnectionTimeoutException; import ch.cyberduck.core.features.Delete; +import ch.cyberduck.core.features.Write; import ch.cyberduck.core.io.BandwidthThrottle; import ch.cyberduck.core.io.Checksum; import ch.cyberduck.core.io.DisabledStreamListener; @@ -276,4 +277,16 @@ public void testUploadOverwrite() throws Exception { assertEquals(0, new SwiftSegmentService(session).list(test).size()); local.delete(); } + + @Test + public void testAppendNoSegmentFound() throws Exception { + final Path container = new Path("test.cyberduck.ch", EnumSet.of(Path.Type.directory, Path.Type.volume)); + container.attributes().setRegion("IAD"); + final SwiftRegionService regionService = new SwiftRegionService(session); + final Write.Append append = new SwiftLargeObjectUploadFeature(session, regionService, + new SwiftWriteFeature(session, regionService)) + .append(new Path(container, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus()); + assertFalse(append.append); + assertEquals(Write.override, append); + } } diff --git a/openstack/src/test/java/ch/cyberduck/core/openstack/SwiftWriteFeatureTest.java b/openstack/src/test/java/ch/cyberduck/core/openstack/SwiftWriteFeatureTest.java index d58995e14ae..21747be34a3 100644 --- a/openstack/src/test/java/ch/cyberduck/core/openstack/SwiftWriteFeatureTest.java +++ b/openstack/src/test/java/ch/cyberduck/core/openstack/SwiftWriteFeatureTest.java @@ -7,7 +7,6 @@ import ch.cyberduck.core.Path; import ch.cyberduck.core.PathAttributes; import ch.cyberduck.core.features.Delete; -import ch.cyberduck.core.features.Write; import ch.cyberduck.core.io.StatusOutputStream; import ch.cyberduck.core.io.StreamCopier; import ch.cyberduck.core.transfer.TransferStatus; @@ -51,9 +50,6 @@ public void testWrite() throws Exception { assertTrue(new SwiftFindFeature(session).find(test)); final PathAttributes attributes = new SwiftListService(session, regionService).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes(); assertEquals(content.length, attributes.getSize()); - final Write.Append append = new SwiftWriteFeature(session, regionService).append(test, status.withRemote(attributes)); - assertFalse(append.append); - assertEquals(0L, append.size, 0L); final byte[] buffer = new byte[content.length]; final InputStream in = new SwiftReadFeature(session, regionService).read(test, new TransferStatus(), new DisabledConnectionCallback()); IOUtils.readFully(in, buffer); @@ -67,14 +63,4 @@ public void testWrite() throws Exception { overwrite.close(); new SwiftDeleteFeature(session).delete(Collections.singletonList(test), new DisabledLoginCallback(), new Delete.DisabledCallback()); } - - @Test - public void testAppendNoSegmentFound() throws Exception { - final Path container = new Path("test.cyberduck.ch", EnumSet.of(Path.Type.directory, Path.Type.volume)); - container.attributes().setRegion("IAD"); - final SwiftRegionService regionService = new SwiftRegionService(session); - final Write.Append append = new SwiftWriteFeature(session, regionService).append(new Path(container, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus()); - assertFalse(append.append); - assertEquals(Write.override, append); - } } diff --git a/s3/src/main/java/ch/cyberduck/core/s3/S3AttributesFinderFeature.java b/s3/src/main/java/ch/cyberduck/core/s3/S3AttributesFinderFeature.java index a4e0c0f3198..dd9ba536247 100644 --- a/s3/src/main/java/ch/cyberduck/core/s3/S3AttributesFinderFeature.java +++ b/s3/src/main/java/ch/cyberduck/core/s3/S3AttributesFinderFeature.java @@ -66,9 +66,9 @@ public PathAttributes find(final Path file, final ListProgressListener listener) return attributes; } if(file.getType().contains(Path.Type.upload)) { - final Write.Append append = new S3WriteFeature(session, acl).append(file, new TransferStatus()); + final Write.Append append = new S3MultipartUploadService(session, new S3WriteFeature(session, acl), acl).append(file, new TransferStatus()); if(append.append) { - return new PathAttributes().withSize(append.size); + return new PathAttributes().withSize(append.offset); } throw new NotfoundException(file.getAbsolute()); } diff --git a/s3/src/main/java/ch/cyberduck/core/s3/S3MultipartUploadService.java b/s3/src/main/java/ch/cyberduck/core/s3/S3MultipartUploadService.java index 0daed4034af..716e2325619 100644 --- a/s3/src/main/java/ch/cyberduck/core/s3/S3MultipartUploadService.java +++ b/s3/src/main/java/ch/cyberduck/core/s3/S3MultipartUploadService.java @@ -263,6 +263,25 @@ public MultipartPart call() throws BackgroundException { }, overall, counter)); } + @Override + public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { + try { + final S3DefaultMultipartService multipartService = new S3DefaultMultipartService(session); + final List upload = multipartService.find(file); + if(!upload.isEmpty()) { + Long size = 0L; + for(MultipartPart completed : multipartService.list(upload.iterator().next())) { + size += completed.getSize(); + } + return new Write.Append(true).withStatus(status).withOffset(size); + } + } + catch(AccessDeniedException | InteroperabilityException e) { + log.warn(String.format("Ignore failure listing incomplete multipart uploads. %s", e)); + } + return Write.override; + } + @Override public Upload withWriter(final Write writer) { this.writer = writer; diff --git a/s3/src/main/java/ch/cyberduck/core/s3/S3ThresholdUploadService.java b/s3/src/main/java/ch/cyberduck/core/s3/S3ThresholdUploadService.java index 9ab5e222ea6..14b28673f6f 100644 --- a/s3/src/main/java/ch/cyberduck/core/s3/S3ThresholdUploadService.java +++ b/s3/src/main/java/ch/cyberduck/core/s3/S3ThresholdUploadService.java @@ -57,21 +57,16 @@ public S3ThresholdUploadService(final S3Session session, final S3AccessControlLi @Override public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { - return writer.append(file, status); + if(this.threshold(status)) { + return new S3MultipartUploadService(session, writer, acl).append(file, status); + } + return new Write.Append(false).withStatus(status); } @Override public StorageObject upload(final Path file, Local local, final BandwidthThrottle throttle, final StreamListener listener, final TransferStatus status, final ConnectionCallback prompt) throws BackgroundException { - if(status.getLength() >= threshold) { - if(!new HostPreferences(session.getHost()).getBoolean("s3.upload.multipart")) { - log.warn("Multipart upload is disabled with property s3.upload.multipart"); - // Disabled by user - if(status.getLength() < new HostPreferences(session.getHost()).getLong("s3.upload.multipart.required.threshold")) { - // Use single upload service - return new S3SingleUploadService(session, writer).upload(file, local, throttle, listener, status, prompt); - } - } + if(this.threshold(status)) { try { return new S3MultipartUploadService(session, writer, acl).upload(file, local, throttle, listener, status, prompt); } @@ -91,6 +86,20 @@ public StorageObject upload(final Path file, Local local, final BandwidthThrottl return new S3SingleUploadService(session, writer).upload(file, local, throttle, listener, status, prompt); } + protected boolean threshold(final TransferStatus status) { + if(status.getLength() >= threshold) { + if(!new HostPreferences(session.getHost()).getBoolean("s3.upload.multipart")) { + log.warn("Multipart upload is disabled with property s3.upload.multipart"); + // Disabled by user + if(status.getLength() < new HostPreferences(session.getHost()).getLong("s3.upload.multipart.required.threshold")) { + return false; + } + } + return true; + } + return false; + } + @Override public Upload withWriter(final Write writer) { this.writer = writer; diff --git a/s3/src/main/java/ch/cyberduck/core/s3/S3WriteFeature.java b/s3/src/main/java/ch/cyberduck/core/s3/S3WriteFeature.java index 3c0b5ed4d3b..c3029beb653 100644 --- a/s3/src/main/java/ch/cyberduck/core/s3/S3WriteFeature.java +++ b/s3/src/main/java/ch/cyberduck/core/s3/S3WriteFeature.java @@ -22,9 +22,7 @@ import ch.cyberduck.core.Header; import ch.cyberduck.core.Path; import ch.cyberduck.core.PathContainerService; -import ch.cyberduck.core.exception.AccessDeniedException; import ch.cyberduck.core.exception.BackgroundException; -import ch.cyberduck.core.exception.InteroperabilityException; import ch.cyberduck.core.features.Encryption; import ch.cyberduck.core.features.Write; import ch.cyberduck.core.http.AbstractHttpWriteFeature; @@ -44,8 +42,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jets3t.service.ServiceException; -import org.jets3t.service.model.MultipartPart; -import org.jets3t.service.model.MultipartUpload; import org.jets3t.service.model.S3Object; import org.jets3t.service.model.StorageObject; @@ -54,7 +50,6 @@ import java.util.Arrays; import java.util.EnumSet; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; @@ -160,27 +155,6 @@ protected S3Object getDetails(final Path file, final TransferStatus status) { return object; } - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - if(new HostPreferences(session.getHost()).getBoolean("s3.upload.multipart")) { - try { - final S3DefaultMultipartService multipartService = new S3DefaultMultipartService(session); - final List upload = multipartService.find(file); - if(!upload.isEmpty()) { - Long size = 0L; - for(MultipartPart completed : multipartService.list(upload.iterator().next())) { - size += completed.getSize(); - } - return new Append(true).withStatus(status).withSize(size); - } - } - catch(AccessDeniedException | InteroperabilityException e) { - log.warn(String.format("Ignore failure listing incomplete multipart uploads. %s", e)); - } - } - return Write.override; - } - @Override public EnumSet features(final Path file) { return EnumSet.of(Flags.timestamp); diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/S3WriteFeatureTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/S3WriteFeatureTest.java index c612e7a8baa..c2eff6bbcf2 100644 --- a/s3/src/test/java/ch/cyberduck/core/cryptomator/S3WriteFeatureTest.java +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/S3WriteFeatureTest.java @@ -88,7 +88,6 @@ public void testWrite() throws Exception { assertTrue(cryptomator.getFeature(session, Find.class, new S3FindFeature(session, acl)).find(test)); final PathAttributes attributes = cryptomator.getFeature(session, AttributesFinder.class, new S3AttributesFinderFeature(session, acl)).find(test); assertEquals(content.length, attributes.getSize()); - assertEquals(content.length, writer.append(test, status.withRemote(attributes)).size, 0L); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); final InputStream in = new CryptoReadFeature(session, new S3ReadFeature(session), cryptomator).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()); new StreamCopier(status, status).transfer(in, buffer); diff --git a/s3/src/test/java/ch/cyberduck/core/s3/S3MultipartUploadServiceTest.java b/s3/src/test/java/ch/cyberduck/core/s3/S3MultipartUploadServiceTest.java index a3e1fa1c087..8e605e3d1e7 100644 --- a/s3/src/test/java/ch/cyberduck/core/s3/S3MultipartUploadServiceTest.java +++ b/s3/src/test/java/ch/cyberduck/core/s3/S3MultipartUploadServiceTest.java @@ -1,5 +1,6 @@ package ch.cyberduck.core.s3; +import ch.cyberduck.core.AlphanumericRandomStringService; import ch.cyberduck.core.BytecountStreamListener; import ch.cyberduck.core.ConnectionCallback; import ch.cyberduck.core.DisabledConnectionCallback; @@ -14,6 +15,7 @@ import ch.cyberduck.core.exception.ConnectionTimeoutException; import ch.cyberduck.core.exception.NotfoundException; import ch.cyberduck.core.features.Delete; +import ch.cyberduck.core.features.Write; import ch.cyberduck.core.io.BandwidthThrottle; import ch.cyberduck.core.io.Checksum; import ch.cyberduck.core.io.DisabledStreamListener; @@ -224,16 +226,17 @@ public void testAppendSecondPart() throws Exception { final AtomicBoolean interrupt = new AtomicBoolean(); final BytecountStreamListener count = new BytecountStreamListener(); final S3AccessControlListFeature acl = new S3AccessControlListFeature(session); - try { - new S3MultipartUploadService(session, new S3WriteFeature(session, acl), acl, 10L * 1024L * 1024L, 1) { - @Override - public StorageObject upload(final Path file, final Local local, final BandwidthThrottle throttle, final StreamListener listener, final TransferStatus status, final StreamCancelation cancel, final StreamProgress progress, final ConnectionCallback callback) throws BackgroundException { - if(status.getOffset() >= 10L * 1024L * 1024L) { - throw new ConnectionTimeoutException("Test"); - } - return super.upload(file, local, throttle, listener, status, cancel, progress, callback); + final S3MultipartUploadService feature = new S3MultipartUploadService(session, new S3WriteFeature(session, acl), acl, 10L * 1024L * 1024L, 1) { + @Override + public StorageObject upload(final Path file, final Local local, final BandwidthThrottle throttle, final StreamListener listener, final TransferStatus status, final StreamCancelation cancel, final StreamProgress progress, final ConnectionCallback callback) throws BackgroundException { + if(status.getOffset() >= 10L * 1024L * 1024L) { + throw new ConnectionTimeoutException("Test"); } - }.upload(test, new Local(System.getProperty("java.io.tmpdir"), name), + return super.upload(file, local, throttle, listener, status, cancel, progress, callback); + } + }; + try { + feature.upload(test, new Local(System.getProperty("java.io.tmpdir"), name), new BandwidthThrottle(BandwidthThrottle.UNLIMITED), count, status, new DisabledLoginCallback()); } @@ -246,11 +249,9 @@ public StorageObject upload(final Path file, final Local local, final BandwidthT assertFalse(status.isComplete()); assertEquals(TransferStatus.UNKNOWN_LENGTH, status.getResponse().getSize()); final Path upload = new S3ListService(session, acl).list(container, new DisabledListProgressListener()).find(new SimplePathPredicate(test)); - assertTrue(new S3FindFeature(session, acl).find(upload)); assertNotNull(upload); assertTrue(upload.getType().contains(Path.Type.upload)); - assertTrue(new S3FindFeature(session, acl).find(upload)); - assertEquals(10L * 1024L * 1024L, new S3AttributesFinderFeature(session, acl).find(upload).getSize()); + assertEquals(10L * 1024L * 1024L, feature.append(upload, status).offset, 0L); final TransferStatus append = new TransferStatus().append(true).withLength(2L * 1024L * 1024L).withOffset(10L * 1024L * 1024L); new S3MultipartUploadService(session, new S3WriteFeature(session, acl), acl, 10L * 1024L * 1024L, 1).upload(test, local, new BandwidthThrottle(BandwidthThrottle.UNLIMITED), count, append, @@ -327,4 +328,31 @@ test, local, new BandwidthThrottle(BandwidthThrottle.UNLIMITED), new S3DefaultDeleteFeature(session).delete(Collections.singletonList(test), new DisabledLoginCallback(), new Delete.DisabledCallback()); local.delete(); } + + @Test + public void testAppendBelowLimit() throws Exception { + final S3AccessControlListFeature acl = new S3AccessControlListFeature(session); + final S3MultipartUploadService feature = new S3MultipartUploadService(session, new S3WriteFeature(session, acl), acl, 5 * 1024L * 1024L, 5); + final Write.Append append = feature.append(new Path("/p", EnumSet.of(Path.Type.file)), new TransferStatus().withLength(0L)); + assertFalse(append.append); + } + + @Test + public void testSize() throws Exception { + final S3AccessControlListFeature acl = new S3AccessControlListFeature(session); + final S3MultipartUploadService feature = new S3MultipartUploadService(session, new S3WriteFeature(session, acl), acl, 5 * 1024L * 1024L, 5); + final Write.Append append = feature.append(new Path("/p", EnumSet.of(Path.Type.file)), new TransferStatus().withLength(0L).withRemote(new PathAttributes().withSize(3L))); + assertFalse(append.append); + assertEquals(0L, append.offset, 0L); + } + + @Test + public void testAppendNoMultipartFound() throws Exception { + final Path container = new Path("test-eu-central-1-cyberduck", EnumSet.of(Path.Type.directory, Path.Type.volume)); + final S3AccessControlListFeature acl = new S3AccessControlListFeature(session); + final S3MultipartUploadService feature = new S3MultipartUploadService(session, new S3WriteFeature(session, acl), acl, 5 * 1024L * 1024L, 5); + assertFalse(feature.append(new Path(container, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus().withLength(Long.MAX_VALUE)).append); + assertEquals(Write.override, feature.append(new Path(container, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus().withLength(Long.MAX_VALUE))); + assertEquals(Write.override, feature.append(new Path(container, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus().withLength(0L))); + } } diff --git a/s3/src/test/java/ch/cyberduck/core/s3/S3WriteFeatureTest.java b/s3/src/test/java/ch/cyberduck/core/s3/S3WriteFeatureTest.java index 4d526180d66..92db086ce15 100644 --- a/s3/src/test/java/ch/cyberduck/core/s3/S3WriteFeatureTest.java +++ b/s3/src/test/java/ch/cyberduck/core/s3/S3WriteFeatureTest.java @@ -11,7 +11,6 @@ import ch.cyberduck.core.SimplePathPredicate; import ch.cyberduck.core.exception.InteroperabilityException; import ch.cyberduck.core.features.Delete; -import ch.cyberduck.core.features.Write; import ch.cyberduck.core.http.HttpResponseOutputStream; import ch.cyberduck.core.io.SHA256ChecksumCompute; import ch.cyberduck.core.io.StreamCopier; @@ -81,29 +80,6 @@ public void testWriteCustomTimestamp() throws Exception { new S3DefaultDeleteFeature(session).delete(Collections.singletonList(moved), new DisabledLoginCallback(), new Delete.DisabledCallback()); } - @Test - public void testAppendBelowLimit() throws Exception { - final S3WriteFeature feature = new S3WriteFeature(session, new S3AccessControlListFeature(session)); - final Write.Append append = feature.append(new Path("/p", EnumSet.of(Path.Type.file)), new TransferStatus().withLength(0L)); - assertFalse(append.append); - } - - @Test - public void testSize() throws Exception { - final S3WriteFeature feature = new S3WriteFeature(session, new S3AccessControlListFeature(session)); - final Write.Append append = feature.append(new Path("/p", EnumSet.of(Path.Type.file)), new TransferStatus().withLength(0L).withRemote(new PathAttributes().withSize(3L))); - assertFalse(append.append); - assertEquals(0L, append.size, 0L); - } - - @Test - public void testAppendNoMultipartFound() throws Exception { - final Path container = new Path("test-eu-central-1-cyberduck", EnumSet.of(Path.Type.directory, Path.Type.volume)); - assertFalse(new S3WriteFeature(session, new S3AccessControlListFeature(session)).append(new Path(container, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus().withLength(Long.MAX_VALUE)).append); - assertEquals(Write.override, new S3WriteFeature(session, new S3AccessControlListFeature(session)).append(new Path(container, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus().withLength(Long.MAX_VALUE))); - assertEquals(Write.override, new S3WriteFeature(session, new S3AccessControlListFeature(session)).append(new Path(container, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus().withLength(0L))); - } - @Test(expected = InteroperabilityException.class) public void testWriteChunkedTransferAWS4Signature() throws Exception { final S3WriteFeature feature = new S3WriteFeature(session, new S3AccessControlListFeature(session)); diff --git a/smb/src/test/java/ch/cyberduck/core/smb/SMBReadFeatureTest.java b/smb/src/test/java/ch/cyberduck/core/smb/SMBReadFeatureTest.java index 2e4226788c9..fd9e4ec42c1 100644 --- a/smb/src/test/java/ch/cyberduck/core/smb/SMBReadFeatureTest.java +++ b/smb/src/test/java/ch/cyberduck/core/smb/SMBReadFeatureTest.java @@ -75,7 +75,6 @@ public void testReadRange() throws Exception { new StreamCopier(status, status).transfer(new ByteArrayInputStream(content), out); assertTrue(new SMBFindFeature(session).find(test)); assertEquals(content.length, new SMBListService(session).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes().getSize()); - assertEquals(content.length, writer.append(test, status.withRemote(new SMBAttributesFinderFeature(session).find(test))).size, 0L); { final ByteArrayOutputStream buffer = new ByteArrayOutputStream(40000); final TransferStatus read = new TransferStatus(); diff --git a/ssh/src/main/java/ch/cyberduck/core/sftp/SFTPSession.java b/ssh/src/main/java/ch/cyberduck/core/sftp/SFTPSession.java index 531a8ca0781..28c1bf72a5d 100644 --- a/ssh/src/main/java/ch/cyberduck/core/sftp/SFTPSession.java +++ b/ssh/src/main/java/ch/cyberduck/core/sftp/SFTPSession.java @@ -473,6 +473,9 @@ public T _getFeature(final Class type) { if(type == Read.class) { return (T) new SFTPReadFeature(this); } + if(type == Upload.class) { + return (T) new SFTPUploadFeature(this); + } if(type == Write.class) { return (T) new SFTPWriteFeature(this); } diff --git a/ssh/src/main/java/ch/cyberduck/core/sftp/SFTPUploadFeature.java b/ssh/src/main/java/ch/cyberduck/core/sftp/SFTPUploadFeature.java new file mode 100644 index 00000000000..8c9aec61341 --- /dev/null +++ b/ssh/src/main/java/ch/cyberduck/core/sftp/SFTPUploadFeature.java @@ -0,0 +1,38 @@ +package ch.cyberduck.core.sftp; + +/* + * Copyright (c) 2002-2024 iterate GmbH. All rights reserved. + * https://cyberduck.io/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +import ch.cyberduck.core.Path; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.Write; +import ch.cyberduck.core.shared.DefaultUploadFeature; +import ch.cyberduck.core.transfer.TransferStatus; + +public class SFTPUploadFeature extends DefaultUploadFeature { + + public SFTPUploadFeature(final SFTPSession session) { + super(new SFTPWriteFeature(session)); + } + + public SFTPUploadFeature(final Write writer) { + super(writer); + } + + @Override + public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { + return new Write.Append(status.isExists()).withStatus(status); + } +} diff --git a/ssh/src/main/java/ch/cyberduck/core/sftp/SFTPWriteFeature.java b/ssh/src/main/java/ch/cyberduck/core/sftp/SFTPWriteFeature.java index c63e3977d75..90d3f45ef6b 100644 --- a/ssh/src/main/java/ch/cyberduck/core/sftp/SFTPWriteFeature.java +++ b/ssh/src/main/java/ch/cyberduck/core/sftp/SFTPWriteFeature.java @@ -20,11 +20,11 @@ import ch.cyberduck.core.ConnectionCallback; import ch.cyberduck.core.Path; import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.Write; import ch.cyberduck.core.io.StatusOutputStream; import ch.cyberduck.core.io.VoidStatusOutputStream; import ch.cyberduck.core.preferences.HostPreferences; import ch.cyberduck.core.preferences.PreferencesReader; -import ch.cyberduck.core.shared.AppendWriteFeature; import ch.cyberduck.core.transfer.TransferStatus; import org.apache.commons.io.output.ChunkedOutputStream; @@ -38,7 +38,7 @@ import net.schmizz.sshj.sftp.OpenMode; import net.schmizz.sshj.sftp.RemoteFile; -public class SFTPWriteFeature extends AppendWriteFeature { +public class SFTPWriteFeature implements Write { private static final Logger log = LogManager.getLogger(SFTPWriteFeature.class); private final SFTPSession session; diff --git a/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPReadFeatureTest.java b/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPReadFeatureTest.java index 43b55c6c479..70594568875 100644 --- a/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPReadFeatureTest.java +++ b/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPReadFeatureTest.java @@ -86,7 +86,6 @@ public void testReadRange() throws Exception { assertTrue(cryptomator.getFeature(session, Find.class, new SFTPFindFeature(session)).find(test)); final PathAttributes attributes = new CryptoListService(session, new SFTPListService(session), cryptomator).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes(); assertEquals(content.length, attributes.getSize()); - assertEquals(content.length, writer.append(test, status.withRemote(attributes)).size, 0L); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(30000); final TransferStatus read = new TransferStatus(); read.setOffset(40000); diff --git a/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPWriteFeatureTest.java b/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPWriteFeatureTest.java index 5e6456e3fde..a99fa716a11 100644 --- a/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPWriteFeatureTest.java +++ b/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPWriteFeatureTest.java @@ -93,7 +93,6 @@ public void testWrite() throws Exception { assertEquals(TransferStatus.UNKNOWN_LENGTH, status.getResponse().getSize()); assertTrue(cryptomator.getFeature(session, Find.class, new SFTPFindFeature(session)).find(test)); assertEquals(content.length, new CryptoListService(session, new SFTPListService(session), cryptomator).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes().getSize()); - assertEquals(content.length, writer.append(test, status.withRemote(cryptomator.getFeature(session, AttributesFinder.class, new SFTPAttributesFinderFeature(session)).find(test))).size, 0L); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); final InputStream in = new CryptoReadFeature(session, new SFTPReadFeature(session), cryptomator).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()); new StreamCopier(status, status).transfer(in, buffer); @@ -133,17 +132,14 @@ public void testWriteWithCache() throws Exception { { final PathAttributes attributes = cryptomator.getFeature(session, AttributesFinder.class, new SFTPAttributesFinderFeature(session)).find(test); assertEquals(content.length, attributes.getSize()); - assertEquals(content.length, writer.append(test, status.withRemote(attributes)).size, 0L); } { final PathAttributes attributes = cryptomator.getFeature(session, AttributesFinder.class, new DefaultAttributesFinderFeature(session)).find(test); assertEquals(content.length, attributes.getSize()); - assertEquals(content.length, writer.append(test, status.withRemote(attributes)).size, 0L); } { final PathAttributes attributes = new CachingAttributesFinderFeature(session, cache, cryptomator.getFeature(session, AttributesFinder.class, new DefaultAttributesFinderFeature(session))).find(test); assertEquals(content.length, attributes.getSize()); - assertEquals(content.length, writer.append(test, status.withRemote(attributes)).size, 0L); } assertEquals(content.length, cache.get(vault).get(0).attributes().getSize()); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); diff --git a/ssh/src/test/java/ch/cyberduck/core/sftp/SFTPUploadFeatureTest.java b/ssh/src/test/java/ch/cyberduck/core/sftp/SFTPUploadFeatureTest.java new file mode 100644 index 00000000000..70ebc74c1e3 --- /dev/null +++ b/ssh/src/test/java/ch/cyberduck/core/sftp/SFTPUploadFeatureTest.java @@ -0,0 +1,42 @@ +package ch.cyberduck.core.sftp; + +/* + * Copyright (c) 2002-2024 iterate GmbH. All rights reserved. + * https://cyberduck.io/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +import ch.cyberduck.core.AlphanumericRandomStringService; +import ch.cyberduck.core.DisabledLoginCallback; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.features.Delete; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.junit.Test; + +import java.util.Collections; +import java.util.EnumSet; + +import static org.junit.Assert.assertTrue; + +public class SFTPUploadFeatureTest extends AbstractSFTPTest { + + @Test + public void testAppend() throws Exception { + final Path workdir = new SFTPHomeDirectoryService(session).find(); + final Path test = new Path(workdir, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)); + new SFTPTouchFeature(session).touch(test, new TransferStatus()); + assertTrue(new SFTPUploadFeature(session).append(test, new TransferStatus().exists(true).withLength(1L).withRemote(new SFTPAttributesFinderFeature(session).find(test))).append); + new SFTPDeleteFeature(session).delete(Collections.singletonList(test), new DisabledLoginCallback(), new Delete.DisabledCallback()); + } + +} \ No newline at end of file diff --git a/ssh/src/test/java/ch/cyberduck/core/sftp/SFTPWriteFeatureTest.java b/ssh/src/test/java/ch/cyberduck/core/sftp/SFTPWriteFeatureTest.java index 0763dd7bb92..cfc0b1e0983 100644 --- a/ssh/src/test/java/ch/cyberduck/core/sftp/SFTPWriteFeatureTest.java +++ b/ssh/src/test/java/ch/cyberduck/core/sftp/SFTPWriteFeatureTest.java @@ -50,7 +50,6 @@ public void testWriteThrottled() throws Exception { out.close(); assertTrue(new SFTPFindFeature(session).find(test)); assertEquals(content.length, new SFTPListService(session).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes().getSize()); - assertEquals(content.length, new SFTPWriteFeature(session).append(test, status.withRemote(new SFTPAttributesFinderFeature(session).find(test))).size, 0L); { final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); final InputStream in = new SFTPReadFeature(session).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()); @@ -148,15 +147,6 @@ public void testWriteNotFound() throws Exception { new SFTPWriteFeature(session).write(test, new TransferStatus(), new DisabledConnectionCallback()); } - @Test - public void testAppend() throws Exception { - final Path workdir = new SFTPHomeDirectoryService(session).find(); - final Path test = new Path(workdir, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)); - new SFTPTouchFeature(session).touch(test, new TransferStatus()); - assertTrue(new SFTPWriteFeature(session).append(test, new TransferStatus().exists(true).withLength(1L).withRemote(new SFTPAttributesFinderFeature(session).find(test))).append); - new SFTPDeleteFeature(session).delete(Collections.singletonList(test), new DisabledLoginCallback(), new Delete.DisabledCallback()); - } - @Test public void testWriteContentRange() throws Exception { final SFTPWriteFeature feature = new SFTPWriteFeature(session); diff --git a/ssh/src/test/java/ch/cyberduck/core/worker/SFTPSingleTransferWorkerTest.java b/ssh/src/test/java/ch/cyberduck/core/worker/SFTPSingleTransferWorkerTest.java index 16a717abf69..edd774d3199 100644 --- a/ssh/src/test/java/ch/cyberduck/core/worker/SFTPSingleTransferWorkerTest.java +++ b/ssh/src/test/java/ch/cyberduck/core/worker/SFTPSingleTransferWorkerTest.java @@ -15,12 +15,24 @@ * GNU General Public License for more details. */ -import ch.cyberduck.core.*; +import ch.cyberduck.core.AlphanumericRandomStringService; +import ch.cyberduck.core.BytecountStreamListener; +import ch.cyberduck.core.ConnectionCallback; +import ch.cyberduck.core.Credentials; +import ch.cyberduck.core.DisabledCancelCallback; +import ch.cyberduck.core.DisabledConnectionCallback; +import ch.cyberduck.core.DisabledHostKeyCallback; +import ch.cyberduck.core.DisabledLoginCallback; +import ch.cyberduck.core.DisabledPasswordStore; +import ch.cyberduck.core.DisabledProgressListener; +import ch.cyberduck.core.Host; +import ch.cyberduck.core.Local; +import ch.cyberduck.core.LoginConnectionService; +import ch.cyberduck.core.Path; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.Delete; -import ch.cyberduck.core.features.Write; +import ch.cyberduck.core.features.Upload; import ch.cyberduck.core.io.StatusOutputStream; -import ch.cyberduck.core.io.StreamCopier; import ch.cyberduck.core.io.VoidStatusOutputStream; import ch.cyberduck.core.notification.DisabledNotificationService; import ch.cyberduck.core.preferences.HostPreferences; @@ -28,11 +40,9 @@ import ch.cyberduck.core.sftp.SFTPAttributesFinderFeature; import ch.cyberduck.core.sftp.SFTPDeleteFeature; import ch.cyberduck.core.sftp.SFTPDirectoryFeature; -import ch.cyberduck.core.sftp.SFTPFindFeature; import ch.cyberduck.core.sftp.SFTPHomeDirectoryService; -import ch.cyberduck.core.sftp.SFTPListService; -import ch.cyberduck.core.sftp.SFTPReadFeature; import ch.cyberduck.core.sftp.SFTPSession; +import ch.cyberduck.core.sftp.SFTPUploadFeature; import ch.cyberduck.core.sftp.SFTPWriteFeature; import ch.cyberduck.core.ssl.DefaultX509KeyManager; import ch.cyberduck.core.ssl.DisabledX509TrustManager; @@ -54,9 +64,7 @@ import org.junit.Test; import org.junit.experimental.categories.Category; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.net.SocketTimeoutException; import java.util.Arrays; @@ -67,7 +75,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.Assert.*; -import static org.junit.Assert.assertArrayEquals; @Category(IntegrationTest.class) public class SFTPSingleTransferWorkerTest extends AbstractSFTPTest { @@ -90,7 +97,7 @@ public String getProperty(final String key) { } return super.getProperty(key); } - }; + }.withCredentials(new Credentials("test", "test")); final SFTPSession session = new SFTPSession(host, new DisabledX509TrustManager(), new DefaultX509KeyManager()) { final SFTPWriteFeature write = new SFTPWriteFeature(this) { @Override @@ -123,8 +130,8 @@ public Void getStatus() throws BackgroundException { @Override @SuppressWarnings("unchecked") public T _getFeature(final Class type) { - if(type == Write.class) { - return (T) write; + if(type == Upload.class) { + return (T) new SFTPUploadFeature(write); } return super._getFeature(type); } @@ -153,7 +160,6 @@ public TransferAction prompt(final TransferItem file) { assertEquals(content.length, counter.getSent(), 0L); assertTrue(failed.get()); new SFTPDeleteFeature(session).delete(Collections.singletonList(test), new DisabledLoginCallback(), new Delete.DisabledCallback()); - local.delete(); } @Test diff --git a/tus/src/main/java/ch/cyberduck/core/tus/TusUploadFeature.java b/tus/src/main/java/ch/cyberduck/core/tus/TusUploadFeature.java index 1b8ce15d4ef..57b77e986fa 100644 --- a/tus/src/main/java/ch/cyberduck/core/tus/TusUploadFeature.java +++ b/tus/src/main/java/ch/cyberduck/core/tus/TusUploadFeature.java @@ -223,7 +223,7 @@ public Long handleResponse(final HttpResponse response) throws HttpResponseExcep throw new HttpResponseException(response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()); } }); - return new Write.Append(true).withStatus(status).withSize(offset); + return new Write.Append(true).withStatus(status).withOffset(offset); } catch(HttpResponseException e) { preferences.deleteProperty(property); diff --git a/webdav/src/main/java/ch/cyberduck/core/dav/DAVUploadFeature.java b/webdav/src/main/java/ch/cyberduck/core/dav/DAVUploadFeature.java index ba70c024cc3..a5c479dbf68 100644 --- a/webdav/src/main/java/ch/cyberduck/core/dav/DAVUploadFeature.java +++ b/webdav/src/main/java/ch/cyberduck/core/dav/DAVUploadFeature.java @@ -17,8 +17,11 @@ * Bug fixes, suggestions and comments should be sent to feedback@cyberduck.ch */ +import ch.cyberduck.core.Path; +import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.Write; import ch.cyberduck.core.http.HttpUploadFeature; +import ch.cyberduck.core.transfer.TransferStatus; import java.security.MessageDigest; @@ -31,4 +34,12 @@ public DAVUploadFeature(final DAVSession session) { public DAVUploadFeature(final Write writer) { super(writer); } + + @Override + public Write.Append append(final Path file, final TransferStatus status) throws BackgroundException { + if(status.getLength() == TransferStatus.UNKNOWN_LENGTH) { + return new Write.Append(false).withStatus(status); + } + return new Write.Append(status.isExists()).withStatus(status); + } } diff --git a/webdav/src/main/java/ch/cyberduck/core/dav/DAVWriteFeature.java b/webdav/src/main/java/ch/cyberduck/core/dav/DAVWriteFeature.java index 75ffe3a13d3..b5bf036cc96 100644 --- a/webdav/src/main/java/ch/cyberduck/core/dav/DAVWriteFeature.java +++ b/webdav/src/main/java/ch/cyberduck/core/dav/DAVWriteFeature.java @@ -157,12 +157,4 @@ public long getContentLength() { public EnumSet features(final Path file) { return EnumSet.of(Flags.random); } - - @Override - public Append append(final Path file, final TransferStatus status) throws BackgroundException { - if(status.getLength() == TransferStatus.UNKNOWN_LENGTH) { - return new Append(false).withStatus(status); - } - return super.append(file, status); - } } diff --git a/webdav/src/test/java/ch/cyberduck/core/cryptomator/DAVReadFeatureTest.java b/webdav/src/test/java/ch/cyberduck/core/cryptomator/DAVReadFeatureTest.java index cc01e304b41..dd1c2be8ffe 100644 --- a/webdav/src/test/java/ch/cyberduck/core/cryptomator/DAVReadFeatureTest.java +++ b/webdav/src/test/java/ch/cyberduck/core/cryptomator/DAVReadFeatureTest.java @@ -24,6 +24,7 @@ import ch.cyberduck.core.Path; import ch.cyberduck.core.cryptomator.features.CryptoListService; import ch.cyberduck.core.cryptomator.features.CryptoReadFeature; +import ch.cyberduck.core.cryptomator.features.CryptoUploadFeature; import ch.cyberduck.core.cryptomator.features.CryptoWriteFeature; import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator; import ch.cyberduck.core.dav.AbstractDAVTest; @@ -32,6 +33,7 @@ import ch.cyberduck.core.dav.DAVFindFeature; import ch.cyberduck.core.dav.DAVListService; import ch.cyberduck.core.dav.DAVReadFeature; +import ch.cyberduck.core.dav.DAVUploadFeature; import ch.cyberduck.core.dav.DAVWriteFeature; import ch.cyberduck.core.features.AttributesFinder; import ch.cyberduck.core.features.Delete; @@ -86,7 +88,7 @@ public void testReadRange() throws Exception { out.close(); assertTrue(cryptomator.getFeature(session, Find.class, new DAVFindFeature(session)).find(test)); assertEquals(content.length, new CryptoListService(session, new DAVListService(session), cryptomator).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes().getSize()); - assertEquals(content.length, writer.append(test, status.withRemote(cryptomator.getFeature(session, AttributesFinder.class, new DAVAttributesFinderFeature(session)).find(test))).size, 0L); + assertEquals(content.length, new CryptoUploadFeature<>(session, new DAVUploadFeature(session), new DAVWriteFeature(session), cryptomator).append(test, status.withRemote(cryptomator.getFeature(session, AttributesFinder.class, new DAVAttributesFinderFeature(session)).find(test))).offset, 0L); { final ByteArrayOutputStream buffer = new ByteArrayOutputStream(40000); final TransferStatus read = new TransferStatus(); diff --git a/webdav/src/test/java/ch/cyberduck/core/cryptomator/DAVWriteFeatureTest.java b/webdav/src/test/java/ch/cyberduck/core/cryptomator/DAVWriteFeatureTest.java index c924a346e5f..d4af12b5bbe 100644 --- a/webdav/src/test/java/ch/cyberduck/core/cryptomator/DAVWriteFeatureTest.java +++ b/webdav/src/test/java/ch/cyberduck/core/cryptomator/DAVWriteFeatureTest.java @@ -24,6 +24,7 @@ import ch.cyberduck.core.Path; import ch.cyberduck.core.cryptomator.features.CryptoListService; import ch.cyberduck.core.cryptomator.features.CryptoReadFeature; +import ch.cyberduck.core.cryptomator.features.CryptoUploadFeature; import ch.cyberduck.core.cryptomator.features.CryptoWriteFeature; import ch.cyberduck.core.cryptomator.random.RotatingNonceGenerator; import ch.cyberduck.core.dav.AbstractDAVTest; @@ -32,6 +33,7 @@ import ch.cyberduck.core.dav.DAVFindFeature; import ch.cyberduck.core.dav.DAVListService; import ch.cyberduck.core.dav.DAVReadFeature; +import ch.cyberduck.core.dav.DAVUploadFeature; import ch.cyberduck.core.dav.DAVWriteFeature; import ch.cyberduck.core.features.AttributesFinder; import ch.cyberduck.core.features.Delete; @@ -86,8 +88,8 @@ public void testWrite() throws Exception { out.close(); assertTrue(cryptomator.getFeature(session, Find.class, new DAVFindFeature(session)).find(test)); assertEquals(content.length, new CryptoListService(session, new DAVListService(session), cryptomator).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes().getSize()); - assertEquals(content.length, new CryptoWriteFeature<>(session, new DAVWriteFeature(session, true), cryptomator).append(test, status - .withRemote(cryptomator.getFeature(session, AttributesFinder.class, new DAVAttributesFinderFeature(session)).find(test))).size, 0L); + assertEquals(content.length, new CryptoUploadFeature<>(session, new DAVUploadFeature(session), new DAVWriteFeature(session), cryptomator).append(test, status + .withRemote(cryptomator.getFeature(session, AttributesFinder.class, new DAVAttributesFinderFeature(session)).find(test))).offset, 0L); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); final InputStream in = new CryptoReadFeature(session, new DAVReadFeature(session), cryptomator).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()); new StreamCopier(status, status).transfer(in, buffer); diff --git a/webdav/src/test/java/ch/cyberduck/core/dav/DAVUploadFeatureTest.java b/webdav/src/test/java/ch/cyberduck/core/dav/DAVUploadFeatureTest.java index d5776872c2b..2e60b22ee05 100644 --- a/webdav/src/test/java/ch/cyberduck/core/dav/DAVUploadFeatureTest.java +++ b/webdav/src/test/java/ch/cyberduck/core/dav/DAVUploadFeatureTest.java @@ -42,8 +42,7 @@ import java.util.Collections; import java.util.EnumSet; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; @Category(IntegrationTest.class) public class DAVUploadFeatureTest extends AbstractDAVTest { @@ -101,4 +100,12 @@ test, local, new BandwidthThrottle(BandwidthThrottle.UNLIMITED), new DisabledStr new DAVDeleteFeature(session).delete(Collections.singletonList(test), new DisabledLoginCallback(), new Delete.DisabledCallback()); local.delete(); } + + @Test + public void testAppendZeroBytes() throws Exception { + final DAVUploadFeature feature = new DAVUploadFeature(session); + final Path test = new DAVTouchFeature(session).touch(new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus()); + assertTrue(feature.append(test, new TransferStatus().exists(true).withLength(0L).withRemote(new DAVAttributesFinderFeature(session).find(test))).append); + new DAVDeleteFeature(session).delete(Collections.singletonList(test), new DisabledLoginCallback(), new Delete.DisabledCallback()); + } } diff --git a/webdav/src/test/java/ch/cyberduck/core/dav/DAVWriteFeatureTest.java b/webdav/src/test/java/ch/cyberduck/core/dav/DAVWriteFeatureTest.java index 28c885bc126..a4777e75749 100644 --- a/webdav/src/test/java/ch/cyberduck/core/dav/DAVWriteFeatureTest.java +++ b/webdav/src/test/java/ch/cyberduck/core/dav/DAVWriteFeatureTest.java @@ -56,7 +56,7 @@ public void testReadWrite() throws Exception { new DisabledStreamListener(), status, new DisabledConnectionCallback()); assertTrue(session.getFeature(Find.class).find(test)); assertEquals(content.length, new DAVListService(session).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes().getSize(), 0L); - assertEquals(content.length, new DAVWriteFeature(session).append(test, status.withRemote(new DAVAttributesFinderFeature(session).find(test))).size, 0L); + assertEquals(content.length, new DAVUploadFeature(session).append(test, status.withRemote(new DAVAttributesFinderFeature(session).find(test))).offset, 0L); { final byte[] buffer = new byte[content.length]; IOUtils.readFully(new DAVReadFeature(session).read(test, new TransferStatus(), new DisabledConnectionCallback()), buffer); @@ -89,7 +89,7 @@ public void testReadWriteChunkedTransfer() throws Exception { new DisabledStreamListener(), status, new DisabledConnectionCallback()); assertTrue(session.getFeature(Find.class).find(test)); assertEquals(content.length, new DAVListService(session).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes().getSize(), 0L); - assertEquals(content.length, new DAVWriteFeature(session).append(test, status.withRemote(new DAVAttributesFinderFeature(session).find(test))).size, 0L); + assertEquals(content.length, new DAVUploadFeature(session).append(test, status.withRemote(new DAVAttributesFinderFeature(session).find(test))).offset, 0L); { final byte[] buffer = new byte[content.length]; IOUtils.readFully(new DAVReadFeature(session).read(test, new TransferStatus(), new DisabledConnectionCallback()), buffer); @@ -297,12 +297,4 @@ public void testWriteAccessDenied() throws Exception { final HttpResponseOutputStream out = new DAVWriteFeature(session).write(test, new TransferStatus().withLength(0L), new DisabledConnectionCallback()); out.close(); } - - @Test - public void testAppend() throws Exception { - final DAVWriteFeature feature = new DAVWriteFeature(session); - final Path test = new DAVTouchFeature(session).touch(new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus()); - assertTrue(feature.append(test, new TransferStatus().exists(true).withLength(0L).withRemote(new DAVAttributesFinderFeature(session).find(test))).append); - new DAVDeleteFeature(session).delete(Collections.singletonList(test), new DisabledLoginCallback(), new Delete.DisabledCallback()); - } } diff --git a/webdav/src/test/java/ch/cyberduck/core/dav/microsoft/MicrosoftIISDAVLockFeatureTest.java b/webdav/src/test/java/ch/cyberduck/core/dav/microsoft/MicrosoftIISDAVLockFeatureTest.java index 56b521e5acd..35429896443 100644 --- a/webdav/src/test/java/ch/cyberduck/core/dav/microsoft/MicrosoftIISDAVLockFeatureTest.java +++ b/webdav/src/test/java/ch/cyberduck/core/dav/microsoft/MicrosoftIISDAVLockFeatureTest.java @@ -25,7 +25,6 @@ import ch.cyberduck.core.dav.DAVDeleteFeature; import ch.cyberduck.core.dav.DAVLockFeature; import ch.cyberduck.core.dav.DAVUploadFeature; -import ch.cyberduck.core.dav.DAVWriteFeature; import ch.cyberduck.core.features.Delete; import ch.cyberduck.core.http.HttpUploadFeature; import ch.cyberduck.core.io.BandwidthThrottle; @@ -66,7 +65,7 @@ public void testLock() throws Exception { assertTrue(new MicrosoftIISDAVFindFeature(session).find(test)); final PathAttributes attributes = new MicrosoftIISDAVListService(session, new MicrosoftIISDAVAttributesFinderFeature(session)).list(test.getParent(), new DisabledListProgressListener()).get(test).attributes(); assertEquals(content.length, attributes.getSize(), 0L); - assertEquals(content.length, new DAVWriteFeature(session).append(test, status.withRemote(attributes)).size, 0L); + assertEquals(content.length, new DAVUploadFeature(session).append(test, status.withRemote(attributes)).offset, 0L); { final byte[] buffer = new byte[content.length]; IOUtils.readFully(new MicrosoftIISDAVReadFeature(session).read(test, new TransferStatus(), new DisabledConnectionCallback()), buffer); From e723637e0e0401e0e4741b69e847203d7df9e231 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Wed, 1 May 2024 11:24:20 +0200 Subject: [PATCH 3/3] Throw failure when upload URL is not found on attempt to resume upload. --- .../main/java/ch/cyberduck/core/tus/TusUploadFeature.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tus/src/main/java/ch/cyberduck/core/tus/TusUploadFeature.java b/tus/src/main/java/ch/cyberduck/core/tus/TusUploadFeature.java index 57b77e986fa..806575b342f 100644 --- a/tus/src/main/java/ch/cyberduck/core/tus/TusUploadFeature.java +++ b/tus/src/main/java/ch/cyberduck/core/tus/TusUploadFeature.java @@ -26,6 +26,7 @@ import ch.cyberduck.core.concurrency.Interruptibles; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.InteroperabilityException; +import ch.cyberduck.core.exception.NotfoundException; import ch.cyberduck.core.features.Upload; import ch.cyberduck.core.features.Write; import ch.cyberduck.core.http.DefaultHttpResponseExceptionMappingService; @@ -97,6 +98,12 @@ public Void upload(final Path file, final Local local, final BandwidthThrottle t final String uploadUrl; if(status.isAppend()) { uploadUrl = preferences.getProperty(toUploadUrlPropertyKey(host, file, status)); + if(StringUtils.isBlank(uploadUrl)) { + if(log.isDebugEnabled()) { + log.debug(String.format("No previous upload URL for %s", file)); + } + throw new NotfoundException(file.getAbsolute()); + } if(log.isDebugEnabled()) { log.debug(String.format("Resume upload to %s for %s from offset %d", uploadUrl, file, status.getOffset())); }