5 // Copyright 2011-2012 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
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.
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.
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.
38 #import "PithosUtilities.h"
39 #import "ASINetworkQueue.h"
41 #import "ASIPithosContainerRequest.h"
42 #import "ASIPithosObjectRequest.h"
43 #import "ASIPithosObject.h"
44 #import "HashMapHash.h"
46 @implementation PithosUtilities
51 + (ASIPithosObjectRequest *)objectDataRequestWithPithos:(ASIPithos *)pithos
52 containerName:(NSString *)containerName
53 objectName:(NSString *)objectName
54 toDirectory:(NSString *)directoryPath
55 checkIfExists:(BOOL)ifExists
56 sharingAccount:(NSString *)sharingAccount {
57 NSString *fileName = [objectName lastPathComponent];
58 if([objectName hasSuffix:@"/"])
59 fileName = [fileName stringByAppendingString:@"/"];
60 fileName = [fileName stringByReplacingOccurrencesOfString:@"/" withString:@":"];
62 NSFileManager *fileManager = [NSFileManager defaultManager];
64 NSString *destinationPath = [directoryPath stringByAppendingPathComponent:fileName];
65 if (ifExists && [fileManager fileExistsAtPath:destinationPath]) {
66 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
67 [alert setMessageText:@"File Exists"];
68 [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", fileName]];
69 [alert addButtonWithTitle:@"OK"];
70 [alert addButtonWithTitle:@"Cancel"];
71 NSInteger choice = [alert runModal];
72 if (choice == NSAlertSecondButtonReturn)
76 BOOL directoryIsDirectory;
77 BOOL directoryExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&directoryIsDirectory];
79 if (!directoryExists) {
80 [fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
81 } else if (!directoryIsDirectory) {
82 [fileManager removeItemAtPath:directoryPath error:&error];
85 NSLog(@"Cannot remove existing file '%@': %@", fileName, error);
86 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
87 [alert setMessageText:@"Removal Error"];
88 [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file '%@': %@", fileName, error]];
89 [alert addButtonWithTitle:@"OK"];
94 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos
95 containerName:containerName
96 objectName:objectName];
98 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
99 objectRequest.downloadDestinationPath = destinationPath;
100 objectRequest.allowResumeForFileDownloads = YES;
101 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
102 fileName, @"fileName",
103 destinationPath, @"filePath",
105 return objectRequest;
108 + (NSArray *)objectDataRequestsForSubdirWithPithos:(ASIPithos *)pithos
109 containerName:(NSString *)containerName
110 objectName:(NSString *)objectName
111 toDirectory:(NSString *)directoryPath
112 checkIfExists:(BOOL)ifExists
113 sharingAccount:(NSString *)sharingAccount {
114 NSString *subdirName = [objectName lastPathComponent];
115 NSString *destinationPath = [directoryPath stringByAppendingPathComponent:subdirName];
116 if (ifExists && [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) {
117 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
118 [alert setMessageText:@"File exists"];
119 [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", subdirName]];
120 [alert addButtonWithTitle:@"OK"];
121 [alert addButtonWithTitle:@"Cancel"];
122 NSInteger choice = [alert runModal];
123 if (choice == NSAlertSecondButtonReturn)
127 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName
128 delimiter:nil sharingAccount:sharingAccount];
132 NSFileManager *fileManager = [NSFileManager defaultManager];
133 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[objects count]];
134 NSUInteger subdirPrefixLength = [objectName length];
136 NSError *error = nil;
137 [fileManager createDirectoryAtPath:[directoryPath stringByAppendingPathComponent:subdirName] withIntermediateDirectories:YES attributes:nil error:&error];
139 NSLog(@"Cannot create directory at '%@': %@", directoryPath, error);
140 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
141 [alert setMessageText:@"Create Directory Error"];
142 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@",
143 directoryPath, error]];
144 [alert addButtonWithTitle:@"OK"];
148 for (ASIPithosObject *object in objects) {
149 if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
150 NSString *subdirDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
151 subdirDirectoryPath = [subdirDirectoryPath stringByAppendingPathComponent:[object.name substringFromIndex:subdirPrefixLength]];
153 BOOL directoryIsDirectory;
154 BOOL directoryExists = [fileManager fileExistsAtPath:subdirDirectoryPath isDirectory:&directoryIsDirectory];
155 NSError *error = nil;
156 if (!directoryExists) {
157 [fileManager createDirectoryAtPath:subdirDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error];
159 NSLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error);
160 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
161 [alert setMessageText:@"Create Directory Error"];
162 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@",
163 subdirDirectoryPath, error]];
164 [alert addButtonWithTitle:@"OK"];
167 } else if (!directoryIsDirectory) {
168 [fileManager removeItemAtPath:subdirDirectoryPath error:&error];
170 NSLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error);
171 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
172 [alert setMessageText:@"Remove File Error"];
173 [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file at '%@': %@",
174 subdirDirectoryPath, error]];
175 [alert addButtonWithTitle:@"OK"];
180 NSString *fileName = [object.name lastPathComponent];
181 if([object.name hasSuffix:@"/"])
182 fileName = [fileName stringByAppendingString:@"/"];
184 NSString *objectDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
185 objectDirectoryPath = [objectDirectoryPath stringByAppendingPathComponent:[object.name substringWithRange:NSMakeRange(subdirPrefixLength, [object.name length] - subdirPrefixLength - [fileName length])]];
187 ASIPithosObjectRequest *objectRequest = [self objectDataRequestWithPithos:pithos
188 containerName:containerName
189 objectName:object.name
190 toDirectory:objectDirectoryPath
192 sharingAccount:sharingAccount];
193 [(NSMutableDictionary *)objectRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:object.bytes] forKey:@"bytes"];
194 [objectRequests addObject:objectRequest];
198 return objectRequests;
202 #pragma mark Download Block
204 + (ASIPithosObjectRequest *)objectBlockDataRequestWithPithos:(ASIPithos *)pithos
205 containerName:(NSString *)containerName
206 object:(ASIPithosObject *)object
207 blockIndex:(NSUInteger)blockIndex
208 blockSize:(NSUInteger)blockSize {
209 NSUInteger rangeStart = blockIndex * blockSize;
210 NSUInteger rangeEnd = (rangeStart + blockSize <= object.bytes) ? (rangeStart + blockSize - 1) : (object.bytes - 1);
211 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos
212 containerName:containerName
213 objectName:object.name
215 range:[NSString stringWithFormat:@"bytes=%lu-%lu", rangeStart, rangeEnd]
216 ifMatch:object.hash];
217 return objectRequest;
220 + (NSIndexSet *)missingBlocksForFile:(NSString *)filePath
221 blockSize:(NSUInteger)blockSize
222 blockHash:(NSString *)blockHash
223 withHashes:(NSArray *)hashes {
224 NSArray *fileHashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
225 return [hashes indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
226 if ((idx >= [fileHashes count]) || ![(NSString *)obj isEqualToString:[fileHashes objectAtIndex:idx]])
235 + (ASIPithosObjectRequest *)writeObjectDataRequestWithPithos:(ASIPithos *)pithos
236 containerName:(NSString *)containerName
237 objectName:(NSString *)objectName
238 contentType:(NSString *)contentType
239 blockSize:(NSUInteger)blockSize
240 blockHash:(NSString *)blockHash
241 forFile:(NSString *)filePath
242 checkIfExists:(BOOL)ifExists
243 hashes:(NSArray **)hashes
244 sharingAccount:(NSString *)sharingAccount {
245 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName
246 sharingAccount:(NSString *)sharingAccount])
250 *hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
253 NSString *fileName = [filePath lastPathComponent];
254 if ([filePath hasSuffix:@"/"])
255 fileName = [fileName stringByAppendingString:@"/"];
256 NSUInteger bytes = [self bytesOfFile:filePath];
257 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
258 containerName:containerName
259 objectName:objectName
260 contentType:contentType
262 contentDisposition:nil
265 isPublic:ASIPithosObjectRequestPublicIgnore
272 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
273 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
274 fileName, @"fileName",
275 [NSNumber numberWithUnsignedInteger:bytes], @"bytes",
277 return objectRequest;
280 + (NSIndexSet *)missingBlocksForHashes:(NSArray *)hashes withMissingHashes:(NSArray *)missingHashes {
281 NSMutableIndexSet *missingBlocks = [NSMutableIndexSet indexSet];
282 for (NSString *missingHash in missingHashes) {
283 if (![missingHash length])
285 NSUInteger missingBlock = [hashes indexOfObject:missingHash];
286 if (missingBlock != NSNotFound)
287 [missingBlocks addIndex:missingBlock];
289 return missingBlocks;
292 + (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos
293 containerName:(NSString *)containerName
294 blockSize:(NSUInteger)blockSize
295 forFile:(NSString *)filePath
296 hashes:(NSArray *)hashes
297 missingHashes:(NSArray *)missingHashes
298 sharingAccount:(NSString *)sharingAccount {
299 NSIndexSet *missingBlocks = [self missingBlocksForHashes:hashes withMissingHashes:missingHashes];
301 NSFileManager *fileManager = [NSFileManager defaultManager];
302 NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
304 // http://cocoawithlove.com/2009/07/temporary-files-and-folders-in-cocoa.html
305 NSString *tempFileTemplate = NSTemporaryDirectory();
306 if (tempFileTemplate == nil)
307 tempFileTemplate = @"/tmp";
308 tempFileTemplate = [tempFileTemplate stringByAppendingPathComponent:@"pithos-macos.XXXXXX"];
309 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
310 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
311 strcpy(tempFileNameCString, tempFileTemplateCString);
312 int fileDescriptor = mkstemp(tempFileNameCString);
313 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
314 if (fileDescriptor == -1) {
315 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
316 [alert setMessageText:@"Create Temporary File Error"];
317 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]];
318 [alert addButtonWithTitle:@"OK"];
322 free(tempFileNameCString);
323 NSFileHandle *tempFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor closeOnDealloc:YES];
325 [missingBlocks enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
326 [fileHandle seekToFileOffset:(idx*blockSize)];
327 [tempFileHandle writeData:[fileHandle readDataOfLength:blockSize]];
329 [tempFileHandle closeFile];
330 [fileHandle closeFile];
332 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos
333 containerName:containerName
339 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
340 return containerRequest;
343 + (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos
344 containerName:(NSString *)containerName
345 blockSize:(NSUInteger)blockSize
346 forFile:(NSString *)filePath
347 missingBlockIndex:(NSUInteger)missingBlockIndex
348 sharingAccount:(NSString *)sharingAccount {
349 NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
350 [fileHandle seekToFileOffset:(missingBlockIndex *blockSize)];
351 NSData *blockData = [fileHandle readDataOfLength:blockSize];
352 [fileHandle closeFile];
353 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos
354 containerName:containerName
360 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
361 return containerRequest;
364 + (NSArray *)writeObjectDataRequestsWithPithos:(ASIPithos *)pithos
365 containerName:(NSString *)containerName
366 objectName:(NSString *)objectName
367 blockSize:(NSUInteger)blockSize
368 blockHash:(NSString *)blockHash
369 forDirectory:(NSString *)directoryPath
370 checkIfExists:(BOOL)ifExists
371 objectNames:(NSMutableArray **)objectNames
372 contentTypes:(NSMutableArray **)contentTypes
373 filePaths:(NSMutableArray **)filePaths
374 hashesArrays:(NSMutableArray **)hashesArrays
375 directoryObjectRequests:(NSMutableArray **) directoryObjectRequests
376 sharingAccount:(NSString *)sharingAccount {
377 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName
378 sharingAccount:sharingAccount])
381 NSFileManager *fileManager = [NSFileManager defaultManager];
382 NSError *error = nil;
383 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:directoryPath error:&error];
385 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
386 [alert setMessageText:@"Directory Read Error"];
387 [alert setInformativeText:[NSString stringWithFormat:@"Cannot read contents of directory '%@': %@",
388 [directoryPath lastPathComponent], error]];
389 [alert addButtonWithTitle:@"OK"];
394 *directoryObjectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
395 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
396 containerName:containerName
397 objectName:objectName
399 contentType:@"application/directory"
401 contentDisposition:nil
404 isPublic:ASIPithosObjectRequestPublicIgnore
408 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
409 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
410 [directoryPath lastPathComponent], @"fileName",
412 [*directoryObjectRequests addObject:objectRequest];
414 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
415 *objectNames = [NSMutableArray arrayWithCapacity:[subPaths count]];
416 *contentTypes = [NSMutableArray arrayWithCapacity:[subPaths count]];
417 *filePaths = [NSMutableArray arrayWithCapacity:[subPaths count]];
418 *hashesArrays = [NSMutableArray arrayWithCapacity:[subPaths count]];
420 NSString *subObjectName;
423 NSString *contentType;
426 for (NSString *objectNameSuffix in subPaths) {
427 filePath = [directoryPath stringByAppendingPathComponent:objectNameSuffix];
428 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
430 hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
432 subObjectName = [objectName stringByAppendingPathComponent:objectNameSuffix];
433 fileName = [filePath lastPathComponent];
434 if ([filePath hasSuffix:@"/"])
435 fileName = [fileName stringByAppendingString:@"/"];
436 bytes = [self bytesOfFile:filePath];
438 contentType = [self contentTypeOfFile:filePath error:&error];
439 if (contentType == nil)
440 contentType = @"application/octet-stream";
442 NSLog(@"contentType detection error: %@", error);
443 objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
444 containerName:containerName
445 objectName:subObjectName
446 contentType:contentType
448 contentDisposition:nil
451 isPublic:ASIPithosObjectRequestPublicIgnore
458 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
459 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
460 fileName, @"fileName",
461 [NSNumber numberWithUnsignedInteger:bytes], @"bytes",
463 [objectRequests addObject:objectRequest];
464 [*objectNames addObject:subObjectName];
465 [*contentTypes addObject:contentType];
466 [*filePaths addObject:filePath];
467 [*hashesArrays addObject:hashes];
471 subObjectName = [objectName stringByAppendingPathComponent:objectNameSuffix];
472 fileName = [filePath lastPathComponent];
473 objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
474 containerName:containerName
475 objectName:subObjectName
477 contentType:@"application/directory"
479 contentDisposition:nil
482 isPublic:ASIPithosObjectRequestPublicIgnore
486 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
487 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
488 fileName, @"fileName",
490 [*directoryObjectRequests addObject:objectRequest];
495 return objectRequests;
501 + (NSArray *)deleteObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos
502 containerName:(NSString *)containerName
503 objectName:(NSString *)objectName {
504 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName delimiter:nil
509 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
510 ASIPithosObjectRequest *objectRequest;
511 if (![objectName hasSuffix:@"/"]) {
512 objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:objectName];
513 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
514 [objectName lastPathComponent], @"fileName",
516 [objectRequests addObject:objectRequest];
519 for (ASIPithosObject *object in objects) {
520 fileName = [object.name lastPathComponent];
521 if ([object.name hasSuffix:@"/"])
522 fileName = [fileName stringByAppendingString:@"/"];
523 objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:object.name];
524 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
525 fileName, @"fileName",
527 [objectRequests addObject:objectRequest];
530 if ([objectRequests count] == 0)
532 return objectRequests;
538 + (ASIPithosObjectRequest *)copyObjectRequestWithPithos:(ASIPithos *)pithos
539 containerName:(NSString *)containerName
540 objectName:(NSString *)objectName
541 destinationContainerName:(NSString *)destinationContainerName
542 destinationObjectName:(NSString *)destinationObjectName
543 checkIfExists:(BOOL)ifExists
544 sharingAccount:(NSString *)sharingAccount {
545 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
549 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
550 containerName:containerName
551 objectName:objectName
554 contentDisposition:nil
557 isPublic:ASIPithosObjectRequestPublicIgnore
559 destinationContainerName:destinationContainerName
560 destinationObjectName:destinationObjectName
561 destinationAccount:nil
563 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
564 containerName, @"sourceContainerName",
565 objectName, @"sourceObjectName",
566 destinationContainerName, @"destinationContainerName",
567 destinationObjectName, @"destinationObjectName",
570 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
571 return objectRequest;
574 + (NSArray *)copyObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos
575 containerName:(NSString *)containerName
576 objectName:(NSString *)objectName
577 destinationContainerName:(NSString *)destinationContainerName
578 destinationObjectName:(NSString *)destinationObjectName
579 checkIfExists:(BOOL)ifExists
580 sharingAccount:(NSString *)sharingAccount {
581 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
585 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName
586 delimiter:nil sharingAccount:sharingAccount];
590 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
591 ASIPithosObjectRequest *objectRequest;
592 if ([objectName isEqualToString:destinationObjectName]) {
593 if (![objectName hasSuffix:@"/"]) {
594 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
595 containerName:containerName
596 objectName:objectName
599 contentDisposition:nil
602 isPublic:ASIPithosObjectRequestPublicIgnore
604 destinationContainerName:destinationContainerName
605 destinationObjectName:objectName
606 destinationAccount:nil
608 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
609 containerName, @"sourceContainerName",
610 objectName, @"sourceObjectName",
611 destinationContainerName, @"destinationContainerName",
612 objectName, @"destinationObjectName",
615 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
616 [objectRequests addObject:objectRequest];
618 for (ASIPithosObject *object in objects) {
619 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
620 containerName:containerName
621 objectName:object.name
624 contentDisposition:nil
627 isPublic:ASIPithosObjectRequestPublicIgnore
629 destinationContainerName:destinationContainerName
630 destinationObjectName:object.name
631 destinationAccount:nil
633 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
634 containerName, @"sourceContainerName",
635 object.name, @"sourceObjectName",
636 destinationContainerName, @"destinationContainerName",
637 object.name, @"destinationObjectName",
640 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
641 [objectRequests addObject:objectRequest];
644 if (![objectName hasSuffix:@"/"]) {
645 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
646 containerName:containerName
647 objectName:objectName
650 contentDisposition:nil
653 isPublic:ASIPithosObjectRequestPublicIgnore
655 destinationContainerName:destinationContainerName
656 destinationObjectName:destinationObjectName
657 destinationAccount:nil
659 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
660 containerName, @"sourceContainerName",
661 objectName, @"sourceObjectName",
662 destinationContainerName, @"destinationContainerName",
663 destinationObjectName, @"destinationObjectName",
666 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
667 [objectRequests addObject:objectRequest];
669 NSRange prefixRange = NSMakeRange(0, [objectName length]);
670 NSString *newObjectName;
671 for (ASIPithosObject *object in objects) {
672 newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
673 withString:destinationObjectName
674 options:NSAnchoredSearch
676 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
677 containerName:containerName
678 objectName:object.name
681 contentDisposition:nil
684 isPublic:ASIPithosObjectRequestPublicIgnore
686 destinationContainerName:destinationContainerName
687 destinationObjectName:newObjectName
688 destinationAccount:nil
690 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
691 containerName, @"sourceContainerName",
692 object.name, @"sourceObjectName",
693 destinationContainerName, @"destinationContainerName",
694 newObjectName, @"destinationObjectName",
697 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
698 [objectRequests addObject:objectRequest];
702 if ([objectRequests count] == 0)
704 return objectRequests;
710 + (ASIPithosObjectRequest *)moveObjectRequestWithPithos:(ASIPithos *)pithos
711 containerName:(NSString *)containerName
712 objectName:(NSString *)objectName
713 destinationContainerName:(NSString *)destinationContainerName
714 destinationObjectName:(NSString *)destinationObjectName
715 checkIfExists:(BOOL)ifExists {
716 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
720 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
721 containerName:containerName
722 objectName:objectName
725 contentDisposition:nil
728 isPublic:ASIPithosObjectRequestPublicIgnore
730 destinationContainerName:destinationContainerName
731 destinationObjectName:destinationObjectName
732 destinationAccount:nil];
733 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
734 containerName, @"sourceContainerName",
735 objectName, @"sourceObjectName",
736 destinationContainerName, @"destinationContainerName",
737 destinationObjectName, @"destinationObjectName",
739 return objectRequest;
742 + (NSArray *)moveObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos
743 containerName:(NSString *)containerName
744 objectName:(NSString *)objectName
745 destinationContainerName:(NSString *)destinationContainerName
746 destinationObjectName:(NSString *)destinationObjectName
747 checkIfExists:(BOOL)ifExists {
748 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
752 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName
753 delimiter:nil sharingAccount:nil];
757 ASIPithosObjectRequest *objectRequest;
758 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
759 if ([objectName isEqualToString:destinationObjectName]) {
760 if (![objectName hasSuffix:@"/"]) {
761 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
762 containerName:containerName
763 objectName:objectName
766 contentDisposition:nil
769 isPublic:ASIPithosObjectRequestPublicIgnore
771 destinationContainerName:destinationContainerName
772 destinationObjectName:objectName
773 destinationAccount:nil];
774 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
775 containerName, @"sourceContainerName",
776 objectName, @"sourceObjectName",
777 destinationContainerName, @"destinationContainerName",
778 objectName, @"destinationObjectName",
780 [objectRequests addObject:objectRequest];
782 for (ASIPithosObject *object in objects) {
783 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
784 containerName:containerName
785 objectName:object.name
788 contentDisposition:nil
791 isPublic:ASIPithosObjectRequestPublicIgnore
793 destinationContainerName:destinationContainerName
794 destinationObjectName:object.name
795 destinationAccount:nil];
796 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
797 containerName, @"sourceContainerName",
798 object.name, @"sourceObjectName",
799 destinationContainerName, @"destinationContainerName",
800 object.name, @"destinationObjectName",
802 [objectRequests addObject:objectRequest];
805 if (![objectName hasSuffix:@"/"]) {
806 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
807 containerName:containerName
808 objectName:objectName
811 contentDisposition:nil
814 isPublic:ASIPithosObjectRequestPublicIgnore
816 destinationContainerName:destinationContainerName
817 destinationObjectName:destinationObjectName
818 destinationAccount:nil];
819 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
820 containerName, @"sourceContainerName",
821 objectName, @"sourceObjectName",
822 destinationContainerName, @"destinationContainerName",
823 destinationObjectName, @"destinationObjectName",
825 [objectRequests addObject:objectRequest];
827 NSRange prefixRange = NSMakeRange(0, [objectName length]);
828 NSString *newObjectName;
829 for (ASIPithosObject *object in objects) {
830 newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
831 withString:destinationObjectName
832 options:NSAnchoredSearch
834 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
835 containerName:containerName
836 objectName:object.name
839 contentDisposition:nil
842 isPublic:ASIPithosObjectRequestPublicIgnore
844 destinationContainerName:destinationContainerName
845 destinationObjectName:newObjectName
846 destinationAccount:nil];
847 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
848 containerName, @"sourceContainerName",
849 object.name, @"sourceObjectName",
850 destinationContainerName, @"destinationContainerName",
851 newObjectName, @"destinationObjectName",
853 [objectRequests addObject:objectRequest];
857 if ([objectRequests count] == 0)
859 return objectRequests;
863 #pragma mark Helper Methods
865 // Size of the file in bytes
866 + (NSUInteger)bytesOfFile:(NSString *)filePath {
867 NSFileManager *fileManager = [NSFileManager defaultManager];
868 NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil];
869 return [[attributes objectForKey:NSFileSize] intValue];
872 // Content type of the file or nil if it cannot be determined
873 + (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error {
874 NSURLResponse *response = nil;
875 [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:filePath]
876 cachePolicy:NSURLCacheStorageNotAllowed
878 returningResponse:&response
880 return [response MIMEType];
883 // Creates a directory if it doesn't exist and returns if successful
884 + (BOOL)safeCreateDirectory:(NSString *)directoryPath error:(NSError **)error {
885 NSFileManager *fileManager = [NSFileManager defaultManager];
887 BOOL fileExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory];
890 if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:error] || *error)
895 // Returns if an object is a directory based on its content type
896 + (BOOL)isContentTypeDirectory:(NSString *)contentType {
897 return ([contentType isEqualToString:@"application/directory"] ||
898 [contentType hasPrefix:@"application/directory;"] ||
899 [contentType isEqualToString:@"application/folder"] ||
900 [contentType hasPrefix:@"application/folder;"]);
903 // Returns if an object exists at the given container/object path and if this object is an application/directory
904 // If an error occured an alert is shown and it is returned so the caller won't proceed
905 + (BOOL)objectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName
906 error:(NSError **)error isDirectory:(BOOL *)isDirectory sharingAccount:(NSString *)sharingAccount {
907 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos
908 containerName:containerName
909 objectName:objectName];
911 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
912 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
914 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:objectRequest]] waitUntilFinished:YES];
915 *error = [objectRequest error];
917 [self httpRequestErrorAlertWithRequest:objectRequest];
919 } else if (objectRequest.responseStatusCode == 200) {
920 *isDirectory = [self isContentTypeDirectory:[objectRequest contentType]];
926 // Returns if the caller should proceed, after an interactive check if an object exists
927 // at the given container/object path is performed
928 + (BOOL)proceedIfObjectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName
929 sharingAccount:(NSString *)sharingAccount {
930 NSError *error = nil;
932 BOOL objectExists = [self objectExistsAtPithos:pithos containerName:containerName objectName:objectName
933 error:&error isDirectory:&isDirectory sharingAccount:sharingAccount];
936 } else if (objectExists) {
937 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
939 [alert setMessageText:@"Directory Exists"];
941 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
943 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
945 [alert setMessageText:@"Object Exists"];
947 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
949 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
951 [alert addButtonWithTitle:@"OK"];
952 [alert addButtonWithTitle:@"Cancel"];
953 NSInteger choice = [alert runModal];
954 if (choice == NSAlertSecondButtonReturn)
960 // List of objects at the given container/object path, with prefix and or delimiter
961 + (NSArray *)objectsWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix
962 delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
963 NSMutableArray *objects = [NSMutableArray array];
964 NSString *marker = nil;
966 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
967 containerName:containerName
970 prefix:objectNamePrefix
977 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
978 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
980 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
981 if ([containerRequest error]) {
982 [self httpRequestErrorAlertWithRequest:containerRequest];
985 NSArray *someObjects = [containerRequest objects];
986 [objects addObjectsFromArray:someObjects];
987 if ([someObjects count] < 10000)
990 marker = [[someObjects lastObject] name];
995 // List of objects at the given container/object path, that may be a subdir or an application/directory,
996 // with prefix and or delimiter
997 + (NSArray *)objectsForSubdirWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName
998 delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
999 NSString *subdirNamePrefix = [NSString stringWithString:objectName];
1000 if (![subdirNamePrefix hasSuffix:@"/"])
1001 subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"];
1002 return [self objectsWithPithos:pithos containerName:containerName objectNamePrefix:subdirNamePrefix
1003 delimiter:delimiter sharingAccount:sharingAccount];
1006 // A safe object name at the given container/object path
1007 // The original name has " %d" appended to it before any ".*" suffix, for the first integer that produces a name that is free to use
1008 // If the original name hasn't got a "." but has a "/" suffix, then it is replaced with " %d/" instead
1009 // Subdirs are taken into consideration
1010 + (NSString *)safeObjectNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName {
1011 NSString *objectNamePrefix;
1012 NSString *objectNameExtraSuffix;
1013 NSRange lastDotRange = [objectName rangeOfString:@"." options:NSBackwardsSearch];
1014 if (lastDotRange.length == 1) {
1015 objectNamePrefix = [objectName substringToIndex:lastDotRange.location];
1016 objectNameExtraSuffix = [objectName substringFromIndex:lastDotRange.location];
1017 } else if ([objectName hasSuffix:@"/"]) {
1018 objectNamePrefix = [objectName substringToIndex:([objectName length] - 1)];
1019 objectNameExtraSuffix = [NSString stringWithString:@"/"];
1021 objectNamePrefix = [NSString stringWithString:objectName];
1022 objectNameExtraSuffix = [NSString string];
1024 NSArray *objects = [self objectsWithPithos:pithos containerName:containerName
1025 objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent]
1026 delimiter:@"/" sharingAccount:nil];
1029 if ([objects count] == 0)
1031 NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1032 [[objects objectsAtIndexes:
1033 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1034 if (pithosObject.subdir)
1037 }]] valueForKey:@"name"]];
1038 for (NSString *name in [[objects objectsAtIndexes:
1039 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1040 if (pithosObject.subdir)
1043 }]] valueForKey:@"name"]) {
1044 [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1046 if (![objectNames containsObject:objectName])
1048 NSUInteger objectNameSuffix = 2;
1049 NSString *safeObjectName;
1051 safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix];
1053 } while ([objectNames containsObject:safeObjectName]);
1054 return safeObjectName;
1057 // A safe object name at the given container/object path that may be a subdir or application/directory
1058 // The original name has " %d" appended to it, for the first integer that produces a name that is free to use
1059 // If the original name has a "/" suffix, then it is replaced with " %d/" instead
1060 // Subdirs are taken into consideration
1061 + (NSString *)safeSubdirNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName subdirName:(NSString *)subdirName {
1062 NSString *subdirNamePrefix;
1063 NSString *subdirNameExtraSuffix;
1064 if ([subdirName hasSuffix:@"/"]) {
1065 subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)];
1066 subdirNameExtraSuffix = [NSString stringWithString:@"/"];
1068 subdirNamePrefix = [NSString stringWithString:subdirName];
1069 subdirNameExtraSuffix = [NSString string];
1071 NSArray *objects = [self objectsWithPithos:pithos containerName:containerName
1072 objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent]
1073 delimiter:@"/" sharingAccount:nil];
1076 if ([objects count] == 0)
1078 NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1079 [[objects objectsAtIndexes:
1080 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1081 if (pithosObject.subdir)
1084 }]] valueForKey:@"name"]];
1085 for (NSString *name in [[objects objectsAtIndexes:
1086 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1087 if (pithosObject.subdir)
1090 }]] valueForKey:@"name"]) {
1091 [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1093 if (![objectNames containsObject:subdirNamePrefix])
1095 NSUInteger subdirNameSuffix = 2;
1096 NSString *safeSubdirName;
1098 safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix];
1100 } while ([objectNames containsObject:safeSubdirName]);
1101 return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix];
1107 + (NSInteger)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
1108 NSString *message = [NSString stringWithFormat:
1109 @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@",
1111 request.requestMethod,
1113 [request requestHeaders],
1114 [request responseHeaders],
1115 [request responseString]];
1116 NSLog(@"%@", message);
1117 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1118 [alert setMessageText:@"HTTP Request Error"];
1119 [alert setInformativeText:message];
1120 [alert addButtonWithTitle:@"OK"];
1121 return [alert runModal];
1124 + (NSInteger)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
1125 NSString *message = [NSString stringWithFormat:
1126 @"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@",
1127 request.responseStatusCode,
1128 request.responseStatusMessage,
1129 request.requestMethod,
1131 [request requestHeaders],
1132 [request responseHeaders],
1133 [request responseString]];
1134 NSLog(@"%@", message);
1135 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1136 [alert setMessageText:@"Unexpected Response Status"];
1137 [alert setInformativeText:message];
1138 [alert addButtonWithTitle:@"OK"];
1139 return [alert runModal];
1142 + (NSInteger)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error {
1143 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1144 [alert setMessageText:title];
1146 [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, error]];
1148 [alert setInformativeText:message];
1149 [alert addButtonWithTitle:@"OK"];
1150 return [alert runModal];
1154 #pragma mark Request Helper Methods
1156 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority {
1157 [request setTimeOutSeconds:60];
1158 request.numberOfTimesToRetryOnTimeout = 10;
1159 [request setQueuePriority:priority];
1163 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request {
1164 return [self prepareRequest:request priority:NSOperationQueuePriorityNormal];
1167 + (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request {
1168 NSMutableDictionary *userInfo = (NSMutableDictionary *)[[request.userInfo retain] autorelease];
1169 request.userInfo = nil;
1170 ASIPithosRequest *newRequest = [request copy];
1171 newRequest.userInfo = userInfo;