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 version:(NSString *)version
55 toDirectory:(NSString *)directoryPath
56 withNewFileName:(NSString *)newFileName
57 checkIfExists:(BOOL)ifExists
58 sharingAccount:(NSString *)sharingAccount {
61 fileName = [NSString stringWithString:newFileName];
63 fileName = [objectName lastPathComponent];
64 if ([objectName hasSuffix:@"/"])
65 fileName = [fileName stringByAppendingString:@"/"];
67 fileName = [fileName stringByReplacingOccurrencesOfString:@"/" withString:@":"];
69 NSFileManager *fileManager = [NSFileManager defaultManager];
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];
82 if (choice == NSAlertSecondButtonReturn)
86 BOOL directoryIsDirectory;
87 BOOL directoryExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&directoryIsDirectory];
89 if (!directoryExists) {
90 [fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
91 } else if (!directoryIsDirectory) {
92 [fileManager removeItemAtPath:directoryPath error:&error];
95 NSLog(@"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 '%@': %@", fileName, error]];
100 [alert addButtonWithTitle:@"OK"];
106 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos
107 containerName:containerName
108 objectName:objectName
111 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
112 objectRequest.downloadDestinationPath = destinationPath;
113 objectRequest.allowResumeForFileDownloads = YES;
114 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
115 fileName, @"fileName",
116 destinationPath, @"filePath",
118 return objectRequest;
121 + (NSArray *)objectDataRequestsForSubdirWithPithos:(ASIPithos *)pithos
122 containerName:(NSString *)containerName
123 objectName:(NSString *)objectName
124 toDirectory:(NSString *)directoryPath
125 checkIfExists:(BOOL)ifExists
126 sharingAccount:(NSString *)sharingAccount {
127 NSString *subdirName = [objectName lastPathComponent];
128 NSString *destinationPath = [directoryPath stringByAppendingPathComponent:subdirName];
129 if (ifExists && [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) {
130 __block NSInteger choice;
131 dispatch_sync(dispatch_get_main_queue(), ^{
132 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
133 [alert setMessageText:@"File exists"];
134 [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", subdirName]];
135 [alert addButtonWithTitle:@"OK"];
136 [alert addButtonWithTitle:@"Cancel"];
137 choice = [alert runModal];
139 if (choice == NSAlertSecondButtonReturn)
143 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName
144 delimiter:nil sharingAccount:sharingAccount];
148 NSFileManager *fileManager = [NSFileManager defaultManager];
149 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[objects count]];
150 NSUInteger subdirPrefixLength = [objectName length];
152 NSError *error = nil;
153 [fileManager createDirectoryAtPath:[directoryPath stringByAppendingPathComponent:subdirName] withIntermediateDirectories:YES attributes:nil error:&error];
155 NSLog(@"Cannot create directory at '%@': %@", directoryPath, error);
156 dispatch_async(dispatch_get_main_queue(), ^{
157 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
158 [alert setMessageText:@"Create Directory Error"];
159 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@",
160 directoryPath, error]];
161 [alert addButtonWithTitle:@"OK"];
166 for (ASIPithosObject *object in objects) {
167 if ([self isContentTypeDirectory:object.contentType]) {
168 NSString *subdirDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
169 subdirDirectoryPath = [subdirDirectoryPath stringByAppendingPathComponent:[object.name substringFromIndex:subdirPrefixLength]];
171 BOOL directoryIsDirectory;
172 BOOL directoryExists = [fileManager fileExistsAtPath:subdirDirectoryPath isDirectory:&directoryIsDirectory];
173 NSError *error = nil;
174 if (!directoryExists) {
175 [fileManager createDirectoryAtPath:subdirDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error];
177 NSLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error);
178 dispatch_async(dispatch_get_main_queue(), ^{
179 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
180 [alert setMessageText:@"Create Directory Error"];
181 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@",
182 subdirDirectoryPath, error]];
183 [alert addButtonWithTitle:@"OK"];
187 } else if (!directoryIsDirectory) {
188 [fileManager removeItemAtPath:subdirDirectoryPath error:&error];
190 NSLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error);
191 dispatch_async(dispatch_get_main_queue(), ^{
192 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
193 [alert setMessageText:@"Remove File Error"];
194 [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file at '%@': %@",
195 subdirDirectoryPath, error]];
196 [alert addButtonWithTitle:@"OK"];
202 NSString *fileName = [object.name lastPathComponent];
203 if([object.name hasSuffix:@"/"])
204 fileName = [fileName stringByAppendingString:@"/"];
206 NSString *objectDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
207 objectDirectoryPath = [objectDirectoryPath stringByAppendingPathComponent:[object.name substringWithRange:NSMakeRange(subdirPrefixLength, [object.name length] - subdirPrefixLength - [fileName length])]];
209 ASIPithosObjectRequest *objectRequest = [self objectDataRequestWithPithos:pithos
210 containerName:containerName
211 objectName:object.name
213 toDirectory:objectDirectoryPath
216 sharingAccount:sharingAccount];
217 [(NSMutableDictionary *)objectRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:object.bytes] forKey:@"bytes"];
218 [objectRequests addObject:objectRequest];
222 return objectRequests;
226 #pragma mark Download Block
228 + (ASIPithosObjectRequest *)objectBlockDataRequestWithPithos:(ASIPithos *)pithos
229 containerName:(NSString *)containerName
230 object:(ASIPithosObject *)object
231 blockIndex:(NSUInteger)blockIndex
232 blockSize:(NSUInteger)blockSize {
233 NSUInteger rangeStart = blockIndex * blockSize;
234 NSUInteger rangeEnd = (rangeStart + blockSize <= object.bytes) ? (rangeStart + blockSize - 1) : (object.bytes - 1);
235 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos
236 containerName:containerName
237 objectName:object.name
239 range:[NSString stringWithFormat:@"bytes=%lu-%lu", rangeStart, rangeEnd]
240 ifMatch:object.hash];
241 return objectRequest;
244 + (NSIndexSet *)missingBlocksForFile:(NSString *)filePath
245 blockSize:(NSUInteger)blockSize
246 blockHash:(NSString *)blockHash
247 withHashes:(NSArray *)hashes {
248 NSArray *fileHashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
249 return [hashes indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
250 if ((idx >= [fileHashes count]) || ![(NSString *)obj isEqualToString:[fileHashes objectAtIndex:idx]])
259 + (ASIPithosObjectRequest *)writeObjectDataRequestWithPithos:(ASIPithos *)pithos
260 containerName:(NSString *)containerName
261 objectName:(NSString *)objectName
262 contentType:(NSString *)contentType
263 blockSize:(NSUInteger)blockSize
264 blockHash:(NSString *)blockHash
265 forFile:(NSString *)filePath
266 checkIfExists:(BOOL)ifExists
267 hashes:(NSArray **)hashes
268 sharingAccount:(NSString *)sharingAccount {
269 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName
270 sharingAccount:(NSString *)sharingAccount])
274 *hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
277 NSString *fileName = [filePath lastPathComponent];
278 if ([filePath hasSuffix:@"/"])
279 fileName = [fileName stringByAppendingString:@"/"];
280 NSUInteger bytes = [self bytesOfFile:filePath];
281 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
282 containerName:containerName
283 objectName:objectName
284 contentType:contentType
286 contentDisposition:nil
289 isPublic:ASIPithosObjectRequestPublicIgnore
296 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
297 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
298 fileName, @"fileName",
299 [NSNumber numberWithUnsignedInteger:bytes], @"bytes",
301 return objectRequest;
304 + (NSIndexSet *)missingBlocksForHashes:(NSArray *)hashes withMissingHashes:(NSArray *)missingHashes {
305 NSMutableIndexSet *missingBlocks = [NSMutableIndexSet indexSet];
306 for (NSString *missingHash in missingHashes) {
307 if (![missingHash length])
309 NSUInteger missingBlock = [hashes indexOfObject:missingHash];
310 if (missingBlock != NSNotFound)
311 [missingBlocks addIndex:missingBlock];
313 return missingBlocks;
316 + (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos
317 containerName:(NSString *)containerName
318 blockSize:(NSUInteger)blockSize
319 forFile:(NSString *)filePath
320 hashes:(NSArray *)hashes
321 missingHashes:(NSArray *)missingHashes
322 sharingAccount:(NSString *)sharingAccount {
323 NSIndexSet *missingBlocks = [self missingBlocksForHashes:hashes withMissingHashes:missingHashes];
325 NSFileManager *fileManager = [NSFileManager defaultManager];
326 NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
328 // http://cocoawithlove.com/2009/07/temporary-files-and-folders-in-cocoa.html
329 NSString *tempFileTemplate = NSTemporaryDirectory();
330 if (tempFileTemplate == nil)
331 tempFileTemplate = @"/tmp";
332 tempFileTemplate = [tempFileTemplate stringByAppendingPathComponent:@"pithos-macos.XXXXXX"];
333 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
334 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
335 strcpy(tempFileNameCString, tempFileTemplateCString);
336 int fileDescriptor = mkstemp(tempFileNameCString);
337 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
338 if (fileDescriptor == -1) {
339 dispatch_async(dispatch_get_main_queue(), ^{
340 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
341 [alert setMessageText:@"Create Temporary File Error"];
342 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]];
343 [alert addButtonWithTitle:@"OK"];
348 free(tempFileNameCString);
349 NSFileHandle *tempFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor closeOnDealloc:YES];
351 [missingBlocks enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
352 [fileHandle seekToFileOffset:(idx*blockSize)];
353 [tempFileHandle writeData:[fileHandle readDataOfLength:blockSize]];
355 [tempFileHandle closeFile];
356 [fileHandle closeFile];
358 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos
359 containerName:containerName
365 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
366 return containerRequest;
369 + (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos
370 containerName:(NSString *)containerName
371 blockSize:(NSUInteger)blockSize
372 forFile:(NSString *)filePath
373 missingBlockIndex:(NSUInteger)missingBlockIndex
374 sharingAccount:(NSString *)sharingAccount {
375 NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
376 [fileHandle seekToFileOffset:(missingBlockIndex *blockSize)];
377 NSData *blockData = [fileHandle readDataOfLength:blockSize];
378 [fileHandle closeFile];
379 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos
380 containerName:containerName
386 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
387 return containerRequest;
390 + (NSArray *)writeObjectDataRequestsWithPithos:(ASIPithos *)pithos
391 containerName:(NSString *)containerName
392 objectName:(NSString *)objectName
393 blockSize:(NSUInteger)blockSize
394 blockHash:(NSString *)blockHash
395 forDirectory:(NSString *)directoryPath
396 checkIfExists:(BOOL)ifExists
397 objectNames:(NSMutableArray **)objectNames
398 contentTypes:(NSMutableArray **)contentTypes
399 filePaths:(NSMutableArray **)filePaths
400 hashesArrays:(NSMutableArray **)hashesArrays
401 directoryObjectRequests:(NSMutableArray **) directoryObjectRequests
402 sharingAccount:(NSString *)sharingAccount {
403 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName
404 sharingAccount:sharingAccount])
407 NSFileManager *fileManager = [NSFileManager defaultManager];
408 NSError *error = nil;
409 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:directoryPath error:&error];
411 dispatch_async(dispatch_get_main_queue(), ^{
412 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
413 [alert setMessageText:@"Directory Read Error"];
414 [alert setInformativeText:[NSString stringWithFormat:@"Cannot read contents of directory '%@': %@",
415 [directoryPath lastPathComponent], error]];
416 [alert addButtonWithTitle:@"OK"];
422 *directoryObjectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
423 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
424 containerName:containerName
425 objectName:objectName
427 contentType:@"application/directory"
429 contentDisposition:nil
432 isPublic:ASIPithosObjectRequestPublicIgnore
436 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
437 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
438 [directoryPath lastPathComponent], @"fileName",
440 [*directoryObjectRequests addObject:objectRequest];
442 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
443 *objectNames = [NSMutableArray arrayWithCapacity:[subPaths count]];
444 *contentTypes = [NSMutableArray arrayWithCapacity:[subPaths count]];
445 *filePaths = [NSMutableArray arrayWithCapacity:[subPaths count]];
446 *hashesArrays = [NSMutableArray arrayWithCapacity:[subPaths count]];
448 NSString *subObjectName;
451 NSString *contentType;
454 for (NSString *objectNameSuffix in subPaths) {
455 filePath = [directoryPath stringByAppendingPathComponent:objectNameSuffix];
456 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
458 hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
460 subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping];
461 fileName = [filePath lastPathComponent];
462 if ([filePath hasSuffix:@"/"])
463 fileName = [fileName stringByAppendingString:@"/"];
464 bytes = [self bytesOfFile:filePath];
466 contentType = [self contentTypeOfFile:filePath error:&error];
467 if (contentType == nil)
468 contentType = @"application/octet-stream";
470 NSLog(@"contentType detection error: %@", error);
471 objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
472 containerName:containerName
473 objectName:subObjectName
474 contentType:contentType
476 contentDisposition:nil
479 isPublic:ASIPithosObjectRequestPublicIgnore
486 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
487 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
488 fileName, @"fileName",
489 [NSNumber numberWithUnsignedInteger:bytes], @"bytes",
491 [objectRequests addObject:objectRequest];
492 [*objectNames addObject:subObjectName];
493 [*contentTypes addObject:contentType];
494 [*filePaths addObject:filePath];
495 [*hashesArrays addObject:hashes];
499 subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping];
500 fileName = [filePath lastPathComponent];
501 objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
502 containerName:containerName
503 objectName:subObjectName
505 contentType:@"application/directory"
507 contentDisposition:nil
510 isPublic:ASIPithosObjectRequestPublicIgnore
514 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
515 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
516 fileName, @"fileName",
518 [*directoryObjectRequests addObject:objectRequest];
523 return objectRequests;
529 + (NSArray *)deleteObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos
530 containerName:(NSString *)containerName
531 objectName:(NSString *)objectName {
532 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName delimiter:nil
537 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
538 ASIPithosObjectRequest *objectRequest;
539 if (![objectName hasSuffix:@"/"]) {
540 objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:objectName];
541 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
542 [objectName lastPathComponent], @"fileName",
544 [objectRequests addObject:objectRequest];
547 for (ASIPithosObject *object in objects) {
548 fileName = [object.name lastPathComponent];
549 if ([object.name hasSuffix:@"/"])
550 fileName = [fileName stringByAppendingString:@"/"];
551 objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:object.name];
552 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
553 fileName, @"fileName",
555 [objectRequests addObject:objectRequest];
558 if ([objectRequests count] == 0)
560 return objectRequests;
566 + (ASIPithosObjectRequest *)copyObjectRequestWithPithos:(ASIPithos *)pithos
567 containerName:(NSString *)containerName
568 objectName:(NSString *)objectName
569 destinationContainerName:(NSString *)destinationContainerName
570 destinationObjectName:(NSString *)destinationObjectName
571 checkIfExists:(BOOL)ifExists
572 sharingAccount:(NSString *)sharingAccount {
573 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
577 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
578 containerName:containerName
579 objectName:objectName
582 contentDisposition:nil
585 isPublic:ASIPithosObjectRequestPublicIgnore
587 destinationContainerName:destinationContainerName
588 destinationObjectName:destinationObjectName
589 destinationAccount:nil
591 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
592 containerName, @"sourceContainerName",
593 objectName, @"sourceObjectName",
594 destinationContainerName, @"destinationContainerName",
595 destinationObjectName, @"destinationObjectName",
598 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
599 return objectRequest;
602 + (NSArray *)copyObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos
603 containerName:(NSString *)containerName
604 objectName:(NSString *)objectName
605 destinationContainerName:(NSString *)destinationContainerName
606 destinationObjectName:(NSString *)destinationObjectName
607 checkIfExists:(BOOL)ifExists
608 sharingAccount:(NSString *)sharingAccount {
609 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
613 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName
614 delimiter:nil sharingAccount:sharingAccount];
618 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
619 ASIPithosObjectRequest *objectRequest;
620 if ([objectName isEqualToString:destinationObjectName]) {
621 if (![objectName hasSuffix:@"/"]) {
622 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
623 containerName:containerName
624 objectName:objectName
627 contentDisposition:nil
630 isPublic:ASIPithosObjectRequestPublicIgnore
632 destinationContainerName:destinationContainerName
633 destinationObjectName:objectName
634 destinationAccount:nil
636 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
637 containerName, @"sourceContainerName",
638 objectName, @"sourceObjectName",
639 destinationContainerName, @"destinationContainerName",
640 objectName, @"destinationObjectName",
643 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
644 [objectRequests addObject:objectRequest];
646 for (ASIPithosObject *object in objects) {
647 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
648 containerName:containerName
649 objectName:object.name
652 contentDisposition:nil
655 isPublic:ASIPithosObjectRequestPublicIgnore
657 destinationContainerName:destinationContainerName
658 destinationObjectName:object.name
659 destinationAccount:nil
661 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
662 containerName, @"sourceContainerName",
663 object.name, @"sourceObjectName",
664 destinationContainerName, @"destinationContainerName",
665 object.name, @"destinationObjectName",
668 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
669 [objectRequests addObject:objectRequest];
672 if (![objectName hasSuffix:@"/"]) {
673 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
674 containerName:containerName
675 objectName:objectName
678 contentDisposition:nil
681 isPublic:ASIPithosObjectRequestPublicIgnore
683 destinationContainerName:destinationContainerName
684 destinationObjectName:destinationObjectName
685 destinationAccount:nil
687 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
688 containerName, @"sourceContainerName",
689 objectName, @"sourceObjectName",
690 destinationContainerName, @"destinationContainerName",
691 destinationObjectName, @"destinationObjectName",
694 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
695 [objectRequests addObject:objectRequest];
697 NSRange prefixRange = NSMakeRange(0, [objectName length]);
698 NSString *newObjectName;
699 for (ASIPithosObject *object in objects) {
700 newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
701 withString:destinationObjectName
702 options:NSAnchoredSearch
704 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
705 containerName:containerName
706 objectName:object.name
709 contentDisposition:nil
712 isPublic:ASIPithosObjectRequestPublicIgnore
714 destinationContainerName:destinationContainerName
715 destinationObjectName:newObjectName
716 destinationAccount:nil
718 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
719 containerName, @"sourceContainerName",
720 object.name, @"sourceObjectName",
721 destinationContainerName, @"destinationContainerName",
722 newObjectName, @"destinationObjectName",
725 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
726 [objectRequests addObject:objectRequest];
730 if ([objectRequests count] == 0)
732 return objectRequests;
738 + (ASIPithosObjectRequest *)moveObjectRequestWithPithos:(ASIPithos *)pithos
739 containerName:(NSString *)containerName
740 objectName:(NSString *)objectName
741 destinationContainerName:(NSString *)destinationContainerName
742 destinationObjectName:(NSString *)destinationObjectName
743 checkIfExists:(BOOL)ifExists {
744 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
748 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
749 containerName:containerName
750 objectName:objectName
753 contentDisposition:nil
756 isPublic:ASIPithosObjectRequestPublicIgnore
758 destinationContainerName:destinationContainerName
759 destinationObjectName:destinationObjectName
760 destinationAccount:nil];
761 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
762 containerName, @"sourceContainerName",
763 objectName, @"sourceObjectName",
764 destinationContainerName, @"destinationContainerName",
765 destinationObjectName, @"destinationObjectName",
767 return objectRequest;
770 + (NSArray *)moveObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos
771 containerName:(NSString *)containerName
772 objectName:(NSString *)objectName
773 destinationContainerName:(NSString *)destinationContainerName
774 destinationObjectName:(NSString *)destinationObjectName
775 checkIfExists:(BOOL)ifExists {
776 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
780 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName
781 delimiter:nil sharingAccount:nil];
785 ASIPithosObjectRequest *objectRequest;
786 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
787 if ([objectName isEqualToString:destinationObjectName]) {
788 if (![objectName hasSuffix:@"/"]) {
789 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
790 containerName:containerName
791 objectName:objectName
794 contentDisposition:nil
797 isPublic:ASIPithosObjectRequestPublicIgnore
799 destinationContainerName:destinationContainerName
800 destinationObjectName:objectName
801 destinationAccount:nil];
802 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
803 containerName, @"sourceContainerName",
804 objectName, @"sourceObjectName",
805 destinationContainerName, @"destinationContainerName",
806 objectName, @"destinationObjectName",
808 [objectRequests addObject:objectRequest];
810 for (ASIPithosObject *object in objects) {
811 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
812 containerName:containerName
813 objectName:object.name
816 contentDisposition:nil
819 isPublic:ASIPithosObjectRequestPublicIgnore
821 destinationContainerName:destinationContainerName
822 destinationObjectName:object.name
823 destinationAccount:nil];
824 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
825 containerName, @"sourceContainerName",
826 object.name, @"sourceObjectName",
827 destinationContainerName, @"destinationContainerName",
828 object.name, @"destinationObjectName",
830 [objectRequests addObject:objectRequest];
833 if (![objectName hasSuffix:@"/"]) {
834 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
835 containerName:containerName
836 objectName:objectName
839 contentDisposition:nil
842 isPublic:ASIPithosObjectRequestPublicIgnore
844 destinationContainerName:destinationContainerName
845 destinationObjectName:destinationObjectName
846 destinationAccount:nil];
847 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
848 containerName, @"sourceContainerName",
849 objectName, @"sourceObjectName",
850 destinationContainerName, @"destinationContainerName",
851 destinationObjectName, @"destinationObjectName",
853 [objectRequests addObject:objectRequest];
855 NSRange prefixRange = NSMakeRange(0, [objectName length]);
856 NSString *newObjectName;
857 for (ASIPithosObject *object in objects) {
858 newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
859 withString:destinationObjectName
860 options:NSAnchoredSearch
862 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
863 containerName:containerName
864 objectName:object.name
867 contentDisposition:nil
870 isPublic:ASIPithosObjectRequestPublicIgnore
872 destinationContainerName:destinationContainerName
873 destinationObjectName:newObjectName
874 destinationAccount:nil];
875 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
876 containerName, @"sourceContainerName",
877 object.name, @"sourceObjectName",
878 destinationContainerName, @"destinationContainerName",
879 newObjectName, @"destinationObjectName",
881 [objectRequests addObject:objectRequest];
885 if ([objectRequests count] == 0)
887 return objectRequests;
891 #pragma mark Helper Methods
893 // Size of the file in bytes
894 + (NSUInteger)bytesOfFile:(NSString *)filePath {
895 NSFileManager *fileManager = [NSFileManager defaultManager];
896 NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil];
897 return [[attributes objectForKey:NSFileSize] unsignedIntegerValue];
900 // Content type of the file or nil if it cannot be determined
901 + (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error {
902 // Based on http://www.ddeville.me/2011/12/mime-to-UTI-cocoa/
903 // and Apple example ImageBrowserViewAppearance/ImageBrowserController.m
904 LSItemInfoRecord info;
905 CFStringRef uti = NULL;
906 CFURLRef url = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)filePath, kCFURLPOSIXPathStyle, FALSE);
907 if (LSCopyItemInfoForURL(url, kLSRequestExtension | kLSRequestTypeCreator, &info) == noErr) {
908 // Obtain the UTI using the file information.
909 // If there is a file extension, get the UTI.
910 if (info.extension != NULL) {
911 uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, info.extension, kUTTypeData);
912 CFRelease(info.extension);
916 // If there is an OSType, get the UTI.
917 CFStringRef typeString = UTCreateStringForOSType(info.filetype);
918 if ( typeString != NULL) {
919 uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, typeString, kUTTypeData);
920 CFRelease(typeString);
924 CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
926 return (NSString *)MIMEType;
932 // Creates a directory if it doesn't exist and returns if successful
933 + (BOOL)safeCreateDirectory:(NSString *)directoryPath error:(NSError **)error {
934 NSFileManager *fileManager = [NSFileManager defaultManager];
936 BOOL fileExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory];
939 if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:error] || *error)
944 // Removes contents of a directory
945 + (void)removeContentsAtPath:(NSString *)dirPath {
946 NSFileManager *fileManager = [NSFileManager defaultManager];
947 NSError *error = nil;
949 if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory])
952 for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:dirPath error:&error]) {
954 dispatch_async(dispatch_get_main_queue(), ^{
955 [self fileActionFailedAlertWithTitle:@"Directory Contents Error"
956 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", dirPath]
961 NSString *subFilePath = [dirPath stringByAppendingPathComponent:subPath];
962 if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
963 dispatch_async(dispatch_get_main_queue(), ^{
964 [self fileActionFailedAlertWithTitle:@"Remove File Error"
965 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
971 } else if (![fileManager removeItemAtPath:dirPath error:&error] || error) {
972 dispatch_async(dispatch_get_main_queue(), ^{
973 [self fileActionFailedAlertWithTitle:@"Remove File Error"
974 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", dirPath]
980 // Returns if an object is a directory based on its content type
981 + (BOOL)isContentTypeDirectory:(NSString *)contentType {
982 return ([contentType isEqualToString:@"application/directory"] ||
983 [contentType hasPrefix:@"application/directory;"] ||
984 [contentType isEqualToString:@"application/folder"] ||
985 [contentType hasPrefix:@"application/folder;"]);
988 // Returns if an object exists at the given container/object path and if this object is an application/directory
989 // If an error occured an alert is shown and it is returned so the caller won't proceed
990 + (BOOL)objectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName
991 error:(NSError **)error isDirectory:(BOOL *)isDirectory sharingAccount:(NSString *)sharingAccount {
992 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos
993 containerName:containerName
994 objectName:objectName];
996 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
997 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
999 [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:objectRequest]] waitUntilFinished:YES];
1000 *error = [objectRequest error];
1002 dispatch_async(dispatch_get_main_queue(), ^{
1003 [self httpRequestErrorAlertWithRequest:objectRequest];
1006 } else if (objectRequest.responseStatusCode == 200) {
1007 *isDirectory = [self isContentTypeDirectory:[objectRequest contentType]];
1013 // Returns if the caller should proceed, after an interactive check if an object exists
1014 // at the given container/object path is performed
1015 + (BOOL)proceedIfObjectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName
1016 sharingAccount:(NSString *)sharingAccount {
1017 NSError *error = nil;
1019 BOOL objectExists = [self objectExistsAtPithos:pithos containerName:containerName objectName:objectName
1020 error:&error isDirectory:&isDirectory sharingAccount:sharingAccount];
1023 } else if (objectExists) {
1024 __block NSInteger choice;
1025 dispatch_sync(dispatch_get_main_queue(), ^{
1026 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1028 [alert setMessageText:@"Directory Exists"];
1030 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
1032 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1034 [alert setMessageText:@"Object Exists"];
1036 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
1038 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1040 [alert addButtonWithTitle:@"OK"];
1041 [alert addButtonWithTitle:@"Cancel"];
1042 choice = [alert runModal];
1044 if (choice == NSAlertSecondButtonReturn)
1050 // List of objects at the given container/object path, with prefix and or delimiter
1051 + (NSArray *)objectsWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix
1052 delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1053 NSMutableArray *objects = [NSMutableArray array];
1054 NSString *marker = nil;
1056 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1057 containerName:containerName
1060 prefix:objectNamePrefix
1067 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
1068 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1070 [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:containerRequest]] waitUntilFinished:YES];
1071 if ([containerRequest error]) {
1072 dispatch_sync(dispatch_get_main_queue(), ^{
1073 [self httpRequestErrorAlertWithRequest:containerRequest];
1077 NSArray *someObjects = [containerRequest objects];
1078 [objects addObjectsFromArray:someObjects];
1079 if ([someObjects count] < 10000)
1082 marker = [[someObjects lastObject] name];
1087 // List of objects at the given container/object path, that may be a subdir or an application/directory,
1088 // with prefix and or delimiter
1089 + (NSArray *)objectsForSubdirWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName
1090 delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1091 NSString *subdirNamePrefix = [NSString stringWithString:objectName];
1092 if (![subdirNamePrefix hasSuffix:@"/"])
1093 subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"];
1094 return [self objectsWithPithos:pithos containerName:containerName objectNamePrefix:subdirNamePrefix
1095 delimiter:delimiter sharingAccount:sharingAccount];
1098 // A safe object name at the given container/object path
1099 // The original name has " %d" appended to it before any ".*" suffix, for the first integer that produces a name that is free to use
1100 // If the original name hasn't got a "." but has a "/" suffix, then it is replaced with " %d/" instead
1101 // Subdirs are taken into consideration
1102 + (NSString *)safeObjectNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName {
1103 NSString *objectNamePrefix;
1104 NSString *objectNameExtraSuffix;
1105 NSRange lastDotRange = [objectName rangeOfString:@"." options:NSBackwardsSearch];
1106 if (lastDotRange.length == 1) {
1107 objectNamePrefix = [objectName substringToIndex:lastDotRange.location];
1108 objectNameExtraSuffix = [objectName substringFromIndex:lastDotRange.location];
1109 } else if ([objectName hasSuffix:@"/"]) {
1110 objectNamePrefix = [objectName substringToIndex:([objectName length] - 1)];
1111 objectNameExtraSuffix = [NSString stringWithString:@"/"];
1113 objectNamePrefix = [NSString stringWithString:objectName];
1114 objectNameExtraSuffix = [NSString string];
1116 NSArray *objects = [self objectsWithPithos:pithos containerName:containerName
1117 objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent]
1118 delimiter:@"/" sharingAccount:nil];
1121 if ([objects count] == 0)
1123 NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1124 [[objects objectsAtIndexes:
1125 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1126 if (pithosObject.subdir)
1129 }]] valueForKey:@"name"]];
1130 for (NSString *name in [[objects objectsAtIndexes:
1131 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1132 if (pithosObject.subdir)
1135 }]] valueForKey:@"name"]) {
1136 [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1138 if (![objectNames containsObject:objectName])
1140 NSUInteger objectNameSuffix = 2;
1141 NSString *safeObjectName;
1143 safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix];
1145 } while ([objectNames containsObject:safeObjectName]);
1146 return safeObjectName;
1149 // A safe object name at the given container/object path that may be a subdir or application/directory
1150 // The original name has " %d" appended to it, for the first integer that produces a name that is free to use
1151 // If the original name has a "/" suffix, then it is replaced with " %d/" instead
1152 // Subdirs are taken into consideration
1153 + (NSString *)safeSubdirNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName subdirName:(NSString *)subdirName {
1154 NSString *subdirNamePrefix;
1155 NSString *subdirNameExtraSuffix;
1156 if ([subdirName hasSuffix:@"/"]) {
1157 subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)];
1158 subdirNameExtraSuffix = [NSString stringWithString:@"/"];
1160 subdirNamePrefix = [NSString stringWithString:subdirName];
1161 subdirNameExtraSuffix = [NSString string];
1163 NSArray *objects = [self objectsWithPithos:pithos containerName:containerName
1164 objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent]
1165 delimiter:@"/" sharingAccount:nil];
1168 if ([objects count] == 0)
1170 NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1171 [[objects objectsAtIndexes:
1172 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1173 if (pithosObject.subdir)
1176 }]] valueForKey:@"name"]];
1177 for (NSString *name in [[objects objectsAtIndexes:
1178 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1179 if (pithosObject.subdir)
1182 }]] valueForKey:@"name"]) {
1183 [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1185 if (![objectNames containsObject:subdirNamePrefix])
1187 NSUInteger subdirNameSuffix = 2;
1188 NSString *safeSubdirName;
1190 safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix];
1192 } while ([objectNames containsObject:safeSubdirName]);
1193 return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix];
1199 + (NSInteger)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
1200 NSString *message = [NSString stringWithFormat:
1201 @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@",
1203 request.requestMethod,
1205 [request requestHeaders],
1206 [request responseHeaders],
1207 [request responseString]];
1208 NSLog(@"%@", message);
1209 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1210 [alert setMessageText:@"HTTP Request Error"];
1211 [alert setInformativeText:message];
1212 [alert addButtonWithTitle:@"OK"];
1213 return [alert runModal];
1216 + (NSInteger)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
1217 NSString *message = [NSString stringWithFormat:
1218 @"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@",
1219 request.responseStatusCode,
1220 request.responseStatusMessage,
1221 request.requestMethod,
1223 [request requestHeaders],
1224 [request responseHeaders],
1225 [request responseString]];
1226 NSLog(@"%@", message);
1227 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1228 [alert setMessageText:@"Unexpected Response Status"];
1229 [alert setInformativeText:message];
1230 [alert addButtonWithTitle:@"OK"];
1231 return [alert runModal];
1234 + (NSInteger)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error {
1235 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1236 [alert setMessageText:title];
1238 [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, error]];
1240 [alert setInformativeText:message];
1241 [alert addButtonWithTitle:@"OK"];
1242 return [alert runModal];
1246 #pragma mark Request Helper Methods
1248 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority {
1249 [request setTimeOutSeconds:60];
1250 request.numberOfTimesToRetryOnTimeout = 10;
1251 [request setQueuePriority:priority];
1255 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request {
1256 return [self prepareRequest:request priority:NSOperationQueuePriorityNormal];
1259 + (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request {
1260 NSMutableDictionary *userInfo = (NSMutableDictionary *)[[request.userInfo retain] autorelease];
1261 request.userInfo = nil;
1262 ASIPithosRequest *newRequest = [request copy];
1263 newRequest.userInfo = userInfo;