Skip to content

Commit

Permalink
Merge pull request #1967 in ITERATE/cyberduck from feature/CD-5990 to…
Browse files Browse the repository at this point in the history
… master

* commit '860dc84d09f67973d168bb08c4269a7eef77faee': (24 commits)
  Review.
  Rename.
  Review
  Fix segment discovery container
  Review
  Add Test
  Fix SwiftSegmentService not returning other bucket segments
  Add failing test
  Add LargeObjectCopyFeatureTests
  Add Test
  Add Test
  Remove obsolete field
  Review. Explicity boolean argument to delete segments for large objects.
  Formatting.
  Changes.
  Move file atomicly by manifest copy on same container
  Create copy of source attributes
  Always overwrite existing segments
  Review
  Update dependency
  ...
  • Loading branch information
dkocher committed Mar 3, 2020
2 parents 9200995 + 860dc84 commit 9e65aca
Show file tree
Hide file tree
Showing 13 changed files with 614 additions and 31 deletions.
1 change: 1 addition & 0 deletions Changelog.txt
Expand Up @@ -8,6 +8,7 @@ https://cyberduck.io/
- [Feature] Support TLS 1.3 (#10962)
- [Bugfix] Download fails for files with whitespace in name (Google Storage) (#10931)
- [Bugfix] Unable to access documents in Shared with me (Google Drive)
- [Bugfix] Read timeout after copying large file (OpenStack Swift)

7.2.6
- [Feature] Importer for bookmarks from Transmit 5 (macOS)
Expand Down
2 changes: 1 addition & 1 deletion openstack/pom.xml
Expand Up @@ -23,7 +23,7 @@
<artifactId>openstack</artifactId>
<packaging>jar</packaging>
<properties>
<openstack-swift-version>2.5.11</openstack-swift-version>
<openstack-swift-version>2.5.12</openstack-swift-version>
</properties>
<dependencies>
<dependency>
Expand Down
Expand Up @@ -31,30 +31,32 @@

import ch.iterate.openstack.swift.exception.GenericException;

public class SwiftCopyFeature implements Copy {

private final SwiftSession session;
public class SwiftDefaultCopyFeature implements Copy {

private final PathContainerService containerService
= new PathContainerService();
= new PathContainerService();

private final SwiftSession session;
private final SwiftRegionService regionService;

public SwiftCopyFeature(final SwiftSession session) {
public SwiftDefaultCopyFeature(final SwiftSession session) {
this(session, new SwiftRegionService(session));
}

public SwiftCopyFeature(final SwiftSession session, final SwiftRegionService regionService) {
public SwiftDefaultCopyFeature(final SwiftSession session, final SwiftRegionService regionService) {
this.session = session;
this.regionService = regionService;
}

@Override
public Path copy(final Path source, final Path target, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException {
try {
// Copies file
// If segmented file, copies manifest (creating a link between new object and original segments)
// Use with caution.
session.getClient().copyObject(regionService.lookup(source),
containerService.getContainer(source).getName(), containerService.getKey(source),
containerService.getContainer(target).getName(), containerService.getKey(target));
containerService.getContainer(source).getName(), containerService.getKey(source),
containerService.getContainer(target).getName(), containerService.getKey(target));
// Copy original file attributes
return new Path(target.getParent(), target.getName(), target.getType(), new PathAttributes(source.attributes()));
}
Expand Down
Expand Up @@ -41,7 +41,7 @@ public class SwiftDeleteFeature implements Delete {
private final SwiftSession session;

private final PathContainerService containerService
= new PathContainerService();
= new PathContainerService();

private final SwiftSegmentService segmentService;

Expand All @@ -64,29 +64,41 @@ public SwiftDeleteFeature(final SwiftSession session, final SwiftSegmentService

@Override
public void delete(final Map<Path, TransferStatus> files, final PasswordCallback prompt, final Callback callback) throws BackgroundException {
this.delete(files, prompt, callback, true);
}

/**
* @param deleteSegments Delete segment files referenced in manifest for large file objects
*/
protected void delete(final Map<Path, TransferStatus> files, final PasswordCallback prompt, final Callback callback,
final boolean deleteSegments) throws BackgroundException {
for(Path file : files.keySet()) {
callback.delete(file);
try {
if(file.isFile()) {
// Collect a list of existing segments. Must do this before deleting the manifest file.
final List<Path> segments = segmentService.list(file);
session.getClient().deleteObject(regionService.lookup(file),
containerService.getContainer(file).getName(), containerService.getKey(file));
// Clean up any old segments
for(Path segment : segments) {
session.getClient().deleteObject(regionService.lookup(segment),
containerService.getContainer(file).getName(), containerService.getKey(file));
// Clean up any old segments, only if rename.remote-transferstatus has not been
// set. This indicates this has been run as a move-operation, which in turn
// copies a manifest for a given file as long as it is on the same container.
if(deleteSegments) {
for(Path segment : segments) {
session.getClient().deleteObject(regionService.lookup(segment),
containerService.getContainer(segment).getName(), containerService.getKey(segment));
}
}
}
else if(file.isDirectory()) {
if(containerService.isContainer(file)) {
session.getClient().deleteContainer(regionService.lookup(file),
containerService.getContainer(file).getName());
containerService.getContainer(file).getName());
}
else {
try {
session.getClient().deleteObject(regionService.lookup(file),
containerService.getContainer(file).getName(), containerService.getKey(file));
containerService.getContainer(file).getName(), containerService.getKey(file));
}
catch(GenericException e) {
if(new SwiftExceptionMappingService().map(e) instanceof NotfoundException) {
Expand Down
@@ -0,0 +1,127 @@
package ch.cyberduck.core.openstack;

/*
* Copyright (c) 2002-2020 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.ConnectionCallback;
import ch.cyberduck.core.DefaultIOExceptionMappingService;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.PathAttributes;
import ch.cyberduck.core.PathContainerService;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.features.Copy;
import ch.cyberduck.core.io.Checksum;
import ch.cyberduck.core.io.HashAlgorithm;
import ch.cyberduck.core.transfer.TransferStatus;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import ch.iterate.openstack.swift.model.StorageObject;

public class SwiftLargeObjectCopyFeature implements Copy {

private final PathContainerService containerService
= new PathContainerService();

private final SwiftSession session;
private final SwiftRegionService regionService;
private final SwiftSegmentService segmentService;
private final SwiftObjectListService listService;

public SwiftLargeObjectCopyFeature(final SwiftSession session) {
this(session, new SwiftRegionService(session));
}

public SwiftLargeObjectCopyFeature(final SwiftSession session, final SwiftRegionService regionService) {
this(session, regionService, new SwiftSegmentService(session));
}

public SwiftLargeObjectCopyFeature(final SwiftSession session, final SwiftRegionService regionService,
final SwiftSegmentService segmentService) {
this(session, regionService, segmentService, new SwiftObjectListService(session, regionService));
}

public SwiftLargeObjectCopyFeature(final SwiftSession session, final SwiftRegionService regionService,
final SwiftSegmentService segmentService, final SwiftObjectListService listService) {
this.session = session;
this.regionService = regionService;
this.segmentService = segmentService;
this.listService = listService;
}

@Override
public Path copy(final Path source, final Path target, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException {
return copy(source, segmentService.list(source), target, status, callback);
}

@Override
public boolean isRecursive(final Path source, final Path target) {
return false;
}

@Override
public boolean isSupported(final Path source, final Path target) {
return !containerService.isContainer(source) && !containerService.isContainer(target);
}

public Path copy(final Path source, final List<Path> sourceParts, final Path target, final TransferStatus status,
final ConnectionCallback callback) throws BackgroundException {
final List<Path> completed = new ArrayList<>();
final Path copySegmentsDirectory = segmentService.getSegmentsDirectory(target, status.getLength());
for(final Path copyPart : sourceParts) {
final Path destination = new Path(copySegmentsDirectory, copyPart.getName(), copyPart.getType());
try {
session.getClient().copyObject(regionService.lookup(copyPart),
containerService.getContainer(copyPart).getName(), containerService.getKey(copyPart),
containerService.getContainer(target).getName(), containerService.getKey(destination));

// copy attributes from source. Should be same?
destination.setAttributes(copyPart.attributes());
completed.add(destination);
}
catch(IOException e) {
throw new DefaultIOExceptionMappingService().map(e);
}
}
final List<StorageObject> manifestObjects = new ArrayList<>();
for(final Path part : completed) {
final StorageObject model = new StorageObject(containerService.getKey(part));
model.setSize(part.attributes().getSize());
model.setMd5sum(part.attributes().getChecksum().hash);
manifestObjects.add(model);
}
final String manifest = segmentService.manifest(containerService.getContainer(target).getName(), manifestObjects);
final StorageObject stored = new StorageObject(containerService.getKey(target));
try {
final String checksum = session.getClient().createSLOManifestObject(regionService.lookup(
containerService.getContainer(target)),
containerService.getContainer(target).getName(),
status.getMime(),
containerService.getKey(target), manifest, Collections.emptyMap());
// The value of the Content-Length header is the total size of all segment objects, and the value of the ETag header is calculated by taking
// the ETag value of each segment, concatenating them together, and then returning the MD5 checksum of the result.
stored.setMd5sum(checksum);
}
catch(IOException e) {
throw new DefaultIOExceptionMappingService().map(e);
}
final PathAttributes attributes = new PathAttributes(source.attributes());
attributes.setChecksum(new Checksum(HashAlgorithm.md5, stored.getMd5sum()));
return new Path(target.getParent(), target.getName(), target.getType(), attributes);
}
}
Expand Up @@ -36,23 +36,28 @@ public class SwiftMoveFeature implements Move {
private final SwiftSession session;
private final SwiftRegionService regionService;

private Delete delete;

public SwiftMoveFeature(final SwiftSession session) {
this(session, new SwiftRegionService(session));
}

public SwiftMoveFeature(final SwiftSession session, final SwiftRegionService regionService) {
this.session = session;
this.regionService = regionService;
this.delete = new SwiftDeleteFeature(session);
}

@Override
public Path move(final Path file, final Path renamed, final TransferStatus status, final Delete.Callback callback, final ConnectionCallback connectionCallback) throws BackgroundException {
final Path copy = new SwiftCopyFeature(session, regionService).copy(file, renamed, new TransferStatus().length(file.attributes().getSize()), connectionCallback);
delete.delete(Collections.singletonMap(file, status), connectionCallback, callback);
return copy;
if(containerService.getContainer(file).equals(containerService.getContainer(renamed))) {
// Either copy complete file contents (small file) or copy manifest (large file)
final Path rename = new SwiftDefaultCopyFeature(session, regionService).copy(file, renamed, new TransferStatus().length(file.attributes().getSize()), connectionCallback);
new SwiftDeleteFeature(session).delete(Collections.singletonMap(file, status), connectionCallback, callback, false);
return rename;
}
else {
final Path copy = new SwiftSegmentCopyService(session, regionService).copy(file, renamed, new TransferStatus().length(file.attributes().getSize()), connectionCallback);
new SwiftDeleteFeature(session).delete(Collections.singletonMap(file, status), connectionCallback, callback);
return copy;
}
}

@Override
Expand Down
@@ -0,0 +1,66 @@
package ch.cyberduck.core.openstack;

/*
* Copyright (c) 2002-2020 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.ConnectionCallback;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.PathContainerService;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.features.Copy;
import ch.cyberduck.core.transfer.TransferStatus;

import java.util.List;

public class SwiftSegmentCopyService implements Copy {

private final PathContainerService containerService
= new PathContainerService();

private final SwiftSession session;
private final SwiftRegionService regionService;

public SwiftSegmentCopyService(final SwiftSession session) {
this(session, new SwiftRegionService(session));
}

public SwiftSegmentCopyService(final SwiftSession session, final SwiftRegionService regionService) {
this.session = session;
this.regionService = regionService;
}

@Override
public Path copy(final Path source, final Path target, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException {
final SwiftSegmentService segmentService = new SwiftSegmentService(session);
final List<Path> segments = segmentService.list(source);
if(segments.isEmpty()) {
return new SwiftDefaultCopyFeature(session, regionService).copy(source, target, status, callback);
}
else {
return new SwiftLargeObjectCopyFeature(session, regionService, segmentService)
.copy(source, segments, target, status, callback);
}
}

@Override
public boolean isRecursive(final Path source, final Path target) {
return false;
}

@Override
public boolean isSupported(final Path source, final Path target) {
return !containerService.isContainer(source) && !containerService.isContainer(target);
}
}
Expand Up @@ -17,6 +17,7 @@
* Bug fixes, suggestions and comments should be sent to feedback@cyberduck.ch
*/

import ch.cyberduck.core.AbstractPath;
import ch.cyberduck.core.DefaultIOExceptionMappingService;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.PathContainerService;
Expand Down Expand Up @@ -93,9 +94,10 @@ public List<Path> list(final Path file) throws BackgroundException {
return Collections.emptyList();
}
final List<Path> objects = new ArrayList<Path>();
if(segments.containsKey(container.getName())) {
for(StorageObject s : segments.get(container.getName())) {
final Path segment = new Path(container, s.getName(), EnumSet.of(Path.Type.file));
for(final String containerName : segments.keySet()) {
final Path containerPath = new Path(containerName, container.getType(), container.attributes());
for(StorageObject s : segments.get(containerName)) {
final Path segment = new Path(containerPath, s.getName(), EnumSet.of(Path.Type.file));
segment.attributes().setSize(s.getSize());
try {
segment.attributes().setModificationDate(dateParser.parse(s.getLastModified()).getTime());
Expand Down
Expand Up @@ -157,7 +157,7 @@ public <T> T _getFeature(final Class<T> type) {
return (T) new SwiftMetadataFeature(this, regionService);
}
if(type == Copy.class) {
return (T) new SwiftCopyFeature(this, regionService);
return (T) new SwiftSegmentCopyService(this, regionService);
}
if(type == Move.class) {
return (T) new SwiftMoveFeature(this, regionService);
Expand Down

0 comments on commit 9e65aca

Please sign in to comment.