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];
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 DLog(@"Cannot remove existing file '%@': %@", fileName, error);
96 dispatch_async(dispatch_get_main_queue(), ^{
97 NSAlert *alert = [[NSAlert alloc] init];
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];
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 DLog(@"Cannot create directory at '%@': %@", directoryPath, error);
157 dispatch_async(dispatch_get_main_queue(), ^{
158 NSAlert *alert = [[NSAlert alloc] init];
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 DLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error);
179 dispatch_async(dispatch_get_main_queue(), ^{
180 NSAlert *alert = [[NSAlert alloc] init];
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 DLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error);
192 dispatch_async(dispatch_get_main_queue(), ^{
193 NSAlert *alert = [[NSAlert alloc] init];
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 free(tempFileNameCString);
340 if (fileDescriptor == -1) {
341 dispatch_async(dispatch_get_main_queue(), ^{
342 NSAlert *alert = [[NSAlert alloc] init];
343 [alert setMessageText:@"Create Temporary File Error"];
344 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]];
345 [alert addButtonWithTitle:@"OK"];
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];
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";
472 DLog(@"contentType detection error: %@", error);
474 objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
475 containerName:containerName
476 objectName:subObjectName
477 contentType:contentType
479 contentDisposition:nil
482 isPublic:ASIPithosObjectRequestPublicIgnore
489 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
490 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
491 fileName, @"fileName",
492 [NSNumber numberWithUnsignedInteger:bytes], @"bytes",
494 [objectRequests addObject:objectRequest];
495 [*objectNames addObject:subObjectName];
496 [*contentTypes addObject:contentType];
497 [*filePaths addObject:filePath];
498 [*hashesArrays addObject:hashes];
502 subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping];
503 fileName = [filePath lastPathComponent];
504 objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
505 containerName:containerName
506 objectName:subObjectName
508 contentType:@"application/directory"
510 contentDisposition:nil
513 isPublic:ASIPithosObjectRequestPublicIgnore
517 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
518 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
519 fileName, @"fileName",
521 [*directoryObjectRequests addObject:objectRequest];
526 return objectRequests;
532 + (NSArray *)deleteObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos
533 containerName:(NSString *)containerName
534 objectName:(NSString *)objectName {
535 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName delimiter:nil
540 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
541 ASIPithosObjectRequest *objectRequest;
542 if (![objectName hasSuffix:@"/"]) {
543 objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:objectName];
544 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
545 [objectName lastPathComponent], @"fileName",
547 [objectRequests addObject:objectRequest];
550 for (ASIPithosObject *object in objects) {
551 fileName = [object.name lastPathComponent];
552 if ([object.name hasSuffix:@"/"])
553 fileName = [fileName stringByAppendingString:@"/"];
554 objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:object.name];
555 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
556 fileName, @"fileName",
558 [objectRequests addObject:objectRequest];
561 if ([objectRequests count] == 0)
563 return objectRequests;
569 + (ASIPithosObjectRequest *)cpyObjectRequestWithPithos:(ASIPithos *)pithos
570 containerName:(NSString *)containerName
571 objectName:(NSString *)objectName
572 destinationContainerName:(NSString *)destinationContainerName
573 destinationObjectName:(NSString *)destinationObjectName
574 checkIfExists:(BOOL)ifExists
575 sharingAccount:(NSString *)sharingAccount {
576 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
580 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos
581 containerName:containerName
582 objectName:objectName
585 contentDisposition:nil
588 isPublic:ASIPithosObjectRequestPublicIgnore
590 destinationContainerName:destinationContainerName
591 destinationObjectName:destinationObjectName
592 destinationAccount:nil
594 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
595 containerName, @"sourceContainerName",
596 objectName, @"sourceObjectName",
597 destinationContainerName, @"destinationContainerName",
598 destinationObjectName, @"destinationObjectName",
601 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
602 return objectRequest;
605 + (NSArray *)cpyObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos
606 containerName:(NSString *)containerName
607 objectName:(NSString *)objectName
608 destinationContainerName:(NSString *)destinationContainerName
609 destinationObjectName:(NSString *)destinationObjectName
610 checkIfExists:(BOOL)ifExists
611 sharingAccount:(NSString *)sharingAccount {
612 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
616 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName
617 delimiter:nil sharingAccount:sharingAccount];
621 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
622 ASIPithosObjectRequest *objectRequest;
623 if ([objectName isEqualToString:destinationObjectName]) {
624 if (![objectName hasSuffix:@"/"]) {
625 objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos
626 containerName:containerName
627 objectName:objectName
630 contentDisposition:nil
633 isPublic:ASIPithosObjectRequestPublicIgnore
635 destinationContainerName:destinationContainerName
636 destinationObjectName:objectName
637 destinationAccount:nil
639 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
640 containerName, @"sourceContainerName",
641 objectName, @"sourceObjectName",
642 destinationContainerName, @"destinationContainerName",
643 objectName, @"destinationObjectName",
646 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
647 [objectRequests addObject:objectRequest];
649 for (ASIPithosObject *object in objects) {
650 objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos
651 containerName:containerName
652 objectName:object.name
655 contentDisposition:nil
658 isPublic:ASIPithosObjectRequestPublicIgnore
660 destinationContainerName:destinationContainerName
661 destinationObjectName:object.name
662 destinationAccount:nil
664 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
665 containerName, @"sourceContainerName",
666 object.name, @"sourceObjectName",
667 destinationContainerName, @"destinationContainerName",
668 object.name, @"destinationObjectName",
671 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
672 [objectRequests addObject:objectRequest];
675 if (![objectName hasSuffix:@"/"]) {
676 objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos
677 containerName:containerName
678 objectName:objectName
681 contentDisposition:nil
684 isPublic:ASIPithosObjectRequestPublicIgnore
686 destinationContainerName:destinationContainerName
687 destinationObjectName:destinationObjectName
688 destinationAccount:nil
690 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
691 containerName, @"sourceContainerName",
692 objectName, @"sourceObjectName",
693 destinationContainerName, @"destinationContainerName",
694 destinationObjectName, @"destinationObjectName",
697 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
698 [objectRequests addObject:objectRequest];
700 NSRange prefixRange = NSMakeRange(0, [objectName length]);
701 NSString *newObjectName;
702 for (ASIPithosObject *object in objects) {
703 newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
704 withString:destinationObjectName
705 options:NSAnchoredSearch
707 objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos
708 containerName:containerName
709 objectName:object.name
712 contentDisposition:nil
715 isPublic:ASIPithosObjectRequestPublicIgnore
717 destinationContainerName:destinationContainerName
718 destinationObjectName:newObjectName
719 destinationAccount:nil
721 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
722 containerName, @"sourceContainerName",
723 object.name, @"sourceObjectName",
724 destinationContainerName, @"destinationContainerName",
725 newObjectName, @"destinationObjectName",
728 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
729 [objectRequests addObject:objectRequest];
733 if ([objectRequests count] == 0)
735 return objectRequests;
741 + (ASIPithosObjectRequest *)moveObjectRequestWithPithos:(ASIPithos *)pithos
742 containerName:(NSString *)containerName
743 objectName:(NSString *)objectName
744 destinationContainerName:(NSString *)destinationContainerName
745 destinationObjectName:(NSString *)destinationObjectName
746 checkIfExists:(BOOL)ifExists {
747 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
751 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
752 containerName:containerName
753 objectName:objectName
756 contentDisposition:nil
759 isPublic:ASIPithosObjectRequestPublicIgnore
761 destinationContainerName:destinationContainerName
762 destinationObjectName:destinationObjectName
763 destinationAccount:nil];
764 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
765 containerName, @"sourceContainerName",
766 objectName, @"sourceObjectName",
767 destinationContainerName, @"destinationContainerName",
768 destinationObjectName, @"destinationObjectName",
770 return objectRequest;
773 + (NSArray *)moveObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos
774 containerName:(NSString *)containerName
775 objectName:(NSString *)objectName
776 destinationContainerName:(NSString *)destinationContainerName
777 destinationObjectName:(NSString *)destinationObjectName
778 checkIfExists:(BOOL)ifExists {
779 if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName
783 NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName
784 delimiter:nil sharingAccount:nil];
788 ASIPithosObjectRequest *objectRequest;
789 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
790 if ([objectName isEqualToString:destinationObjectName]) {
791 if (![objectName hasSuffix:@"/"]) {
792 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
793 containerName:containerName
794 objectName:objectName
797 contentDisposition:nil
800 isPublic:ASIPithosObjectRequestPublicIgnore
802 destinationContainerName:destinationContainerName
803 destinationObjectName:objectName
804 destinationAccount:nil];
805 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
806 containerName, @"sourceContainerName",
807 objectName, @"sourceObjectName",
808 destinationContainerName, @"destinationContainerName",
809 objectName, @"destinationObjectName",
811 [objectRequests addObject:objectRequest];
813 for (ASIPithosObject *object in objects) {
814 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
815 containerName:containerName
816 objectName:object.name
819 contentDisposition:nil
822 isPublic:ASIPithosObjectRequestPublicIgnore
824 destinationContainerName:destinationContainerName
825 destinationObjectName:object.name
826 destinationAccount:nil];
827 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
828 containerName, @"sourceContainerName",
829 object.name, @"sourceObjectName",
830 destinationContainerName, @"destinationContainerName",
831 object.name, @"destinationObjectName",
833 [objectRequests addObject:objectRequest];
836 if (![objectName hasSuffix:@"/"]) {
837 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
838 containerName:containerName
839 objectName:objectName
842 contentDisposition:nil
845 isPublic:ASIPithosObjectRequestPublicIgnore
847 destinationContainerName:destinationContainerName
848 destinationObjectName:destinationObjectName
849 destinationAccount:nil];
850 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
851 containerName, @"sourceContainerName",
852 objectName, @"sourceObjectName",
853 destinationContainerName, @"destinationContainerName",
854 destinationObjectName, @"destinationObjectName",
856 [objectRequests addObject:objectRequest];
858 NSRange prefixRange = NSMakeRange(0, [objectName length]);
859 NSString *newObjectName;
860 for (ASIPithosObject *object in objects) {
861 newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
862 withString:destinationObjectName
863 options:NSAnchoredSearch
865 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos
866 containerName:containerName
867 objectName:object.name
870 contentDisposition:nil
873 isPublic:ASIPithosObjectRequestPublicIgnore
875 destinationContainerName:destinationContainerName
876 destinationObjectName:newObjectName
877 destinationAccount:nil];
878 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
879 containerName, @"sourceContainerName",
880 object.name, @"sourceObjectName",
881 destinationContainerName, @"destinationContainerName",
882 newObjectName, @"destinationObjectName",
884 [objectRequests addObject:objectRequest];
888 if ([objectRequests count] == 0)
890 return objectRequests;
894 #pragma mark Helper Methods
896 // Size of the file in bytes
897 + (NSUInteger)bytesOfFile:(NSString *)filePath {
898 NSFileManager *fileManager = [NSFileManager defaultManager];
899 NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil];
900 return [[attributes objectForKey:NSFileSize] unsignedIntegerValue];
903 // Content type of the file or nil if it cannot be determined
904 + (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error {
905 // Based on http://www.ddeville.me/2011/12/mime-to-UTI-cocoa/
906 // and Apple example ImageBrowserViewAppearance/ImageBrowserController.m
907 LSItemInfoRecord info;
908 CFStringRef uti = NULL;
909 CFURLRef url = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)filePath, kCFURLPOSIXPathStyle, FALSE);
910 if (LSCopyItemInfoForURL(url, kLSRequestExtension | kLSRequestTypeCreator, &info) == noErr) {
911 // Obtain the UTI using the file information.
912 // If there is a file extension, get the UTI.
913 if (info.extension != NULL) {
914 uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, info.extension, kUTTypeData);
915 CFRelease(info.extension);
919 // If there is an OSType, get the UTI.
920 CFStringRef typeString = UTCreateStringForOSType(info.filetype);
921 if ( typeString != NULL) {
922 uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, typeString, kUTTypeData);
923 CFRelease(typeString);
927 CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
930 return (__bridge_transfer NSString *)MIMEType;
937 // Creates a directory if it doesn't exist and returns if successful
938 + (BOOL)safeCreateDirectory:(NSString *)directoryPath error:(NSError **)error {
939 NSFileManager *fileManager = [NSFileManager defaultManager];
941 BOOL fileExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory];
944 if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:error] || *error)
949 // Removes contents of a directory
950 + (void)removeContentsAtPath:(NSString *)dirPath {
951 NSFileManager *fileManager = [NSFileManager defaultManager];
952 NSError *error = nil;
954 if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory])
957 for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:dirPath error:&error]) {
959 [self fileActionFailedAlertWithTitle:@"Directory Contents Error"
960 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", dirPath]
964 NSString *subFilePath = [dirPath stringByAppendingPathComponent:subPath];
965 if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
966 [self fileActionFailedAlertWithTitle:@"Remove File Error"
967 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
972 } else if (![fileManager removeItemAtPath:dirPath error:&error] || error) {
973 [self fileActionFailedAlertWithTitle:@"Remove File Error"
974 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", dirPath]
979 // Returns if an object is a directory based on its content type
980 + (BOOL)isContentTypeDirectory:(NSString *)contentType {
981 return ([contentType isEqualToString:@"application/directory"] ||
982 [contentType hasPrefix:@"application/directory;"] ||
983 [contentType isEqualToString:@"application/folder"] ||
984 [contentType hasPrefix:@"application/folder;"]);
987 // Returns if an object exists at the given container/object path and if this object is an application/directory
988 // If an error occured an alert is shown and it is returned so the caller won't proceed
989 + (BOOL)objectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName
990 error:(NSError **)error isDirectory:(BOOL *)isDirectory sharingAccount:(NSString *)sharingAccount {
991 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos
992 containerName:containerName
993 objectName:objectName];
995 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
996 [self startAndWaitForRequest:objectRequest];
998 *error = [objectRequest error];
1000 [self httpRequestErrorAlertWithRequest:objectRequest];
1002 } else if (objectRequest.responseStatusCode == 200) {
1003 *isDirectory = [self isContentTypeDirectory:[objectRequest contentType]];
1010 // Returns if the caller should proceed, after an interactive check if an object exists
1011 // at the given container/object path is performed
1012 + (BOOL)proceedIfObjectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName
1013 sharingAccount:(NSString *)sharingAccount {
1014 NSError *error = nil;
1016 BOOL objectExists = [self objectExistsAtPithos:pithos containerName:containerName objectName:objectName
1017 error:&error isDirectory:&isDirectory sharingAccount:sharingAccount];
1020 } else if (objectExists) {
1021 __block NSInteger choice;
1022 dispatch_sync(dispatch_get_main_queue(), ^{
1023 NSAlert *alert = [[NSAlert alloc] init];
1025 [alert setMessageText:@"Directory Exists"];
1027 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
1029 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1031 [alert setMessageText:@"Object Exists"];
1033 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
1035 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1037 [alert addButtonWithTitle:@"OK"];
1038 [alert addButtonWithTitle:@"Cancel"];
1039 choice = [alert runModal];
1041 if (choice == NSAlertSecondButtonReturn)
1047 // List of objects at the given container/object path, with prefix and or delimiter
1048 + (NSArray *)objectsWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix
1049 delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1050 NSMutableArray *objects = [NSMutableArray array];
1051 NSString *marker = nil;
1053 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1054 containerName:containerName
1057 prefix:objectNamePrefix
1064 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
1065 [self startAndWaitForRequest:containerRequest];
1066 if ([containerRequest error]) {
1067 [self httpRequestErrorAlertWithRequest:containerRequest];
1070 NSArray *someObjects = [containerRequest objects];
1071 [objects addObjectsFromArray:someObjects];
1072 if ([someObjects count] < 10000)
1075 marker = [[someObjects lastObject] name];
1080 // List of objects at the given container/object path, that may be a subdir or an application/directory,
1081 // with prefix and or delimiter
1082 + (NSArray *)objectsForSubdirWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName
1083 delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1084 NSString *subdirNamePrefix = [NSString stringWithString:objectName];
1085 if (![subdirNamePrefix hasSuffix:@"/"])
1086 subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"];
1087 return [self objectsWithPithos:pithos containerName:containerName objectNamePrefix:subdirNamePrefix
1088 delimiter:delimiter sharingAccount:sharingAccount];
1091 // A safe object name at the given container/object path
1092 // The original name has " %d" appended to it before any ".*" suffix, for the first integer that produces a name that is free to use
1093 // If the original name hasn't got a "." but has a "/" suffix, then it is replaced with " %d/" instead
1094 // Subdirs are taken into consideration
1095 + (NSString *)safeObjectNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName {
1096 NSString *objectNamePrefix;
1097 NSString *objectNameExtraSuffix;
1098 NSRange lastDotRange = [objectName rangeOfString:@"." options:NSBackwardsSearch];
1099 if (lastDotRange.length == 1) {
1100 objectNamePrefix = [objectName substringToIndex:lastDotRange.location];
1101 objectNameExtraSuffix = [objectName substringFromIndex:lastDotRange.location];
1102 } else if ([objectName hasSuffix:@"/"]) {
1103 objectNamePrefix = [objectName substringToIndex:([objectName length] - 1)];
1104 objectNameExtraSuffix = @"/";
1106 objectNamePrefix = [NSString stringWithString:objectName];
1107 objectNameExtraSuffix = [NSString string];
1109 NSArray *objects = [self objectsWithPithos:pithos containerName:containerName
1110 objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent]
1111 delimiter:@"/" sharingAccount:nil];
1114 if ([objects count] == 0)
1116 NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1117 [[objects objectsAtIndexes:
1118 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1119 if (pithosObject.subdir)
1122 }]] valueForKey:@"name"]];
1123 for (NSString *name in [[objects objectsAtIndexes:
1124 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1125 if (pithosObject.subdir)
1128 }]] valueForKey:@"name"]) {
1129 [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1131 if (![objectNames containsObject:objectName])
1133 NSUInteger objectNameSuffix = 2;
1134 NSString *safeObjectName;
1136 safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix];
1138 } while ([objectNames containsObject:safeObjectName]);
1139 return safeObjectName;
1142 // A safe object name at the given container/object path that may be a subdir or application/directory
1143 // The original name has " %d" appended to it, for the first integer that produces a name that is free to use
1144 // If the original name has a "/" suffix, then it is replaced with " %d/" instead
1145 // Subdirs are taken into consideration
1146 + (NSString *)safeSubdirNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName subdirName:(NSString *)subdirName {
1147 NSString *subdirNamePrefix;
1148 NSString *subdirNameExtraSuffix;
1149 if ([subdirName hasSuffix:@"/"]) {
1150 subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)];
1151 subdirNameExtraSuffix = @"/";
1153 subdirNamePrefix = [NSString stringWithString:subdirName];
1154 subdirNameExtraSuffix = [NSString string];
1156 NSArray *objects = [self objectsWithPithos:pithos containerName:containerName
1157 objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent]
1158 delimiter:@"/" sharingAccount:nil];
1161 if ([objects count] == 0)
1163 NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1164 [[objects objectsAtIndexes:
1165 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1166 if (pithosObject.subdir)
1169 }]] valueForKey:@"name"]];
1170 for (NSString *name in [[objects objectsAtIndexes:
1171 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1172 if (pithosObject.subdir)
1175 }]] valueForKey:@"name"]) {
1176 [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1178 if (![objectNames containsObject:subdirNamePrefix])
1180 NSUInteger subdirNameSuffix = 2;
1181 NSString *safeSubdirName;
1183 safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix];
1185 } while ([objectNames containsObject:safeSubdirName]);
1186 return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix];
1192 + (void)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
1193 if (request.responseStatusCode == 401) {
1194 [self httpAuthenticationError];
1197 NSString *message = [NSString stringWithFormat:
1198 @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@",
1199 [[request error] localizedDescription],
1200 request.requestMethod,
1202 [request requestHeaders],
1203 [request responseHeaders],
1204 [request responseString]];
1205 DLog(@"%@", message);
1206 dispatch_async(dispatch_get_main_queue(), ^{
1208 NSAlert *alert = [[NSAlert alloc] init];
1209 [alert setMessageText:@"HTTP Request Error"];
1210 [alert setInformativeText:message];
1211 [alert addButtonWithTitle:@"OK"];
1217 + (void)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
1218 if (request.responseStatusCode == 401) {
1219 [self httpAuthenticationError];
1222 NSString *message = [NSString stringWithFormat:
1223 @"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@",
1224 request.responseStatusCode,
1225 request.responseStatusMessage,
1226 request.requestMethod,
1228 [request requestHeaders],
1229 [request responseHeaders],
1230 [request responseString]];
1231 DLog(@"%@", message);
1232 dispatch_async(dispatch_get_main_queue(), ^{
1234 NSAlert *alert = [[NSAlert alloc] init];
1235 [alert setMessageText:@"Unexpected Response Status"];
1236 [alert setInformativeText:message];
1237 [alert addButtonWithTitle:@"OK"];
1243 + (void)httpAuthenticationError {
1244 dispatch_async(dispatch_get_main_queue(), ^{
1246 NSAlert *alert = [[NSAlert alloc] init];
1247 [alert setMessageText:@"Authentication Error"];
1248 [alert setInformativeText:@"Authentication error, please check your token or login again"];
1249 [alert addButtonWithTitle:@"OK"];
1255 + (void)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error {
1256 dispatch_async(dispatch_get_main_queue(), ^{
1258 NSAlert *alert = [[NSAlert alloc] init];
1259 [alert setMessageText:title];
1261 [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, [error localizedDescription]]];
1263 [alert setInformativeText:message];
1264 [alert addButtonWithTitle:@"OK"];
1271 #pragma mark Request Helper Methods
1273 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority {
1274 [request setTimeOutSeconds:60];
1275 request.numberOfTimesToRetryOnTimeout = 10;
1276 [request setQueuePriority:priority];
1280 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request {
1281 return [self prepareRequest:request priority:NSOperationQueuePriorityNormal];
1284 + (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request {
1285 NSMutableDictionary *userInfo = (NSMutableDictionary *)request.userInfo;
1286 request.userInfo = nil;
1287 ASIPithosRequest *newRequest = [request copy];
1288 newRequest.userInfo = userInfo;
1292 + (void)startAndWaitForRequest:(ASIPithosRequest *)request {
1293 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1295 [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:request]] waitUntilFinished:YES];