Run alerts from background threads in the main thread.
[pithos-macos] / pithos-macos / PithosUtilities.m
1 //
2 //  PithosUtilities.m
3 //  pithos-macos
4 //
5 // Copyright 2011-2012 GRNET S.A. All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
9 // conditions are met:
10 // 
11 //   1. Redistributions of source code must retain the above
12 //      copyright notice, this list of conditions and the following
13 //      disclaimer.
14 // 
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.
19 // 
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.
32 // 
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.
37
38 #import "PithosUtilities.h"
39 #import "ASINetworkQueue.h"
40 #import "ASIPithos.h"
41 #import "ASIPithosContainerRequest.h"
42 #import "ASIPithosObjectRequest.h"
43 #import "ASIPithosObject.h"
44 #import "HashMapHash.h"
45
46 @implementation PithosUtilities
47
48 #pragma mark -
49 #pragma mark Download
50
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 {
59     NSString *fileName;
60     if (newFileName) {
61         fileName = [NSString stringWithString:newFileName];
62     } else {
63         fileName = [objectName lastPathComponent];
64         if ([objectName hasSuffix:@"/"])
65             fileName = [fileName stringByAppendingString:@"/"];    
66     }
67     fileName = [fileName stringByReplacingOccurrencesOfString:@"/" withString:@":"];
68     
69     NSFileManager *fileManager = [NSFileManager defaultManager];
70     
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];
81         });
82         if (choice == NSAlertSecondButtonReturn)
83             return nil;
84     }
85     
86     BOOL directoryIsDirectory;
87     BOOL directoryExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&directoryIsDirectory];
88     NSError *error = nil;
89     if (!directoryExists) {
90         [fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
91     } else if (!directoryIsDirectory) {
92         [fileManager removeItemAtPath:directoryPath error:&error];
93     }
94     if (error) {
95         NSLog(@"Cannot remove existing file '%@': %@", fileName, error);
96         dispatch_async(dispatch_get_main_queue(), ^{
97             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
98             [alert setMessageText:@"Removal Error"];
99             [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file '%@': %@", fileName, error]];
100             [alert addButtonWithTitle:@"OK"];
101             [alert runModal];
102         });
103         return nil;
104     }
105
106     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos 
107                                                                                   containerName:containerName 
108                                                                                      objectName:objectName 
109                                                                                         version:version];
110     if (sharingAccount)
111         [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
112     objectRequest.downloadDestinationPath = destinationPath;
113     objectRequest.allowResumeForFileDownloads = YES;
114     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
115                               fileName, @"fileName", 
116                               destinationPath, @"filePath", 
117                               nil];
118     return objectRequest;
119 }
120
121 + (NSArray *)objectDataRequestsForSubdirWithPithos:(ASIPithos *)pithos 
122                                      containerName:(NSString *)containerName 
123                                         objectName:(NSString *)objectName 
124                                        toDirectory:(NSString *)directoryPath 
125                                      checkIfExists:(BOOL)ifExists 
126                                     sharingAccount:(NSString *)sharingAccount {
127     NSString *subdirName = [objectName lastPathComponent];
128     NSString *destinationPath = [directoryPath stringByAppendingPathComponent:subdirName];
129     if (ifExists && [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) {
130         __block NSInteger choice;
131         dispatch_sync(dispatch_get_main_queue(), ^{
132             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
133             [alert setMessageText:@"File exists"];
134             [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", subdirName]];
135             [alert addButtonWithTitle:@"OK"];
136             [alert addButtonWithTitle:@"Cancel"];
137             choice = [alert runModal];
138         });
139         if (choice == NSAlertSecondButtonReturn)
140             return nil;
141     }
142     
143     NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName 
144                                               delimiter:nil sharingAccount:sharingAccount];
145     if (objects == nil)
146         return nil;
147     
148     NSFileManager *fileManager = [NSFileManager defaultManager];
149     NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[objects count]];
150     NSUInteger subdirPrefixLength = [objectName length];
151
152     NSError *error = nil;
153     [fileManager createDirectoryAtPath:[directoryPath stringByAppendingPathComponent:subdirName] withIntermediateDirectories:YES attributes:nil error:&error];
154     if (error) {
155         NSLog(@"Cannot create directory at '%@': %@", directoryPath, error);
156         dispatch_async(dispatch_get_main_queue(), ^{
157             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
158             [alert setMessageText:@"Create Directory Error"];
159             [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@", 
160                                        directoryPath, error]];
161             [alert addButtonWithTitle:@"OK"];
162             [alert runModal];
163         });
164     }
165     
166     for (ASIPithosObject *object in objects) {
167         if ([self isContentTypeDirectory:object.contentType]) {
168             NSString *subdirDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
169             subdirDirectoryPath = [subdirDirectoryPath stringByAppendingPathComponent:[object.name substringFromIndex:subdirPrefixLength]];
170             
171             BOOL directoryIsDirectory;
172             BOOL directoryExists = [fileManager fileExistsAtPath:subdirDirectoryPath isDirectory:&directoryIsDirectory];
173             NSError *error = nil;
174             if (!directoryExists) {
175                 [fileManager createDirectoryAtPath:subdirDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error];
176                 if (error) {
177                     NSLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error);
178                     dispatch_async(dispatch_get_main_queue(), ^{
179                         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
180                         [alert setMessageText:@"Create Directory Error"];
181                         [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@", 
182                                                    subdirDirectoryPath, error]];
183                         [alert addButtonWithTitle:@"OK"];
184                         [alert runModal];
185                     });
186                 }
187             } else if (!directoryIsDirectory) {
188                 [fileManager removeItemAtPath:subdirDirectoryPath error:&error];
189                 if (error) {
190                     NSLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error);
191                     dispatch_async(dispatch_get_main_queue(), ^{
192                         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
193                         [alert setMessageText:@"Remove File Error"];
194                         [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file at '%@': %@", 
195                                                    subdirDirectoryPath, error]];
196                         [alert addButtonWithTitle:@"OK"];
197                         [alert runModal];
198                     });
199                 }
200             }
201         } else {
202             NSString *fileName = [object.name lastPathComponent];
203             if([object.name hasSuffix:@"/"])
204                 fileName = [fileName stringByAppendingString:@"/"];
205             
206             NSString *objectDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
207             objectDirectoryPath = [objectDirectoryPath stringByAppendingPathComponent:[object.name substringWithRange:NSMakeRange(subdirPrefixLength, [object.name length] - subdirPrefixLength - [fileName length])]];
208             
209             ASIPithosObjectRequest *objectRequest = [self objectDataRequestWithPithos:pithos 
210                                                                         containerName:containerName 
211                                                                            objectName:object.name 
212                                                                               version:nil 
213                                                                           toDirectory:objectDirectoryPath 
214                                                                       withNewFileName:nil 
215                                                                         checkIfExists:NO 
216                                                                        sharingAccount:sharingAccount];
217             [(NSMutableDictionary *)objectRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:object.bytes] forKey:@"bytes"];
218             [objectRequests addObject:objectRequest];
219         }
220     }
221     
222     return objectRequests;
223 }
224
225 #pragma mark -
226 #pragma mark Download Block
227
228 + (ASIPithosObjectRequest *)objectBlockDataRequestWithPithos:(ASIPithos *)pithos 
229                                                containerName:(NSString *)containerName 
230                                                       object:(ASIPithosObject *)object 
231                                                   blockIndex:(NSUInteger)blockIndex 
232                                                    blockSize:(NSUInteger)blockSize {
233     NSUInteger rangeStart = blockIndex * blockSize;
234     NSUInteger rangeEnd = (rangeStart + blockSize <= object.bytes) ? (rangeStart + blockSize - 1) : (object.bytes - 1);
235     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos 
236                                                                                   containerName:containerName
237                                                                                      objectName:object.name
238                                                                                         version:nil
239                                                                                           range:[NSString stringWithFormat:@"bytes=%lu-%lu", rangeStart, rangeEnd]
240                                                                                         ifMatch:object.hash];
241     return objectRequest;
242 }
243
244 + (NSIndexSet *)missingBlocksForFile:(NSString *)filePath
245                            blockSize:(NSUInteger)blockSize 
246                            blockHash:(NSString *)blockHash 
247                           withHashes:(NSArray *)hashes {
248     NSArray *fileHashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
249     return [hashes indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
250         if ((idx >= [fileHashes count]) || ![(NSString *)obj isEqualToString:[fileHashes objectAtIndex:idx]])
251             return YES;
252         return NO;
253     }];
254 }
255
256 #pragma mark -
257 #pragma mark Upload
258
259 + (ASIPithosObjectRequest *)writeObjectDataRequestWithPithos:(ASIPithos *)pithos 
260                                                containerName:(NSString *)containerName
261                                                   objectName:(NSString *)objectName
262                                                  contentType:(NSString *)contentType 
263                                                    blockSize:(NSUInteger)blockSize 
264                                                    blockHash:(NSString *)blockHash 
265                                                      forFile:(NSString *)filePath 
266                                                checkIfExists:(BOOL)ifExists 
267                                                       hashes:(NSArray **)hashes 
268                                               sharingAccount:(NSString *)sharingAccount {
269     if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName 
270                                           sharingAccount:(NSString *)sharingAccount])
271         return nil;
272     
273     if (*hashes == nil)
274         *hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
275     if (*hashes == nil)
276         return nil;
277     NSString *fileName = [filePath lastPathComponent];
278     if ([filePath hasSuffix:@"/"])
279         fileName = [fileName stringByAppendingString:@"/"];
280     NSUInteger bytes = [self bytesOfFile:filePath];
281     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
282                                                                                        containerName:containerName 
283                                                                                           objectName:objectName 
284                                                                                          contentType:contentType 
285                                                                                      contentEncoding:nil 
286                                                                                   contentDisposition:nil 
287                                                                                             manifest:nil 
288                                                                                              sharing:nil 
289                                                                                             isPublic:ASIPithosObjectRequestPublicIgnore 
290                                                                                             metadata:nil
291                                                                                            blockSize:blockSize
292                                                                                            blockHash:blockHash 
293                                                                                               hashes:*hashes 
294                                                                                                bytes:bytes];
295     if (sharingAccount) 
296         [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
297     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
298                               fileName, @"fileName", 
299                               [NSNumber numberWithUnsignedInteger:bytes], @"bytes", 
300                               nil];
301     return objectRequest;
302 }
303
304 + (NSIndexSet *)missingBlocksForHashes:(NSArray *)hashes withMissingHashes:(NSArray *)missingHashes {
305     NSMutableIndexSet *missingBlocks = [NSMutableIndexSet indexSet];
306     for (NSString *missingHash in missingHashes) {
307         if (![missingHash length])
308             break;
309         NSUInteger missingBlock = [hashes indexOfObject:missingHash];
310         if (missingBlock != NSNotFound)
311             [missingBlocks addIndex:missingBlock];
312     }
313     return missingBlocks;
314 }
315
316 + (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos 
317                                                       containerName:(NSString *)containerName 
318                                                           blockSize:(NSUInteger)blockSize 
319                                                             forFile:(NSString *)filePath 
320                                                              hashes:(NSArray *)hashes 
321                                                       missingHashes:(NSArray *)missingHashes 
322                                                      sharingAccount:(NSString *)sharingAccount {
323     NSIndexSet *missingBlocks = [self missingBlocksForHashes:hashes withMissingHashes:missingHashes];
324     
325     NSFileManager *fileManager = [NSFileManager defaultManager];
326     NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
327     
328     // http://cocoawithlove.com/2009/07/temporary-files-and-folders-in-cocoa.html
329     NSString *tempFileTemplate = NSTemporaryDirectory();
330     if (tempFileTemplate == nil)
331         tempFileTemplate = @"/tmp";
332     tempFileTemplate = [tempFileTemplate stringByAppendingPathComponent:@"pithos-macos.XXXXXX"];
333     const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
334     char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
335     strcpy(tempFileNameCString, tempFileTemplateCString);
336     int fileDescriptor = mkstemp(tempFileNameCString);
337     NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
338     if (fileDescriptor == -1) {
339         dispatch_async(dispatch_get_main_queue(), ^{
340             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
341             [alert setMessageText:@"Create Temporary File Error"];
342             [alert setInformativeText:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]];
343             [alert addButtonWithTitle:@"OK"];
344             [alert runModal];
345         });
346         return nil;
347     }
348     free(tempFileNameCString);
349     NSFileHandle *tempFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor closeOnDealloc:YES];
350
351     [missingBlocks enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
352         [fileHandle seekToFileOffset:(idx*blockSize)];
353         [tempFileHandle writeData:[fileHandle readDataOfLength:blockSize]];
354     }];
355     [tempFileHandle closeFile];
356     [fileHandle closeFile];
357
358     ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos 
359                                                                                                     containerName:containerName 
360                                                                                                            policy:nil 
361                                                                                                          metadata:nil 
362                                                                                                            update:YES 
363                                                                                                              file:tempFilePath];
364     if (sharingAccount)
365         [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
366     return containerRequest;
367 }
368
369 + (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos 
370                                                       containerName:(NSString *)containerName 
371                                                           blockSize:(NSUInteger)blockSize 
372                                                             forFile:(NSString *)filePath 
373                                                   missingBlockIndex:(NSUInteger)missingBlockIndex 
374                                                      sharingAccount:(NSString *)sharingAccount {
375     NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
376     [fileHandle seekToFileOffset:(missingBlockIndex *blockSize)];
377     NSData *blockData = [fileHandle readDataOfLength:blockSize];
378     [fileHandle closeFile];
379     ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos 
380                                                                                                     containerName:containerName 
381                                                                                                            policy:nil 
382                                                                                                          metadata:nil 
383                                                                                                            update:YES 
384                                                                                                              data:blockData];
385     if (sharingAccount)
386         [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
387     return containerRequest;
388 }
389
390 + (NSArray *)writeObjectDataRequestsWithPithos:(ASIPithos *)pithos 
391                                  containerName:(NSString *)containerName
392                                     objectName:(NSString *)objectName
393                                      blockSize:(NSUInteger)blockSize 
394                                      blockHash:(NSString *)blockHash 
395                                   forDirectory:(NSString *)directoryPath 
396                                  checkIfExists:(BOOL)ifExists 
397                                    objectNames:(NSMutableArray **)objectNames
398                                   contentTypes:(NSMutableArray **)contentTypes
399                                      filePaths:(NSMutableArray **)filePaths 
400                                   hashesArrays:(NSMutableArray **)hashesArrays 
401                        directoryObjectRequests:(NSMutableArray **) directoryObjectRequests 
402                                 sharingAccount:(NSString *)sharingAccount {
403     if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName 
404                                           sharingAccount:sharingAccount])
405         return nil;
406
407     NSFileManager *fileManager = [NSFileManager defaultManager];
408     NSError *error = nil;
409     NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:directoryPath error:&error];
410     if (error) {
411         dispatch_async(dispatch_get_main_queue(), ^{
412             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
413             [alert setMessageText:@"Directory Read Error"];
414             [alert setInformativeText:[NSString stringWithFormat:@"Cannot read contents of directory '%@': %@", 
415                                        [directoryPath lastPathComponent], error]];
416             [alert addButtonWithTitle:@"OK"];
417             [alert runModal];
418         });
419         return nil;
420     }
421     
422     *directoryObjectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
423     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
424                                                                                        containerName:containerName 
425                                                                                           objectName:objectName 
426                                                                                                 eTag:nil 
427                                                                                          contentType:@"application/directory" 
428                                                                                      contentEncoding:nil 
429                                                                                   contentDisposition:nil 
430                                                                                             manifest:nil 
431                                                                                              sharing:nil 
432                                                                                             isPublic:ASIPithosObjectRequestPublicIgnore 
433                                                                                             metadata:nil 
434                                                                                                 data:[NSData data]];
435     if (sharingAccount)
436         [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
437     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
438                               [directoryPath lastPathComponent], @"fileName", 
439                               nil];
440     [*directoryObjectRequests addObject:objectRequest];
441     
442     NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
443     *objectNames = [NSMutableArray arrayWithCapacity:[subPaths count]];
444     *contentTypes = [NSMutableArray arrayWithCapacity:[subPaths count]];
445     *filePaths = [NSMutableArray arrayWithCapacity:[subPaths count]];
446     *hashesArrays = [NSMutableArray arrayWithCapacity:[subPaths count]];
447     BOOL isDirectory;
448     NSString *subObjectName;
449     NSArray *hashes;
450     NSUInteger bytes;
451     NSString *contentType;
452     NSString *filePath;
453     NSString *fileName;
454     for (NSString *objectNameSuffix in subPaths) {
455         filePath = [directoryPath stringByAppendingPathComponent:objectNameSuffix];
456         if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
457             if (!isDirectory) {
458                 hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
459                 if (hashes) {
460                     subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping];
461                     fileName = [filePath lastPathComponent];
462                     if ([filePath hasSuffix:@"/"])
463                         fileName = [fileName stringByAppendingString:@"/"];
464                     bytes = [self bytesOfFile:filePath];
465                     error = nil;
466                     contentType = [self contentTypeOfFile:filePath error:&error];
467                     if (contentType == nil)
468                         contentType = @"application/octet-stream";
469                     if (error)
470                         NSLog(@"contentType detection error: %@", error);
471                     objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
472                                                                                containerName:containerName 
473                                                                                   objectName:subObjectName 
474                                                                                  contentType:contentType 
475                                                                              contentEncoding:nil 
476                                                                           contentDisposition:nil 
477                                                                                     manifest:nil 
478                                                                                      sharing:nil 
479                                                                                     isPublic:ASIPithosObjectRequestPublicIgnore 
480                                                                                     metadata:nil
481                                                                                    blockSize:blockSize
482                                                                                    blockHash:blockHash 
483                                                                                       hashes:hashes 
484                                                                                        bytes:bytes];
485                     if (sharingAccount)
486                         [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
487                     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
488                                               fileName, @"fileName", 
489                                               [NSNumber numberWithUnsignedInteger:bytes], @"bytes", 
490                                               nil];
491                     [objectRequests addObject:objectRequest];
492                     [*objectNames addObject:subObjectName];
493                     [*contentTypes addObject:contentType];
494                     [*filePaths addObject:filePath];
495                     [*hashesArrays addObject:hashes];
496                 }
497                 
498             } else {
499                 subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping];
500                 fileName = [filePath lastPathComponent];
501                 objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
502                                                                            containerName:containerName 
503                                                                               objectName:subObjectName 
504                                                                                     eTag:nil 
505                                                                              contentType:@"application/directory" 
506                                                                          contentEncoding:nil 
507                                                                       contentDisposition:nil 
508                                                                                 manifest:nil 
509                                                                                  sharing:nil 
510                                                                                 isPublic:ASIPithosObjectRequestPublicIgnore 
511                                                                                 metadata:nil 
512                                                                                     data:[NSData data]];
513                 if (sharingAccount)
514                     [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
515                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
516                                           fileName, @"fileName", 
517                                           nil];
518                 [*directoryObjectRequests addObject:objectRequest];
519             }
520         }
521     }
522     
523     return objectRequests;
524 }
525
526 #pragma mark -
527 #pragma mark Delete
528
529 + (NSArray *)deleteObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos 
530                                        containerName:(NSString *)containerName 
531                                           objectName:(NSString *)objectName {
532     NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName delimiter:nil 
533                                          sharingAccount:nil];
534     if (objects == nil)
535         return nil;
536
537     NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
538     ASIPithosObjectRequest *objectRequest;
539     if (![objectName hasSuffix:@"/"]) {
540         objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:objectName];
541         objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
542                                   [objectName lastPathComponent], @"fileName", 
543                                   nil];
544         [objectRequests addObject:objectRequest];
545     }
546     NSString *fileName;
547     for (ASIPithosObject *object in objects) {
548         fileName = [object.name lastPathComponent];
549         if ([object.name hasSuffix:@"/"])
550             fileName = [fileName stringByAppendingString:@"/"];
551         objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:object.name];
552         objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
553                                   fileName, @"fileName", 
554                                   nil];
555         [objectRequests addObject:objectRequest];
556     }
557     
558     if ([objectRequests count] == 0)
559         return nil;
560     return objectRequests;
561 }
562
563 #pragma mark -
564 #pragma mark Copy
565
566 + (ASIPithosObjectRequest *)copyObjectRequestWithPithos:(ASIPithos *)pithos 
567                                           containerName:(NSString *)containerName 
568                                              objectName:(NSString *)objectName 
569                                destinationContainerName:(NSString *)destinationContainerName 
570                                   destinationObjectName:(NSString *)destinationObjectName 
571                                           checkIfExists:(BOOL)ifExists 
572                                          sharingAccount:(NSString *)sharingAccount {
573     if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName 
574                                           sharingAccount:nil])
575         return nil;
576     
577     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos 
578                                                                                       containerName:containerName 
579                                                                                          objectName:objectName 
580                                                                                         contentType:nil 
581                                                                                     contentEncoding:nil 
582                                                                                  contentDisposition:nil 
583                                                                                            manifest:nil 
584                                                                                             sharing:nil 
585                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
586                                                                                            metadata:nil 
587                                                                            destinationContainerName:destinationContainerName 
588                                                                               destinationObjectName:destinationObjectName 
589                                                                                  destinationAccount:nil
590                                                                                       sourceVersion:nil];
591     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
592                               containerName, @"sourceContainerName", 
593                               objectName, @"sourceObjectName", 
594                               destinationContainerName, @"destinationContainerName", 
595                               destinationObjectName, @"destinationObjectName", 
596                               nil];
597     if (sharingAccount) 
598         [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
599     return objectRequest;
600 }
601
602 + (NSArray *)copyObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos 
603                                      containerName:(NSString *)containerName 
604                                         objectName:(NSString *)objectName 
605                           destinationContainerName:(NSString *)destinationContainerName 
606                              destinationObjectName:(NSString *)destinationObjectName 
607                                      checkIfExists:(BOOL)ifExists 
608                                     sharingAccount:(NSString *)sharingAccount {
609     if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName 
610                                           sharingAccount:nil])
611         return nil;
612     
613     NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName 
614                                               delimiter:nil sharingAccount:sharingAccount];
615     if (objects == nil)
616         return nil;
617     
618     NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
619     ASIPithosObjectRequest *objectRequest;
620     if ([objectName isEqualToString:destinationObjectName]) {
621         if (![objectName hasSuffix:@"/"]) {
622             objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos 
623                                                                       containerName:containerName 
624                                                                          objectName:objectName 
625                                                                         contentType:nil 
626                                                                     contentEncoding:nil 
627                                                                  contentDisposition:nil 
628                                                                            manifest:nil 
629                                                                             sharing:nil 
630                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
631                                                                            metadata:nil 
632                                                            destinationContainerName:destinationContainerName 
633                                                               destinationObjectName:objectName 
634                                                                  destinationAccount:nil 
635                                                                       sourceVersion:nil];
636             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
637                                       containerName, @"sourceContainerName", 
638                                       objectName, @"sourceObjectName", 
639                                       destinationContainerName, @"destinationContainerName", 
640                                       objectName, @"destinationObjectName", 
641                                       nil];
642             if (sharingAccount)
643                 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
644             [objectRequests addObject:objectRequest];
645         }
646         for (ASIPithosObject *object in objects) {
647             objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos 
648                                                                       containerName:containerName 
649                                                                          objectName:object.name 
650                                                                         contentType:nil 
651                                                                     contentEncoding:nil 
652                                                                  contentDisposition:nil 
653                                                                            manifest:nil 
654                                                                             sharing:nil 
655                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
656                                                                            metadata:nil 
657                                                            destinationContainerName:destinationContainerName 
658                                                               destinationObjectName:object.name 
659                                                                  destinationAccount:nil 
660                                                                       sourceVersion:nil];
661             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
662                                       containerName, @"sourceContainerName", 
663                                       object.name, @"sourceObjectName", 
664                                       destinationContainerName, @"destinationContainerName", 
665                                       object.name, @"destinationObjectName", 
666                                       nil];
667             if (sharingAccount)
668                 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
669             [objectRequests addObject:objectRequest];
670         }
671     } else {
672         if (![objectName hasSuffix:@"/"]) {
673             objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos 
674                                                                       containerName:containerName 
675                                                                          objectName:objectName 
676                                                                         contentType:nil 
677                                                                     contentEncoding:nil 
678                                                                  contentDisposition:nil 
679                                                                            manifest:nil 
680                                                                             sharing:nil 
681                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
682                                                                            metadata:nil 
683                                                            destinationContainerName:destinationContainerName 
684                                                               destinationObjectName:destinationObjectName 
685                                                                  destinationAccount:nil 
686                                                                       sourceVersion:nil];
687             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
688                                       containerName, @"sourceContainerName", 
689                                       objectName, @"sourceObjectName", 
690                                       destinationContainerName, @"destinationContainerName", 
691                                       destinationObjectName, @"destinationObjectName", 
692                                       nil];
693             if (sharingAccount)
694                 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
695             [objectRequests addObject:objectRequest];
696         }
697         NSRange prefixRange = NSMakeRange(0, [objectName length]);
698         NSString *newObjectName;
699         for (ASIPithosObject *object in objects) {
700             newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
701                                                                    withString:destinationObjectName
702                                                                       options:NSAnchoredSearch
703                                                                         range:prefixRange];
704             objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos 
705                                                                       containerName:containerName 
706                                                                          objectName:object.name 
707                                                                         contentType:nil 
708                                                                     contentEncoding:nil 
709                                                                  contentDisposition:nil 
710                                                                            manifest:nil 
711                                                                             sharing:nil 
712                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
713                                                                            metadata:nil 
714                                                            destinationContainerName:destinationContainerName 
715                                                               destinationObjectName:newObjectName 
716                                                                  destinationAccount:nil 
717                                                                       sourceVersion:nil];
718             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
719                                       containerName, @"sourceContainerName", 
720                                       object.name, @"sourceObjectName", 
721                                       destinationContainerName, @"destinationContainerName", 
722                                       newObjectName, @"destinationObjectName", 
723                                       nil];
724             if (sharingAccount)
725                 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
726             [objectRequests addObject:objectRequest];
727         }
728     }
729     
730     if ([objectRequests count] == 0)
731         return nil;
732     return objectRequests;
733 }
734
735 #pragma mark -
736 #pragma mark Move
737
738 + (ASIPithosObjectRequest *)moveObjectRequestWithPithos:(ASIPithos *)pithos 
739                                           containerName:(NSString *)containerName 
740                                              objectName:(NSString *)objectName 
741                                destinationContainerName:(NSString *)destinationContainerName 
742                                   destinationObjectName:(NSString *)destinationObjectName 
743                                           checkIfExists:(BOOL)ifExists {
744     if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName 
745                                           sharingAccount:nil])
746         return nil;
747     
748     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
749                                                                                       containerName:containerName 
750                                                                                          objectName:objectName 
751                                                                                         contentType:nil 
752                                                                                     contentEncoding:nil 
753                                                                                  contentDisposition:nil 
754                                                                                            manifest:nil 
755                                                                                             sharing:nil 
756                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
757                                                                                            metadata:nil 
758                                                                            destinationContainerName:destinationContainerName 
759                                                                               destinationObjectName:destinationObjectName 
760                                                                                  destinationAccount:nil];
761     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
762                               containerName, @"sourceContainerName", 
763                               objectName, @"sourceObjectName", 
764                               destinationContainerName, @"destinationContainerName", 
765                               destinationObjectName, @"destinationObjectName", 
766                               nil];
767     return objectRequest;
768 }
769
770 + (NSArray *)moveObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos 
771                                      containerName:(NSString *)containerName 
772                                         objectName:(NSString *)objectName 
773                           destinationContainerName:(NSString *)destinationContainerName 
774                              destinationObjectName:(NSString *)destinationObjectName 
775                                      checkIfExists:(BOOL)ifExists {
776     if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName 
777                                           sharingAccount:nil])
778         return nil;
779     
780     NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName 
781                                               delimiter:nil sharingAccount:nil];
782     if (objects == nil)
783         return nil;
784     
785     ASIPithosObjectRequest *objectRequest;
786     NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
787     if ([objectName isEqualToString:destinationObjectName]) {
788         if (![objectName hasSuffix:@"/"]) {
789             objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
790                                                                       containerName:containerName 
791                                                                          objectName:objectName 
792                                                                         contentType:nil 
793                                                                     contentEncoding:nil 
794                                                                  contentDisposition:nil 
795                                                                            manifest:nil 
796                                                                             sharing:nil 
797                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
798                                                                            metadata:nil 
799                                                            destinationContainerName:destinationContainerName 
800                                                               destinationObjectName:objectName 
801                                                                  destinationAccount:nil];
802             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
803                                       containerName, @"sourceContainerName", 
804                                       objectName, @"sourceObjectName", 
805                                       destinationContainerName, @"destinationContainerName", 
806                                       objectName, @"destinationObjectName", 
807                                       nil];
808             [objectRequests addObject:objectRequest];
809         }
810         for (ASIPithosObject *object in objects) {
811             objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
812                                                                       containerName:containerName 
813                                                                          objectName:object.name 
814                                                                         contentType:nil 
815                                                                     contentEncoding:nil 
816                                                                  contentDisposition:nil 
817                                                                            manifest:nil 
818                                                                             sharing:nil 
819                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
820                                                                            metadata:nil 
821                                                            destinationContainerName:destinationContainerName 
822                                                               destinationObjectName:object.name 
823                                                                  destinationAccount:nil];
824             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
825                                       containerName, @"sourceContainerName", 
826                                       object.name, @"sourceObjectName", 
827                                       destinationContainerName, @"destinationContainerName", 
828                                       object.name, @"destinationObjectName", 
829                                       nil];
830             [objectRequests addObject:objectRequest];
831         }
832     } else {
833         if (![objectName hasSuffix:@"/"]) {
834             objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
835                                                                       containerName:containerName 
836                                                                          objectName:objectName 
837                                                                         contentType:nil 
838                                                                     contentEncoding:nil 
839                                                                  contentDisposition:nil 
840                                                                            manifest:nil 
841                                                                             sharing:nil 
842                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
843                                                                            metadata:nil 
844                                                            destinationContainerName:destinationContainerName 
845                                                               destinationObjectName:destinationObjectName 
846                                                                  destinationAccount:nil];
847             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
848                                       containerName, @"sourceContainerName", 
849                                       objectName, @"sourceObjectName", 
850                                       destinationContainerName, @"destinationContainerName", 
851                                       destinationObjectName, @"destinationObjectName", 
852                                       nil];
853             [objectRequests addObject:objectRequest];
854         }
855         NSRange prefixRange = NSMakeRange(0, [objectName length]);
856         NSString *newObjectName;
857         for (ASIPithosObject *object in objects) {
858             newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
859                                                                    withString:destinationObjectName
860                                                                       options:NSAnchoredSearch
861                                                                         range:prefixRange];
862             objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
863                                                                       containerName:containerName 
864                                                                          objectName:object.name
865                                                                         contentType:nil 
866                                                                     contentEncoding:nil 
867                                                                  contentDisposition:nil 
868                                                                            manifest:nil 
869                                                                             sharing:nil 
870                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
871                                                                            metadata:nil 
872                                                            destinationContainerName:destinationContainerName 
873                                                               destinationObjectName:newObjectName 
874                                                                  destinationAccount:nil];
875             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
876                                       containerName, @"sourceContainerName", 
877                                       object.name, @"sourceObjectName", 
878                                       destinationContainerName, @"destinationContainerName", 
879                                       newObjectName, @"destinationObjectName", 
880                                       nil];
881             [objectRequests addObject:objectRequest];
882         }
883     }
884      
885     if ([objectRequests count] == 0)
886         return nil;
887     return objectRequests;
888 }
889
890 #pragma mark -
891 #pragma mark Helper Methods
892
893 // Size of the file in bytes
894 + (NSUInteger)bytesOfFile:(NSString *)filePath {
895     NSFileManager *fileManager = [NSFileManager defaultManager];
896     NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil];
897     return [[attributes objectForKey:NSFileSize] unsignedIntegerValue];
898 }
899
900 // Content type of the file or nil if it cannot be determined
901 + (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error {
902     // Based on http://www.ddeville.me/2011/12/mime-to-UTI-cocoa/
903     // and Apple example ImageBrowserViewAppearance/ImageBrowserController.m
904     LSItemInfoRecord info;
905     CFStringRef uti = NULL;
906     CFURLRef url = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)filePath, kCFURLPOSIXPathStyle, FALSE);
907     if (LSCopyItemInfoForURL(url, kLSRequestExtension | kLSRequestTypeCreator, &info) == noErr) {
908         // Obtain the UTI using the file information.
909         // If there is a file extension, get the UTI.
910         if (info.extension != NULL) {
911             uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, info.extension, kUTTypeData);
912             CFRelease(info.extension);
913         }
914         // No UTI yet
915         if (uti == NULL) {
916             // If there is an OSType, get the UTI.
917             CFStringRef typeString = UTCreateStringForOSType(info.filetype);
918             if ( typeString != NULL) {
919                 uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, typeString, kUTTypeData);
920                 CFRelease(typeString);
921             }
922         }
923         if (uti != NULL) {
924             CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
925             CFRelease(uti);
926             return (NSString *)MIMEType;
927         }
928     }
929     return nil;
930 }
931
932 // Creates a directory if it doesn't exist and returns if successful
933 + (BOOL)safeCreateDirectory:(NSString *)directoryPath error:(NSError **)error {
934     NSFileManager *fileManager = [NSFileManager defaultManager];
935     BOOL isDirectory;
936     BOOL fileExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory];
937     if (fileExists)
938         return isDirectory;
939     if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:error] || *error)
940         return NO;
941     return YES;
942 }
943
944 // Removes contents of a directory
945 + (void)removeContentsAtPath:(NSString *)dirPath {
946     NSFileManager *fileManager = [NSFileManager defaultManager];
947     NSError *error = nil;
948     BOOL isDirectory;
949     if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory])
950         return;
951     if (isDirectory) {
952         for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:dirPath error:&error]) {
953             if (error) {
954                 dispatch_async(dispatch_get_main_queue(), ^{
955                     [self fileActionFailedAlertWithTitle:@"Directory Contents Error" 
956                                                  message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", dirPath] 
957                                                    error:error];
958                 });
959                 break;
960             }
961             NSString *subFilePath = [dirPath stringByAppendingPathComponent:subPath];
962             if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
963                 dispatch_async(dispatch_get_main_queue(), ^{
964                     [self fileActionFailedAlertWithTitle:@"Remove File Error" 
965                                                  message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
966                                                    error:error];
967                 });
968             }
969             error = nil;
970         }
971     } else if (![fileManager removeItemAtPath:dirPath error:&error] || error) {
972         dispatch_async(dispatch_get_main_queue(), ^{
973             [self fileActionFailedAlertWithTitle:@"Remove File Error" 
974                                          message:[NSString stringWithFormat:@"Cannot remove file at '%@'", dirPath] 
975                                            error:error];
976         });
977     }
978 }
979
980 // Returns if an object is a directory based on its content type
981 + (BOOL)isContentTypeDirectory:(NSString *)contentType {
982     return ([contentType isEqualToString:@"application/directory"] ||
983             [contentType hasPrefix:@"application/directory;"] ||
984             [contentType isEqualToString:@"application/folder"] ||
985             [contentType hasPrefix:@"application/folder;"]);
986 }
987
988 // Returns if an object exists at the given container/object path and if this object is an application/directory
989 // If an error occured an alert is shown and it is returned so the caller won't proceed
990 + (BOOL)objectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName 
991                        error:(NSError **)error isDirectory:(BOOL *)isDirectory sharingAccount:(NSString *)sharingAccount {
992     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos 
993                                                                                       containerName:containerName 
994                                                                                          objectName:objectName];
995     if (sharingAccount)
996         [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
997     ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
998     [networkQueue go];
999     [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:objectRequest]] waitUntilFinished:YES];
1000     *error = [objectRequest error];
1001     if (*error) {
1002         dispatch_async(dispatch_get_main_queue(), ^{
1003             [self httpRequestErrorAlertWithRequest:objectRequest];
1004         });
1005         return NO;
1006     } else if (objectRequest.responseStatusCode == 200) {
1007         *isDirectory = [self isContentTypeDirectory:[objectRequest contentType]];
1008         return YES;
1009     }
1010     return NO;
1011 }
1012
1013 // Returns if the caller should proceed, after an interactive check if an object exists 
1014 // at the given container/object path is performed
1015 + (BOOL)proceedIfObjectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName 
1016                        sharingAccount:(NSString *)sharingAccount {
1017     NSError *error = nil;
1018     BOOL isDirectory;
1019     BOOL objectExists = [self objectExistsAtPithos:pithos containerName:containerName objectName:objectName 
1020                                              error:&error isDirectory:&isDirectory sharingAccount:sharingAccount];
1021     if (error) {
1022         return NO;
1023     } else if (objectExists) {
1024         __block NSInteger choice;
1025         dispatch_sync(dispatch_get_main_queue(), ^{
1026             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1027             if (isDirectory) {
1028                 [alert setMessageText:@"Directory Exists"];
1029                 if (sharingAccount)
1030                     [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
1031                 else
1032                     [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1033             } else {
1034                 [alert setMessageText:@"Object Exists"];
1035                 if (sharingAccount)
1036                     [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
1037                 else
1038                     [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1039             }
1040             [alert addButtonWithTitle:@"OK"];
1041             [alert addButtonWithTitle:@"Cancel"];
1042             choice = [alert runModal];
1043         });
1044         if (choice == NSAlertSecondButtonReturn)
1045             return NO;
1046     }
1047     return YES;
1048 }
1049
1050 // List of objects at the given container/object path, with prefix and or delimiter
1051 + (NSArray *)objectsWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix 
1052                      delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1053     NSMutableArray *objects = [NSMutableArray array];
1054     NSString *marker = nil;
1055     do {
1056         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
1057                                                                                                 containerName:containerName 
1058                                                                                                         limit:0 
1059                                                                                                        marker:marker 
1060                                                                                                        prefix:objectNamePrefix 
1061                                                                                                     delimiter:delimiter 
1062                                                                                                          path:nil 
1063                                                                                                          meta:nil 
1064                                                                                                        shared:NO 
1065                                                                                                         until:nil];
1066         if (sharingAccount)
1067             [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
1068         ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1069         [networkQueue go];
1070         [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:containerRequest]] waitUntilFinished:YES];
1071         if ([containerRequest error]) {
1072             dispatch_sync(dispatch_get_main_queue(), ^{
1073                 [self httpRequestErrorAlertWithRequest:containerRequest];
1074             });
1075             return nil;
1076         }
1077         NSArray *someObjects = [containerRequest objects];
1078         [objects addObjectsFromArray:someObjects];
1079         if ([someObjects count] < 10000)
1080             marker = nil;
1081         else
1082             marker = [[someObjects lastObject] name];
1083     } while (marker);
1084     return objects;
1085 }
1086
1087 // List of objects at the given container/object path, that may be a subdir or an application/directory, 
1088 // with prefix and or delimiter
1089 + (NSArray *)objectsForSubdirWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName 
1090                               delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1091     NSString *subdirNamePrefix = [NSString stringWithString:objectName];
1092     if (![subdirNamePrefix hasSuffix:@"/"])
1093         subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"];
1094     return [self objectsWithPithos:pithos containerName:containerName objectNamePrefix:subdirNamePrefix 
1095                          delimiter:delimiter sharingAccount:sharingAccount];
1096 }
1097
1098 // A safe object name at the given container/object path
1099 // The original name has " %d" appended to it before any ".*" suffix, for the first integer that produces a name that is free to use
1100 // If the original name hasn't got a "." but has a "/" suffix, then it is replaced with " %d/" instead
1101 // Subdirs are taken into consideration
1102 + (NSString *)safeObjectNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName {
1103     NSString *objectNamePrefix;
1104     NSString *objectNameExtraSuffix;
1105     NSRange lastDotRange = [objectName rangeOfString:@"." options:NSBackwardsSearch];
1106     if (lastDotRange.length == 1) {
1107         objectNamePrefix = [objectName substringToIndex:lastDotRange.location];
1108         objectNameExtraSuffix = [objectName substringFromIndex:lastDotRange.location];
1109     } else if ([objectName hasSuffix:@"/"]) {
1110         objectNamePrefix = [objectName substringToIndex:([objectName length] - 1)];
1111         objectNameExtraSuffix = [NSString stringWithString:@"/"];
1112     } else {
1113         objectNamePrefix = [NSString stringWithString:objectName];
1114         objectNameExtraSuffix = [NSString string];
1115     }
1116     NSArray *objects = [self objectsWithPithos:pithos containerName:containerName 
1117                               objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent] 
1118                                      delimiter:@"/" sharingAccount:nil];
1119     if (objects == nil)
1120         return nil;
1121     if ([objects count] == 0)
1122         return objectName;
1123     NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1124                                    [[objects objectsAtIndexes:
1125                                      [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1126         if (pithosObject.subdir)
1127             return NO;
1128         return YES;
1129     }]] valueForKey:@"name"]];
1130     for (NSString *name in [[objects objectsAtIndexes:
1131                              [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1132         if (pithosObject.subdir)
1133             return YES;
1134         return NO;
1135     }]] valueForKey:@"name"]) {
1136         [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1137     }
1138     if (![objectNames containsObject:objectName])
1139         return objectName;
1140     NSUInteger objectNameSuffix = 2;
1141     NSString *safeObjectName;
1142     do {
1143         safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix];
1144         objectNameSuffix++;
1145     } while ([objectNames containsObject:safeObjectName]);
1146     return safeObjectName;    
1147 }
1148
1149 // A safe object name at the given container/object path that may be a subdir or application/directory
1150 // The original name has " %d" appended to it, for the first integer that produces a name that is free to use
1151 // If the original name has a "/" suffix, then it is replaced with " %d/" instead
1152 // Subdirs are taken into consideration
1153 + (NSString *)safeSubdirNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName subdirName:(NSString *)subdirName {
1154     NSString *subdirNamePrefix;
1155     NSString *subdirNameExtraSuffix;
1156     if ([subdirName hasSuffix:@"/"]) {
1157         subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)];
1158         subdirNameExtraSuffix = [NSString stringWithString:@"/"];
1159     } else {
1160         subdirNamePrefix = [NSString stringWithString:subdirName];
1161         subdirNameExtraSuffix = [NSString string];
1162     }
1163     NSArray *objects = [self objectsWithPithos:pithos containerName:containerName 
1164                               objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent] 
1165                                      delimiter:@"/" sharingAccount:nil];
1166     if (objects == nil)
1167         return nil;
1168     if ([objects count] == 0)
1169         return subdirName;
1170     NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1171                                    [[objects objectsAtIndexes:
1172                                      [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1173         if (pithosObject.subdir)
1174             return NO;
1175         return YES;
1176     }]] valueForKey:@"name"]];
1177     for (NSString *name in [[objects objectsAtIndexes:
1178                              [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1179         if (pithosObject.subdir)
1180             return YES;
1181         return NO;
1182     }]] valueForKey:@"name"]) {
1183         [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1184     }
1185     if (![objectNames containsObject:subdirNamePrefix])
1186         return subdirName;
1187     NSUInteger subdirNameSuffix = 2;
1188     NSString *safeSubdirName;
1189     do {
1190         safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix];
1191         subdirNameSuffix++;
1192     } while ([objectNames containsObject:safeSubdirName]);
1193     return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix];
1194 }
1195
1196 #pragma mark -
1197 #pragma mark Alerts
1198
1199 + (NSInteger)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
1200     NSString *message = [NSString stringWithFormat:
1201                          @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", 
1202                          [request error], 
1203                          request.requestMethod, 
1204                          request.url, 
1205                          [request requestHeaders], 
1206                          [request responseHeaders], 
1207                          [request responseString]];
1208     NSLog(@"%@", message);
1209     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1210     [alert setMessageText:@"HTTP Request Error"];
1211     [alert setInformativeText:message];
1212     [alert addButtonWithTitle:@"OK"];
1213     return [alert runModal];
1214 }
1215
1216 + (NSInteger)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
1217     NSString *message = [NSString stringWithFormat:
1218                          @"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", 
1219                          request.responseStatusCode, 
1220                          request.responseStatusMessage, 
1221                          request.requestMethod, 
1222                          request.url, 
1223                          [request requestHeaders], 
1224                          [request responseHeaders], 
1225                          [request responseString]];
1226     NSLog(@"%@", message);
1227     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1228     [alert setMessageText:@"Unexpected Response Status"];
1229     [alert setInformativeText:message];
1230     [alert addButtonWithTitle:@"OK"];
1231     return [alert runModal];
1232 }
1233
1234 + (NSInteger)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error {
1235     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1236     [alert setMessageText:title];
1237     if (error)
1238         [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, error]];
1239     else
1240         [alert setInformativeText:message];
1241     [alert addButtonWithTitle:@"OK"];
1242     return [alert runModal];
1243 }
1244
1245 #pragma mark -
1246 #pragma mark Request Helper Methods
1247
1248 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority {
1249     [request setTimeOutSeconds:60];
1250     request.numberOfTimesToRetryOnTimeout = 10;
1251     [request setQueuePriority:priority];
1252     return request;
1253 }
1254
1255 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request {
1256     return [self prepareRequest:request priority:NSOperationQueuePriorityNormal];
1257 }
1258
1259 + (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request {
1260     NSMutableDictionary *userInfo = (NSMutableDictionary *)[[request.userInfo retain] autorelease];
1261     request.userInfo = nil;
1262     ASIPithosRequest *newRequest = [request copy];
1263     newRequest.userInfo = userInfo;
1264     return newRequest;
1265 }
1266
1267 @end