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 '%@': %@",
100 fileName, [error localizedDescription]]];
101 [alert addButtonWithTitle:@"OK"];
107 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos
108 containerName:containerName
109 objectName:objectName
112 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
113 objectRequest.downloadDestinationPath = destinationPath;
114 objectRequest.allowResumeForFileDownloads = YES;
115 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
116 fileName, @"fileName",
117 destinationPath, @"filePath",
119 return objectRequest;
122 + (NSArray *)objectDataRequestsForSubdirWithPithos:(ASIPithos *)pithos
123 containerName:(NSString *)containerName
124 objectName:(NSString *)objectName
125 toDirectory:(NSString *)directoryPath
126 checkIfExists:(BOOL)ifExists
127 sharingAccount:(NSString *)sharingAccount {
128 NSString *subdirName = [objectName lastPathComponent];
129 NSString *destinationPath = [directoryPath stringByAppendingPathComponent:subdirName];
130 if (ifExists && [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) {
131 __block NSInteger choice;
132 dispatch_sync(dispatch_get_main_queue(), ^{
133 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
134 [alert setMessageText:@"File exists"];
135 [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", subdirName]];
136 [alert addButtonWithTitle:@"OK"];
137 [alert addButtonWithTitle:@"Cancel"];
138 choice = [alert runModal];
140 if (choice == NSAlertSecondButtonReturn)
144 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName
145 delimiter:nil sharingAccount:sharingAccount];
149 NSFileManager *fileManager = [NSFileManager defaultManager];
150 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[objects count]];
151 NSUInteger subdirPrefixLength = [objectName length];
153 NSError *error = nil;
154 [fileManager createDirectoryAtPath:[directoryPath stringByAppendingPathComponent:subdirName] withIntermediateDirectories:YES attributes:nil error:&error];
156 NSLog(@"Cannot create directory at '%@': %@", directoryPath, error);
157 dispatch_async(dispatch_get_main_queue(), ^{
158 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
159 [alert setMessageText:@"Create Directory Error"];
160 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@",
161 directoryPath, [error localizedDescription]]];
162 [alert addButtonWithTitle:@"OK"];
167 for (ASIPithosObject *object in objects) {
168 if ([self isContentTypeDirectory:object.contentType]) {
169 NSString *subdirDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
170 subdirDirectoryPath = [subdirDirectoryPath stringByAppendingPathComponent:[object.name substringFromIndex:subdirPrefixLength]];
172 BOOL directoryIsDirectory;
173 BOOL directoryExists = [fileManager fileExistsAtPath:subdirDirectoryPath isDirectory:&directoryIsDirectory];
174 NSError *error = nil;
175 if (!directoryExists) {
176 [fileManager createDirectoryAtPath:subdirDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error];
178 NSLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error);
179 dispatch_async(dispatch_get_main_queue(), ^{
180 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
181 [alert setMessageText:@"Create Directory Error"];
182 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@",
183 subdirDirectoryPath, [error localizedDescription]]];
184 [alert addButtonWithTitle:@"OK"];
188 } else if (!directoryIsDirectory) {
189 [fileManager removeItemAtPath:subdirDirectoryPath error:&error];
191 NSLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error);
192 dispatch_async(dispatch_get_main_queue(), ^{
193 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
194 [alert setMessageText:@"Remove File Error"];
195 [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file at '%@': %@",
196 subdirDirectoryPath, [error localizedDescription]]];
197 [alert addButtonWithTitle:@"OK"];
203 NSString *fileName = [object.name lastPathComponent];
204 if([object.name hasSuffix:@"/"])
205 fileName = [fileName stringByAppendingString:@"/"];
207 NSString *objectDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
208 objectDirectoryPath = [objectDirectoryPath stringByAppendingPathComponent:[object.name substringWithRange:NSMakeRange(subdirPrefixLength, [object.name length] - subdirPrefixLength - [fileName length])]];
210 ASIPithosObjectRequest *objectRequest = [self objectDataRequestWithPithos:pithos
211 containerName:containerName
212 objectName:object.name
214 toDirectory:objectDirectoryPath
217 sharingAccount:sharingAccount];
218 [(NSMutableDictionary *)objectRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:object.bytes] forKey:@"bytes"];
219 [objectRequests addObject:objectRequest];
223 return objectRequests;
227 #pragma mark Download Block
229 + (ASIPithosObjectRequest *)objectBlockDataRequestWithPithos:(ASIPithos *)pithos
230 containerName:(NSString *)containerName
231 object:(ASIPithosObject *)object
232 blockIndex:(NSUInteger)blockIndex
233 blockSize:(NSUInteger)blockSize {
234 NSUInteger rangeStart = blockIndex * blockSize;
235 NSUInteger rangeEnd = (rangeStart + blockSize <= object.bytes) ? (rangeStart + blockSize - 1) : (object.bytes - 1);
236 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos
237 containerName:containerName
238 objectName:object.name
240 range:[NSString stringWithFormat:@"bytes=%lu-%lu", rangeStart, rangeEnd]
241 ifMatch:object.hash];
242 return objectRequest;
245 + (NSIndexSet *)missingBlocksForFile:(NSString *)filePath
246 blockSize:(NSUInteger)blockSize
247 blockHash:(NSString *)blockHash
248 withHashes:(NSArray *)hashes {
249 NSArray *fileHashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
250 return [hashes indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
251 if ((idx >= [fileHashes count]) || ![(NSString *)obj isEqualToString:[fileHashes objectAtIndex:idx]])
260 + (ASIPithosObjectRequest *)writeObjectDataRequestWithPithos:(ASIPithos *)pithos
261 containerName:(NSString *)containerName
262 objectName:(NSString *)objectName
263 contentType:(NSString *)contentType
264 blockSize:(NSUInteger)blockSize
265 blockHash:(NSString *)blockHash
266 forFile:(NSString *)filePath
267 checkIfExists:(BOOL)ifExists
268 hashes:(NSArray **)hashes
269 sharingAccount:(NSString *)sharingAccount {
270 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName
271 sharingAccount:(NSString *)sharingAccount])
275 *hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
278 NSString *fileName = [filePath lastPathComponent];
279 if ([filePath hasSuffix:@"/"])
280 fileName = [fileName stringByAppendingString:@"/"];
281 NSUInteger bytes = [self bytesOfFile:filePath];
282 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
283 containerName:containerName
284 objectName:objectName
285 contentType:contentType
287 contentDisposition:nil
290 isPublic:ASIPithosObjectRequestPublicIgnore
297 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
298 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
299 fileName, @"fileName",
300 [NSNumber numberWithUnsignedInteger:bytes], @"bytes",
302 return objectRequest;
305 + (NSIndexSet *)missingBlocksForHashes:(NSArray *)hashes withMissingHashes:(NSArray *)missingHashes {
306 NSMutableIndexSet *missingBlocks = [NSMutableIndexSet indexSet];
307 for (NSString *missingHash in missingHashes) {
308 if (![missingHash length])
310 NSUInteger missingBlock = [hashes indexOfObject:missingHash];
311 if (missingBlock != NSNotFound)
312 [missingBlocks addIndex:missingBlock];
314 return missingBlocks;
317 + (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos
318 containerName:(NSString *)containerName
319 blockSize:(NSUInteger)blockSize
320 forFile:(NSString *)filePath
321 hashes:(NSArray *)hashes
322 missingHashes:(NSArray *)missingHashes
323 sharingAccount:(NSString *)sharingAccount {
324 NSIndexSet *missingBlocks = [self missingBlocksForHashes:hashes withMissingHashes:missingHashes];
326 NSFileManager *fileManager = [NSFileManager defaultManager];
327 NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
329 // http://cocoawithlove.com/2009/07/temporary-files-and-folders-in-cocoa.html
330 NSString *tempFileTemplate = NSTemporaryDirectory();
331 if (tempFileTemplate == nil)
332 tempFileTemplate = @"/tmp";
333 tempFileTemplate = [tempFileTemplate stringByAppendingPathComponent:@"pithos-macos.XXXXXX"];
334 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
335 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
336 strcpy(tempFileNameCString, tempFileTemplateCString);
337 int fileDescriptor = mkstemp(tempFileNameCString);
338 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
339 if (fileDescriptor == -1) {
340 dispatch_async(dispatch_get_main_queue(), ^{
341 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
342 [alert setMessageText:@"Create Temporary File Error"];
343 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]];
344 [alert addButtonWithTitle:@"OK"];
349 free(tempFileNameCString);
350 NSFileHandle *tempFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor closeOnDealloc:YES];
352 [missingBlocks enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
353 [fileHandle seekToFileOffset:(idx*blockSize)];
354 [tempFileHandle writeData:[fileHandle readDataOfLength:blockSize]];
356 [tempFileHandle closeFile];
357 [fileHandle closeFile];
359 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos
360 containerName:containerName
366 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
367 return containerRequest;
370 + (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos
371 containerName:(NSString *)containerName
372 blockSize:(NSUInteger)blockSize
373 forFile:(NSString *)filePath
374 missingBlockIndex:(NSUInteger)missingBlockIndex
375 sharingAccount:(NSString *)sharingAccount {
376 NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
377 [fileHandle seekToFileOffset:(missingBlockIndex *blockSize)];
378 NSData *blockData = [fileHandle readDataOfLength:blockSize];
379 [fileHandle closeFile];
380 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos
381 containerName:containerName
387 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
388 return containerRequest;
391 + (NSArray *)writeObjectDataRequestsWithPithos:(ASIPithos *)pithos
392 containerName:(NSString *)containerName
393 objectName:(NSString *)objectName
394 blockSize:(NSUInteger)blockSize
395 blockHash:(NSString *)blockHash
396 forDirectory:(NSString *)directoryPath
397 checkIfExists:(BOOL)ifExists
398 objectNames:(NSMutableArray **)objectNames
399 contentTypes:(NSMutableArray **)contentTypes
400 filePaths:(NSMutableArray **)filePaths
401 hashesArrays:(NSMutableArray **)hashesArrays
402 directoryObjectRequests:(NSMutableArray **) directoryObjectRequests
403 sharingAccount:(NSString *)sharingAccount {
404 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName
405 sharingAccount:sharingAccount])
408 NSFileManager *fileManager = [NSFileManager defaultManager];
409 NSError *error = nil;
410 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:directoryPath error:&error];
412 dispatch_async(dispatch_get_main_queue(), ^{
413 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
414 [alert setMessageText:@"Directory Read Error"];
415 [alert setInformativeText:[NSString stringWithFormat:@"Cannot read contents of directory '%@': %@",
416 [directoryPath lastPathComponent], [error localizedDescription]]];
417 [alert addButtonWithTitle:@"OK"];
423 *directoryObjectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
424 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
425 containerName:containerName
426 objectName:objectName
428 contentType:@"application/directory"
430 contentDisposition:nil
433 isPublic:ASIPithosObjectRequestPublicIgnore
437 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
438 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
439 [directoryPath lastPathComponent], @"fileName",
441 [*directoryObjectRequests addObject:objectRequest];
443 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
444 *objectNames = [NSMutableArray arrayWithCapacity:[subPaths count]];
445 *contentTypes = [NSMutableArray arrayWithCapacity:[subPaths count]];
446 *filePaths = [NSMutableArray arrayWithCapacity:[subPaths count]];
447 *hashesArrays = [NSMutableArray arrayWithCapacity:[subPaths count]];
449 NSString *subObjectName;
452 NSString *contentType;
455 for (NSString *objectNameSuffix in subPaths) {
456 filePath = [directoryPath stringByAppendingPathComponent:objectNameSuffix];
457 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
459 hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
461 subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping];
462 fileName = [filePath lastPathComponent];
463 if ([filePath hasSuffix:@"/"])
464 fileName = [fileName stringByAppendingString:@"/"];
465 bytes = [self bytesOfFile:filePath];
467 contentType = [self contentTypeOfFile:filePath error:&error];
468 if (contentType == nil)
469 contentType = @"application/octet-stream";
471 NSLog(@"contentType detection error: %@", error);
472 objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
473 containerName:containerName
474 objectName:subObjectName
475 contentType:contentType
477 contentDisposition:nil
480 isPublic:ASIPithosObjectRequestPublicIgnore
487 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
488 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
489 fileName, @"fileName",
490 [NSNumber numberWithUnsignedInteger:bytes], @"bytes",
492 [objectRequests addObject:objectRequest];
493 [*objectNames addObject:subObjectName];
494 [*contentTypes addObject:contentType];
495 [*filePaths addObject:filePath];
496 [*hashesArrays addObject:hashes];
500 subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping];
501 fileName = [filePath lastPathComponent];
502 objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
503 containerName:containerName
504 objectName:subObjectName
506 contentType:@"application/directory"
508 contentDisposition:nil
511 isPublic:ASIPithosObjectRequestPublicIgnore
515 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
516 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
517 fileName, @"fileName",
519 [*directoryObjectRequests addObject:objectRequest];
524 return objectRequests;
530 + (NSArray *)deleteObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos
531 containerName:(NSString *)containerName
532 objectName:(NSString *)objectName {
533 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName delimiter:nil
538 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
539 ASIPithosObjectRequest *objectRequest;
540 if (![objectName hasSuffix:@"/"]) {
541 objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:objectName];
542 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
543 [objectName lastPathComponent], @"fileName",
545 [objectRequests addObject:objectRequest];
548 for (ASIPithosObject *object in objects) {
549 fileName = [object.name lastPathComponent];
550 if ([object.name hasSuffix:@"/"])
551 fileName = [fileName stringByAppendingString:@"/"];
552 objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:object.name];
553 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
554 fileName, @"fileName",
556 [objectRequests addObject:objectRequest];
559 if ([objectRequests count] == 0)
561 return objectRequests;
567 + (ASIPithosObjectRequest *)copyObjectRequestWithPithos:(ASIPithos *)pithos
568 containerName:(NSString *)containerName
569 objectName:(NSString *)objectName
570 destinationContainerName:(NSString *)destinationContainerName
571 destinationObjectName:(NSString *)destinationObjectName
572 checkIfExists:(BOOL)ifExists
573 sharingAccount:(NSString *)sharingAccount {
574 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
578 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
579 containerName:containerName
580 objectName:objectName
583 contentDisposition:nil
586 isPublic:ASIPithosObjectRequestPublicIgnore
588 destinationContainerName:destinationContainerName
589 destinationObjectName:destinationObjectName
590 destinationAccount:nil
592 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
593 containerName, @"sourceContainerName",
594 objectName, @"sourceObjectName",
595 destinationContainerName, @"destinationContainerName",
596 destinationObjectName, @"destinationObjectName",
599 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
600 return objectRequest;
603 + (NSArray *)copyObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos
604 containerName:(NSString *)containerName
605 objectName:(NSString *)objectName
606 destinationContainerName:(NSString *)destinationContainerName
607 destinationObjectName:(NSString *)destinationObjectName
608 checkIfExists:(BOOL)ifExists
609 sharingAccount:(NSString *)sharingAccount {
610 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
614 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName
615 delimiter:nil sharingAccount:sharingAccount];
619 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
620 ASIPithosObjectRequest *objectRequest;
621 if ([objectName isEqualToString:destinationObjectName]) {
622 if (![objectName hasSuffix:@"/"]) {
623 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
624 containerName:containerName
625 objectName:objectName
628 contentDisposition:nil
631 isPublic:ASIPithosObjectRequestPublicIgnore
633 destinationContainerName:destinationContainerName
634 destinationObjectName:objectName
635 destinationAccount:nil
637 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
638 containerName, @"sourceContainerName",
639 objectName, @"sourceObjectName",
640 destinationContainerName, @"destinationContainerName",
641 objectName, @"destinationObjectName",
644 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
645 [objectRequests addObject:objectRequest];
647 for (ASIPithosObject *object in objects) {
648 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
649 containerName:containerName
650 objectName:object.name
653 contentDisposition:nil
656 isPublic:ASIPithosObjectRequestPublicIgnore
658 destinationContainerName:destinationContainerName
659 destinationObjectName:object.name
660 destinationAccount:nil
662 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
663 containerName, @"sourceContainerName",
664 object.name, @"sourceObjectName",
665 destinationContainerName, @"destinationContainerName",
666 object.name, @"destinationObjectName",
669 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
670 [objectRequests addObject:objectRequest];
673 if (![objectName hasSuffix:@"/"]) {
674 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
675 containerName:containerName
676 objectName:objectName
679 contentDisposition:nil
682 isPublic:ASIPithosObjectRequestPublicIgnore
684 destinationContainerName:destinationContainerName
685 destinationObjectName:destinationObjectName
686 destinationAccount:nil
688 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
689 containerName, @"sourceContainerName",
690 objectName, @"sourceObjectName",
691 destinationContainerName, @"destinationContainerName",
692 destinationObjectName, @"destinationObjectName",
695 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
696 [objectRequests addObject:objectRequest];
698 NSRange prefixRange = NSMakeRange(0, [objectName length]);
699 NSString *newObjectName;
700 for (ASIPithosObject *object in objects) {
701 newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
702 withString:destinationObjectName
703 options:NSAnchoredSearch
705 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos
706 containerName:containerName
707 objectName:object.name
710 contentDisposition:nil
713 isPublic:ASIPithosObjectRequestPublicIgnore
715 destinationContainerName:destinationContainerName
716 destinationObjectName:newObjectName
717 destinationAccount:nil
719 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
720 containerName, @"sourceContainerName",
721 object.name, @"sourceObjectName",
722 destinationContainerName, @"destinationContainerName",
723 newObjectName, @"destinationObjectName",
726 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
727 [objectRequests addObject:objectRequest];
731 if ([objectRequests count] == 0)
733 return objectRequests;
739 + (ASIPithosObjectRequest *)moveObjectRequestWithPithos:(ASIPithos *)pithos
740 containerName:(NSString *)containerName
741 objectName:(NSString *)objectName
742 destinationContainerName:(NSString *)destinationContainerName
743 destinationObjectName:(NSString *)destinationObjectName
744 checkIfExists:(BOOL)ifExists {
745 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
749 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
750 containerName:containerName
751 objectName:objectName
754 contentDisposition:nil
757 isPublic:ASIPithosObjectRequestPublicIgnore
759 destinationContainerName:destinationContainerName
760 destinationObjectName:destinationObjectName
761 destinationAccount:nil];
762 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
763 containerName, @"sourceContainerName",
764 objectName, @"sourceObjectName",
765 destinationContainerName, @"destinationContainerName",
766 destinationObjectName, @"destinationObjectName",
768 return objectRequest;
771 + (NSArray *)moveObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos
772 containerName:(NSString *)containerName
773 objectName:(NSString *)objectName
774 destinationContainerName:(NSString *)destinationContainerName
775 destinationObjectName:(NSString *)destinationObjectName
776 checkIfExists:(BOOL)ifExists {
777 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
781 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName
782 delimiter:nil sharingAccount:nil];
786 ASIPithosObjectRequest *objectRequest;
787 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
788 if ([objectName isEqualToString:destinationObjectName]) {
789 if (![objectName hasSuffix:@"/"]) {
790 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
791 containerName:containerName
792 objectName:objectName
795 contentDisposition:nil
798 isPublic:ASIPithosObjectRequestPublicIgnore
800 destinationContainerName:destinationContainerName
801 destinationObjectName:objectName
802 destinationAccount:nil];
803 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
804 containerName, @"sourceContainerName",
805 objectName, @"sourceObjectName",
806 destinationContainerName, @"destinationContainerName",
807 objectName, @"destinationObjectName",
809 [objectRequests addObject:objectRequest];
811 for (ASIPithosObject *object in objects) {
812 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
813 containerName:containerName
814 objectName:object.name
817 contentDisposition:nil
820 isPublic:ASIPithosObjectRequestPublicIgnore
822 destinationContainerName:destinationContainerName
823 destinationObjectName:object.name
824 destinationAccount:nil];
825 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
826 containerName, @"sourceContainerName",
827 object.name, @"sourceObjectName",
828 destinationContainerName, @"destinationContainerName",
829 object.name, @"destinationObjectName",
831 [objectRequests addObject:objectRequest];
834 if (![objectName hasSuffix:@"/"]) {
835 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
836 containerName:containerName
837 objectName:objectName
840 contentDisposition:nil
843 isPublic:ASIPithosObjectRequestPublicIgnore
845 destinationContainerName:destinationContainerName
846 destinationObjectName:destinationObjectName
847 destinationAccount:nil];
848 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
849 containerName, @"sourceContainerName",
850 objectName, @"sourceObjectName",
851 destinationContainerName, @"destinationContainerName",
852 destinationObjectName, @"destinationObjectName",
854 [objectRequests addObject:objectRequest];
856 NSRange prefixRange = NSMakeRange(0, [objectName length]);
857 NSString *newObjectName;
858 for (ASIPithosObject *object in objects) {
859 newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
860 withString:destinationObjectName
861 options:NSAnchoredSearch
863 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
864 containerName:containerName
865 objectName:object.name
868 contentDisposition:nil
871 isPublic:ASIPithosObjectRequestPublicIgnore
873 destinationContainerName:destinationContainerName
874 destinationObjectName:newObjectName
875 destinationAccount:nil];
876 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
877 containerName, @"sourceContainerName",
878 object.name, @"sourceObjectName",
879 destinationContainerName, @"destinationContainerName",
880 newObjectName, @"destinationObjectName",
882 [objectRequests addObject:objectRequest];
886 if ([objectRequests count] == 0)
888 return objectRequests;
892 #pragma mark Helper Methods
894 // Size of the file in bytes
895 + (NSUInteger)bytesOfFile:(NSString *)filePath {
896 NSFileManager *fileManager = [NSFileManager defaultManager];
897 NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil];
898 return [[attributes objectForKey:NSFileSize] unsignedIntegerValue];
901 // Content type of the file or nil if it cannot be determined
902 + (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error {
903 // Based on http://www.ddeville.me/2011/12/mime-to-UTI-cocoa/
904 // and Apple example ImageBrowserViewAppearance/ImageBrowserController.m
905 LSItemInfoRecord info;
906 CFStringRef uti = NULL;
907 CFURLRef url = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)filePath, kCFURLPOSIXPathStyle, FALSE);
908 if (LSCopyItemInfoForURL(url, kLSRequestExtension | kLSRequestTypeCreator, &info) == noErr) {
909 // Obtain the UTI using the file information.
910 // If there is a file extension, get the UTI.
911 if (info.extension != NULL) {
912 uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, info.extension, kUTTypeData);
913 CFRelease(info.extension);
917 // If there is an OSType, get the UTI.
918 CFStringRef typeString = UTCreateStringForOSType(info.filetype);
919 if ( typeString != NULL) {
920 uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, typeString, kUTTypeData);
921 CFRelease(typeString);
925 CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
927 return (NSString *)MIMEType;
933 // Creates a directory if it doesn't exist and returns if successful
934 + (BOOL)safeCreateDirectory:(NSString *)directoryPath error:(NSError **)error {
935 NSFileManager *fileManager = [NSFileManager defaultManager];
937 BOOL fileExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory];
940 if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:error] || *error)
945 // Removes contents of a directory
946 + (void)removeContentsAtPath:(NSString *)dirPath {
947 NSFileManager *fileManager = [NSFileManager defaultManager];
948 NSError *error = nil;
950 if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory])
953 for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:dirPath error:&error]) {
955 [self fileActionFailedAlertWithTitle:@"Directory Contents Error"
956 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", dirPath]
960 NSString *subFilePath = [dirPath stringByAppendingPathComponent:subPath];
961 if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
962 [self fileActionFailedAlertWithTitle:@"Remove File Error"
963 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
968 } else if (![fileManager removeItemAtPath:dirPath error:&error] || error) {
969 [self fileActionFailedAlertWithTitle:@"Remove File Error"
970 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", dirPath]
975 // Returns if an object is a directory based on its content type
976 + (BOOL)isContentTypeDirectory:(NSString *)contentType {
977 return ([contentType isEqualToString:@"application/directory"] ||
978 [contentType hasPrefix:@"application/directory;"] ||
979 [contentType isEqualToString:@"application/folder"] ||
980 [contentType hasPrefix:@"application/folder;"]);
983 // Returns if an object exists at the given container/object path and if this object is an application/directory
984 // If an error occured an alert is shown and it is returned so the caller won't proceed
985 + (BOOL)objectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName
986 error:(NSError **)error isDirectory:(BOOL *)isDirectory sharingAccount:(NSString *)sharingAccount {
987 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos
988 containerName:containerName
989 objectName:objectName];
991 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
992 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
994 [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:objectRequest]] waitUntilFinished:YES];
995 *error = [objectRequest error];
997 [self httpRequestErrorAlertWithRequest:objectRequest];
999 } else if (objectRequest.responseStatusCode == 200) {
1000 *isDirectory = [self isContentTypeDirectory:[objectRequest contentType]];
1006 // Returns if the caller should proceed, after an interactive check if an object exists
1007 // at the given container/object path is performed
1008 + (BOOL)proceedIfObjectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName
1009 sharingAccount:(NSString *)sharingAccount {
1010 NSError *error = nil;
1012 BOOL objectExists = [self objectExistsAtPithos:pithos containerName:containerName objectName:objectName
1013 error:&error isDirectory:&isDirectory sharingAccount:sharingAccount];
1016 } else if (objectExists) {
1017 __block NSInteger choice;
1018 dispatch_sync(dispatch_get_main_queue(), ^{
1019 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1021 [alert setMessageText:@"Directory Exists"];
1023 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
1025 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1027 [alert setMessageText:@"Object Exists"];
1029 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
1031 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1033 [alert addButtonWithTitle:@"OK"];
1034 [alert addButtonWithTitle:@"Cancel"];
1035 choice = [alert runModal];
1037 if (choice == NSAlertSecondButtonReturn)
1043 // List of objects at the given container/object path, with prefix and or delimiter
1044 + (NSArray *)objectsWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix
1045 delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1046 NSMutableArray *objects = [NSMutableArray array];
1047 NSString *marker = nil;
1049 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1050 containerName:containerName
1053 prefix:objectNamePrefix
1060 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
1061 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1063 [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:containerRequest]] waitUntilFinished:YES];
1064 if ([containerRequest error]) {
1065 [self httpRequestErrorAlertWithRequest:containerRequest];
1068 NSArray *someObjects = [containerRequest objects];
1069 [objects addObjectsFromArray:someObjects];
1070 if ([someObjects count] < 10000)
1073 marker = [[someObjects lastObject] name];
1078 // List of objects at the given container/object path, that may be a subdir or an application/directory,
1079 // with prefix and or delimiter
1080 + (NSArray *)objectsForSubdirWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName
1081 delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1082 NSString *subdirNamePrefix = [NSString stringWithString:objectName];
1083 if (![subdirNamePrefix hasSuffix:@"/"])
1084 subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"];
1085 return [self objectsWithPithos:pithos containerName:containerName objectNamePrefix:subdirNamePrefix
1086 delimiter:delimiter sharingAccount:sharingAccount];
1089 // A safe object name at the given container/object path
1090 // The original name has " %d" appended to it before any ".*" suffix, for the first integer that produces a name that is free to use
1091 // If the original name hasn't got a "." but has a "/" suffix, then it is replaced with " %d/" instead
1092 // Subdirs are taken into consideration
1093 + (NSString *)safeObjectNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName {
1094 NSString *objectNamePrefix;
1095 NSString *objectNameExtraSuffix;
1096 NSRange lastDotRange = [objectName rangeOfString:@"." options:NSBackwardsSearch];
1097 if (lastDotRange.length == 1) {
1098 objectNamePrefix = [objectName substringToIndex:lastDotRange.location];
1099 objectNameExtraSuffix = [objectName substringFromIndex:lastDotRange.location];
1100 } else if ([objectName hasSuffix:@"/"]) {
1101 objectNamePrefix = [objectName substringToIndex:([objectName length] - 1)];
1102 objectNameExtraSuffix = [NSString stringWithString:@"/"];
1104 objectNamePrefix = [NSString stringWithString:objectName];
1105 objectNameExtraSuffix = [NSString string];
1107 NSArray *objects = [self objectsWithPithos:pithos containerName:containerName
1108 objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent]
1109 delimiter:@"/" sharingAccount:nil];
1112 if ([objects count] == 0)
1114 NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1115 [[objects objectsAtIndexes:
1116 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1117 if (pithosObject.subdir)
1120 }]] valueForKey:@"name"]];
1121 for (NSString *name in [[objects objectsAtIndexes:
1122 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1123 if (pithosObject.subdir)
1126 }]] valueForKey:@"name"]) {
1127 [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1129 if (![objectNames containsObject:objectName])
1131 NSUInteger objectNameSuffix = 2;
1132 NSString *safeObjectName;
1134 safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix];
1136 } while ([objectNames containsObject:safeObjectName]);
1137 return safeObjectName;
1140 // A safe object name at the given container/object path that may be a subdir or application/directory
1141 // The original name has " %d" appended to it, for the first integer that produces a name that is free to use
1142 // If the original name has a "/" suffix, then it is replaced with " %d/" instead
1143 // Subdirs are taken into consideration
1144 + (NSString *)safeSubdirNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName subdirName:(NSString *)subdirName {
1145 NSString *subdirNamePrefix;
1146 NSString *subdirNameExtraSuffix;
1147 if ([subdirName hasSuffix:@"/"]) {
1148 subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)];
1149 subdirNameExtraSuffix = [NSString stringWithString:@"/"];
1151 subdirNamePrefix = [NSString stringWithString:subdirName];
1152 subdirNameExtraSuffix = [NSString string];
1154 NSArray *objects = [self objectsWithPithos:pithos containerName:containerName
1155 objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent]
1156 delimiter:@"/" sharingAccount:nil];
1159 if ([objects count] == 0)
1161 NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1162 [[objects objectsAtIndexes:
1163 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1164 if (pithosObject.subdir)
1167 }]] valueForKey:@"name"]];
1168 for (NSString *name in [[objects objectsAtIndexes:
1169 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1170 if (pithosObject.subdir)
1173 }]] valueForKey:@"name"]) {
1174 [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1176 if (![objectNames containsObject:subdirNamePrefix])
1178 NSUInteger subdirNameSuffix = 2;
1179 NSString *safeSubdirName;
1181 safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix];
1183 } while ([objectNames containsObject:safeSubdirName]);
1184 return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix];
1190 + (void)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
1191 NSString *message = [NSString stringWithFormat:
1192 @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@",
1193 [[request error] localizedDescription],
1194 request.requestMethod,
1196 [request requestHeaders],
1197 [request responseHeaders],
1198 [request responseString]];
1199 NSLog(@"%@", message);
1200 dispatch_async(dispatch_get_main_queue(), ^{
1201 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1202 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1203 [alert setMessageText:@"HTTP Request Error"];
1204 [alert setInformativeText:message];
1205 [alert addButtonWithTitle:@"OK"];
1211 + (void)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
1212 NSString *message = [NSString stringWithFormat:
1213 @"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@",
1214 request.responseStatusCode,
1215 request.responseStatusMessage,
1216 request.requestMethod,
1218 [request requestHeaders],
1219 [request responseHeaders],
1220 [request responseString]];
1221 NSLog(@"%@", message);
1222 dispatch_async(dispatch_get_main_queue(), ^{
1223 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1224 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1225 [alert setMessageText:@"Unexpected Response Status"];
1226 [alert setInformativeText:message];
1227 [alert addButtonWithTitle:@"OK"];
1233 + (void)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error {
1234 dispatch_async(dispatch_get_main_queue(), ^{
1235 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1236 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1237 [alert setMessageText:title];
1239 [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, [error localizedDescription]]];
1241 [alert setInformativeText:message];
1242 [alert addButtonWithTitle:@"OK"];
1249 #pragma mark Request Helper Methods
1251 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority {
1252 [request setTimeOutSeconds:60];
1253 request.numberOfTimesToRetryOnTimeout = 10;
1254 [request setQueuePriority:priority];
1258 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request {
1259 return [self prepareRequest:request priority:NSOperationQueuePriorityNormal];
1262 + (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request {
1263 NSMutableDictionary *userInfo = (NSMutableDictionary *)[[request.userInfo retain] autorelease];
1264 request.userInfo = nil;
1265 ASIPithosRequest *newRequest = [request copy];
1266 newRequest.userInfo = userInfo;