Add callback for delete operation.
[cyberduck.git] / source / ch / cyberduck / core / s3 / S3MultipleDeleteFeature.java
blob9375680ee41967608d7998a2cbb75fdf05452118
1 package ch.cyberduck.core.s3;
3 /*
4 * Copyright (c) 2002-2013 David Kocher. All rights reserved.
5 * http://cyberduck.ch/
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * Bug fixes, suggestions and comments should be sent to feedback@cyberduck.ch
20 import ch.cyberduck.core.Credentials;
21 import ch.cyberduck.core.LoginCallback;
22 import ch.cyberduck.core.Path;
23 import ch.cyberduck.core.PathContainerService;
24 import ch.cyberduck.core.collections.Partition;
25 import ch.cyberduck.core.exception.AccessDeniedException;
26 import ch.cyberduck.core.exception.BackgroundException;
27 import ch.cyberduck.core.features.Delete;
28 import ch.cyberduck.core.features.Versioning;
29 import ch.cyberduck.core.preferences.PreferencesFactory;
31 import org.apache.log4j.Logger;
32 import org.jets3t.service.ServiceException;
33 import org.jets3t.service.model.MultipartUpload;
34 import org.jets3t.service.model.MultipleDeleteResult;
35 import org.jets3t.service.model.container.ObjectKeyAndVersion;
37 import java.util.ArrayList;
38 import java.util.EnumSet;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Map;
43 /**
44 * @version $Id$
46 public class S3MultipleDeleteFeature implements Delete {
47 private static final Logger log = Logger.getLogger(S3MultipleDeleteFeature.class);
49 private S3Session session;
51 private PathContainerService containerService
52 = new S3PathContainerService();
54 private S3MultipartService multipartService;
56 private Versioning versioningService;
58 public S3MultipleDeleteFeature(final S3Session session) {
59 this.session = session;
60 this.versioningService = session.getFeature(Versioning.class);
61 this.multipartService = new S3MultipartService(session);
64 public void delete(final List<Path> files, final LoginCallback prompt, final Callback callback) throws BackgroundException {
65 if(files.size() == 1) {
66 new S3DefaultDeleteFeature(session).delete(files, prompt, callback);
68 else {
69 final Map<Path, List<ObjectKeyAndVersion>> map = new HashMap<Path, List<ObjectKeyAndVersion>>();
70 for(Path file : files) {
71 if(containerService.isContainer(file)) {
72 continue;
74 callback.delete(file);
75 final Path container = containerService.getContainer(file);
76 final List<ObjectKeyAndVersion> keys = new ArrayList<ObjectKeyAndVersion>();
77 // Always returning 204 even if the key does not exist. Does not return 404 for non-existing keys
78 keys.add(new ObjectKeyAndVersion(containerService.getKey(file), file.attributes().getVersionId()));
79 if(map.containsKey(container)) {
80 map.get(container).addAll(keys);
82 else {
83 map.put(container, keys);
86 // Iterate over all containers and delete list of keys
87 for(Map.Entry<Path, List<ObjectKeyAndVersion>> entry : map.entrySet()) {
88 final Path container = entry.getKey();
89 final List<ObjectKeyAndVersion> keys = entry.getValue();
90 this.delete(container, keys, prompt);
92 for(Path file : files) {
93 if(containerService.isContainer(file)) {
94 callback.delete(file);
95 // Finally delete bucket itself
96 try {
97 session.getClient().deleteBucket(containerService.getContainer(file).getName());
99 catch(ServiceException e) {
100 throw new ServiceExceptionMappingService().map("Cannot delete {0}", e, file);
105 for(Path file : files) {
106 if(file.isFile()) {
107 try {
108 // Delete interrupted multipart uploads
109 for(MultipartUpload upload : multipartService.find(file)) {
110 multipartService.delete(upload);
113 catch(AccessDeniedException e) {
114 // Workaround for #9000
115 log.warn(String.format("Failure looking for multipart uploads. %s", e.getMessage()));
122 * @param container Bucket
123 * @param keys Key and version ID for versioned object or null
124 * @throws ch.cyberduck.core.exception.ConnectionCanceledException Authentication canceled for MFA delete
126 protected void delete(final Path container, final List<ObjectKeyAndVersion> keys, final LoginCallback prompt)
127 throws BackgroundException {
128 try {
129 if(versioningService != null
130 && versioningService.getConfiguration(container).isMultifactor()) {
131 final Credentials factor = versioningService.getToken(prompt);
132 final MultipleDeleteResult result = session.getClient().deleteMultipleObjectsWithMFA(container.getName(),
133 keys.toArray(new ObjectKeyAndVersion[keys.size()]),
134 factor.getUsername(),
135 factor.getPassword(),
136 // Only include errors in response
137 true);
138 if(result.hasErrors()) {
139 for(MultipleDeleteResult.ErrorResult error : result.getErrorResults()) {
140 final ServiceException failure = new ServiceException();
141 failure.setErrorCode(error.getErrorCode());
142 failure.setErrorMessage(error.getMessage());
143 throw new ServiceExceptionMappingService().map("Cannot delete {0}", failure,
144 new Path(container, error.getKey(), EnumSet.of(Path.Type.file)));
148 else {
149 // Request contains a list of up to 1000 keys that you want to delete
150 for(List<ObjectKeyAndVersion> partition : new Partition<ObjectKeyAndVersion>(keys, PreferencesFactory.get().getInteger("s3.delete.multiple.partition"))) {
151 final MultipleDeleteResult result = session.getClient().deleteMultipleObjects(container.getName(),
152 partition.toArray(new ObjectKeyAndVersion[partition.size()]),
153 // Only include errors in response
154 true);
155 if(result.hasErrors()) {
156 for(MultipleDeleteResult.ErrorResult error : result.getErrorResults()) {
157 final ServiceException failure = new ServiceException();
158 failure.setErrorCode(error.getErrorCode());
159 failure.setErrorMessage(error.getMessage());
160 throw new ServiceExceptionMappingService().map("Cannot delete {0}", failure,
161 new Path(container, error.getKey(), EnumSet.of(Path.Type.file)));
167 catch(ServiceException e) {
168 throw new ServiceExceptionMappingService().map("Cannot delete {0}", e, container);