From 2a8325b3a2cb392d5981cc976de4eb129a4694e9 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Thu, 14 Nov 2024 13:17:23 +0100 Subject: [PATCH] Extract filters for protocol features used for specific file attributes in file transfers. --- .../core/transfer/ChainedFeatureFilter.java | 53 +++ .../core/transfer/FeatureFilter.java | 46 +++ .../download/AbstractDownloadFilter.java | 296 +--------------- .../features/ChecksumFeatureFilter.java | 69 ++++ .../DefaultDownloadOptionsFilterChain.java | 34 ++ .../download/features/LauncherFilter.java | 39 +++ .../features/PermissionFeatureFilter.java | 86 +++++ .../download/features/QuarantineFilter.java | 67 ++++ .../features/SegmentedFeatureFilter.java | 172 +++++++++ .../features/TemporaryFeatureFilter.java | 68 ++++ .../features/TimestampFeatureFilter.java | 54 +++ .../transfer/upload/AbstractUploadFilter.java | 325 ++---------------- .../core/transfer/upload/RenameFilter.java | 3 + .../upload/features/AclFeatureFilter.java | 86 +++++ .../features/ChecksumFeatureFilter.java | 66 ++++ .../DefaultLocalUploadOptionsFilterChain.java | 39 +++ .../features/EncryptionFeatureFilter.java | 62 ++++ .../upload/features/HiddenFeatureFilter.java | 46 +++ .../features/MetadataFeatureFilter.java | 62 ++++ .../upload/features/MimeFeatureFilter.java | 48 +++ .../features/PermissionFeatureFilter.java | 91 +++++ .../RedundancyClassFeatureFilter.java | 64 ++++ .../features/TemporaryFeatureFilter.java | 91 +++++ .../features/TimestampFeatureFilter.java | 77 +++++ .../features/VersioningFeatureFilter.java | 65 ++++ .../download/AbstractDownloadFilterTest.java | 34 +- 26 files changed, 1557 insertions(+), 586 deletions(-) create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/ChainedFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/FeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/download/features/ChecksumFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/download/features/DefaultDownloadOptionsFilterChain.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/download/features/LauncherFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/download/features/PermissionFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/download/features/QuarantineFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/download/features/SegmentedFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/download/features/TemporaryFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/download/features/TimestampFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/upload/features/AclFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/upload/features/ChecksumFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/upload/features/DefaultLocalUploadOptionsFilterChain.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/upload/features/EncryptionFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/upload/features/HiddenFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/upload/features/MetadataFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/upload/features/MimeFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/upload/features/PermissionFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/upload/features/RedundancyClassFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/upload/features/TemporaryFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/upload/features/TimestampFeatureFilter.java create mode 100644 core/src/main/java/ch/cyberduck/core/transfer/upload/features/VersioningFeatureFilter.java diff --git a/core/src/main/java/ch/cyberduck/core/transfer/ChainedFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/ChainedFeatureFilter.java new file mode 100644 index 00000000000..bd49d967f49 --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/ChainedFeatureFilter.java @@ -0,0 +1,53 @@ +package ch.cyberduck.core.transfer; + +/* + * 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.Local; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.exception.BackgroundException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +public class ChainedFeatureFilter implements FeatureFilter { + private static final Logger log = LogManager.getLogger(ChainedFeatureFilter.class); + + private final FeatureFilter[] filters; + + public ChainedFeatureFilter(final FeatureFilter... filters) { + this.filters = filters; + } + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + for(final FeatureFilter filter : filters) { + log.debug("Prepare {} with {}", file, filter); + filter.prepare(file, local, status, progress); + } + return status; + } + + @Override + public void complete(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + for(final FeatureFilter filter : filters) { + log.debug("Complete {} with {}", file, filter); + filter.complete(file, local, status, progress); + } + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/FeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/FeatureFilter.java new file mode 100644 index 00000000000..543477e56eb --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/FeatureFilter.java @@ -0,0 +1,46 @@ +package ch.cyberduck.core.transfer; + +/* + * 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.Local; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.exception.BackgroundException; + +import java.util.Optional; + +public interface FeatureFilter { + default TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + // No-op + return status; + } + + default void apply(final Path file, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + // No-op + } + + default void complete(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + // No-op + } + + FeatureFilter noop = new FeatureFilter() { + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) { + // No-op + return status; + } + }; +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/download/AbstractDownloadFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/download/AbstractDownloadFilter.java index bdcb78d8af4..02b772c62ec 100644 --- a/core/src/main/java/ch/cyberduck/core/transfer/download/AbstractDownloadFilter.java +++ b/core/src/main/java/ch/cyberduck/core/transfer/download/AbstractDownloadFilter.java @@ -17,75 +17,47 @@ * Bug fixes, suggestions and comments should be sent to feedback@cyberduck.ch */ -import ch.cyberduck.core.DescriptiveUrl; -import ch.cyberduck.core.DescriptiveUrlBag; -import ch.cyberduck.core.HostUrlProvider; import ch.cyberduck.core.Local; -import ch.cyberduck.core.LocalFactory; -import ch.cyberduck.core.LocaleFactory; import ch.cyberduck.core.Path; import ch.cyberduck.core.PathAttributes; -import ch.cyberduck.core.Permission; import ch.cyberduck.core.ProgressListener; import ch.cyberduck.core.Session; -import ch.cyberduck.core.UrlProvider; -import ch.cyberduck.core.exception.AccessDeniedException; import ch.cyberduck.core.exception.BackgroundException; -import ch.cyberduck.core.exception.ChecksumException; import ch.cyberduck.core.exception.LocalAccessDeniedException; import ch.cyberduck.core.exception.NotfoundException; import ch.cyberduck.core.features.AttributesFinder; -import ch.cyberduck.core.features.Read; -import ch.cyberduck.core.io.Checksum; -import ch.cyberduck.core.io.ChecksumCompute; -import ch.cyberduck.core.io.ChecksumComputeFactory; import ch.cyberduck.core.local.ApplicationLauncher; import ch.cyberduck.core.local.ApplicationLauncherFactory; -import ch.cyberduck.core.local.QuarantineService; -import ch.cyberduck.core.local.QuarantineServiceFactory; import ch.cyberduck.core.preferences.HostPreferencesFactory; import ch.cyberduck.core.preferences.PreferencesReader; -import ch.cyberduck.core.transfer.AutoTransferConnectionLimiter; -import ch.cyberduck.core.transfer.Speedometer; +import ch.cyberduck.core.transfer.FeatureFilter; import ch.cyberduck.core.transfer.TransferPathFilter; import ch.cyberduck.core.transfer.TransferStatus; +import ch.cyberduck.core.transfer.download.features.DefaultDownloadOptionsFilterChain; import ch.cyberduck.core.transfer.symlink.SymlinkResolver; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.Iterator; -import java.util.List; +import java.util.Optional; public abstract class AbstractDownloadFilter implements TransferPathFilter { private static final Logger log = LogManager.getLogger(AbstractDownloadFilter.class); private final PreferencesReader preferences; - private final Session session; - private final SymlinkResolver symlinkResolver; - private final QuarantineService quarantine = QuarantineServiceFactory.get(); private final ApplicationLauncher launcher = ApplicationLauncherFactory.get(); - + private final SymlinkResolver resolver; protected final AttributesFinder attribute; - protected final DownloadFilterOptions options; + protected final FeatureFilter chain; - public AbstractDownloadFilter(final SymlinkResolver symlinkResolver, final Session session, final DownloadFilterOptions options) { - this(symlinkResolver, session, session.getFeature(AttributesFinder.class), options); + public AbstractDownloadFilter(final SymlinkResolver resolver, final Session session, final DownloadFilterOptions options) { + this(resolver, session, session.getFeature(AttributesFinder.class), options); } - public AbstractDownloadFilter(final SymlinkResolver symlinkResolver, final Session session, final AttributesFinder attribute, final DownloadFilterOptions options) { - this.session = session; - this.symlinkResolver = symlinkResolver; + public AbstractDownloadFilter(final SymlinkResolver resolver, final Session session, final AttributesFinder attribute, final DownloadFilterOptions options) { + this.resolver = resolver; this.attribute = attribute; - this.options = options; + this.chain = new DefaultDownloadOptionsFilterChain(session, options); this.preferences = HostPreferencesFactory.get(session.getHost()); } @@ -123,7 +95,7 @@ public TransferStatus prepare(final Path file, final Local local, final Transfer final Path target = file.getSymlinkTarget(); // Read remote attributes of symlink target attributes = attribute.find(target); - if(!symlinkResolver.resolve(file)) { + if(!resolver.resolve(file)) { if(file.isFile()) { // Content length status.setLength(attributes.getSize()); @@ -137,269 +109,35 @@ public TransferStatus prepare(final Path file, final Local local, final Transfer if(file.isFile()) { // Content length status.setLength(attributes.getSize()); - if(StringUtils.startsWith(attributes.getDisplayname(), "file:")) { - final String filename = StringUtils.removeStart(attributes.getDisplayname(), "file:"); - if(!StringUtils.equals(file.getName(), filename)) { - status.setDisplayname(LocalFactory.get(local.getParent(), filename)); - int no = 0; - while(status.getDisplayname().local.exists()) { - String proposal = String.format("%s-%d", FilenameUtils.getBaseName(filename), ++no); - if(StringUtils.isNotBlank(Path.getExtension(filename))) { - proposal += String.format(".%s", Path.getExtension(filename)); - } - status.setDisplayname(LocalFactory.get(local.getParent(), proposal)); - } - } - } } } status.setRemote(attributes); - if(options.timestamp) { - status.setModified(attributes.getModificationDate()); - } - if(options.permissions) { - Permission permission = Permission.EMPTY; - if(preferences.getBoolean("queue.download.permissions.default")) { - if(file.isFile()) { - permission = new Permission( - preferences.getInteger("queue.download.permissions.file.default")); - } - if(file.isDirectory()) { - permission = new Permission( - preferences.getInteger("queue.download.permissions.folder.default")); - } - } - else { - permission = attributes.getPermission(); - } - status.setPermission(permission); - } - status.setAcl(attributes.getAcl()); - if(options.segments) { - final Read read = session.getFeature(Read.class); - if(!read.offset(file)) { - log.warn("Reading with offset not supported with {} for {}", read, file); - } - else { - if(file.isFile()) { - // Free space on disk - long space = 0L; - try { - space = Files.getFileStore(Paths.get(local.getParent().getAbsolute())).getUsableSpace(); - } - catch(IOException e) { - log.warn("Failure to determine disk space for {}", file.getParent()); - } - long threshold = preferences.getLong("queue.download.segments.threshold"); - if(status.getLength() * 2 > space) { - log.warn("Insufficient free disk space {} for segmented download of {}", space, file); - } - else if(status.getLength() > threshold) { - // if file is smaller than threshold do not attempt to segment - final long segmentSize; - if(preferences.getBoolean("queue.download.segments.size.dynamic")) { - segmentSize = findSegmentSize(status.getLength(), - new AutoTransferConnectionLimiter().getLimit(session.getHost()), threshold, - preferences.getLong("queue.download.segments.size"), - preferences.getLong("queue.download.segments.count")); - } - else { - segmentSize = preferences.getLong("queue.download.segments.size"); - } - // with default settings this can handle files up to 16 GiB, with 128 segments at 128 MiB. - // this scales down to files of size 20MiB with 2 segments at 10 MiB - long remaining = status.getLength(), offset = 0; - // Sorted list - final List segments = new ArrayList<>(); - final Local segmentsFolder = LocalFactory.get(local.getParent(), String.format("%s.cyberducksegment", local.getName())); - for(int segmentNumber = 1; remaining > 0; segmentNumber++) { - final Local segmentFile = LocalFactory.get( - segmentsFolder, String.format("%d.cyberducksegment", segmentNumber)); - // Last part can be less than 5 MB. Adjust part size. - long length = Math.min(segmentSize, remaining); - final TransferStatus segmentStatus = new TransferStatus() - .setSegment(true) // Skip completion filter for single segment - .setAppend(true) // Read with offset - .setOffset(offset) - .setLength(length) - .setRename(segmentFile); - log.debug("Adding status {} for segment {}", segmentStatus, segmentFile); - segments.add(segmentStatus); - remaining -= length; - offset += length; - } - status.setSegments(segments); - } - } - } - } - if(options.checksum) { - status.setChecksum(attributes.getChecksum()); - } - return status; + return chain.prepare(file, Optional.of(local), status, progress); } @Override - public void apply(final Path file, final Local local, final TransferStatus status, - final ProgressListener listener) throws BackgroundException { - // + public void apply(final Path file, final Local local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + chain.apply(file, status, progress); } /** * Update timestamp and permission */ @Override - public void complete(final Path file, final Local local, - final TransferStatus status, final ProgressListener listener) throws BackgroundException { + public void complete(final Path file, final Local local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { log.debug("Complete {} with status {}", file.getAbsolute(), status); if(status.isSegment()) { log.debug("Skip completion for single segment {}", status); return; } if(status.isComplete()) { - if(status.isSegmented()) { - // Obtain ordered list of segments to reassemble - final List segments = status.getSegments(); - log.info("Compile {} segments to file {}", segments.size(), local); - if(local.exists()) { - local.delete(); - } - final Speedometer meter = new Speedometer(); - long concatLength = 0L; - for(Iterator iterator = segments.iterator(); iterator.hasNext(); ) { - final TransferStatus segmentStatus = iterator.next(); - concatLength += segmentStatus.getLength(); - listener.message(String.format("%s (%s)", MessageFormat.format(LocaleFactory.localizedString("Finalize {0}", "Status"), - file.getName()), meter.getProgress(false, status.getLength(), concatLength))); - // Segment - final Local segmentFile = segmentStatus.getRename().local; - log.info("Append segment {} to {}", segmentFile, local); - segmentFile.copy(local, new Local.CopyOptions().append(true)); - log.info("Delete segment {}", segmentFile); - segmentFile.delete(); - if(!iterator.hasNext()) { - final Local folder = segmentFile.getParent(); - log.info("Remove segment folder {}", folder); - folder.delete(); - } - } - } - log.debug("Run completion for file {} with status {}", local, status); + chain.complete(file, Optional.of(local), status, progress); if(file.isFile()) { - // Bounce Downloads folder dock icon by sending download finished notification if(preferences.getBoolean("queue.download.complete.bounce")) { + // Bounce Downloads folder dock icon by sending download finished notification launcher.bounce(local); } - if(options.quarantine || options.wherefrom) { - final DescriptiveUrlBag provider = session.getFeature(UrlProvider.class).toUrl(file, - EnumSet.of(DescriptiveUrl.Type.provider)).filter(DescriptiveUrl.Type.provider, DescriptiveUrl.Type.http); - for(DescriptiveUrl url : provider) { - try { - if(options.quarantine) { - // Set quarantine attributes - quarantine.setQuarantine(local, new HostUrlProvider().withUsername(false).get(session.getHost()), url.getUrl()); - } - if(options.wherefrom) { - // Set quarantine attributes - quarantine.setWhereFrom(local, url.getUrl()); - } - } - catch(LocalAccessDeniedException e) { - log.warn("Failure to quarantine file {}. {}", file, e.getMessage()); - } - break; - } - } - } - if(!Permission.EMPTY.equals(status.getPermission())) { - if(file.isDirectory()) { - // Make sure we can read & write files to directory created. - status.getPermission().setUser(status.getPermission().getUser().or(Permission.Action.read).or(Permission.Action.write).or(Permission.Action.execute)); - } - if(file.isFile()) { - // Make sure the owner can always read and write. - status.getPermission().setUser(status.getPermission().getUser().or(Permission.Action.read).or(Permission.Action.write)); - } - log.info("Updating permissions of {} to {}", local, status.getPermission()); - try { - local.attributes().setPermission(status.getPermission()); - } - catch(AccessDeniedException e) { - // Ignore - log.warn(e.getMessage()); - } - } - if(status.getModified() != null) { - log.info("Updating timestamp of {} to {}", local, status.getModified()); - try { - local.attributes().setModificationDate(status.getModified()); - } - catch(AccessDeniedException e) { - // Ignore - log.warn(e.getMessage()); - } - } - if(file.isFile()) { - if(options.checksum) { - if(file.getType().contains(Path.Type.decrypted)) { - log.warn("Skip checksum verification for {} with client side encryption enabled", file); - } - else { - final Checksum checksum = status.getChecksum(); - if(Checksum.NONE != checksum) { - final ChecksumCompute compute = ChecksumComputeFactory.get(checksum.algorithm); - listener.message(MessageFormat.format(LocaleFactory.localizedString("Calculate checksum for {0}", "Status"), - file.getName())); - final Checksum download = compute.compute(local.getInputStream(), new TransferStatus()); - if(!checksum.equals(download)) { - throw new ChecksumException( - MessageFormat.format(LocaleFactory.localizedString("Download {0} failed", "Error"), file.getName()), - MessageFormat.format(LocaleFactory.localizedString("Mismatch between {0} hash {1} of downloaded data and checksum {2} returned by the server", "Error"), - download.algorithm.toString(), download.hash, checksum.hash)); - } - } - } - } - } - if(file.isFile()) { - if(status.getDisplayname().local != null) { - log.info("Rename file {} to {}", file, status.getDisplayname().local); - local.rename(status.getDisplayname().local); - } - if(options.open) { - launcher.open(local); - } - } - } - } - - static long findSegmentSize(final long length, final int initialSplit, final long segmentThreshold, final long segmentSizeMaximum, final long segmentCountLimit) { - // Make segments - long parts, segmentSize, nextParts = initialSplit; - // find segment size - // starting with part count of queue.connections.limit - // but not more than queue.download.segments.count - // or until smaller than queue.download.segments.threshold - do { - parts = nextParts; - nextParts = Math.min(nextParts * 2, segmentCountLimit); - // round up to next byte - segmentSize = (length + 1) / parts; - } - while(segmentSize > segmentThreshold && parts < segmentCountLimit); - // round to next divisible by 2 - segmentSize = (segmentSize * 2 + 1) / 2; - // if larger than maximum segment size - if(segmentSize > segmentSizeMaximum) { - // double segment size until parts smaller than queue.download.segments.count - long nextSize = segmentSizeMaximum; - do { - segmentSize = nextSize; - nextSize *= 2; - parts = length / segmentSize; } - while(parts > segmentCountLimit); } - return segmentSize; } } diff --git a/core/src/main/java/ch/cyberduck/core/transfer/download/features/ChecksumFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/download/features/ChecksumFeatureFilter.java new file mode 100644 index 00000000000..1ad318e39a3 --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/download/features/ChecksumFeatureFilter.java @@ -0,0 +1,69 @@ +package ch.cyberduck.core.transfer.download.features; + +/* + * 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.Local; +import ch.cyberduck.core.LocaleFactory; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.exception.ChecksumException; +import ch.cyberduck.core.io.Checksum; +import ch.cyberduck.core.io.ChecksumCompute; +import ch.cyberduck.core.io.ChecksumComputeFactory; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.text.MessageFormat; +import java.util.Optional; + +public class ChecksumFeatureFilter implements FeatureFilter { + private static final Logger log = LogManager.getLogger(ChecksumFeatureFilter.class); + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + return status.setChecksum(status.getRemote().getChecksum()); + } + + @Override + public void complete(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(file.isFile()) { + if(file.getType().contains(Path.Type.decrypted)) { + log.warn("Skip checksum verification for {} with client side encryption enabled", file); + } + else { + final Checksum checksum = status.getChecksum(); + if(Checksum.NONE != checksum) { + if(local.isPresent()) { + final ChecksumCompute compute = ChecksumComputeFactory.get(checksum.algorithm); + progress.message(MessageFormat.format(LocaleFactory.localizedString("Calculate checksum for {0}", "Status"), + file.getName())); + final Checksum download = compute.compute(local.get().getInputStream(), new TransferStatus()); + if(!checksum.equals(download)) { + throw new ChecksumException( + MessageFormat.format(LocaleFactory.localizedString("Download {0} failed", "Error"), file.getName()), + MessageFormat.format(LocaleFactory.localizedString("Mismatch between {0} hash {1} of downloaded data and checksum {2} returned by the server", "Error"), + download.algorithm.toString(), download.hash, checksum.hash)); + } + } + } + } + } + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/download/features/DefaultDownloadOptionsFilterChain.java b/core/src/main/java/ch/cyberduck/core/transfer/download/features/DefaultDownloadOptionsFilterChain.java new file mode 100644 index 00000000000..d72d0469934 --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/download/features/DefaultDownloadOptionsFilterChain.java @@ -0,0 +1,34 @@ +package ch.cyberduck.core.transfer.download.features; + +/* + * 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.Session; +import ch.cyberduck.core.transfer.ChainedFeatureFilter; +import ch.cyberduck.core.transfer.download.DownloadFilterOptions; + +public class DefaultDownloadOptionsFilterChain extends ChainedFeatureFilter { + + public DefaultDownloadOptionsFilterChain(final Session session, final DownloadFilterOptions options) { + super( + options.timestamp ? new TimestampFeatureFilter() : noop, + options.permissions ? new PermissionFeatureFilter(session) : noop, + options.checksum ? new ChecksumFeatureFilter() : noop, + new TemporaryFeatureFilter(), + options.quarantine ? new QuarantineFilter(session) : noop, + options.open ? new LauncherFilter() : noop + ); + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/download/features/LauncherFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/download/features/LauncherFilter.java new file mode 100644 index 00000000000..cdfbcc84e7c --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/download/features/LauncherFilter.java @@ -0,0 +1,39 @@ +package ch.cyberduck.core.transfer.download.features; + +/* + * 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.Local; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.local.ApplicationLauncher; +import ch.cyberduck.core.local.ApplicationLauncherFactory; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import java.util.Optional; + +public class LauncherFilter implements FeatureFilter { + + private final ApplicationLauncher launcher = ApplicationLauncherFactory.get(); + + @Override + public void complete(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(file.isFile()) { + local.ifPresent(launcher::open); + } + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/download/features/PermissionFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/download/features/PermissionFeatureFilter.java new file mode 100644 index 00000000000..12184eb4070 --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/download/features/PermissionFeatureFilter.java @@ -0,0 +1,86 @@ +package ch.cyberduck.core.transfer.download.features; + +/* + * 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.Local; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.Permission; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.exception.AccessDeniedException; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.preferences.HostPreferencesFactory; +import ch.cyberduck.core.preferences.PreferencesReader; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +public class PermissionFeatureFilter implements FeatureFilter { + private static final Logger log = LogManager.getLogger(PermissionFeatureFilter.class); + + private final PreferencesReader preferences; + + public PermissionFeatureFilter(final Session session) { + this.preferences = HostPreferencesFactory.get(session.getHost()); + } + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + Permission permission = Permission.EMPTY; + if(preferences.getBoolean("queue.download.permissions.default")) { + if(file.isFile()) { + permission = new Permission( + preferences.getInteger("queue.download.permissions.file.default")); + } + if(file.isDirectory()) { + permission = new Permission( + preferences.getInteger("queue.download.permissions.folder.default")); + } + } + else { + permission = status.getRemote().getPermission(); + } + return status.setPermission(permission); + } + + @Override + public void complete(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(!Permission.EMPTY.equals(status.getPermission())) { + if(file.isDirectory()) { + // Make sure we can read & write files to directory created. + status.getPermission().setUser(status.getPermission().getUser().or(Permission.Action.read).or(Permission.Action.write).or(Permission.Action.execute)); + } + if(file.isFile()) { + // Make sure the owner can always read and write. + status.getPermission().setUser(status.getPermission().getUser().or(Permission.Action.read).or(Permission.Action.write)); + } + if(local.isPresent()) { + log.info("Updating permissions of {} to {}", local, status.getPermission()); + try { + local.get().attributes().setPermission(status.getPermission()); + } + catch(AccessDeniedException e) { + // Ignore + log.warn(e.getMessage()); + } + } + } + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/download/features/QuarantineFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/download/features/QuarantineFilter.java new file mode 100644 index 00000000000..97e69ab7c14 --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/download/features/QuarantineFilter.java @@ -0,0 +1,67 @@ +package ch.cyberduck.core.transfer.download.features; + +/* + * 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.DescriptiveUrl; +import ch.cyberduck.core.DescriptiveUrlBag; +import ch.cyberduck.core.HostUrlProvider; +import ch.cyberduck.core.Local; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.UrlProvider; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.exception.LocalAccessDeniedException; +import ch.cyberduck.core.local.QuarantineService; +import ch.cyberduck.core.local.QuarantineServiceFactory; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +public class QuarantineFilter implements FeatureFilter { + private static final Logger log = LogManager.getLogger(QuarantineFilter.class); + + private final QuarantineService quarantine = QuarantineServiceFactory.get(); + + private final Session session; + + public QuarantineFilter(final Session session) { + this.session = session; + } + + @Override + public void complete(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(local.isPresent()) { + final DescriptiveUrlBag provider = session.getFeature(UrlProvider.class).toUrl(file).filter(DescriptiveUrl.Type.provider, DescriptiveUrl.Type.http); + for(DescriptiveUrl url : provider) { + try { + // Set quarantine attributes + quarantine.setQuarantine(local.get(), new HostUrlProvider().withUsername(false).get(session.getHost()), url.getUrl()); + // Set quarantine attributes + quarantine.setWhereFrom(local.get(), url.getUrl()); + } + catch(LocalAccessDeniedException e) { + log.warn("Failure to quarantine file {}. {}", file, e.getMessage()); + } + break; + } + } + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/download/features/SegmentedFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/download/features/SegmentedFeatureFilter.java new file mode 100644 index 00000000000..62131d9b0ff --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/download/features/SegmentedFeatureFilter.java @@ -0,0 +1,172 @@ +package ch.cyberduck.core.transfer.download.features; + +/* + * 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.Local; +import ch.cyberduck.core.LocalFactory; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.Read; +import ch.cyberduck.core.preferences.HostPreferencesFactory; +import ch.cyberduck.core.preferences.PreferencesReader; +import ch.cyberduck.core.transfer.AutoTransferConnectionLimiter; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; + +public class SegmentedFeatureFilter implements FeatureFilter { + private static final Logger log = LogManager.getLogger(SegmentedFeatureFilter.class); + + private final PreferencesReader preferences; + private final Session session; + + public SegmentedFeatureFilter(final Session session) { + this.session = session; + this.preferences = HostPreferencesFactory.get(session.getHost()); + } + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(!session.getFeature(Read.class).offset(file)) { + log.warn("Reading with offsets not supported for {}", file); + } + else { + if(file.isFile()) { + if(local.isPresent()) { + // Free space on disk + long space = 0L; + try { + space = Files.getFileStore(Paths.get(local.get().getParent().getAbsolute())).getUsableSpace(); + } + catch(IOException e) { + log.warn("Failure to determine disk space for {}", file.getParent()); + } + long threshold = preferences.getLong("queue.download.segments.threshold"); + if(status.getLength() * 2 > space) { + log.warn("Insufficient free disk space {} for segmented download of {}", space, file); + } + else if(status.getLength() > threshold) { + // if file is smaller than threshold do not attempt to segment + final long segmentSize; + if(preferences.getBoolean("queue.download.segments.size.dynamic")) { + segmentSize = findSegmentSize(status.getLength(), + new AutoTransferConnectionLimiter().getLimit(session.getHost()), threshold, + preferences.getLong("queue.download.segments.size"), + preferences.getLong("queue.download.segments.count")); + } + else { + segmentSize = preferences.getLong("queue.download.segments.size"); + } + // with default settings this can handle files up to 16 GiB, with 128 segments at 128 MiB. + // this scales down to files of size 20MiB with 2 segments at 10 MiB + long remaining = status.getLength(), offset = 0; + // Sorted list + final List segments = new ArrayList<>(); + final Local segmentsFolder = LocalFactory.get(local.get().getParent(), String.format("%s.cyberducksegment", local.get().getName())); + for(int segmentNumber = 1; remaining > 0; segmentNumber++) { + final Local segmentFile = LocalFactory.get( + segmentsFolder, String.format("%d.cyberducksegment", segmentNumber)); + // Last part can be less than 5 MB. Adjust part size. + long length = Math.min(segmentSize, remaining); + final TransferStatus segmentStatus = new TransferStatus() + .setSegment(true) // Skip completion filter for single segment + .setAppend(true) // Read with offset + .setOffset(offset) + .setLength(length) + .setRename(segmentFile); + log.debug("Adding status {} for segment {}", segmentStatus, segmentFile); + segments.add(segmentStatus); + remaining -= length; + offset += length; + } + status.setSegments(segments); + } + } + } + } + return status; + } + + @Override + public void complete(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(status.isSegmented()) { + if(local.isPresent()) { + // Obtain ordered list of segments to reassemble + final List segments = status.getSegments(); + log.info("Compile {} segments to file {}", segments.size(), local); + if(local.get().exists()) { + local.get().delete(); + } + for(Iterator iterator = segments.iterator(); iterator.hasNext(); ) { + final TransferStatus segmentStatus = iterator.next(); + // Segment + final Local segmentFile = segmentStatus.getRename().local; + log.info("Append segment {} to {}", segmentFile, local); + segmentFile.copy(local.get(), new Local.CopyOptions().append(true)); + log.info("Delete segment {}", segmentFile); + segmentFile.delete(); + if(!iterator.hasNext()) { + final Local folder = segmentFile.getParent(); + log.info("Remove segment folder {}", folder); + folder.delete(); + } + } + } + } + } + + public static long findSegmentSize(final long length, final int initialSplit, final long segmentThreshold, final long segmentSizeMaximum, final long segmentCountLimit) { + // Make segments + long parts, segmentSize, nextParts = initialSplit; + // find segment size + // starting with part count of queue.connections.limit + // but not more than queue.download.segments.count + // or until smaller than queue.download.segments.threshold + do { + parts = nextParts; + nextParts = Math.min(nextParts * 2, segmentCountLimit); + // round up to next byte + segmentSize = (length + 1) / parts; + } + while(segmentSize > segmentThreshold && parts < segmentCountLimit); + // round to next divisible by 2 + segmentSize = (segmentSize * 2 + 1) / 2; + // if larger than maximum segment size + if(segmentSize > segmentSizeMaximum) { + // double segment size until parts smaller than queue.download.segments.count + long nextSize = segmentSizeMaximum; + do { + segmentSize = nextSize; + nextSize *= 2; + parts = length / segmentSize; + } + while(parts > segmentCountLimit); + } + return segmentSize; + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/download/features/TemporaryFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/download/features/TemporaryFeatureFilter.java new file mode 100644 index 00000000000..5bb46f0fc35 --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/download/features/TemporaryFeatureFilter.java @@ -0,0 +1,68 @@ +package ch.cyberduck.core.transfer.download.features; + +/* + * 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.Local; +import ch.cyberduck.core.LocalFactory; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +public class TemporaryFeatureFilter implements FeatureFilter { + private static final Logger log = LogManager.getLogger(TemporaryFeatureFilter.class); + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(local.isPresent()) { + if(StringUtils.startsWith(status.getRemote().getDisplayname(), "file:")) { + final String filename = StringUtils.removeStart(status.getRemote().getDisplayname(), "file:"); + if(!StringUtils.equals(file.getName(), filename)) { + status.setDisplayname(LocalFactory.get(local.get().getParent(), filename)); + int no = 0; + while(status.getDisplayname().local.exists()) { + String proposal = String.format("%s-%d", FilenameUtils.getBaseName(filename), ++no); + if(StringUtils.isNotBlank(Path.getExtension(filename))) { + proposal += String.format(".%s", Path.getExtension(filename)); + } + status.setDisplayname(LocalFactory.get(local.get().getParent(), proposal)); + } + } + } + } + return status; + } + + @Override + public void complete(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(file.isFile()) { + if(status.getDisplayname().local != null) { + if(local.isPresent()) { + log.info("Rename file {} to {}", file, status.getDisplayname().local); + local.get().rename(status.getDisplayname().local); + } + } + } + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/download/features/TimestampFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/download/features/TimestampFeatureFilter.java new file mode 100644 index 00000000000..0f3f947b35b --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/download/features/TimestampFeatureFilter.java @@ -0,0 +1,54 @@ +package ch.cyberduck.core.transfer.download.features; + +/* + * 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.Local; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.exception.AccessDeniedException; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +public class TimestampFeatureFilter implements FeatureFilter { + private static final Logger log = LogManager.getLogger(TimestampFeatureFilter.class); + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + return status.setModified(status.getRemote().getModificationDate()).setCreated(status.getRemote().getCreationDate()); + } + + @Override + public void complete(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(status.getModified() != null) { + if(local.isPresent()) { + log.info("Updating timestamp of {} to {}", local, status.getModified()); + try { + local.get().attributes().setModificationDate(status.getModified()); + } + catch(AccessDeniedException e) { + // Ignore + log.warn(e.getMessage()); + } + } + } + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/upload/AbstractUploadFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/upload/AbstractUploadFilter.java index 775a74fddbb..f6f942270b2 100644 --- a/core/src/main/java/ch/cyberduck/core/transfer/upload/AbstractUploadFilter.java +++ b/core/src/main/java/ch/cyberduck/core/transfer/upload/AbstractUploadFilter.java @@ -17,76 +17,48 @@ * Bug fixes, suggestions and comments should be sent to feedback@cyberduck.ch */ -import ch.cyberduck.core.Acl; -import ch.cyberduck.core.AlphanumericRandomStringService; -import ch.cyberduck.core.DisabledConnectionCallback; -import ch.cyberduck.core.Filter; import ch.cyberduck.core.Local; -import ch.cyberduck.core.LocaleFactory; -import ch.cyberduck.core.MappingMimeTypeService; import ch.cyberduck.core.Path; -import ch.cyberduck.core.PathAttributes; -import ch.cyberduck.core.Permission; import ch.cyberduck.core.ProgressListener; import ch.cyberduck.core.Session; -import ch.cyberduck.core.UserDateFormatterFactory; import ch.cyberduck.core.exception.AccessDeniedException; import ch.cyberduck.core.exception.BackgroundException; -import ch.cyberduck.core.exception.InteroperabilityException; -import ch.cyberduck.core.exception.LocalAccessDeniedException; import ch.cyberduck.core.exception.LocalNotfoundException; -import ch.cyberduck.core.exception.NotfoundException; -import ch.cyberduck.core.features.AclPermission; import ch.cyberduck.core.features.AttributesFinder; -import ch.cyberduck.core.features.Delete; -import ch.cyberduck.core.features.Encryption; import ch.cyberduck.core.features.Find; -import ch.cyberduck.core.features.Headers; -import ch.cyberduck.core.features.Move; -import ch.cyberduck.core.features.Redundancy; -import ch.cyberduck.core.features.Timestamp; -import ch.cyberduck.core.features.UnixPermission; -import ch.cyberduck.core.features.Versioning; -import ch.cyberduck.core.features.Write; -import ch.cyberduck.core.io.ChecksumCompute; -import ch.cyberduck.core.preferences.HostPreferencesFactory; -import ch.cyberduck.core.preferences.PreferencesReader; +import ch.cyberduck.core.transfer.FeatureFilter; import ch.cyberduck.core.transfer.TransferPathFilter; import ch.cyberduck.core.transfer.TransferStatus; import ch.cyberduck.core.transfer.symlink.SymlinkResolver; -import ch.cyberduck.ui.browser.SearchFilterFactory; +import ch.cyberduck.core.transfer.upload.features.DefaultLocalUploadOptionsFilterChain; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.text.MessageFormat; import java.util.EnumSet; import java.util.Optional; public abstract class AbstractUploadFilter implements TransferPathFilter { private static final Logger log = LogManager.getLogger(AbstractUploadFilter.class); - private final PreferencesReader preferences; - private final Session session; - private final SymlinkResolver symlinkResolver; - private final Filter hidden = SearchFilterFactory.HIDDEN_FILTER; - + private final SymlinkResolver resolver; protected final Find find; protected final AttributesFinder attribute; - protected final UploadFilterOptions options; + protected final FeatureFilter chain; + + public AbstractUploadFilter(final SymlinkResolver resolver, final Session session, final UploadFilterOptions options) { + this(resolver, session, session.getFeature(Find.class), session.getFeature(AttributesFinder.class), options); + } - public AbstractUploadFilter(final SymlinkResolver symlinkResolver, final Session session, final UploadFilterOptions options) { - this(symlinkResolver, session, session.getFeature(Find.class), session.getFeature(AttributesFinder.class), options); + public AbstractUploadFilter(final SymlinkResolver resolver, final Session session, final Find find, final AttributesFinder attribute, final UploadFilterOptions options) { + this(resolver, find, attribute, new DefaultLocalUploadOptionsFilterChain(session, options)); } - public AbstractUploadFilter(final SymlinkResolver symlinkResolver, final Session session, final Find find, final AttributesFinder attribute, final UploadFilterOptions options) { - this.session = session; - this.symlinkResolver = symlinkResolver; + public AbstractUploadFilter(final SymlinkResolver resolver, final Find find, final AttributesFinder attribute, final FeatureFilter chain) { + this.resolver = resolver; this.find = find; this.attribute = attribute; - this.options = options; - this.preferences = HostPreferencesFactory.get(session.getHost()); + this.chain = chain; } @Override @@ -101,35 +73,11 @@ public boolean accept(final Path file, final Local local, final TransferStatus p @Override public TransferStatus prepare(final Path file, final Local local, final TransferStatus parent, final ProgressListener progress) throws BackgroundException { log.debug("Prepare {}", file); - final TransferStatus status = new TransferStatus() - .setHidden(!hidden.accept(file)) - .setLockId(parent.getLockId()); - // Read remote attributes first - if(parent.isExists()) { - if(find.find(file)) { - status.setExists(true); - // Read remote attributes - final PathAttributes attributes = attribute.find(file); - status.setRemote(attributes); - } - else { - // Look if there is directory or file that clashes with this upload - if(file.getType().contains(Path.Type.file)) { - if(find.find(new Path(file.getParent(), file.getName(), EnumSet.of(Path.Type.directory)))) { - throw new AccessDeniedException(String.format("Cannot replace folder %s with file %s", file.getAbsolute(), file.getName())); - } - } - if(file.getType().contains(Path.Type.directory)) { - if(find.find(new Path(file.getParent(), file.getName(), EnumSet.of(Path.Type.file)))) { - throw new AccessDeniedException(String.format("Cannot replace file %s with folder %s", file.getAbsolute(), file.getName())); - } - } - } - } + final TransferStatus status = new TransferStatus().setLockId(parent.getLockId()); if(file.isFile()) { // Set content length from local file if(local.isSymbolicLink()) { - if(!symlinkResolver.resolve(local)) { + if(!resolver.resolve(local)) { // Will resolve the symbolic link when the file is requested. final Local target = local.getSymlinkTarget(); status.setLength(target.attributes().getSize()); @@ -140,249 +88,44 @@ public TransferStatus prepare(final Path file, final Local local, final Transfer // Read file size from filesystem status.setLength(local.attributes().getSize()); } - if(options.temporary) { - final Move feature = session.getFeature(Move.class); - final Path renamed = new Path(file.getParent(), - MessageFormat.format(preferences.getProperty("queue.upload.file.temporary.format"), - file.getName(), new AlphanumericRandomStringService().random()), file.getType()); - if(feature.isSupported(file, Optional.of(renamed))) { - log.debug("Set temporary filename {}", renamed); - // Set target name after transfer - status.setRename(renamed).setDisplayname(file); - // Remember status of target file for later rename - status.getDisplayname().exists(status.isExists()); - // Keep exist flag for subclasses to determine additional rename strategy - } - else { - log.warn("Cannot use temporary filename for upload with missing rename support for {}", file); - } - } - status.setMime(new MappingMimeTypeService().getMime(file.getName())); } if(file.isDirectory()) { status.setLength(0L); } - if(options.permissions) { - final UnixPermission feature = session.getFeature(UnixPermission.class); - if(feature != null) { - if(status.isExists()) { - // Already set when reading attributes of file - status.setPermission(status.getRemote().getPermission()); - } - else { - if(HostPreferencesFactory.get(session.getHost()).getBoolean("queue.upload.permissions.default")) { - status.setPermission(feature.getDefault(file.getParent(), file.getType())); - } - else { - // Read permissions from local file - status.setPermission(local.attributes().getPermission()); - } - } - } - else { - // Setting target UNIX permissions in transfer status - status.setPermission(Permission.EMPTY); - } - } - if(options.acl) { - final AclPermission feature = session.getFeature(AclPermission.class); - if(feature != null) { - if(status.isExists()) { - progress.message(MessageFormat.format(LocaleFactory.localizedString("Getting permission of {0}", "Status"), - file.getName())); - try { - status.setAcl(feature.getPermission(file)); - } - catch(NotfoundException | AccessDeniedException | InteroperabilityException e) { - status.setAcl(feature.getDefault(file)); - } - } - else { - status.setAcl(feature.getDefault(file)); - } + // Read remote attributes first + if(parent.isExists()) { + if(find.find(file)) { + status.setExists(true); + // Read remote attributes + status.setRemote(attribute.find(file)); } else { - // Setting target ACL in transfer status - status.setAcl(Acl.EMPTY); - } - } - if(options.timestamp) { - if(1L != local.attributes().getModificationDate()) { - status.setModified(local.attributes().getModificationDate()); - } - if(1L != local.attributes().getCreationDate()) { - status.setCreated(local.attributes().getCreationDate()); - } - } - if(options.metadata) { - final Headers feature = session.getFeature(Headers.class); - if(feature != null) { - if(status.isExists()) { - progress.message(MessageFormat.format(LocaleFactory.localizedString("Reading metadata of {0}", "Status"), - file.getName())); - try { - status.setMetadata(feature.getMetadata(file)); - } - catch(NotfoundException | AccessDeniedException | InteroperabilityException e) { - status.setMetadata(feature.getDefault(file)); - } - } - else { - status.setMetadata(feature.getDefault(file)); - } - } - } - if(options.encryption) { - final Encryption feature = session.getFeature(Encryption.class); - if(feature != null) { - if(status.isExists()) { - progress.message(MessageFormat.format(LocaleFactory.localizedString("Reading metadata of {0}", "Status"), - file.getName())); - try { - status.setEncryption(feature.getEncryption(file)); - } - catch(NotfoundException | AccessDeniedException | InteroperabilityException e) { - status.setEncryption(feature.getDefault(file)); - } - } - else { - status.setEncryption(feature.getDefault(file)); - } - } - } - if(options.redundancy) { - if(file.isFile()) { - final Redundancy feature = session.getFeature(Redundancy.class); - if(feature != null) { - if(status.isExists()) { - progress.message(MessageFormat.format(LocaleFactory.localizedString("Reading metadata of {0}", "Status"), - file.getName())); - try { - status.setStorageClass(feature.getClass(file)); - } - catch(NotfoundException | AccessDeniedException | InteroperabilityException e) { - status.setStorageClass(feature.getDefault(file)); - } - } - else { - status.setStorageClass(feature.getDefault(file)); + // Look if there is directory or file that clashes with this upload + if(file.getType().contains(Path.Type.file)) { + if(find.find(new Path(file.getParent(), file.getName(), EnumSet.of(Path.Type.directory)))) { + throw new AccessDeniedException(String.format("Cannot replace folder %s with file %s", file.getAbsolute(), file.getName())); } } - } - } - if(options.checksum) { - if(file.isFile()) { - final ChecksumCompute feature = session.getFeature(Write.class).checksum(file, status); - if(feature != null) { - progress.message(MessageFormat.format(LocaleFactory.localizedString("Calculate checksum for {0}", "Status"), - file.getName())); - try { - status.setChecksum(feature.compute(local.getInputStream(), status)); - } - catch(LocalAccessDeniedException e) { - // Ignore failure reading file when in sandbox when we miss a security scoped access bookmark. - // Lock for files is obtained only later in Transfer#pre - log.warn(e.getMessage()); + if(file.getType().contains(Path.Type.directory)) { + if(find.find(new Path(file.getParent(), file.getName(), EnumSet.of(Path.Type.file)))) { + throw new AccessDeniedException(String.format("Cannot replace file %s with folder %s", file.getAbsolute(), file.getName())); } } } } - return status; + return chain.prepare(file, Optional.of(local), status, progress); } @Override - public void apply(final Path file, final Local local, final TransferStatus status, - final ProgressListener listener) throws BackgroundException { - if(file.isFile()) { - if(status.isExists()) { - if(status.isAppend()) { - // Append to existing file - log.debug("Resume upload for existing file {}", file); - } - else { - if(options.versioning) { - switch(session.getHost().getProtocol().getVersioningMode()) { - case custom: - final Versioning feature = session.getFeature(Versioning.class); - if(feature != null) { - log.debug("Use custom versioning {}", feature); - if(feature.getConfiguration(file).isEnabled()) { - log.debug("Enabled versioning for {}", file); - if(feature.save(file)) { - log.debug("Clear exist flag for file {}", file); - status.setExists(false).getDisplayname().exists(false); - } - } - } - } - } - } - } - } - if(status.getRename().remote != null) { - log.debug("Clear exist flag for file {}", local); - // Reset exist flag after subclass hae applied strategy - status.setExists(false); - } + public void apply(final Path file, final Local local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + chain.apply(file, status, progress); } @Override - public void complete(final Path file, final Local local, - final TransferStatus status, final ProgressListener listener) throws BackgroundException { + public void complete(final Path file, final Local local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { log.debug("Complete {} with status {}", file.getAbsolute(), status); if(status.isComplete()) { - if(!Permission.EMPTY.equals(status.getPermission())) { - final UnixPermission feature = session.getFeature(UnixPermission.class); - if(feature != null) { - try { - listener.message(MessageFormat.format(LocaleFactory.localizedString("Changing permission of {0} to {1}", "Status"), - file.getName(), status.getPermission())); - feature.setUnixPermission(file, status); - } - catch(BackgroundException e) { - // Ignore - log.warn(e.getMessage()); - } - } - } - if(!Acl.EMPTY.equals(status.getAcl())) { - final AclPermission feature = session.getFeature(AclPermission.class); - if(feature != null) { - try { - listener.message(MessageFormat.format(LocaleFactory.localizedString("Changing permission of {0} to {1}", "Status"), - file.getName(), StringUtils.isBlank(status.getAcl().getCannedString()) ? LocaleFactory.localizedString("Unknown") : status.getAcl().getCannedString())); - feature.setPermission(file, status); - } - catch(BackgroundException e) { - // Ignore - log.warn(e.getMessage()); - } - } - } - if(status.getModified() != null) { - if(!session.getFeature(Write.class).timestamp(file)) { - final Timestamp feature = session.getFeature(Timestamp.class); - if(feature != null) { - try { - listener.message(MessageFormat.format(LocaleFactory.localizedString("Changing timestamp of {0} to {1}", "Status"), - file.getName(), UserDateFormatterFactory.get().getShortFormat(status.getModified()))); - feature.setTimestamp(file, status); - } - catch(BackgroundException e) { - // Ignore - log.warn(e.getMessage()); - } - } - } - } - if(file.isFile()) { - if(status.getDisplayname().remote != null) { - final Move move = session.getFeature(Move.class); - log.info("Rename file {} to {}", file, status.getDisplayname().remote); - move.move(file, status.getDisplayname().remote, new TransferStatus(status).setExists(status.getDisplayname().exists), - new Delete.DisabledCallback(), new DisabledConnectionCallback()); - } - } + chain.complete(file, Optional.empty(), status, progress); } } -} +} \ No newline at end of file diff --git a/core/src/main/java/ch/cyberduck/core/transfer/upload/RenameFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/upload/RenameFilter.java index 9b90a378e1a..08d4fcb4036 100644 --- a/core/src/main/java/ch/cyberduck/core/transfer/upload/RenameFilter.java +++ b/core/src/main/java/ch/cyberduck/core/transfer/upload/RenameFilter.java @@ -35,6 +35,8 @@ public class RenameFilter extends AbstractUploadFilter { private static final Logger log = LogManager.getLogger(RenameFilter.class); + private final UploadFilterOptions options; + public RenameFilter(final SymlinkResolver symlinkResolver, final Session session) { this(symlinkResolver, session, new UploadFilterOptions(session.getHost())); } @@ -49,6 +51,7 @@ public RenameFilter(final SymlinkResolver symlinkResolver, final Session< public RenameFilter(final SymlinkResolver symlinkResolver, final Session session, final Find find, final AttributesFinder attribute, final UploadFilterOptions options) { super(symlinkResolver, session, find, attribute, options); + this.options = options; } @Override diff --git a/core/src/main/java/ch/cyberduck/core/transfer/upload/features/AclFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/AclFeatureFilter.java new file mode 100644 index 00000000000..06c0a0d8750 --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/AclFeatureFilter.java @@ -0,0 +1,86 @@ +package ch.cyberduck.core.transfer.upload.features; + +/* + * 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.Acl; +import ch.cyberduck.core.Local; +import ch.cyberduck.core.LocaleFactory; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.exception.AccessDeniedException; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.exception.InteroperabilityException; +import ch.cyberduck.core.exception.NotfoundException; +import ch.cyberduck.core.features.AclPermission; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.text.MessageFormat; +import java.util.Optional; + +public class AclFeatureFilter implements FeatureFilter { + private static final Logger log = LogManager.getLogger(AclFeatureFilter.class); + + private final Session session; + + public AclFeatureFilter(final Session session) { + this.session = session; + } + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + final AclPermission feature = session.getFeature(AclPermission.class); + if(feature != null) { + if(status.isExists()) { + progress.message(MessageFormat.format(LocaleFactory.localizedString("Getting permission of {0}", "Status"), + file.getName())); + try { + status.setAcl(feature.getPermission(file)); + } + catch(NotfoundException | AccessDeniedException | InteroperabilityException e) { + status.setAcl(feature.getDefault(file)); + } + } + else { + status.setAcl(feature.getDefault(file)); + } + } + return status; + } + + @Override + public void complete(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(!Acl.EMPTY.equals(status.getAcl())) { + final AclPermission feature = session.getFeature(AclPermission.class); + if(feature != null) { + try { + progress.message(MessageFormat.format(LocaleFactory.localizedString("Changing permission of {0} to {1}", "Status"), + file.getName(), StringUtils.isBlank(status.getAcl().getCannedString()) ? LocaleFactory.localizedString("Unknown") : status.getAcl().getCannedString())); + feature.setPermission(file, status); + } + catch(BackgroundException e) { + // Ignore + log.warn(e.getMessage()); + } + } + } + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/upload/features/ChecksumFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/ChecksumFeatureFilter.java new file mode 100644 index 00000000000..d526d44d4c0 --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/ChecksumFeatureFilter.java @@ -0,0 +1,66 @@ +package ch.cyberduck.core.transfer.upload.features; + +/* + * 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.Local; +import ch.cyberduck.core.LocaleFactory; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.exception.LocalAccessDeniedException; +import ch.cyberduck.core.features.Write; +import ch.cyberduck.core.io.ChecksumCompute; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.text.MessageFormat; +import java.util.Optional; + +public class ChecksumFeatureFilter implements FeatureFilter { + private static final Logger log = LogManager.getLogger(ChecksumFeatureFilter.class); + + private final Session session; + + public ChecksumFeatureFilter(final Session session) { + this.session = session; + } + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(local.isPresent()) { + if(file.isFile()) { + final ChecksumCompute feature = session.getFeature(Write.class).checksum(file, status); + if(feature != null) { + progress.message(MessageFormat.format(LocaleFactory.localizedString("Calculate checksum for {0}", "Status"), + file.getName())); + try { + status.setChecksum(feature.compute(local.get().getInputStream(), status)); + } + catch(LocalAccessDeniedException e) { + // Ignore failure reading file when in sandbox when we miss a security scoped access bookmark. + // Lock for files is obtained only later in Transfer#pre + log.warn(e.getMessage()); + } + } + } + } + return status; + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/upload/features/DefaultLocalUploadOptionsFilterChain.java b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/DefaultLocalUploadOptionsFilterChain.java new file mode 100644 index 00000000000..3d4ca3a103c --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/DefaultLocalUploadOptionsFilterChain.java @@ -0,0 +1,39 @@ +package ch.cyberduck.core.transfer.upload.features; + +/* + * 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.Session; +import ch.cyberduck.core.transfer.ChainedFeatureFilter; +import ch.cyberduck.core.transfer.upload.UploadFilterOptions; + +public final class DefaultLocalUploadOptionsFilterChain extends ChainedFeatureFilter { + + public DefaultLocalUploadOptionsFilterChain(final Session session, final UploadFilterOptions options) { + super( + new MimeFeatureFilter(), + new HiddenFeatureFilter(), + options.permissions ? new PermissionFeatureFilter(session) : noop, + options.acl ? new AclFeatureFilter(session) : noop, + options.timestamp ? new TimestampFeatureFilter(session) : noop, + options.metadata ? new MetadataFeatureFilter(session) : noop, + options.encryption ? new EncryptionFeatureFilter(session) : noop, + options.redundancy ? new RedundancyClassFeatureFilter(session) : noop, + options.checksum ? new ChecksumFeatureFilter(session) : noop, + options.versioning ? new VersioningFeatureFilter(session) : noop, + options.temporary ? new TemporaryFeatureFilter(session) : noop + ); + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/upload/features/EncryptionFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/EncryptionFeatureFilter.java new file mode 100644 index 00000000000..261aa5e6991 --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/EncryptionFeatureFilter.java @@ -0,0 +1,62 @@ +package ch.cyberduck.core.transfer.upload.features; + +/* + * 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.Local; +import ch.cyberduck.core.LocaleFactory; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.exception.AccessDeniedException; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.exception.InteroperabilityException; +import ch.cyberduck.core.exception.NotfoundException; +import ch.cyberduck.core.features.Encryption; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import java.text.MessageFormat; +import java.util.Optional; + +public class EncryptionFeatureFilter implements FeatureFilter { + + private final Session session; + + public EncryptionFeatureFilter(final Session session) { + this.session = session; + } + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + final Encryption feature = session.getFeature(Encryption.class); + if(feature != null) { + if(status.isExists()) { + progress.message(MessageFormat.format(LocaleFactory.localizedString("Reading metadata of {0}", "Status"), + file.getName())); + try { + status.setEncryption(feature.getEncryption(file)); + } + catch(NotfoundException | AccessDeniedException | InteroperabilityException e) { + status.setEncryption(feature.getDefault(file)); + } + } + else { + status.setEncryption(feature.getDefault(file)); + } + } + return status; + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/upload/features/HiddenFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/HiddenFeatureFilter.java new file mode 100644 index 00000000000..1e97887a9b7 --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/HiddenFeatureFilter.java @@ -0,0 +1,46 @@ +package ch.cyberduck.core.transfer.upload.features; + +/* + * 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.Filter; +import ch.cyberduck.core.Local; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; +import ch.cyberduck.ui.browser.SearchFilterFactory; + +import java.util.Optional; + +public class HiddenFeatureFilter implements FeatureFilter { + + private final Filter hidden; + + public HiddenFeatureFilter() { + this(SearchFilterFactory.HIDDEN_FILTER); + } + + public HiddenFeatureFilter(final Filter hidden) { + this.hidden = hidden; + } + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + status.setHidden(!hidden.accept(file)); + return status; + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/upload/features/MetadataFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/MetadataFeatureFilter.java new file mode 100644 index 00000000000..cac310ec89b --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/MetadataFeatureFilter.java @@ -0,0 +1,62 @@ +package ch.cyberduck.core.transfer.upload.features; + +/* + * 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.Local; +import ch.cyberduck.core.LocaleFactory; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.exception.AccessDeniedException; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.exception.InteroperabilityException; +import ch.cyberduck.core.exception.NotfoundException; +import ch.cyberduck.core.features.Headers; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import java.text.MessageFormat; +import java.util.Optional; + +public class MetadataFeatureFilter implements FeatureFilter { + + private final Session session; + + public MetadataFeatureFilter(final Session session) { + this.session = session; + } + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + final Headers feature = session.getFeature(Headers.class); + if(feature != null) { + if(status.isExists()) { + progress.message(MessageFormat.format(LocaleFactory.localizedString("Reading metadata of {0}", "Status"), + file.getName())); + try { + status.setMetadata(feature.getMetadata(file)); + } + catch(NotfoundException | AccessDeniedException | InteroperabilityException e) { + status.setMetadata(feature.getDefault(file)); + } + } + else { + status.setMetadata(feature.getDefault(file)); + } + } + return status; + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/upload/features/MimeFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/MimeFeatureFilter.java new file mode 100644 index 00000000000..1b78dc1740d --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/MimeFeatureFilter.java @@ -0,0 +1,48 @@ +package ch.cyberduck.core.transfer.upload.features; + +/* + * 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.Local; +import ch.cyberduck.core.MappingMimeTypeService; +import ch.cyberduck.core.MimeTypeService; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import java.util.Optional; + +public class MimeFeatureFilter implements FeatureFilter { + + private final MimeTypeService service; + + public MimeFeatureFilter() { + this(new MappingMimeTypeService()); + } + + public MimeFeatureFilter(final MimeTypeService service) { + this.service = service; + } + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(file.isFile()) { + status.setMime(service.getMime(file.getName())); + } + return status; + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/upload/features/PermissionFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/PermissionFeatureFilter.java new file mode 100644 index 00000000000..6983d1cd561 --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/PermissionFeatureFilter.java @@ -0,0 +1,91 @@ +package ch.cyberduck.core.transfer.upload.features; + +/* + * 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.Local; +import ch.cyberduck.core.LocaleFactory; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.Permission; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.UnixPermission; +import ch.cyberduck.core.preferences.HostPreferencesFactory; +import ch.cyberduck.core.preferences.PreferencesReader; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.text.MessageFormat; +import java.util.Optional; + +public class PermissionFeatureFilter implements FeatureFilter { + private static final Logger log = LogManager.getLogger(PermissionFeatureFilter.class); + + private final PreferencesReader preferences; + private final Session session; + + public PermissionFeatureFilter(final Session session) { + this.session = session; + this.preferences = HostPreferencesFactory.get(session.getHost()); + } + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + final UnixPermission feature = session.getFeature(UnixPermission.class); + if(feature != null) { + if(status.isExists()) { + // Already set when reading attributes of file + status.setPermission(status.getRemote().getPermission()); + } + else { + if(preferences.getBoolean("queue.upload.permissions.default")) { + status.setPermission(feature.getDefault(file.getParent(), file.getType())); + } + else { + if(local.isPresent()) { + // Read permissions from local file + status.setPermission(local.get().attributes().getPermission()); + } + else { + status.setPermission(feature.getDefault(file.getParent(), file.getType())); + } + } + } + } + return status; + } + + @Override + public void complete(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(!Permission.EMPTY.equals(status.getPermission())) { + final UnixPermission feature = session.getFeature(UnixPermission.class); + if(feature != null) { + try { + progress.message(MessageFormat.format(LocaleFactory.localizedString("Changing permission of {0} to {1}", "Status"), + file.getName(), status.getPermission())); + feature.setUnixPermission(file, status); + } + catch(BackgroundException e) { + // Ignore + log.warn(e.getMessage()); + } + } + } + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/upload/features/RedundancyClassFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/RedundancyClassFeatureFilter.java new file mode 100644 index 00000000000..fcbe17b662a --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/RedundancyClassFeatureFilter.java @@ -0,0 +1,64 @@ +package ch.cyberduck.core.transfer.upload.features; + +/* + * 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.Local; +import ch.cyberduck.core.LocaleFactory; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.exception.AccessDeniedException; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.exception.InteroperabilityException; +import ch.cyberduck.core.exception.NotfoundException; +import ch.cyberduck.core.features.Redundancy; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import java.text.MessageFormat; +import java.util.Optional; + +public class RedundancyClassFeatureFilter implements FeatureFilter { + + private final Session session; + + public RedundancyClassFeatureFilter(final Session session) { + this.session = session; + } + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(file.isFile()) { + final Redundancy feature = session.getFeature(Redundancy.class); + if(feature != null) { + if(status.isExists()) { + progress.message(MessageFormat.format(LocaleFactory.localizedString("Reading metadata of {0}", "Status"), + file.getName())); + try { + status.setStorageClass(feature.getClass(file)); + } + catch(NotfoundException | AccessDeniedException | InteroperabilityException e) { + status.setStorageClass(feature.getDefault(file)); + } + } + else { + status.setStorageClass(feature.getDefault(file)); + } + } + } + return status; + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/upload/features/TemporaryFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/TemporaryFeatureFilter.java new file mode 100644 index 00000000000..fe6bd504b59 --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/TemporaryFeatureFilter.java @@ -0,0 +1,91 @@ +package ch.cyberduck.core.transfer.upload.features; + +/* + * 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.DisabledConnectionCallback; +import ch.cyberduck.core.Local; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.Delete; +import ch.cyberduck.core.features.Move; +import ch.cyberduck.core.preferences.HostPreferencesFactory; +import ch.cyberduck.core.preferences.PreferencesReader; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.text.MessageFormat; +import java.util.Optional; + +public class TemporaryFeatureFilter implements FeatureFilter { + private static final Logger log = LogManager.getLogger(TemporaryFeatureFilter.class); + + private final PreferencesReader preferences; + private final Session session; + + public TemporaryFeatureFilter(final Session session) { + this.session = session; + this.preferences = HostPreferencesFactory.get(session.getHost()); + } + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(file.isFile()) { + final Move feature = session.getFeature(Move.class); + final Path renamed = new Path(file.getParent(), + MessageFormat.format(preferences.getProperty("queue.upload.file.temporary.format"), + file.getName(), new AlphanumericRandomStringService().random()), file.getType()); + if(feature.isSupported(file, Optional.of(renamed))) { + log.debug("Set temporary filename {}", renamed); + // Set target name after transfer + status.setRename(renamed).setDisplayname(file); + // Remember status of target file for later rename + status.getDisplayname().exists(status.isExists()); + // Keep exist flag for subclasses to determine additional rename strategy + } + else { + log.warn("Cannot use temporary filename for upload with missing rename support for {}", file); + } + } + return status; + } + + @Override + public void apply(final Path file, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(status.getRename().remote != null) { + log.debug("Clear exist flag for file {}", file); + // Reset exist flag after subclass has applied strategy + status.setExists(false); + } + } + + @Override + public void complete(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(file.isFile()) { + if(status.getDisplayname().remote != null) { + log.info("Rename file {} to {}", file, status.getDisplayname().remote); + final Move feature = session.getFeature(Move.class); + feature.move(file, status.getDisplayname().remote, new TransferStatus(status).setExists(status.getDisplayname().exists), + new Delete.DisabledCallback(), new DisabledConnectionCallback()); + } + } + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/upload/features/TimestampFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/TimestampFeatureFilter.java new file mode 100644 index 00000000000..99f6ee28158 --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/TimestampFeatureFilter.java @@ -0,0 +1,77 @@ +package ch.cyberduck.core.transfer.upload.features; + +/* + * 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.Local; +import ch.cyberduck.core.LocaleFactory; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.ProgressListener; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.UserDateFormatterFactory; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.Timestamp; +import ch.cyberduck.core.features.Write; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.text.MessageFormat; +import java.util.Optional; + +public class TimestampFeatureFilter implements FeatureFilter { + private static final Logger log = LogManager.getLogger(TimestampFeatureFilter.class); + + private final Session session; + + public TimestampFeatureFilter(final Session session) { + this.session = session; + } + + @Override + public TransferStatus prepare(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(local.isPresent()) { + if(1L != local.get().attributes().getModificationDate()) { + status.setModified(local.get().attributes().getModificationDate()); + } + if(1L != local.get().attributes().getCreationDate()) { + status.setCreated(local.get().attributes().getCreationDate()); + } + } + return status; + } + + @Override + public void complete(final Path file, final Optional local, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(status.getModified() != null) { + if(!session.getFeature(Write.class).timestamp(file)) { + final Timestamp feature = session.getFeature(Timestamp.class); + if(feature != null) { + try { + progress.message(MessageFormat.format(LocaleFactory.localizedString("Changing timestamp of {0} to {1}", "Status"), + file.getName(), UserDateFormatterFactory.get().getShortFormat(status.getModified()))); + feature.setTimestamp(file, status); + } + catch(BackgroundException e) { + // Ignore + log.warn(e.getMessage()); + } + } + } + } + } +} diff --git a/core/src/main/java/ch/cyberduck/core/transfer/upload/features/VersioningFeatureFilter.java b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/VersioningFeatureFilter.java new file mode 100644 index 00000000000..b8396631182 --- /dev/null +++ b/core/src/main/java/ch/cyberduck/core/transfer/upload/features/VersioningFeatureFilter.java @@ -0,0 +1,65 @@ +package ch.cyberduck.core.transfer.upload.features; + +/* + * 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.ProgressListener; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.Versioning; +import ch.cyberduck.core.transfer.FeatureFilter; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class VersioningFeatureFilter implements FeatureFilter { + private static final Logger log = LogManager.getLogger(VersioningFeatureFilter.class); + + private final Session session; + + public VersioningFeatureFilter(final Session session) { + this.session = session; + } + + @Override + public void apply(final Path file, final TransferStatus status, final ProgressListener progress) throws BackgroundException { + if(file.isFile()) { + if(status.isExists()) { + if(status.isAppend()) { + // Append to existing file + log.debug("Resume upload for existing file {}", file); + } + else { + switch(session.getHost().getProtocol().getVersioningMode()) { + case custom: + final Versioning feature = session.getFeature(Versioning.class); + if(feature != null) { + log.debug("Use custom versioning {}", feature); + if(feature.getConfiguration(file).isEnabled()) { + log.debug("Enabled versioning for {}", file); + if(feature.save(file)) { + log.debug("Clear exist flag for file {}", file); + status.setExists(false).getDisplayname().exists(false); + } + } + } + } + } + } + } + } +} diff --git a/core/src/test/java/ch/cyberduck/core/transfer/download/AbstractDownloadFilterTest.java b/core/src/test/java/ch/cyberduck/core/transfer/download/AbstractDownloadFilterTest.java index 4913dfba206..d9ee443c9bb 100644 --- a/core/src/test/java/ch/cyberduck/core/transfer/download/AbstractDownloadFilterTest.java +++ b/core/src/test/java/ch/cyberduck/core/transfer/download/AbstractDownloadFilterTest.java @@ -15,6 +15,8 @@ * GNU General Public License for more details. */ +import ch.cyberduck.core.transfer.download.features.SegmentedFeatureFilter; + import org.junit.Test; import static ch.cyberduck.core.transfer.download.AbstractDownloadFilterTest.Unit.GiB; @@ -45,47 +47,47 @@ public void testFindSegmentSize() { final SegmentSizePair[] tests = new SegmentSizePair[]{ // split 20 MiB on one connection down to two 10 MiB segments new SegmentSizePair( - convertSize(20, MiB), 1, - convertSize(10, MiB), convertSize(128, MiB), 128) + convertSize(20, MiB), 1, + convertSize(10, MiB), convertSize(128, MiB), 128) .withExpected(convertSize(10, MiB)), // split 16 GiB down to 128 segments of size 128 MiB new SegmentSizePair( - convertSize(16, GiB), 1, - convertSize(10, MiB), convertSize(128, MiB), 128) + convertSize(16, GiB), 1, + convertSize(10, MiB), convertSize(128, MiB), 128) .withExpected(convertSize(128, MiB)), // halving allowed segments increases segment size for 16 GiB file new SegmentSizePair( - convertSize(16, GiB), 1, - convertSize(10, MiB), convertSize(128, MiB), 64) + convertSize(16, GiB), 1, + convertSize(10, MiB), convertSize(128, MiB), 64) .withExpected(convertSize(256, MiB)), // doubling file size new SegmentSizePair( - convertSize(32, GiB), 1, - convertSize(10, MiB), convertSize(128, MiB), 128) + convertSize(32, GiB), 1, + convertSize(10, MiB), convertSize(128, MiB), 128) .withExpected(convertSize(256, MiB)), new SegmentSizePair( - convertSize(20, MiB) + 1, 1, - convertSize(10, MiB), convertSize(128, MiB), 128) + convertSize(20, MiB) + 1, 1, + convertSize(10, MiB), convertSize(128, MiB), 128) .withExpected(convertSize(5, MiB)), new SegmentSizePair( - convertSize(20, GiB), 1, - convertSize(10, MiB), convertSize(128, MiB), 128) + convertSize(20, GiB), 1, + convertSize(10, MiB), convertSize(128, MiB), 128) .withExpected(convertSize(256, MiB)), new SegmentSizePair( - 4893263872L, 2, - convertSize(10, MiB), convertSize(128, MiB), 128) + 4893263872L, 2, + convertSize(10, MiB), convertSize(128, MiB), 128) .withExpected(38228624L) }; for(final SegmentSizePair test : tests) { assertEquals(test.toString(), test.expected, - AbstractDownloadFilter.findSegmentSize( + SegmentedFeatureFilter.findSegmentSize( test.length, test.connections, test.segmentThreshold, test.segmentSizeMaximum, test.segmentCount)); } } - class SegmentSizePair { + static class SegmentSizePair { public final long length; public final int connections; public final long segmentThreshold;