root / pithos-macos / PithosUtilities.m @ 9990166e
History | View | Annotate | Download (77.1 kB)
1 |
// |
---|---|
2 |
// PithosUtilities.m |
3 |
// pithos-macos |
4 |
// |
5 |
// Copyright 2011-2012 GRNET S.A. All rights reserved. |
6 |
// |
7 |
// Redistribution and use in source and binary forms, with or |
8 |
// without modification, are permitted provided that the following |
9 |
// conditions are met: |
10 |
// |
11 |
// 1. Redistributions of source code must retain the above |
12 |
// copyright notice, this list of conditions and the following |
13 |
// disclaimer. |
14 |
// |
15 |
// 2. Redistributions in binary form must reproduce the above |
16 |
// copyright notice, this list of conditions and the following |
17 |
// disclaimer in the documentation and/or other materials |
18 |
// provided with the distribution. |
19 |
// |
20 |
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
21 |
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
22 |
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
23 |
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
24 |
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
25 |
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
26 |
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
27 |
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
28 |
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
29 |
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
30 |
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
31 |
// POSSIBILITY OF SUCH DAMAGE. |
32 |
// |
33 |
// The views and conclusions contained in the software and |
34 |
// documentation are those of the authors and should not be |
35 |
// interpreted as representing official policies, either expressed |
36 |
// or implied, of GRNET S.A. |
37 |
|
38 |
#import "PithosUtilities.h" |
39 |
#import "ASINetworkQueue.h" |
40 |
#import "ASIPithos.h" |
41 |
#import "ASIPithosContainerRequest.h" |
42 |
#import "ASIPithosObjectRequest.h" |
43 |
#import "ASIPithosObject.h" |
44 |
#import "HashMapHash.h" |
45 |
|
46 |
@implementation PithosUtilities |
47 |
|
48 |
#pragma mark - |
49 |
#pragma mark Download |
50 |
|
51 |
+ (ASIPithosObjectRequest *)objectDataRequestWithPithos:(ASIPithos *)pithos |
52 |
containerName:(NSString *)containerName |
53 |
objectName:(NSString *)objectName |
54 |
version:(NSString *)version |
55 |
toDirectory:(NSString *)directoryPath |
56 |
withNewFileName:(NSString *)newFileName |
57 |
checkIfExists:(BOOL)ifExists |
58 |
sharingAccount:(NSString *)sharingAccount { |
59 |
NSString *fileName; |
60 |
if (newFileName) { |
61 |
fileName = [NSString stringWithString:newFileName]; |
62 |
} else { |
63 |
fileName = [objectName lastPathComponent]; |
64 |
if ([objectName hasSuffix:@"/"]) |
65 |
fileName = [fileName stringByAppendingString:@"/"]; |
66 |
} |
67 |
fileName = [fileName stringByReplacingOccurrencesOfString:@"/" withString:@":"]; |
68 |
|
69 |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
70 |
|
71 |
NSString *destinationPath = [directoryPath stringByAppendingPathComponent:fileName]; |
72 |
if (ifExists && [fileManager fileExistsAtPath:destinationPath]) { |
73 |
__block NSInteger choice; |
74 |
dispatch_sync(dispatch_get_main_queue(), ^{ |
75 |
NSAlert *alert = [[[NSAlert alloc] init] autorelease]; |
76 |
[alert setMessageText:@"File Exists"]; |
77 |
[alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", fileName]]; |
78 |
[alert addButtonWithTitle:@"OK"]; |
79 |
[alert addButtonWithTitle:@"Cancel"]; |
80 |
choice = [alert runModal]; |
81 |
}); |
82 |
if (choice == NSAlertSecondButtonReturn) |
83 |
return nil; |
84 |
} |
85 |
|
86 |
BOOL directoryIsDirectory; |
87 |
BOOL directoryExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&directoryIsDirectory]; |
88 |
NSError *error = nil; |
89 |
if (!directoryExists) { |
90 |
[fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error]; |
91 |
} else if (!directoryIsDirectory) { |
92 |
[fileManager removeItemAtPath:directoryPath error:&error]; |
93 |
} |
94 |
if (error) { |
95 |
DLog(@"Cannot remove existing file '%@': %@", fileName, error); |
96 |
dispatch_async(dispatch_get_main_queue(), ^{ |
97 |
NSAlert *alert = [[[NSAlert alloc] init] autorelease]; |
98 |
[alert setMessageText:@"Removal Error"]; |
99 |
[alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file '%@': %@", |
100 |
fileName, [error localizedDescription]]]; |
101 |
[alert addButtonWithTitle:@"OK"]; |
102 |
[alert runModal]; |
103 |
}); |
104 |
return nil; |
105 |
} |
106 |
|
107 |
ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos |
108 |
containerName:containerName |
109 |
objectName:objectName |
110 |
version:version]; |
111 |
if (sharingAccount) |
112 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
113 |
objectRequest.downloadDestinationPath = destinationPath; |
114 |
objectRequest.allowResumeForFileDownloads = YES; |
115 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
116 |
fileName, @"fileName", |
117 |
destinationPath, @"filePath", |
118 |
nil]; |
119 |
return objectRequest; |
120 |
} |
121 |
|
122 |
+ (NSArray *)objectDataRequestsForSubdirWithPithos:(ASIPithos *)pithos |
123 |
containerName:(NSString *)containerName |
124 |
objectName:(NSString *)objectName |
125 |
toDirectory:(NSString *)directoryPath |
126 |
checkIfExists:(BOOL)ifExists |
127 |
sharingAccount:(NSString *)sharingAccount { |
128 |
NSString *subdirName = [objectName lastPathComponent]; |
129 |
NSString *destinationPath = [directoryPath stringByAppendingPathComponent:subdirName]; |
130 |
if (ifExists && [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) { |
131 |
__block NSInteger choice; |
132 |
dispatch_sync(dispatch_get_main_queue(), ^{ |
133 |
NSAlert *alert = [[[NSAlert alloc] init] autorelease]; |
134 |
[alert setMessageText:@"File exists"]; |
135 |
[alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", subdirName]]; |
136 |
[alert addButtonWithTitle:@"OK"]; |
137 |
[alert addButtonWithTitle:@"Cancel"]; |
138 |
choice = [alert runModal]; |
139 |
}); |
140 |
if (choice == NSAlertSecondButtonReturn) |
141 |
return nil; |
142 |
} |
143 |
|
144 |
NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName |
145 |
delimiter:nil sharingAccount:sharingAccount]; |
146 |
if (objects == nil) |
147 |
return nil; |
148 |
|
149 |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
150 |
NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[objects count]]; |
151 |
NSUInteger subdirPrefixLength = [objectName length]; |
152 |
|
153 |
NSError *error = nil; |
154 |
[fileManager createDirectoryAtPath:[directoryPath stringByAppendingPathComponent:subdirName] withIntermediateDirectories:YES attributes:nil error:&error]; |
155 |
if (error) { |
156 |
DLog(@"Cannot create directory at '%@': %@", directoryPath, error); |
157 |
dispatch_async(dispatch_get_main_queue(), ^{ |
158 |
NSAlert *alert = [[[NSAlert alloc] init] autorelease]; |
159 |
[alert setMessageText:@"Create Directory Error"]; |
160 |
[alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@", |
161 |
directoryPath, [error localizedDescription]]]; |
162 |
[alert addButtonWithTitle:@"OK"]; |
163 |
[alert runModal]; |
164 |
}); |
165 |
} |
166 |
|
167 |
for (ASIPithosObject *object in objects) { |
168 |
if ([self isContentTypeDirectory:object.contentType]) { |
169 |
NSString *subdirDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName]; |
170 |
subdirDirectoryPath = [subdirDirectoryPath stringByAppendingPathComponent:[object.name substringFromIndex:subdirPrefixLength]]; |
171 |
|
172 |
BOOL directoryIsDirectory; |
173 |
BOOL directoryExists = [fileManager fileExistsAtPath:subdirDirectoryPath isDirectory:&directoryIsDirectory]; |
174 |
NSError *error = nil; |
175 |
if (!directoryExists) { |
176 |
[fileManager createDirectoryAtPath:subdirDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error]; |
177 |
if (error) { |
178 |
DLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error); |
179 |
dispatch_async(dispatch_get_main_queue(), ^{ |
180 |
NSAlert *alert = [[[NSAlert alloc] init] autorelease]; |
181 |
[alert setMessageText:@"Create Directory Error"]; |
182 |
[alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@", |
183 |
subdirDirectoryPath, [error localizedDescription]]]; |
184 |
[alert addButtonWithTitle:@"OK"]; |
185 |
[alert runModal]; |
186 |
}); |
187 |
} |
188 |
} else if (!directoryIsDirectory) { |
189 |
[fileManager removeItemAtPath:subdirDirectoryPath error:&error]; |
190 |
if (error) { |
191 |
DLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error); |
192 |
dispatch_async(dispatch_get_main_queue(), ^{ |
193 |
NSAlert *alert = [[[NSAlert alloc] init] autorelease]; |
194 |
[alert setMessageText:@"Remove File Error"]; |
195 |
[alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file at '%@': %@", |
196 |
subdirDirectoryPath, [error localizedDescription]]]; |
197 |
[alert addButtonWithTitle:@"OK"]; |
198 |
[alert runModal]; |
199 |
}); |
200 |
} |
201 |
} |
202 |
} else { |
203 |
NSString *fileName = [object.name lastPathComponent]; |
204 |
if([object.name hasSuffix:@"/"]) |
205 |
fileName = [fileName stringByAppendingString:@"/"]; |
206 |
|
207 |
NSString *objectDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName]; |
208 |
objectDirectoryPath = [objectDirectoryPath stringByAppendingPathComponent:[object.name substringWithRange:NSMakeRange(subdirPrefixLength, [object.name length] - subdirPrefixLength - [fileName length])]]; |
209 |
|
210 |
ASIPithosObjectRequest *objectRequest = [self objectDataRequestWithPithos:pithos |
211 |
containerName:containerName |
212 |
objectName:object.name |
213 |
version:nil |
214 |
toDirectory:objectDirectoryPath |
215 |
withNewFileName:nil |
216 |
checkIfExists:NO |
217 |
sharingAccount:sharingAccount]; |
218 |
[(NSMutableDictionary *)objectRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:object.bytes] forKey:@"bytes"]; |
219 |
[objectRequests addObject:objectRequest]; |
220 |
} |
221 |
} |
222 |
|
223 |
return objectRequests; |
224 |
} |
225 |
|
226 |
#pragma mark - |
227 |
#pragma mark Download Block |
228 |
|
229 |
+ (ASIPithosObjectRequest *)objectBlockDataRequestWithPithos:(ASIPithos *)pithos |
230 |
containerName:(NSString *)containerName |
231 |
object:(ASIPithosObject *)object |
232 |
blockIndex:(NSUInteger)blockIndex |
233 |
blockSize:(NSUInteger)blockSize { |
234 |
NSUInteger rangeStart = blockIndex * blockSize; |
235 |
NSUInteger rangeEnd = (rangeStart + blockSize <= object.bytes) ? (rangeStart + blockSize - 1) : (object.bytes - 1); |
236 |
ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos |
237 |
containerName:containerName |
238 |
objectName:object.name |
239 |
version:nil |
240 |
range:[NSString stringWithFormat:@"bytes=%lu-%lu", rangeStart, rangeEnd] |
241 |
ifMatch:object.hash]; |
242 |
return objectRequest; |
243 |
} |
244 |
|
245 |
+ (NSIndexSet *)missingBlocksForFile:(NSString *)filePath |
246 |
blockSize:(NSUInteger)blockSize |
247 |
blockHash:(NSString *)blockHash |
248 |
withHashes:(NSArray *)hashes { |
249 |
NSArray *fileHashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize]; |
250 |
return [hashes indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) { |
251 |
if ((idx >= [fileHashes count]) || ![(NSString *)obj isEqualToString:[fileHashes objectAtIndex:idx]]) |
252 |
return YES; |
253 |
return NO; |
254 |
}]; |
255 |
} |
256 |
|
257 |
#pragma mark - |
258 |
#pragma mark Upload |
259 |
|
260 |
+ (ASIPithosObjectRequest *)writeObjectDataRequestWithPithos:(ASIPithos *)pithos |
261 |
containerName:(NSString *)containerName |
262 |
objectName:(NSString *)objectName |
263 |
contentType:(NSString *)contentType |
264 |
blockSize:(NSUInteger)blockSize |
265 |
blockHash:(NSString *)blockHash |
266 |
forFile:(NSString *)filePath |
267 |
checkIfExists:(BOOL)ifExists |
268 |
hashes:(NSArray **)hashes |
269 |
sharingAccount:(NSString *)sharingAccount { |
270 |
if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName |
271 |
sharingAccount:(NSString *)sharingAccount]) |
272 |
return nil; |
273 |
|
274 |
if (*hashes == nil) |
275 |
*hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize]; |
276 |
if (*hashes == nil) |
277 |
return nil; |
278 |
NSString *fileName = [filePath lastPathComponent]; |
279 |
if ([filePath hasSuffix:@"/"]) |
280 |
fileName = [fileName stringByAppendingString:@"/"]; |
281 |
NSUInteger bytes = [self bytesOfFile:filePath]; |
282 |
ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos |
283 |
containerName:containerName |
284 |
objectName:objectName |
285 |
contentType:contentType |
286 |
contentEncoding:nil |
287 |
contentDisposition:nil |
288 |
manifest:nil |
289 |
sharing:nil |
290 |
isPublic:ASIPithosObjectRequestPublicIgnore |
291 |
metadata:nil |
292 |
blockSize:blockSize |
293 |
blockHash:blockHash |
294 |
hashes:*hashes |
295 |
bytes:bytes]; |
296 |
if (sharingAccount) |
297 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
298 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
299 |
fileName, @"fileName", |
300 |
[NSNumber numberWithUnsignedInteger:bytes], @"bytes", |
301 |
nil]; |
302 |
return objectRequest; |
303 |
} |
304 |
|
305 |
+ (NSIndexSet *)missingBlocksForHashes:(NSArray *)hashes withMissingHashes:(NSArray *)missingHashes { |
306 |
NSMutableIndexSet *missingBlocks = [NSMutableIndexSet indexSet]; |
307 |
for (NSString *missingHash in missingHashes) { |
308 |
if (![missingHash length]) |
309 |
break; |
310 |
NSUInteger missingBlock = [hashes indexOfObject:missingHash]; |
311 |
if (missingBlock != NSNotFound) |
312 |
[missingBlocks addIndex:missingBlock]; |
313 |
} |
314 |
return missingBlocks; |
315 |
} |
316 |
|
317 |
+ (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos |
318 |
containerName:(NSString *)containerName |
319 |
blockSize:(NSUInteger)blockSize |
320 |
forFile:(NSString *)filePath |
321 |
hashes:(NSArray *)hashes |
322 |
missingHashes:(NSArray *)missingHashes |
323 |
sharingAccount:(NSString *)sharingAccount { |
324 |
NSIndexSet *missingBlocks = [self missingBlocksForHashes:hashes withMissingHashes:missingHashes]; |
325 |
|
326 |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
327 |
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath]; |
328 |
|
329 |
// http://cocoawithlove.com/2009/07/temporary-files-and-folders-in-cocoa.html |
330 |
NSString *tempFileTemplate = NSTemporaryDirectory(); |
331 |
if (tempFileTemplate == nil) |
332 |
tempFileTemplate = @"/tmp"; |
333 |
tempFileTemplate = [tempFileTemplate stringByAppendingPathComponent:@"pithos-macos.XXXXXX"]; |
334 |
const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation]; |
335 |
char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1); |
336 |
strcpy(tempFileNameCString, tempFileTemplateCString); |
337 |
int fileDescriptor = mkstemp(tempFileNameCString); |
338 |
NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)]; |
339 |
if (fileDescriptor == -1) { |
340 |
dispatch_async(dispatch_get_main_queue(), ^{ |
341 |
NSAlert *alert = [[[NSAlert alloc] init] autorelease]; |
342 |
[alert setMessageText:@"Create Temporary File Error"]; |
343 |
[alert setInformativeText:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]]; |
344 |
[alert addButtonWithTitle:@"OK"]; |
345 |
[alert runModal]; |
346 |
}); |
347 |
return nil; |
348 |
} |
349 |
free(tempFileNameCString); |
350 |
NSFileHandle *tempFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor closeOnDealloc:YES]; |
351 |
|
352 |
[missingBlocks enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){ |
353 |
[fileHandle seekToFileOffset:(idx*blockSize)]; |
354 |
[tempFileHandle writeData:[fileHandle readDataOfLength:blockSize]]; |
355 |
}]; |
356 |
[tempFileHandle closeFile]; |
357 |
[fileHandle closeFile]; |
358 |
|
359 |
ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos |
360 |
containerName:containerName |
361 |
policy:nil |
362 |
metadata:nil |
363 |
update:YES |
364 |
file:tempFilePath]; |
365 |
if (sharingAccount) |
366 |
[containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
367 |
return containerRequest; |
368 |
} |
369 |
|
370 |
+ (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos |
371 |
containerName:(NSString *)containerName |
372 |
blockSize:(NSUInteger)blockSize |
373 |
forFile:(NSString *)filePath |
374 |
missingBlockIndex:(NSUInteger)missingBlockIndex |
375 |
sharingAccount:(NSString *)sharingAccount { |
376 |
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath]; |
377 |
[fileHandle seekToFileOffset:(missingBlockIndex *blockSize)]; |
378 |
NSData *blockData = [fileHandle readDataOfLength:blockSize]; |
379 |
[fileHandle closeFile]; |
380 |
ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos |
381 |
containerName:containerName |
382 |
policy:nil |
383 |
metadata:nil |
384 |
update:YES |
385 |
data:blockData]; |
386 |
if (sharingAccount) |
387 |
[containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
388 |
return containerRequest; |
389 |
} |
390 |
|
391 |
+ (NSArray *)writeObjectDataRequestsWithPithos:(ASIPithos *)pithos |
392 |
containerName:(NSString *)containerName |
393 |
objectName:(NSString *)objectName |
394 |
blockSize:(NSUInteger)blockSize |
395 |
blockHash:(NSString *)blockHash |
396 |
forDirectory:(NSString *)directoryPath |
397 |
checkIfExists:(BOOL)ifExists |
398 |
objectNames:(NSMutableArray **)objectNames |
399 |
contentTypes:(NSMutableArray **)contentTypes |
400 |
filePaths:(NSMutableArray **)filePaths |
401 |
hashesArrays:(NSMutableArray **)hashesArrays |
402 |
directoryObjectRequests:(NSMutableArray **) directoryObjectRequests |
403 |
sharingAccount:(NSString *)sharingAccount { |
404 |
if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName |
405 |
sharingAccount:sharingAccount]) |
406 |
return nil; |
407 |
|
408 |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
409 |
NSError *error = nil; |
410 |
NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:directoryPath error:&error]; |
411 |
if (error) { |
412 |
dispatch_async(dispatch_get_main_queue(), ^{ |
413 |
NSAlert *alert = [[[NSAlert alloc] init] autorelease]; |
414 |
[alert setMessageText:@"Directory Read Error"]; |
415 |
[alert setInformativeText:[NSString stringWithFormat:@"Cannot read contents of directory '%@': %@", |
416 |
[directoryPath lastPathComponent], [error localizedDescription]]]; |
417 |
[alert addButtonWithTitle:@"OK"]; |
418 |
[alert runModal]; |
419 |
}); |
420 |
return nil; |
421 |
} |
422 |
|
423 |
*directoryObjectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]]; |
424 |
ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos |
425 |
containerName:containerName |
426 |
objectName:objectName |
427 |
eTag:nil |
428 |
contentType:@"application/directory" |
429 |
contentEncoding:nil |
430 |
contentDisposition:nil |
431 |
manifest:nil |
432 |
sharing:nil |
433 |
isPublic:ASIPithosObjectRequestPublicIgnore |
434 |
metadata:nil |
435 |
data:[NSData data]]; |
436 |
if (sharingAccount) |
437 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
438 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
439 |
[directoryPath lastPathComponent], @"fileName", |
440 |
nil]; |
441 |
[*directoryObjectRequests addObject:objectRequest]; |
442 |
|
443 |
NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]]; |
444 |
*objectNames = [NSMutableArray arrayWithCapacity:[subPaths count]]; |
445 |
*contentTypes = [NSMutableArray arrayWithCapacity:[subPaths count]]; |
446 |
*filePaths = [NSMutableArray arrayWithCapacity:[subPaths count]]; |
447 |
*hashesArrays = [NSMutableArray arrayWithCapacity:[subPaths count]]; |
448 |
BOOL isDirectory; |
449 |
NSString *subObjectName; |
450 |
NSArray *hashes; |
451 |
NSUInteger bytes; |
452 |
NSString *contentType; |
453 |
NSString *filePath; |
454 |
NSString *fileName; |
455 |
for (NSString *objectNameSuffix in subPaths) { |
456 |
filePath = [directoryPath stringByAppendingPathComponent:objectNameSuffix]; |
457 |
if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) { |
458 |
if (!isDirectory) { |
459 |
hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize]; |
460 |
if (hashes) { |
461 |
subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping]; |
462 |
fileName = [filePath lastPathComponent]; |
463 |
if ([filePath hasSuffix:@"/"]) |
464 |
fileName = [fileName stringByAppendingString:@"/"]; |
465 |
bytes = [self bytesOfFile:filePath]; |
466 |
error = nil; |
467 |
contentType = [self contentTypeOfFile:filePath error:&error]; |
468 |
if (contentType == nil) |
469 |
contentType = @"application/octet-stream"; |
470 |
#if DEBUG_PITHOS |
471 |
if (error) |
472 |
DLog(@"contentType detection error: %@", error); |
473 |
#endif |
474 |
objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos |
475 |
containerName:containerName |
476 |
objectName:subObjectName |
477 |
contentType:contentType |
478 |
contentEncoding:nil |
479 |
contentDisposition:nil |
480 |
manifest:nil |
481 |
sharing:nil |
482 |
isPublic:ASIPithosObjectRequestPublicIgnore |
483 |
metadata:nil |
484 |
blockSize:blockSize |
485 |
blockHash:blockHash |
486 |
hashes:hashes |
487 |
bytes:bytes]; |
488 |
if (sharingAccount) |
489 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
490 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
491 |
fileName, @"fileName", |
492 |
[NSNumber numberWithUnsignedInteger:bytes], @"bytes", |
493 |
nil]; |
494 |
[objectRequests addObject:objectRequest]; |
495 |
[*objectNames addObject:subObjectName]; |
496 |
[*contentTypes addObject:contentType]; |
497 |
[*filePaths addObject:filePath]; |
498 |
[*hashesArrays addObject:hashes]; |
499 |
} |
500 |
|
501 |
} else { |
502 |
subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping]; |
503 |
fileName = [filePath lastPathComponent]; |
504 |
objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos |
505 |
containerName:containerName |
506 |
objectName:subObjectName |
507 |
eTag:nil |
508 |
contentType:@"application/directory" |
509 |
contentEncoding:nil |
510 |
contentDisposition:nil |
511 |
manifest:nil |
512 |
sharing:nil |
513 |
isPublic:ASIPithosObjectRequestPublicIgnore |
514 |
metadata:nil |
515 |
data:[NSData data]]; |
516 |
if (sharingAccount) |
517 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
518 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
519 |
fileName, @"fileName", |
520 |
nil]; |
521 |
[*directoryObjectRequests addObject:objectRequest]; |
522 |
} |
523 |
} |
524 |
} |
525 |
|
526 |
return objectRequests; |
527 |
} |
528 |
|
529 |
#pragma mark - |
530 |
#pragma mark Delete |
531 |
|
532 |
+ (NSArray *)deleteObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos |
533 |
containerName:(NSString *)containerName |
534 |
objectName:(NSString *)objectName { |
535 |
NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName delimiter:nil |
536 |
sharingAccount:nil]; |
537 |
if (objects == nil) |
538 |
return nil; |
539 |
|
540 |
NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)]; |
541 |
ASIPithosObjectRequest *objectRequest; |
542 |
if (![objectName hasSuffix:@"/"]) { |
543 |
objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:objectName]; |
544 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
545 |
[objectName lastPathComponent], @"fileName", |
546 |
nil]; |
547 |
[objectRequests addObject:objectRequest]; |
548 |
} |
549 |
NSString *fileName; |
550 |
for (ASIPithosObject *object in objects) { |
551 |
fileName = [object.name lastPathComponent]; |
552 |
if ([object.name hasSuffix:@"/"]) |
553 |
fileName = [fileName stringByAppendingString:@"/"]; |
554 |
objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:object.name]; |
555 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
556 |
fileName, @"fileName", |
557 |
nil]; |
558 |
[objectRequests addObject:objectRequest]; |
559 |
} |
560 |
|
561 |
if ([objectRequests count] == 0) |
562 |
return nil; |
563 |
return objectRequests; |
564 |
} |
565 |
|
566 |
#pragma mark - |
567 |
#pragma mark Copy |
568 |
|
569 |
+ (ASIPithosObjectRequest *)copyObjectRequestWithPithos:(ASIPithos *)pithos |
570 |
containerName:(NSString *)containerName |
571 |
objectName:(NSString *)objectName |
572 |
destinationContainerName:(NSString *)destinationContainerName |
573 |
destinationObjectName:(NSString *)destinationObjectName |
574 |
checkIfExists:(BOOL)ifExists |
575 |
sharingAccount:(NSString *)sharingAccount { |
576 |
if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName |
577 |
sharingAccount:nil]) |
578 |
return nil; |
579 |
|
580 |
ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos |
581 |
containerName:containerName |
582 |
objectName:objectName |
583 |
contentType:nil |
584 |
contentEncoding:nil |
585 |
contentDisposition:nil |
586 |
manifest:nil |
587 |
sharing:nil |
588 |
isPublic:ASIPithosObjectRequestPublicIgnore |
589 |
metadata:nil |
590 |
destinationContainerName:destinationContainerName |
591 |
destinationObjectName:destinationObjectName |
592 |
destinationAccount:nil |
593 |
sourceVersion:nil]; |
594 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
595 |
containerName, @"sourceContainerName", |
596 |
objectName, @"sourceObjectName", |
597 |
destinationContainerName, @"destinationContainerName", |
598 |
destinationObjectName, @"destinationObjectName", |
599 |
nil]; |
600 |
if (sharingAccount) |
601 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
602 |
return objectRequest; |
603 |
} |
604 |
|
605 |
+ (NSArray *)copyObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos |
606 |
containerName:(NSString *)containerName |
607 |
objectName:(NSString *)objectName |
608 |
destinationContainerName:(NSString *)destinationContainerName |
609 |
destinationObjectName:(NSString *)destinationObjectName |
610 |
checkIfExists:(BOOL)ifExists |
611 |
sharingAccount:(NSString *)sharingAccount { |
612 |
if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName |
613 |
sharingAccount:nil]) |
614 |
return nil; |
615 |
|
616 |
NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName |
617 |
delimiter:nil sharingAccount:sharingAccount]; |
618 |
if (objects == nil) |
619 |
return nil; |
620 |
|
621 |
NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)]; |
622 |
ASIPithosObjectRequest *objectRequest; |
623 |
if ([objectName isEqualToString:destinationObjectName]) { |
624 |
if (![objectName hasSuffix:@"/"]) { |
625 |
objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos |
626 |
containerName:containerName |
627 |
objectName:objectName |
628 |
contentType:nil |
629 |
contentEncoding:nil |
630 |
contentDisposition:nil |
631 |
manifest:nil |
632 |
sharing:nil |
633 |
isPublic:ASIPithosObjectRequestPublicIgnore |
634 |
metadata:nil |
635 |
destinationContainerName:destinationContainerName |
636 |
destinationObjectName:objectName |
637 |
destinationAccount:nil |
638 |
sourceVersion:nil]; |
639 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
640 |
containerName, @"sourceContainerName", |
641 |
objectName, @"sourceObjectName", |
642 |
destinationContainerName, @"destinationContainerName", |
643 |
objectName, @"destinationObjectName", |
644 |
nil]; |
645 |
if (sharingAccount) |
646 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
647 |
[objectRequests addObject:objectRequest]; |
648 |
} |
649 |
for (ASIPithosObject *object in objects) { |
650 |
objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos |
651 |
containerName:containerName |
652 |
objectName:object.name |
653 |
contentType:nil |
654 |
contentEncoding:nil |
655 |
contentDisposition:nil |
656 |
manifest:nil |
657 |
sharing:nil |
658 |
isPublic:ASIPithosObjectRequestPublicIgnore |
659 |
metadata:nil |
660 |
destinationContainerName:destinationContainerName |
661 |
destinationObjectName:object.name |
662 |
destinationAccount:nil |
663 |
sourceVersion:nil]; |
664 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
665 |
containerName, @"sourceContainerName", |
666 |
object.name, @"sourceObjectName", |
667 |
destinationContainerName, @"destinationContainerName", |
668 |
object.name, @"destinationObjectName", |
669 |
nil]; |
670 |
if (sharingAccount) |
671 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
672 |
[objectRequests addObject:objectRequest]; |
673 |
} |
674 |
} else { |
675 |
if (![objectName hasSuffix:@"/"]) { |
676 |
objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos |
677 |
containerName:containerName |
678 |
objectName:objectName |
679 |
contentType:nil |
680 |
contentEncoding:nil |
681 |
contentDisposition:nil |
682 |
manifest:nil |
683 |
sharing:nil |
684 |
isPublic:ASIPithosObjectRequestPublicIgnore |
685 |
metadata:nil |
686 |
destinationContainerName:destinationContainerName |
687 |
destinationObjectName:destinationObjectName |
688 |
destinationAccount:nil |
689 |
sourceVersion:nil]; |
690 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
691 |
containerName, @"sourceContainerName", |
692 |
objectName, @"sourceObjectName", |
693 |
destinationContainerName, @"destinationContainerName", |
694 |
destinationObjectName, @"destinationObjectName", |
695 |
nil]; |
696 |
if (sharingAccount) |
697 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
698 |
[objectRequests addObject:objectRequest]; |
699 |
} |
700 |
NSRange prefixRange = NSMakeRange(0, [objectName length]); |
701 |
NSString *newObjectName; |
702 |
for (ASIPithosObject *object in objects) { |
703 |
newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName |
704 |
withString:destinationObjectName |
705 |
options:NSAnchoredSearch |
706 |
range:prefixRange]; |
707 |
objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos |
708 |
containerName:containerName |
709 |
objectName:object.name |
710 |
contentType:nil |
711 |
contentEncoding:nil |
712 |
contentDisposition:nil |
713 |
manifest:nil |
714 |
sharing:nil |
715 |
isPublic:ASIPithosObjectRequestPublicIgnore |
716 |
metadata:nil |
717 |
destinationContainerName:destinationContainerName |
718 |
destinationObjectName:newObjectName |
719 |
destinationAccount:nil |
720 |
sourceVersion:nil]; |
721 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
722 |
containerName, @"sourceContainerName", |
723 |
object.name, @"sourceObjectName", |
724 |
destinationContainerName, @"destinationContainerName", |
725 |
newObjectName, @"destinationObjectName", |
726 |
nil]; |
727 |
if (sharingAccount) |
728 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
729 |
[objectRequests addObject:objectRequest]; |
730 |
} |
731 |
} |
732 |
|
733 |
if ([objectRequests count] == 0) |
734 |
return nil; |
735 |
return objectRequests; |
736 |
} |
737 |
|
738 |
#pragma mark - |
739 |
#pragma mark Move |
740 |
|
741 |
+ (ASIPithosObjectRequest *)moveObjectRequestWithPithos:(ASIPithos *)pithos |
742 |
containerName:(NSString *)containerName |
743 |
objectName:(NSString *)objectName |
744 |
destinationContainerName:(NSString *)destinationContainerName |
745 |
destinationObjectName:(NSString *)destinationObjectName |
746 |
checkIfExists:(BOOL)ifExists { |
747 |
if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName |
748 |
sharingAccount:nil]) |
749 |
return nil; |
750 |
|
751 |
ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos |
752 |
containerName:containerName |
753 |
objectName:objectName |
754 |
contentType:nil |
755 |
contentEncoding:nil |
756 |
contentDisposition:nil |
757 |
manifest:nil |
758 |
sharing:nil |
759 |
isPublic:ASIPithosObjectRequestPublicIgnore |
760 |
metadata:nil |
761 |
destinationContainerName:destinationContainerName |
762 |
destinationObjectName:destinationObjectName |
763 |
destinationAccount:nil]; |
764 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
765 |
containerName, @"sourceContainerName", |
766 |
objectName, @"sourceObjectName", |
767 |
destinationContainerName, @"destinationContainerName", |
768 |
destinationObjectName, @"destinationObjectName", |
769 |
nil]; |
770 |
return objectRequest; |
771 |
} |
772 |
|
773 |
+ (NSArray *)moveObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos |
774 |
containerName:(NSString *)containerName |
775 |
objectName:(NSString *)objectName |
776 |
destinationContainerName:(NSString *)destinationContainerName |
777 |
destinationObjectName:(NSString *)destinationObjectName |
778 |
checkIfExists:(BOOL)ifExists { |
779 |
if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName |
780 |
sharingAccount:nil]) |
781 |
return nil; |
782 |
|
783 |
NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName |
784 |
delimiter:nil sharingAccount:nil]; |
785 |
if (objects == nil) |
786 |
return nil; |
787 |
|
788 |
ASIPithosObjectRequest *objectRequest; |
789 |
NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)]; |
790 |
if ([objectName isEqualToString:destinationObjectName]) { |
791 |
if (![objectName hasSuffix:@"/"]) { |
792 |
objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos |
793 |
containerName:containerName |
794 |
objectName:objectName |
795 |
contentType:nil |
796 |
contentEncoding:nil |
797 |
contentDisposition:nil |
798 |
manifest:nil |
799 |
sharing:nil |
800 |
isPublic:ASIPithosObjectRequestPublicIgnore |
801 |
metadata:nil |
802 |
destinationContainerName:destinationContainerName |
803 |
destinationObjectName:objectName |
804 |
destinationAccount:nil]; |
805 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
806 |
containerName, @"sourceContainerName", |
807 |
objectName, @"sourceObjectName", |
808 |
destinationContainerName, @"destinationContainerName", |
809 |
objectName, @"destinationObjectName", |
810 |
nil]; |
811 |
[objectRequests addObject:objectRequest]; |
812 |
} |
813 |
for (ASIPithosObject *object in objects) { |
814 |
objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos |
815 |
containerName:containerName |
816 |
objectName:object.name |
817 |
contentType:nil |
818 |
contentEncoding:nil |
819 |
contentDisposition:nil |
820 |
manifest:nil |
821 |
sharing:nil |
822 |
isPublic:ASIPithosObjectRequestPublicIgnore |
823 |
metadata:nil |
824 |
destinationContainerName:destinationContainerName |
825 |
destinationObjectName:object.name |
826 |
destinationAccount:nil]; |
827 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
828 |
containerName, @"sourceContainerName", |
829 |
object.name, @"sourceObjectName", |
830 |
destinationContainerName, @"destinationContainerName", |
831 |
object.name, @"destinationObjectName", |
832 |
nil]; |
833 |
[objectRequests addObject:objectRequest]; |
834 |
} |
835 |
} else { |
836 |
if (![objectName hasSuffix:@"/"]) { |
837 |
objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos |
838 |
containerName:containerName |
839 |
objectName:objectName |
840 |
contentType:nil |
841 |
contentEncoding:nil |
842 |
contentDisposition:nil |
843 |
manifest:nil |
844 |
sharing:nil |
845 |
isPublic:ASIPithosObjectRequestPublicIgnore |
846 |
metadata:nil |
847 |
destinationContainerName:destinationContainerName |
848 |
destinationObjectName:destinationObjectName |
849 |
destinationAccount:nil]; |
850 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
851 |
containerName, @"sourceContainerName", |
852 |
objectName, @"sourceObjectName", |
853 |
destinationContainerName, @"destinationContainerName", |
854 |
destinationObjectName, @"destinationObjectName", |
855 |
nil]; |
856 |
[objectRequests addObject:objectRequest]; |
857 |
} |
858 |
NSRange prefixRange = NSMakeRange(0, [objectName length]); |
859 |
NSString *newObjectName; |
860 |
for (ASIPithosObject *object in objects) { |
861 |
newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName |
862 |
withString:destinationObjectName |
863 |
options:NSAnchoredSearch |
864 |
range:prefixRange]; |
865 |
objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos |
866 |
containerName:containerName |
867 |
objectName:object.name |
868 |
contentType:nil |
869 |
contentEncoding:nil |
870 |
contentDisposition:nil |
871 |
manifest:nil |
872 |
sharing:nil |
873 |
isPublic:ASIPithosObjectRequestPublicIgnore |
874 |
metadata:nil |
875 |
destinationContainerName:destinationContainerName |
876 |
destinationObjectName:newObjectName |
877 |
destinationAccount:nil]; |
878 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
879 |
containerName, @"sourceContainerName", |
880 |
object.name, @"sourceObjectName", |
881 |
destinationContainerName, @"destinationContainerName", |
882 |
newObjectName, @"destinationObjectName", |
883 |
nil]; |
884 |
[objectRequests addObject:objectRequest]; |
885 |
} |
886 |
} |
887 |
|
888 |
if ([objectRequests count] == 0) |
889 |
return nil; |
890 |
return objectRequests; |
891 |
} |
892 |
|
893 |
#pragma mark - |
894 |
#pragma mark Helper Methods |
895 |
|
896 |
// Size of the file in bytes |
897 |
+ (NSUInteger)bytesOfFile:(NSString *)filePath { |
898 |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
899 |
NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil]; |
900 |
return [[attributes objectForKey:NSFileSize] unsignedIntegerValue]; |
901 |
} |
902 |
|
903 |
// Content type of the file or nil if it cannot be determined |
904 |
+ (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error { |
905 |
// Based on http://www.ddeville.me/2011/12/mime-to-UTI-cocoa/ |
906 |
// and Apple example ImageBrowserViewAppearance/ImageBrowserController.m |
907 |
LSItemInfoRecord info; |
908 |
CFStringRef uti = NULL; |
909 |
CFURLRef url = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)filePath, kCFURLPOSIXPathStyle, FALSE); |
910 |
if (LSCopyItemInfoForURL(url, kLSRequestExtension | kLSRequestTypeCreator, &info) == noErr) { |
911 |
// Obtain the UTI using the file information. |
912 |
// If there is a file extension, get the UTI. |
913 |
if (info.extension != NULL) { |
914 |
uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, info.extension, kUTTypeData); |
915 |
CFRelease(info.extension); |
916 |
} |
917 |
// No UTI yet |
918 |
if (uti == NULL) { |
919 |
// If there is an OSType, get the UTI. |
920 |
CFStringRef typeString = UTCreateStringForOSType(info.filetype); |
921 |
if ( typeString != NULL) { |
922 |
uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, typeString, kUTTypeData); |
923 |
CFRelease(typeString); |
924 |
} |
925 |
} |
926 |
if (uti != NULL) { |
927 |
CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType); |
928 |
CFRelease(uti); |
929 |
return (NSString *)MIMEType; |
930 |
} |
931 |
} |
932 |
return nil; |
933 |
} |
934 |
|
935 |
// Creates a directory if it doesn't exist and returns if successful |
936 |
+ (BOOL)safeCreateDirectory:(NSString *)directoryPath error:(NSError **)error { |
937 |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
938 |
BOOL isDirectory; |
939 |
BOOL fileExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory]; |
940 |
if (fileExists) |
941 |
return isDirectory; |
942 |
if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:error] || *error) |
943 |
return NO; |
944 |
return YES; |
945 |
} |
946 |
|
947 |
// Removes contents of a directory |
948 |
+ (void)removeContentsAtPath:(NSString *)dirPath { |
949 |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
950 |
NSError *error = nil; |
951 |
BOOL isDirectory; |
952 |
if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory]) |
953 |
return; |
954 |
if (isDirectory) { |
955 |
for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:dirPath error:&error]) { |
956 |
if (error) { |
957 |
[self fileActionFailedAlertWithTitle:@"Directory Contents Error" |
958 |
message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", dirPath] |
959 |
error:error]; |
960 |
break; |
961 |
} |
962 |
NSString *subFilePath = [dirPath stringByAppendingPathComponent:subPath]; |
963 |
if (![fileManager removeItemAtPath:subFilePath error:&error] || error) { |
964 |
[self fileActionFailedAlertWithTitle:@"Remove File Error" |
965 |
message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] |
966 |
error:error]; |
967 |
} |
968 |
error = nil; |
969 |
} |
970 |
} else if (![fileManager removeItemAtPath:dirPath error:&error] || error) { |
971 |
[self fileActionFailedAlertWithTitle:@"Remove File Error" |
972 |
message:[NSString stringWithFormat:@"Cannot remove file at '%@'", dirPath] |
973 |
error:error]; |
974 |
} |
975 |
} |
976 |
|
977 |
// Returns if an object is a directory based on its content type |
978 |
+ (BOOL)isContentTypeDirectory:(NSString *)contentType { |
979 |
return ([contentType isEqualToString:@"application/directory"] || |
980 |
[contentType hasPrefix:@"application/directory;"] || |
981 |
[contentType isEqualToString:@"application/folder"] || |
982 |
[contentType hasPrefix:@"application/folder;"]); |
983 |
} |
984 |
|
985 |
// Returns if an object exists at the given container/object path and if this object is an application/directory |
986 |
// If an error occured an alert is shown and it is returned so the caller won't proceed |
987 |
+ (BOOL)objectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName |
988 |
error:(NSError **)error isDirectory:(BOOL *)isDirectory sharingAccount:(NSString *)sharingAccount { |
989 |
ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos |
990 |
containerName:containerName |
991 |
objectName:objectName]; |
992 |
if (sharingAccount) |
993 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
994 |
ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; |
995 |
[networkQueue go]; |
996 |
[networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:objectRequest]] waitUntilFinished:YES]; |
997 |
*error = [objectRequest error]; |
998 |
if (*error) { |
999 |
[self httpRequestErrorAlertWithRequest:objectRequest]; |
1000 |
return NO; |
1001 |
} else if (objectRequest.responseStatusCode == 200) { |
1002 |
*isDirectory = [self isContentTypeDirectory:[objectRequest contentType]]; |
1003 |
return YES; |
1004 |
} |
1005 |
return NO; |
1006 |
} |
1007 |
|
1008 |
// Returns if the caller should proceed, after an interactive check if an object exists |
1009 |
// at the given container/object path is performed |
1010 |
+ (BOOL)proceedIfObjectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName |
1011 |
sharingAccount:(NSString *)sharingAccount { |
1012 |
NSError *error = nil; |
1013 |
BOOL isDirectory; |
1014 |
BOOL objectExists = [self objectExistsAtPithos:pithos containerName:containerName objectName:objectName |
1015 |
error:&error isDirectory:&isDirectory sharingAccount:sharingAccount]; |
1016 |
if (error) { |
1017 |
return NO; |
1018 |
} else if (objectExists) { |
1019 |
__block NSInteger choice; |
1020 |
dispatch_sync(dispatch_get_main_queue(), ^{ |
1021 |
NSAlert *alert = [[[NSAlert alloc] init] autorelease]; |
1022 |
if (isDirectory) { |
1023 |
[alert setMessageText:@"Directory Exists"]; |
1024 |
if (sharingAccount) |
1025 |
[alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]]; |
1026 |
else |
1027 |
[alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]]; |
1028 |
} else { |
1029 |
[alert setMessageText:@"Object Exists"]; |
1030 |
if (sharingAccount) |
1031 |
[alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]]; |
1032 |
else |
1033 |
[alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]]; |
1034 |
} |
1035 |
[alert addButtonWithTitle:@"OK"]; |
1036 |
[alert addButtonWithTitle:@"Cancel"]; |
1037 |
choice = [alert runModal]; |
1038 |
}); |
1039 |
if (choice == NSAlertSecondButtonReturn) |
1040 |
return NO; |
1041 |
} |
1042 |
return YES; |
1043 |
} |
1044 |
|
1045 |
// List of objects at the given container/object path, with prefix and or delimiter |
1046 |
+ (NSArray *)objectsWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix |
1047 |
delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount { |
1048 |
NSMutableArray *objects = [NSMutableArray array]; |
1049 |
NSString *marker = nil; |
1050 |
do { |
1051 |
ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos |
1052 |
containerName:containerName |
1053 |
limit:0 |
1054 |
marker:marker |
1055 |
prefix:objectNamePrefix |
1056 |
delimiter:delimiter |
1057 |
path:nil |
1058 |
meta:nil |
1059 |
shared:NO |
1060 |
until:nil]; |
1061 |
if (sharingAccount) |
1062 |
[containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
1063 |
ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; |
1064 |
[networkQueue go]; |
1065 |
[networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:containerRequest]] waitUntilFinished:YES]; |
1066 |
if ([containerRequest error]) { |
1067 |
[self httpRequestErrorAlertWithRequest:containerRequest]; |
1068 |
return nil; |
1069 |
} |
1070 |
NSArray *someObjects = [containerRequest objects]; |
1071 |
[objects addObjectsFromArray:someObjects]; |
1072 |
if ([someObjects count] < 10000) |
1073 |
marker = nil; |
1074 |
else |
1075 |
marker = [[someObjects lastObject] name]; |
1076 |
} while (marker); |
1077 |
return objects; |
1078 |
} |
1079 |
|
1080 |
// List of objects at the given container/object path, that may be a subdir or an application/directory, |
1081 |
// with prefix and or delimiter |
1082 |
+ (NSArray *)objectsForSubdirWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName |
1083 |
delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount { |
1084 |
NSString *subdirNamePrefix = [NSString stringWithString:objectName]; |
1085 |
if (![subdirNamePrefix hasSuffix:@"/"]) |
1086 |
subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"]; |
1087 |
return [self objectsWithPithos:pithos containerName:containerName objectNamePrefix:subdirNamePrefix |
1088 |
delimiter:delimiter sharingAccount:sharingAccount]; |
1089 |
} |
1090 |
|
1091 |
// A safe object name at the given container/object path |
1092 |
// The original name has " %d" appended to it before any ".*" suffix, for the first integer that produces a name that is free to use |
1093 |
// If the original name hasn't got a "." but has a "/" suffix, then it is replaced with " %d/" instead |
1094 |
// Subdirs are taken into consideration |
1095 |
+ (NSString *)safeObjectNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName { |
1096 |
NSString *objectNamePrefix; |
1097 |
NSString *objectNameExtraSuffix; |
1098 |
NSRange lastDotRange = [objectName rangeOfString:@"." options:NSBackwardsSearch]; |
1099 |
if (lastDotRange.length == 1) { |
1100 |
objectNamePrefix = [objectName substringToIndex:lastDotRange.location]; |
1101 |
objectNameExtraSuffix = [objectName substringFromIndex:lastDotRange.location]; |
1102 |
} else if ([objectName hasSuffix:@"/"]) { |
1103 |
objectNamePrefix = [objectName substringToIndex:([objectName length] - 1)]; |
1104 |
objectNameExtraSuffix = [NSString stringWithString:@"/"]; |
1105 |
} else { |
1106 |
objectNamePrefix = [NSString stringWithString:objectName]; |
1107 |
objectNameExtraSuffix = [NSString string]; |
1108 |
} |
1109 |
NSArray *objects = [self objectsWithPithos:pithos containerName:containerName |
1110 |
objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent] |
1111 |
delimiter:@"/" sharingAccount:nil]; |
1112 |
if (objects == nil) |
1113 |
return nil; |
1114 |
if ([objects count] == 0) |
1115 |
return objectName; |
1116 |
NSMutableArray *objectNames = [NSMutableArray arrayWithArray: |
1117 |
[[objects objectsAtIndexes: |
1118 |
[objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){ |
1119 |
if (pithosObject.subdir) |
1120 |
return NO; |
1121 |
return YES; |
1122 |
}]] valueForKey:@"name"]]; |
1123 |
for (NSString *name in [[objects objectsAtIndexes: |
1124 |
[objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){ |
1125 |
if (pithosObject.subdir) |
1126 |
return YES; |
1127 |
return NO; |
1128 |
}]] valueForKey:@"name"]) { |
1129 |
[objectNames addObject:[name substringToIndex:([name length] - 1)]]; |
1130 |
} |
1131 |
if (![objectNames containsObject:objectName]) |
1132 |
return objectName; |
1133 |
NSUInteger objectNameSuffix = 2; |
1134 |
NSString *safeObjectName; |
1135 |
do { |
1136 |
safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix]; |
1137 |
objectNameSuffix++; |
1138 |
} while ([objectNames containsObject:safeObjectName]); |
1139 |
return safeObjectName; |
1140 |
} |
1141 |
|
1142 |
// A safe object name at the given container/object path that may be a subdir or application/directory |
1143 |
// The original name has " %d" appended to it, for the first integer that produces a name that is free to use |
1144 |
// If the original name has a "/" suffix, then it is replaced with " %d/" instead |
1145 |
// Subdirs are taken into consideration |
1146 |
+ (NSString *)safeSubdirNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName subdirName:(NSString *)subdirName { |
1147 |
NSString *subdirNamePrefix; |
1148 |
NSString *subdirNameExtraSuffix; |
1149 |
if ([subdirName hasSuffix:@"/"]) { |
1150 |
subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)]; |
1151 |
subdirNameExtraSuffix = [NSString stringWithString:@"/"]; |
1152 |
} else { |
1153 |
subdirNamePrefix = [NSString stringWithString:subdirName]; |
1154 |
subdirNameExtraSuffix = [NSString string]; |
1155 |
} |
1156 |
NSArray *objects = [self objectsWithPithos:pithos containerName:containerName |
1157 |
objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent] |
1158 |
delimiter:@"/" sharingAccount:nil]; |
1159 |
if (objects == nil) |
1160 |
return nil; |
1161 |
if ([objects count] == 0) |
1162 |
return subdirName; |
1163 |
NSMutableArray *objectNames = [NSMutableArray arrayWithArray: |
1164 |
[[objects objectsAtIndexes: |
1165 |
[objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){ |
1166 |
if (pithosObject.subdir) |
1167 |
return NO; |
1168 |
return YES; |
1169 |
}]] valueForKey:@"name"]]; |
1170 |
for (NSString *name in [[objects objectsAtIndexes: |
1171 |
[objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){ |
1172 |
if (pithosObject.subdir) |
1173 |
return YES; |
1174 |
return NO; |
1175 |
}]] valueForKey:@"name"]) { |
1176 |
[objectNames addObject:[name substringToIndex:([name length] - 1)]]; |
1177 |
} |
1178 |
if (![objectNames containsObject:subdirNamePrefix]) |
1179 |
return subdirName; |
1180 |
NSUInteger subdirNameSuffix = 2; |
1181 |
NSString *safeSubdirName; |
1182 |
do { |
1183 |
safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix]; |
1184 |
subdirNameSuffix++; |
1185 |
} while ([objectNames containsObject:safeSubdirName]); |
1186 |
return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix]; |
1187 |
} |
1188 |
|
1189 |
#pragma mark - |
1190 |
#pragma mark Alerts |
1191 |
|
1192 |
+ (void)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request { |
1193 |
if (request.responseStatusCode == 401) { |
1194 |
[self httpAuthenticationError]; |
1195 |
return; |
1196 |
} |
1197 |
NSString *message = [NSString stringWithFormat: |
1198 |
@"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", |
1199 |
[[request error] localizedDescription], |
1200 |
request.requestMethod, |
1201 |
request.url, |
1202 |
[request requestHeaders], |
1203 |
[request responseHeaders], |
1204 |
[request responseString]]; |
1205 |
DLog(@"%@", message); |
1206 |
dispatch_async(dispatch_get_main_queue(), ^{ |
1207 |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
1208 |
NSAlert *alert = [[[NSAlert alloc] init] autorelease]; |
1209 |
[alert setMessageText:@"HTTP Request Error"]; |
1210 |
[alert setInformativeText:message]; |
1211 |
[alert addButtonWithTitle:@"OK"]; |
1212 |
[alert runModal]; |
1213 |
[pool drain]; |
1214 |
}); |
1215 |
} |
1216 |
|
1217 |
+ (void)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request { |
1218 |
if (request.responseStatusCode == 401) { |
1219 |
[self httpAuthenticationError]; |
1220 |
return; |
1221 |
} |
1222 |
NSString *message = [NSString stringWithFormat: |
1223 |
@"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", |
1224 |
request.responseStatusCode, |
1225 |
request.responseStatusMessage, |
1226 |
request.requestMethod, |
1227 |
request.url, |
1228 |
[request requestHeaders], |
1229 |
[request responseHeaders], |
1230 |
[request responseString]]; |
1231 |
DLog(@"%@", message); |
1232 |
dispatch_async(dispatch_get_main_queue(), ^{ |
1233 |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
1234 |
NSAlert *alert = [[[NSAlert alloc] init] autorelease]; |
1235 |
[alert setMessageText:@"Unexpected Response Status"]; |
1236 |
[alert setInformativeText:message]; |
1237 |
[alert addButtonWithTitle:@"OK"]; |
1238 |
[alert runModal]; |
1239 |
[pool drain]; |
1240 |
}); |
1241 |
} |
1242 |
|
1243 |
+ (void)httpAuthenticationError { |
1244 |
dispatch_async(dispatch_get_main_queue(), ^{ |
1245 |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
1246 |
NSAlert *alert = [[[NSAlert alloc] init] autorelease]; |
1247 |
[alert setMessageText:@"Authentication Error"]; |
1248 |
[alert setInformativeText:@"Authentication error, please check your token or login again"]; |
1249 |
[alert addButtonWithTitle:@"OK"]; |
1250 |
[alert runModal]; |
1251 |
[pool drain]; |
1252 |
}); |
1253 |
} |
1254 |
|
1255 |
+ (void)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error { |
1256 |
dispatch_async(dispatch_get_main_queue(), ^{ |
1257 |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
1258 |
NSAlert *alert = [[[NSAlert alloc] init] autorelease]; |
1259 |
[alert setMessageText:title]; |
1260 |
if (error) |
1261 |
[alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, [error localizedDescription]]]; |
1262 |
else |
1263 |
[alert setInformativeText:message]; |
1264 |
[alert addButtonWithTitle:@"OK"]; |
1265 |
[alert runModal]; |
1266 |
[pool drain]; |
1267 |
}); |
1268 |
} |
1269 |
|
1270 |
#pragma mark - |
1271 |
#pragma mark Request Helper Methods |
1272 |
|
1273 |
+ (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority { |
1274 |
[request setTimeOutSeconds:60]; |
1275 |
request.numberOfTimesToRetryOnTimeout = 10; |
1276 |
[request setQueuePriority:priority]; |
1277 |
return request; |
1278 |
} |
1279 |
|
1280 |
+ (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request { |
1281 |
return [self prepareRequest:request priority:NSOperationQueuePriorityNormal]; |
1282 |
} |
1283 |
|
1284 |
+ (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request { |
1285 |
NSMutableDictionary *userInfo = (NSMutableDictionary *)[[request.userInfo retain] autorelease]; |
1286 |
request.userInfo = nil; |
1287 |
ASIPithosRequest *newRequest = [request copy]; |
1288 |
newRequest.userInfo = userInfo; |
1289 |
return newRequest; |
1290 |
} |
1291 |
|
1292 |
@end |