mirror of
https://github.com/iterate-ch/cyberduck.git
synced 2026-05-26 19:10:49 +00:00
Extract filters for protocol features used for specific file attributes in file transfers.
This commit is contained in:
@@ -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
-279
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+69
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+172
@@ -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;
|
||||
}
|
||||
}
|
||||
+68
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+66
@@ -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;
|
||||
}
|
||||
}
|
||||
+39
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
+62
@@ -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;
|
||||
}
|
||||
}
|
||||
+46
@@ -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;
|
||||
}
|
||||
}
|
||||
+62
@@ -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;
|
||||
}
|
||||
}
|
||||
+91
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+64
@@ -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;
|
||||
}
|
||||
}
|
||||
+91
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+77
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-16
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user