Extract filters for protocol features used for specific file attributes in file transfers.

This commit is contained in:
David Kocher
2024-11-14 13:17:23 +01:00
parent a7b3e754d6
commit 2a8325b3a2
26 changed files with 1562 additions and 591 deletions
@@ -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> 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> 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);
}
}
}
@@ -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> 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> 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> local, final TransferStatus status, final ProgressListener progress) {
// No-op
return status;
}
};
}
@@ -17,75 +17,47 @@ package ch.cyberduck.core.transfer.download;
* 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<Path> symlinkResolver;
private final QuarantineService quarantine = QuarantineServiceFactory.get();
private final ApplicationLauncher launcher = ApplicationLauncherFactory.get();
private final SymlinkResolver<Path> resolver;
protected final AttributesFinder attribute;
protected final DownloadFilterOptions options;
protected final FeatureFilter chain;
public AbstractDownloadFilter(final SymlinkResolver<Path> symlinkResolver, final Session<?> session, final DownloadFilterOptions options) {
this(symlinkResolver, session, session.getFeature(AttributesFinder.class), options);
public AbstractDownloadFilter(final SymlinkResolver<Path> resolver, final Session<?> session, final DownloadFilterOptions options) {
this(resolver, session, session.getFeature(AttributesFinder.class), options);
}
public AbstractDownloadFilter(final SymlinkResolver<Path> symlinkResolver, final Session<?> session, final AttributesFinder attribute, final DownloadFilterOptions options) {
this.session = session;
this.symlinkResolver = symlinkResolver;
public AbstractDownloadFilter(final SymlinkResolver<Path> 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 abstract class AbstractDownloadFilter implements TransferPathFilter {
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 abstract class AbstractDownloadFilter implements TransferPathFilter {
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<TransferStatus> 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<TransferStatus> 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<TransferStatus> 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;
}
}
@@ -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> 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> 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));
}
}
}
}
}
}
}
@@ -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
);
}
}
@@ -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> local, final TransferStatus status, final ProgressListener progress) throws BackgroundException {
if(file.isFile()) {
local.ifPresent(launcher::open);
}
}
}
@@ -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> 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> 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());
}
}
}
}
}
@@ -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> 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;
}
}
}
}
@@ -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> 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<TransferStatus> 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> local, final TransferStatus status, final ProgressListener progress) throws BackgroundException {
if(status.isSegmented()) {
if(local.isPresent()) {
// Obtain ordered list of segments to reassemble
final List<TransferStatus> segments = status.getSegments();
log.info("Compile {} segments to file {}", segments.size(), local);
if(local.get().exists()) {
local.get().delete();
}
for(Iterator<TransferStatus> 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;
}
}
@@ -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> 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> 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);
}
}
}
}
}
@@ -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> 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> 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());
}
}
}
}
}
@@ -17,76 +17,48 @@ package ch.cyberduck.core.transfer.upload;
* 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<Local> symlinkResolver;
private final Filter<Path> hidden = SearchFilterFactory.HIDDEN_FILTER;
private final SymlinkResolver<Local> resolver;
protected final Find find;
protected final AttributesFinder attribute;
protected final UploadFilterOptions options;
protected final FeatureFilter chain;
public AbstractUploadFilter(final SymlinkResolver<Local> symlinkResolver, final Session<?> session, final UploadFilterOptions options) {
this(symlinkResolver, session, session.getFeature(Find.class), session.getFeature(AttributesFinder.class), options);
public AbstractUploadFilter(final SymlinkResolver<Local> resolver, final Session<?> session, final UploadFilterOptions options) {
this(resolver, session, session.getFeature(Find.class), session.getFeature(AttributesFinder.class), options);
}
public AbstractUploadFilter(final SymlinkResolver<Local> symlinkResolver, final Session<?> session, final Find find, final AttributesFinder attribute, final UploadFilterOptions options) {
this.session = session;
this.symlinkResolver = symlinkResolver;
public AbstractUploadFilter(final SymlinkResolver<Local> 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<Local> 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,16 +73,31 @@ public abstract class AbstractUploadFilter implements TransferPathFilter {
@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());
final TransferStatus status = new TransferStatus().setLockId(parent.getLockId());
if(file.isFile()) {
// Set content length from local file
if(local.isSymbolicLink()) {
if(!resolver.resolve(local)) {
// Will resolve the symbolic link when the file is requested.
final Local target = local.getSymlinkTarget();
status.setLength(target.attributes().getSize());
}
// No file size increase for symbolic link to be created on the server
}
else {
// Read file size from filesystem
status.setLength(local.attributes().getSize());
}
}
if(file.isDirectory()) {
status.setLength(0L);
}
// 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);
status.setRemote(attribute.find(file));
}
else {
// Look if there is directory or file that clashes with this upload
@@ -126,263 +113,19 @@ public abstract class AbstractUploadFilter implements TransferPathFilter {
}
}
}
if(file.isFile()) {
// Set content length from local file
if(local.isSymbolicLink()) {
if(!symlinkResolver.resolve(local)) {
// Will resolve the symbolic link when the file is requested.
final Local target = local.getSymlinkTarget();
status.setLength(target.attributes().getSize());
}
// No file size increase for symbolic link to be created on the server
}
else {
// 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));
}
}
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));
}
}
}
}
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());
}
}
}
}
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);
}
}
}
}
@@ -35,6 +35,8 @@ import org.apache.logging.log4j.Logger;
public class RenameFilter extends AbstractUploadFilter {
private static final Logger log = LogManager.getLogger(RenameFilter.class);
private final UploadFilterOptions options;
public RenameFilter(final SymlinkResolver<Local> symlinkResolver, final Session<?> session) {
this(symlinkResolver, session, new UploadFilterOptions(session.getHost()));
}
@@ -49,6 +51,7 @@ public class RenameFilter extends AbstractUploadFilter {
public RenameFilter(final SymlinkResolver<Local> symlinkResolver, final Session<?> session, final Find find, final AttributesFinder attribute, final UploadFilterOptions options) {
super(symlinkResolver, session, find, attribute, options);
this.options = options;
}
@Override
@@ -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> 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> 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());
}
}
}
}
}
@@ -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> 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;
}
}
@@ -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
);
}
}
@@ -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> 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;
}
}
@@ -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<Path> hidden;
public HiddenFeatureFilter() {
this(SearchFilterFactory.HIDDEN_FILTER);
}
public HiddenFeatureFilter(final Filter<Path> hidden) {
this.hidden = hidden;
}
@Override
public TransferStatus prepare(final Path file, final Optional<Local> local, final TransferStatus status, final ProgressListener progress) throws BackgroundException {
status.setHidden(!hidden.accept(file));
return status;
}
}
@@ -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> 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;
}
}
@@ -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> local, final TransferStatus status, final ProgressListener progress) throws BackgroundException {
if(file.isFile()) {
status.setMime(service.getMime(file.getName()));
}
return status;
}
}
@@ -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> 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> 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());
}
}
}
}
}
@@ -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> 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;
}
}
@@ -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> 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> 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());
}
}
}
}
@@ -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> 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> 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());
}
}
}
}
}
}
@@ -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);
}
}
}
}
}
}
}
}
}
@@ -15,6 +15,8 @@ package ch.cyberduck.core.transfer.download;
* 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 class AbstractDownloadFilterTest {
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;