1 package ch
.cyberduck
.core
.s3
;
4 * Copyright (c) 2002-2013 David Kocher. All rights reserved.
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
;
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
);
69 final Map
<Path
, List
<ObjectKeyAndVersion
>> map
= new HashMap
<Path
, List
<ObjectKeyAndVersion
>>();
70 for(Path file
: files
) {
71 if(containerService
.isContainer(file
)) {
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
);
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
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
) {
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
{
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
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
)));
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
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
);