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 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
74 [alert setMessageText:@"File Exists"];
75 [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", fileName]];
76 [alert addButtonWithTitle:@"OK"];
77 [alert addButtonWithTitle:@"Cancel"];
78 NSInteger choice = [alert runModal];
79 if (choice == NSAlertSecondButtonReturn)
83 BOOL directoryIsDirectory;
84 BOOL directoryExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&directoryIsDirectory];
86 if (!directoryExists) {
87 [fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
88 } else if (!directoryIsDirectory) {
89 [fileManager removeItemAtPath:directoryPath error:&error];
92 NSLog(@"Cannot remove existing file '%@': %@", fileName, error);
93 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
94 [alert setMessageText:@"Removal Error"];
95 [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file '%@': %@", fileName, error]];
96 [alert addButtonWithTitle:@"OK"];
101 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos
102 containerName:containerName
103 objectName:objectName
106 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
107 objectRequest.downloadDestinationPath = destinationPath;
108 objectRequest.allowResumeForFileDownloads = YES;
109 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
110 fileName, @"fileName",
111 destinationPath, @"filePath",
113 return objectRequest;
116 + (NSArray *)objectDataRequestsForSubdirWithPithos:(ASIPithos *)pithos
117 containerName:(NSString *)containerName
118 objectName:(NSString *)objectName
119 toDirectory:(NSString *)directoryPath
120 checkIfExists:(BOOL)ifExists
121 sharingAccount:(NSString *)sharingAccount {
122 NSString *subdirName = [objectName lastPathComponent];
123 NSString *destinationPath = [directoryPath stringByAppendingPathComponent:subdirName];
124 if (ifExists && [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) {
125 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
126 [alert setMessageText:@"File exists"];
127 [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", subdirName]];
128 [alert addButtonWithTitle:@"OK"];
129 [alert addButtonWithTitle:@"Cancel"];
130 NSInteger choice = [alert runModal];
131 if (choice == NSAlertSecondButtonReturn)
135 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName
136 delimiter:nil sharingAccount:sharingAccount];
140 NSFileManager *fileManager = [NSFileManager defaultManager];
141 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[objects count]];
142 NSUInteger subdirPrefixLength = [objectName length];
144 NSError *error = nil;
145 [fileManager createDirectoryAtPath:[directoryPath stringByAppendingPathComponent:subdirName] withIntermediateDirectories:YES attributes:nil error:&error];
147 NSLog(@"Cannot create directory at '%@': %@", directoryPath, error);
148 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
149 [alert setMessageText:@"Create Directory Error"];
150 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@",
151 directoryPath, error]];
152 [alert addButtonWithTitle:@"OK"];
156 for (ASIPithosObject *object in objects) {
157 if ([self isContentTypeDirectory:object.contentType]) {
158 NSString *subdirDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
159 subdirDirectoryPath = [subdirDirectoryPath stringByAppendingPathComponent:[object.name substringFromIndex:subdirPrefixLength]];
161 BOOL directoryIsDirectory;
162 BOOL directoryExists = [fileManager fileExistsAtPath:subdirDirectoryPath isDirectory:&directoryIsDirectory];
163 NSError *error = nil;
164 if (!directoryExists) {
165 [fileManager createDirectoryAtPath:subdirDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error];
167 NSLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error);
168 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
169 [alert setMessageText:@"Create Directory Error"];
170 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@",
171 subdirDirectoryPath, error]];
172 [alert addButtonWithTitle:@"OK"];
175 } else if (!directoryIsDirectory) {
176 [fileManager removeItemAtPath:subdirDirectoryPath error:&error];
178 NSLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error);
179 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
180 [alert setMessageText:@"Remove File Error"];
181 [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file at '%@': %@",
182 subdirDirectoryPath, error]];
183 [alert addButtonWithTitle:@"OK"];
188 NSString *fileName = [object.name lastPathComponent];
189 if([object.name hasSuffix:@"/"])
190 fileName = [fileName stringByAppendingString:@"/"];
192 NSString *objectDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
193 objectDirectoryPath = [objectDirectoryPath stringByAppendingPathComponent:[object.name substringWithRange:NSMakeRange(subdirPrefixLength, [object.name length] - subdirPrefixLength - [fileName length])]];
195 ASIPithosObjectRequest *objectRequest = [self objectDataRequestWithPithos:pithos
196 containerName:containerName
197 objectName:object.name
199 toDirectory:objectDirectoryPath
202 sharingAccount:sharingAccount];
203 [(NSMutableDictionary *)objectRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:object.bytes] forKey:@"bytes"];
204 [objectRequests addObject:objectRequest];
208 return objectRequests;
212 #pragma mark Download Block
214 + (ASIPithosObjectRequest *)objectBlockDataRequestWithPithos:(ASIPithos *)pithos
215 containerName:(NSString *)containerName
216 object:(ASIPithosObject *)object
217 blockIndex:(NSUInteger)blockIndex
218 blockSize:(NSUInteger)blockSize {
219 NSUInteger rangeStart = blockIndex * blockSize;
220 NSUInteger rangeEnd = (rangeStart + blockSize <= object.bytes) ? (rangeStart + blockSize - 1) : (object.bytes - 1);
221 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos
222 containerName:containerName
223 objectName:object.name
225 range:[NSString stringWithFormat:@"bytes=%lu-%lu", rangeStart, rangeEnd]
226 ifMatch:object.hash];
227 return objectRequest;
230 + (NSIndexSet *)missingBlocksForFile:(NSString *)filePath
231 blockSize:(NSUInteger)blockSize
232 blockHash:(NSString *)blockHash
233 withHashes:(NSArray *)hashes {
234 NSArray *fileHashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
235 return [hashes indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
236 if ((idx >= [fileHashes count]) || ![(NSString *)obj isEqualToString:[fileHashes objectAtIndex:idx]])
245 + (ASIPithosObjectRequest *)writeObjectDataRequestWithPithos:(ASIPithos *)pithos
246 containerName:(NSString *)containerName
247 objectName:(NSString *)objectName
248 contentType:(NSString *)contentType
249 blockSize:(NSUInteger)blockSize
250 blockHash:(NSString *)blockHash
251 forFile:(NSString *)filePath
252 checkIfExists:(BOOL)ifExists
253 hashes:(NSArray **)hashes
254 sharingAccount:(NSString *)sharingAccount {
255 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName
256 sharingAccount:(NSString *)sharingAccount])
260 *hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
263 NSString *fileName = [filePath lastPathComponent];
264 if ([filePath hasSuffix:@"/"])
265 fileName = [fileName stringByAppendingString:@"/"];
266 NSUInteger bytes = [self bytesOfFile:filePath];
267 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
268 containerName:containerName
269 objectName:objectName
270 contentType:contentType
272 contentDisposition:nil
275 isPublic:ASIPithosObjectRequestPublicIgnore
282 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
283 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
284 fileName, @"fileName",
285 [NSNumber numberWithUnsignedInteger:bytes], @"bytes",
287 return objectRequest;
290 + (NSIndexSet *)missingBlocksForHashes:(NSArray *)hashes withMissingHashes:(NSArray *)missingHashes {
291 NSMutableIndexSet *missingBlocks = [NSMutableIndexSet indexSet];
292 for (NSString *missingHash in missingHashes) {
293 if (![missingHash length])
295 NSUInteger missingBlock = [hashes indexOfObject:missingHash];
296 if (missingBlock != NSNotFound)
297 [missingBlocks addIndex:missingBlock];
299 return missingBlocks;
302 + (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos
303 containerName:(NSString *)containerName
304 blockSize:(NSUInteger)blockSize
305 forFile:(NSString *)filePath
306 hashes:(NSArray *)hashes
307 missingHashes:(NSArray *)missingHashes
308 sharingAccount:(NSString *)sharingAccount {
309 NSIndexSet *missingBlocks = [self missingBlocksForHashes:hashes withMissingHashes:missingHashes];
311 NSFileManager *fileManager = [NSFileManager defaultManager];
312 NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
314 // http://cocoawithlove.com/2009/07/temporary-files-and-folders-in-cocoa.html
315 NSString *tempFileTemplate = NSTemporaryDirectory();
316 if (tempFileTemplate == nil)
317 tempFileTemplate = @"/tmp";
318 tempFileTemplate = [tempFileTemplate stringByAppendingPathComponent:@"pithos-macos.XXXXXX"];
319 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
320 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
321 strcpy(tempFileNameCString, tempFileTemplateCString);
322 int fileDescriptor = mkstemp(tempFileNameCString);
323 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
324 if (fileDescriptor == -1) {
325 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
326 [alert setMessageText:@"Create Temporary File Error"];
327 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]];
328 [alert addButtonWithTitle:@"OK"];
332 free(tempFileNameCString);
333 NSFileHandle *tempFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor closeOnDealloc:YES];
335 [missingBlocks enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
336 [fileHandle seekToFileOffset:(idx*blockSize)];
337 [tempFileHandle writeData:[fileHandle readDataOfLength:blockSize]];
339 [tempFileHandle closeFile];
340 [fileHandle closeFile];
342 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos
343 containerName:containerName
349 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
350 return containerRequest;
353 + (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos
354 containerName:(NSString *)containerName
355 blockSize:(NSUInteger)blockSize
356 forFile:(NSString *)filePath
357 missingBlockIndex:(NSUInteger)missingBlockIndex
358 sharingAccount:(NSString *)sharingAccount {
359 NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
360 [fileHandle seekToFileOffset:(missingBlockIndex *blockSize)];
361 NSData *blockData = [fileHandle readDataOfLength:blockSize];
362 [fileHandle closeFile];
363 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos
364 containerName:containerName
370 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
371 return containerRequest;
374 + (NSArray *)writeObjectDataRequestsWithPithos:(ASIPithos *)pithos
375 containerName:(NSString *)containerName
376 objectName:(NSString *)objectName
377 blockSize:(NSUInteger)blockSize
378 blockHash:(NSString *)blockHash
379 forDirectory:(NSString *)directoryPath
380 checkIfExists:(BOOL)ifExists
381 objectNames:(NSMutableArray **)objectNames
382 contentTypes:(NSMutableArray **)contentTypes
383 filePaths:(NSMutableArray **)filePaths
384 hashesArrays:(NSMutableArray **)hashesArrays
385 directoryObjectRequests:(NSMutableArray **) directoryObjectRequests
386 sharingAccount:(NSString *)sharingAccount {
387 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName
388 sharingAccount:sharingAccount])
391 NSFileManager *fileManager = [NSFileManager defaultManager];
392 NSError *error = nil;
393 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:directoryPath error:&error];
395 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
396 [alert setMessageText:@"Directory Read Error"];
397 [alert setInformativeText:[NSString stringWithFormat:@"Cannot read contents of directory '%@': %@",
398 [directoryPath lastPathComponent], error]];
399 [alert addButtonWithTitle:@"OK"];
404 *directoryObjectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
405 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
406 containerName:containerName
407 objectName:objectName
409 contentType:@"application/directory"
411 contentDisposition:nil
414 isPublic:ASIPithosObjectRequestPublicIgnore
418 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
419 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
420 [directoryPath lastPathComponent], @"fileName",
422 [*directoryObjectRequests addObject:objectRequest];
424 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
425 *objectNames = [NSMutableArray arrayWithCapacity:[subPaths count]];
426 *contentTypes = [NSMutableArray arrayWithCapacity:[subPaths count]];
427 *filePaths = [NSMutableArray arrayWithCapacity:[subPaths count]];
428 *hashesArrays = [NSMutableArray arrayWithCapacity:[subPaths count]];
430 NSString *subObjectName;
433 NSString *contentType;
436 for (NSString *objectNameSuffix in subPaths) {
437 filePath = [directoryPath stringByAppendingPathComponent:objectNameSuffix];
438 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
440 hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
442 subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping];
443 fileName = [filePath lastPathComponent];
444 if ([filePath hasSuffix:@"/"])
445 fileName = [fileName stringByAppendingString:@"/"];
446 bytes = [self bytesOfFile:filePath];
448 contentType = [self contentTypeOfFile:filePath error:&error];
449 if (contentType == nil)
450 contentType = @"application/octet-stream";
452 NSLog(@"contentType detection error: %@", error);
453 objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
454 containerName:containerName
455 objectName:subObjectName
456 contentType:contentType
458 contentDisposition:nil
461 isPublic:ASIPithosObjectRequestPublicIgnore
468 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
469 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
470 fileName, @"fileName",
471 [NSNumber numberWithUnsignedInteger:bytes], @"bytes",
473 [objectRequests addObject:objectRequest];
474 [*objectNames addObject:subObjectName];
475 [*contentTypes addObject:contentType];
476 [*filePaths addObject:filePath];
477 [*hashesArrays addObject:hashes];
481 subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping];
482 fileName = [filePath lastPathComponent];
483 objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
484 containerName:containerName
485 objectName:subObjectName
487 contentType:@"application/directory"
489 contentDisposition:nil
492 isPublic:ASIPithosObjectRequestPublicIgnore
496 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
497 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
498 fileName, @"fileName",
500 [*directoryObjectRequests addObject:objectRequest];
505 return objectRequests;
511 + (NSArray *)deleteObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos
512 containerName:(NSString *)containerName
513 objectName:(NSString *)objectName {
514 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName delimiter:nil
519 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
520 ASIPithosObjectRequest *objectRequest;
521 if (![objectName hasSuffix:@"/"]) {
522 objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:objectName];
523 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
524 [objectName lastPathComponent], @"fileName",
526 [objectRequests addObject:objectRequest];
529 for (ASIPithosObject *object in objects) {
530 fileName = [object.name lastPathComponent];
531 if ([object.name hasSuffix:@"/"])
532 fileName = [fileName stringByAppendingString:@"/"];
533 objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:object.name];
534 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
535 fileName, @"fileName",
537 [objectRequests addObject:objectRequest];
540 if ([objectRequests count] == 0)
542 return objectRequests;
548 + (ASIPithosObjectRequest *)copyObjectRequestWithPithos:(ASIPithos *)pithos
549 containerName:(NSString *)containerName
550 objectName:(NSString *)objectName
551 destinationContainerName:(NSString *)destinationContainerName
552 destinationObjectName:(NSString *)destinationObjectName
553 checkIfExists:(BOOL)ifExists
554 sharingAccount:(NSString *)sharingAccount {
555 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
559 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
560 containerName:containerName
561 objectName:objectName
564 contentDisposition:nil
567 isPublic:ASIPithosObjectRequestPublicIgnore
569 destinationContainerName:destinationContainerName
570 destinationObjectName:destinationObjectName
571 destinationAccount:nil
573 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
574 containerName, @"sourceContainerName",
575 objectName, @"sourceObjectName",
576 destinationContainerName, @"destinationContainerName",
577 destinationObjectName, @"destinationObjectName",
580 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
581 return objectRequest;
584 + (NSArray *)copyObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos
585 containerName:(NSString *)containerName
586 objectName:(NSString *)objectName
587 destinationContainerName:(NSString *)destinationContainerName
588 destinationObjectName:(NSString *)destinationObjectName
589 checkIfExists:(BOOL)ifExists
590 sharingAccount:(NSString *)sharingAccount {
591 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
595 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName
596 delimiter:nil sharingAccount:sharingAccount];
600 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
601 ASIPithosObjectRequest *objectRequest;
602 if ([objectName isEqualToString:destinationObjectName]) {
603 if (![objectName hasSuffix:@"/"]) {
604 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
605 containerName:containerName
606 objectName:objectName
609 contentDisposition:nil
612 isPublic:ASIPithosObjectRequestPublicIgnore
614 destinationContainerName:destinationContainerName
615 destinationObjectName:objectName
616 destinationAccount:nil
618 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
619 containerName, @"sourceContainerName",
620 objectName, @"sourceObjectName",
621 destinationContainerName, @"destinationContainerName",
622 objectName, @"destinationObjectName",
625 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
626 [objectRequests addObject:objectRequest];
628 for (ASIPithosObject *object in objects) {
629 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
630 containerName:containerName
631 objectName:object.name
634 contentDisposition:nil
637 isPublic:ASIPithosObjectRequestPublicIgnore
639 destinationContainerName:destinationContainerName
640 destinationObjectName:object.name
641 destinationAccount:nil
643 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
644 containerName, @"sourceContainerName",
645 object.name, @"sourceObjectName",
646 destinationContainerName, @"destinationContainerName",
647 object.name, @"destinationObjectName",
650 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
651 [objectRequests addObject:objectRequest];
654 if (![objectName hasSuffix:@"/"]) {
655 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
656 containerName:containerName
657 objectName:objectName
660 contentDisposition:nil
663 isPublic:ASIPithosObjectRequestPublicIgnore
665 destinationContainerName:destinationContainerName
666 destinationObjectName:destinationObjectName
667 destinationAccount:nil
669 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
670 containerName, @"sourceContainerName",
671 objectName, @"sourceObjectName",
672 destinationContainerName, @"destinationContainerName",
673 destinationObjectName, @"destinationObjectName",
676 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
677 [objectRequests addObject:objectRequest];
679 NSRange prefixRange = NSMakeRange(0, [objectName length]);
680 NSString *newObjectName;
681 for (ASIPithosObject *object in objects) {
682 newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
683 withString:destinationObjectName
684 options:NSAnchoredSearch
686 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
687 containerName:containerName
688 objectName:object.name
691 contentDisposition:nil
694 isPublic:ASIPithosObjectRequestPublicIgnore
696 destinationContainerName:destinationContainerName
697 destinationObjectName:newObjectName
698 destinationAccount:nil
700 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
701 containerName, @"sourceContainerName",
702 object.name, @"sourceObjectName",
703 destinationContainerName, @"destinationContainerName",
704 newObjectName, @"destinationObjectName",
707 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
708 [objectRequests addObject:objectRequest];
712 if ([objectRequests count] == 0)
714 return objectRequests;
720 + (ASIPithosObjectRequest *)moveObjectRequestWithPithos:(ASIPithos *)pithos
721 containerName:(NSString *)containerName
722 objectName:(NSString *)objectName
723 destinationContainerName:(NSString *)destinationContainerName
724 destinationObjectName:(NSString *)destinationObjectName
725 checkIfExists:(BOOL)ifExists {
726 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
730 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
731 containerName:containerName
732 objectName:objectName
735 contentDisposition:nil
738 isPublic:ASIPithosObjectRequestPublicIgnore
740 destinationContainerName:destinationContainerName
741 destinationObjectName:destinationObjectName
742 destinationAccount:nil];
743 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
744 containerName, @"sourceContainerName",
745 objectName, @"sourceObjectName",
746 destinationContainerName, @"destinationContainerName",
747 destinationObjectName, @"destinationObjectName",
749 return objectRequest;
752 + (NSArray *)moveObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos
753 containerName:(NSString *)containerName
754 objectName:(NSString *)objectName
755 destinationContainerName:(NSString *)destinationContainerName
756 destinationObjectName:(NSString *)destinationObjectName
757 checkIfExists:(BOOL)ifExists {
758 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
762 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName
763 delimiter:nil sharingAccount:nil];
767 ASIPithosObjectRequest *objectRequest;
768 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
769 if ([objectName isEqualToString:destinationObjectName]) {
770 if (![objectName hasSuffix:@"/"]) {
771 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
772 containerName:containerName
773 objectName:objectName
776 contentDisposition:nil
779 isPublic:ASIPithosObjectRequestPublicIgnore
781 destinationContainerName:destinationContainerName
782 destinationObjectName:objectName
783 destinationAccount:nil];
784 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
785 containerName, @"sourceContainerName",
786 objectName, @"sourceObjectName",
787 destinationContainerName, @"destinationContainerName",
788 objectName, @"destinationObjectName",
790 [objectRequests addObject:objectRequest];
792 for (ASIPithosObject *object in objects) {
793 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
794 containerName:containerName
795 objectName:object.name
798 contentDisposition:nil
801 isPublic:ASIPithosObjectRequestPublicIgnore
803 destinationContainerName:destinationContainerName
804 destinationObjectName:object.name
805 destinationAccount:nil];
806 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
807 containerName, @"sourceContainerName",
808 object.name, @"sourceObjectName",
809 destinationContainerName, @"destinationContainerName",
810 object.name, @"destinationObjectName",
812 [objectRequests addObject:objectRequest];
815 if (![objectName hasSuffix:@"/"]) {
816 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
817 containerName:containerName
818 objectName:objectName
821 contentDisposition:nil
824 isPublic:ASIPithosObjectRequestPublicIgnore
826 destinationContainerName:destinationContainerName
827 destinationObjectName:destinationObjectName
828 destinationAccount:nil];
829 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
830 containerName, @"sourceContainerName",
831 objectName, @"sourceObjectName",
832 destinationContainerName, @"destinationContainerName",
833 destinationObjectName, @"destinationObjectName",
835 [objectRequests addObject:objectRequest];
837 NSRange prefixRange = NSMakeRange(0, [objectName length]);
838 NSString *newObjectName;
839 for (ASIPithosObject *object in objects) {
840 newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
841 withString:destinationObjectName
842 options:NSAnchoredSearch
844 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
845 containerName:containerName
846 objectName:object.name
849 contentDisposition:nil
852 isPublic:ASIPithosObjectRequestPublicIgnore
854 destinationContainerName:destinationContainerName
855 destinationObjectName:newObjectName
856 destinationAccount:nil];
857 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
858 containerName, @"sourceContainerName",
859 object.name, @"sourceObjectName",
860 destinationContainerName, @"destinationContainerName",
861 newObjectName, @"destinationObjectName",
863 [objectRequests addObject:objectRequest];
867 if ([objectRequests count] == 0)
869 return objectRequests;
873 #pragma mark Helper Methods
875 // Size of the file in bytes
876 + (NSUInteger)bytesOfFile:(NSString *)filePath {
877 NSFileManager *fileManager = [NSFileManager defaultManager];
878 NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil];
879 return [[attributes objectForKey:NSFileSize] unsignedIntegerValue];
882 // Content type of the file or nil if it cannot be determined
883 + (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error {
884 // Based on http://www.ddeville.me/2011/12/mime-to-UTI-cocoa/
885 // and Apple example ImageBrowserViewAppearance/ImageBrowserController.m
886 LSItemInfoRecord info;
887 CFStringRef uti = NULL;
888 CFURLRef url = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)filePath, kCFURLPOSIXPathStyle, FALSE);
889 if (LSCopyItemInfoForURL(url, kLSRequestExtension | kLSRequestTypeCreator, &info) == noErr) {
890 // Obtain the UTI using the file information.
891 // If there is a file extension, get the UTI.
892 if (info.extension != NULL) {
893 uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, info.extension, kUTTypeData);
894 CFRelease(info.extension);
898 // If there is an OSType, get the UTI.
899 CFStringRef typeString = UTCreateStringForOSType(info.filetype);
900 if ( typeString != NULL) {
901 uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, typeString, kUTTypeData);
902 CFRelease(typeString);
906 CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
908 return (NSString *)MIMEType;
914 // Creates a directory if it doesn't exist and returns if successful
915 + (BOOL)safeCreateDirectory:(NSString *)directoryPath error:(NSError **)error {
916 NSFileManager *fileManager = [NSFileManager defaultManager];
918 BOOL fileExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory];
921 if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:error] || *error)
926 // Removes contents of a directory
927 + (void)removeContentsAtPath:(NSString *)dirPath {
928 NSFileManager *fileManager = [NSFileManager defaultManager];
929 NSError *error = nil;
931 if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory])
934 for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:dirPath error:&error]) {
936 [self fileActionFailedAlertWithTitle:@"Directory Contents Error"
937 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", dirPath]
941 NSString *subFilePath = [dirPath stringByAppendingPathComponent:subPath];
942 if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
943 [self fileActionFailedAlertWithTitle:@"Remove File Error"
944 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
949 } else if (![fileManager removeItemAtPath:dirPath error:&error] || error) {
950 [self fileActionFailedAlertWithTitle:@"Remove File Error"
951 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", dirPath]
956 // Returns if an object is a directory based on its content type
957 + (BOOL)isContentTypeDirectory:(NSString *)contentType {
958 return ([contentType isEqualToString:@"application/directory"] ||
959 [contentType hasPrefix:@"application/directory;"] ||
960 [contentType isEqualToString:@"application/folder"] ||
961 [contentType hasPrefix:@"application/folder;"]);
964 // Returns if an object exists at the given container/object path and if this object is an application/directory
965 // If an error occured an alert is shown and it is returned so the caller won't proceed
966 + (BOOL)objectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName
967 error:(NSError **)error isDirectory:(BOOL *)isDirectory sharingAccount:(NSString *)sharingAccount {
968 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos
969 containerName:containerName
970 objectName:objectName];
972 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
973 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
975 [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:objectRequest]] waitUntilFinished:YES];
976 *error = [objectRequest error];
978 [self httpRequestErrorAlertWithRequest:objectRequest];
980 } else if (objectRequest.responseStatusCode == 200) {
981 *isDirectory = [self isContentTypeDirectory:[objectRequest contentType]];
987 // Returns if the caller should proceed, after an interactive check if an object exists
988 // at the given container/object path is performed
989 + (BOOL)proceedIfObjectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName
990 sharingAccount:(NSString *)sharingAccount {
991 NSError *error = nil;
993 BOOL objectExists = [self objectExistsAtPithos:pithos containerName:containerName objectName:objectName
994 error:&error isDirectory:&isDirectory sharingAccount:sharingAccount];
997 } else if (objectExists) {
998 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1000 [alert setMessageText:@"Directory Exists"];
1002 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
1004 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1006 [alert setMessageText:@"Object Exists"];
1008 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
1010 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1012 [alert addButtonWithTitle:@"OK"];
1013 [alert addButtonWithTitle:@"Cancel"];
1014 NSInteger choice = [alert runModal];
1015 if (choice == NSAlertSecondButtonReturn)
1021 // List of objects at the given container/object path, with prefix and or delimiter
1022 + (NSArray *)objectsWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix
1023 delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1024 NSMutableArray *objects = [NSMutableArray array];
1025 NSString *marker = nil;
1027 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1028 containerName:containerName
1031 prefix:objectNamePrefix
1038 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
1039 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1041 [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:containerRequest]] waitUntilFinished:YES];
1042 if ([containerRequest error]) {
1043 [self httpRequestErrorAlertWithRequest:containerRequest];
1046 NSArray *someObjects = [containerRequest objects];
1047 [objects addObjectsFromArray:someObjects];
1048 if ([someObjects count] < 10000)
1051 marker = [[someObjects lastObject] name];
1056 // List of objects at the given container/object path, that may be a subdir or an application/directory,
1057 // with prefix and or delimiter
1058 + (NSArray *)objectsForSubdirWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName
1059 delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1060 NSString *subdirNamePrefix = [NSString stringWithString:objectName];
1061 if (![subdirNamePrefix hasSuffix:@"/"])
1062 subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"];
1063 return [self objectsWithPithos:pithos containerName:containerName objectNamePrefix:subdirNamePrefix
1064 delimiter:delimiter sharingAccount:sharingAccount];
1067 // A safe object name at the given container/object path
1068 // The original name has " %d" appended to it before any ".*" suffix, for the first integer that produces a name that is free to use
1069 // If the original name hasn't got a "." but has a "/" suffix, then it is replaced with " %d/" instead
1070 // Subdirs are taken into consideration
1071 + (NSString *)safeObjectNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName {
1072 NSString *objectNamePrefix;
1073 NSString *objectNameExtraSuffix;
1074 NSRange lastDotRange = [objectName rangeOfString:@"." options:NSBackwardsSearch];
1075 if (lastDotRange.length == 1) {
1076 objectNamePrefix = [objectName substringToIndex:lastDotRange.location];
1077 objectNameExtraSuffix = [objectName substringFromIndex:lastDotRange.location];
1078 } else if ([objectName hasSuffix:@"/"]) {
1079 objectNamePrefix = [objectName substringToIndex:([objectName length] - 1)];
1080 objectNameExtraSuffix = [NSString stringWithString:@"/"];
1082 objectNamePrefix = [NSString stringWithString:objectName];
1083 objectNameExtraSuffix = [NSString string];
1085 NSArray *objects = [self objectsWithPithos:pithos containerName:containerName
1086 objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent]
1087 delimiter:@"/" sharingAccount:nil];
1090 if ([objects count] == 0)
1092 NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1093 [[objects objectsAtIndexes:
1094 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1095 if (pithosObject.subdir)
1098 }]] valueForKey:@"name"]];
1099 for (NSString *name in [[objects objectsAtIndexes:
1100 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1101 if (pithosObject.subdir)
1104 }]] valueForKey:@"name"]) {
1105 [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1107 if (![objectNames containsObject:objectName])
1109 NSUInteger objectNameSuffix = 2;
1110 NSString *safeObjectName;
1112 safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix];
1114 } while ([objectNames containsObject:safeObjectName]);
1115 return safeObjectName;
1118 // A safe object name at the given container/object path that may be a subdir or application/directory
1119 // The original name has " %d" appended to it, for the first integer that produces a name that is free to use
1120 // If the original name has a "/" suffix, then it is replaced with " %d/" instead
1121 // Subdirs are taken into consideration
1122 + (NSString *)safeSubdirNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName subdirName:(NSString *)subdirName {
1123 NSString *subdirNamePrefix;
1124 NSString *subdirNameExtraSuffix;
1125 if ([subdirName hasSuffix:@"/"]) {
1126 subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)];
1127 subdirNameExtraSuffix = [NSString stringWithString:@"/"];
1129 subdirNamePrefix = [NSString stringWithString:subdirName];
1130 subdirNameExtraSuffix = [NSString string];
1132 NSArray *objects = [self objectsWithPithos:pithos containerName:containerName
1133 objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent]
1134 delimiter:@"/" sharingAccount:nil];
1137 if ([objects count] == 0)
1139 NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1140 [[objects objectsAtIndexes:
1141 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1142 if (pithosObject.subdir)
1145 }]] valueForKey:@"name"]];
1146 for (NSString *name in [[objects objectsAtIndexes:
1147 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1148 if (pithosObject.subdir)
1151 }]] valueForKey:@"name"]) {
1152 [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1154 if (![objectNames containsObject:subdirNamePrefix])
1156 NSUInteger subdirNameSuffix = 2;
1157 NSString *safeSubdirName;
1159 safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix];
1161 } while ([objectNames containsObject:safeSubdirName]);
1162 return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix];
1168 + (NSInteger)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
1169 NSString *message = [NSString stringWithFormat:
1170 @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@",
1172 request.requestMethod,
1174 [request requestHeaders],
1175 [request responseHeaders],
1176 [request responseString]];
1177 NSLog(@"%@", message);
1178 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1179 [alert setMessageText:@"HTTP Request Error"];
1180 [alert setInformativeText:message];
1181 [alert addButtonWithTitle:@"OK"];
1182 return [alert runModal];
1185 + (NSInteger)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
1186 NSString *message = [NSString stringWithFormat:
1187 @"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@",
1188 request.responseStatusCode,
1189 request.responseStatusMessage,
1190 request.requestMethod,
1192 [request requestHeaders],
1193 [request responseHeaders],
1194 [request responseString]];
1195 NSLog(@"%@", message);
1196 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1197 [alert setMessageText:@"Unexpected Response Status"];
1198 [alert setInformativeText:message];
1199 [alert addButtonWithTitle:@"OK"];
1200 return [alert runModal];
1203 + (NSInteger)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error {
1204 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1205 [alert setMessageText:title];
1207 [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, error]];
1209 [alert setInformativeText:message];
1210 [alert addButtonWithTitle:@"OK"];
1211 return [alert runModal];
1215 #pragma mark Request Helper Methods
1217 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority {
1218 [request setTimeOutSeconds:60];
1219 request.numberOfTimesToRetryOnTimeout = 10;
1220 [request setQueuePriority:priority];
1224 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request {
1225 return [self prepareRequest:request priority:NSOperationQueuePriorityNormal];
1228 + (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request {
1229 NSMutableDictionary *userInfo = (NSMutableDictionary *)[[request.userInfo retain] autorelease];
1230 request.userInfo = nil;
1231 ASIPithosRequest *newRequest = [request copy];
1232 newRequest.userInfo = userInfo;