Add initial support for user catalog
[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];
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         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"];
102             [alert runModal];
103         });
104         return nil;
105     }
106
107     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos 
108                                                                                   containerName:containerName 
109                                                                                      objectName:objectName 
110                                                                                         version:version];
111     if (sharingAccount)
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", 
118                               nil];
119     return objectRequest;
120 }
121
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];
139         });
140         if (choice == NSAlertSecondButtonReturn)
141             return nil;
142     }
143     
144     NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName 
145                                               delimiter:nil sharingAccount:sharingAccount];
146     if (objects == nil)
147         return nil;
148     
149     NSFileManager *fileManager = [NSFileManager defaultManager];
150     NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[objects count]];
151     NSUInteger subdirPrefixLength = [objectName length];
152
153     NSError *error = nil;
154     [fileManager createDirectoryAtPath:[directoryPath stringByAppendingPathComponent:subdirName] withIntermediateDirectories:YES attributes:nil error:&error];
155     if (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"];
163             [alert runModal];
164         });
165     }
166     
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]];
171             
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];
177                 if (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"];
185                         [alert runModal];
186                     });
187                 }
188             } else if (!directoryIsDirectory) {
189                 [fileManager removeItemAtPath:subdirDirectoryPath error:&error];
190                 if (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"];
198                         [alert runModal];
199                     });
200                 }
201             }
202         } else {
203             NSString *fileName = [object.name lastPathComponent];
204             if([object.name hasSuffix:@"/"])
205                 fileName = [fileName stringByAppendingString:@"/"];
206             
207             NSString *objectDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
208             objectDirectoryPath = [objectDirectoryPath stringByAppendingPathComponent:[object.name substringWithRange:NSMakeRange(subdirPrefixLength, [object.name length] - subdirPrefixLength - [fileName length])]];
209             
210             ASIPithosObjectRequest *objectRequest = [self objectDataRequestWithPithos:pithos 
211                                                                         containerName:containerName 
212                                                                            objectName:object.name 
213                                                                               version:nil 
214                                                                           toDirectory:objectDirectoryPath 
215                                                                       withNewFileName:nil 
216                                                                         checkIfExists:NO 
217                                                                        sharingAccount:sharingAccount];
218             [(NSMutableDictionary *)objectRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:object.bytes] forKey:@"bytes"];
219             [objectRequests addObject:objectRequest];
220         }
221     }
222     
223     return objectRequests;
224 }
225
226 #pragma mark -
227 #pragma mark Download Block
228
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
239                                                                                         version:nil
240                                                                                           range:[NSString stringWithFormat:@"bytes=%lu-%lu", rangeStart, rangeEnd]
241                                                                                         ifMatch:object.hash];
242     return objectRequest;
243 }
244
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]])
252             return YES;
253         return NO;
254     }];
255 }
256
257 #pragma mark -
258 #pragma mark Upload
259
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])
272         return nil;
273     
274     if (*hashes == nil)
275         *hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
276     if (*hashes == nil)
277         return nil;
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 
286                                                                                      contentEncoding:nil 
287                                                                                   contentDisposition:nil 
288                                                                                             manifest:nil 
289                                                                                              sharing:nil 
290                                                                                             isPublic:ASIPithosObjectRequestPublicIgnore 
291                                                                                             metadata:nil
292                                                                                            blockSize:blockSize
293                                                                                            blockHash:blockHash 
294                                                                                               hashes:*hashes 
295                                                                                                bytes:bytes];
296     if (sharingAccount) 
297         [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
298     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
299                               fileName, @"fileName", 
300                               [NSNumber numberWithUnsignedInteger:bytes], @"bytes", 
301                               nil];
302     return objectRequest;
303 }
304
305 + (NSIndexSet *)missingBlocksForHashes:(NSArray *)hashes withMissingHashes:(NSArray *)missingHashes {
306     NSMutableIndexSet *missingBlocks = [NSMutableIndexSet indexSet];
307     for (NSString *missingHash in missingHashes) {
308         if (![missingHash length])
309             break;
310         NSUInteger missingBlock = [hashes indexOfObject:missingHash];
311         if (missingBlock != NSNotFound)
312             [missingBlocks addIndex:missingBlock];
313     }
314     return missingBlocks;
315 }
316
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];
325     
326     NSFileManager *fileManager = [NSFileManager defaultManager];
327     NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
328     
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"];
346             [alert runModal];
347         });
348         return nil;
349     }
350     NSFileHandle *tempFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor closeOnDealloc:YES];
351
352     [missingBlocks enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
353         [fileHandle seekToFileOffset:(idx*blockSize)];
354         [tempFileHandle writeData:[fileHandle readDataOfLength:blockSize]];
355     }];
356     [tempFileHandle closeFile];
357     [fileHandle closeFile];
358
359     ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos 
360                                                                                                     containerName:containerName 
361                                                                                                            policy:nil 
362                                                                                                          metadata:nil 
363                                                                                                            update:YES 
364                                                                                                              file:tempFilePath];
365     if (sharingAccount)
366         [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
367     return containerRequest;
368 }
369
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 
382                                                                                                            policy:nil 
383                                                                                                          metadata:nil 
384                                                                                                            update:YES 
385                                                                                                              data:blockData];
386     if (sharingAccount)
387         [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
388     return containerRequest;
389 }
390
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])
406         return nil;
407
408     NSFileManager *fileManager = [NSFileManager defaultManager];
409     NSError *error = nil;
410     NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:directoryPath error:&error];
411     if (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"];
418             [alert runModal];
419         });
420         return nil;
421     }
422     
423     *directoryObjectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
424     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
425                                                                                        containerName:containerName 
426                                                                                           objectName:objectName 
427                                                                                                 eTag:nil 
428                                                                                          contentType:@"application/directory" 
429                                                                                      contentEncoding:nil 
430                                                                                   contentDisposition:nil 
431                                                                                             manifest:nil 
432                                                                                              sharing:nil 
433                                                                                             isPublic:ASIPithosObjectRequestPublicIgnore 
434                                                                                             metadata:nil 
435                                                                                                 data:[NSData data]];
436     if (sharingAccount)
437         [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
438     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
439                               [directoryPath lastPathComponent], @"fileName", 
440                               nil];
441     [*directoryObjectRequests addObject:objectRequest];
442     
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]];
448     BOOL isDirectory;
449     NSString *subObjectName;
450     NSArray *hashes;
451     NSUInteger bytes;
452     NSString *contentType;
453     NSString *filePath;
454     NSString *fileName;
455     for (NSString *objectNameSuffix in subPaths) {
456         filePath = [directoryPath stringByAppendingPathComponent:objectNameSuffix];
457         if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
458             if (!isDirectory) {
459                 hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
460                 if (hashes) {
461                     subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping];
462                     fileName = [filePath lastPathComponent];
463                     if ([filePath hasSuffix:@"/"])
464                         fileName = [fileName stringByAppendingString:@"/"];
465                     bytes = [self bytesOfFile:filePath];
466                     error = nil;
467                     contentType = [self contentTypeOfFile:filePath error:&error];
468                     if (contentType == nil)
469                         contentType = @"application/octet-stream";
470                     #if DEBUG_PITHOS
471                     if (error)
472                         DLog(@"contentType detection error: %@", error);
473                     #endif
474                     objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
475                                                                                containerName:containerName 
476                                                                                   objectName:subObjectName 
477                                                                                  contentType:contentType 
478                                                                              contentEncoding:nil 
479                                                                           contentDisposition:nil 
480                                                                                     manifest:nil 
481                                                                                      sharing:nil 
482                                                                                     isPublic:ASIPithosObjectRequestPublicIgnore 
483                                                                                     metadata:nil
484                                                                                    blockSize:blockSize
485                                                                                    blockHash:blockHash 
486                                                                                       hashes:hashes 
487                                                                                        bytes:bytes];
488                     if (sharingAccount)
489                         [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
490                     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
491                                               fileName, @"fileName", 
492                                               [NSNumber numberWithUnsignedInteger:bytes], @"bytes", 
493                                               nil];
494                     [objectRequests addObject:objectRequest];
495                     [*objectNames addObject:subObjectName];
496                     [*contentTypes addObject:contentType];
497                     [*filePaths addObject:filePath];
498                     [*hashesArrays addObject:hashes];
499                 }
500                 
501             } else {
502                 subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping];
503                 fileName = [filePath lastPathComponent];
504                 objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
505                                                                            containerName:containerName 
506                                                                               objectName:subObjectName 
507                                                                                     eTag:nil 
508                                                                              contentType:@"application/directory" 
509                                                                          contentEncoding:nil 
510                                                                       contentDisposition:nil 
511                                                                                 manifest:nil 
512                                                                                  sharing:nil 
513                                                                                 isPublic:ASIPithosObjectRequestPublicIgnore 
514                                                                                 metadata:nil 
515                                                                                     data:[NSData data]];
516                 if (sharingAccount)
517                     [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
518                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
519                                           fileName, @"fileName", 
520                                           nil];
521                 [*directoryObjectRequests addObject:objectRequest];
522             }
523         }
524     }
525     
526     return objectRequests;
527 }
528
529 #pragma mark -
530 #pragma mark Delete
531
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 
536                                          sharingAccount:nil];
537     if (objects == nil)
538         return nil;
539
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", 
546                                   nil];
547         [objectRequests addObject:objectRequest];
548     }
549     NSString *fileName;
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", 
557                                   nil];
558         [objectRequests addObject:objectRequest];
559     }
560     
561     if ([objectRequests count] == 0)
562         return nil;
563     return objectRequests;
564 }
565
566 #pragma mark -
567 #pragma mark Copy
568
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 
577                                           sharingAccount:nil])
578         return nil;
579     
580     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos 
581                                                                                       containerName:containerName 
582                                                                                          objectName:objectName 
583                                                                                         contentType:nil 
584                                                                                     contentEncoding:nil 
585                                                                                  contentDisposition:nil 
586                                                                                            manifest:nil 
587                                                                                             sharing:nil 
588                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
589                                                                                            metadata:nil 
590                                                                            destinationContainerName:destinationContainerName 
591                                                                               destinationObjectName:destinationObjectName 
592                                                                                  destinationAccount:nil
593                                                                                       sourceVersion:nil];
594     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
595                               containerName, @"sourceContainerName", 
596                               objectName, @"sourceObjectName", 
597                               destinationContainerName, @"destinationContainerName", 
598                               destinationObjectName, @"destinationObjectName", 
599                               nil];
600     if (sharingAccount) 
601         [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
602     return objectRequest;
603 }
604
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 
613                                           sharingAccount:nil])
614         return nil;
615     
616     NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName 
617                                               delimiter:nil sharingAccount:sharingAccount];
618     if (objects == nil)
619         return nil;
620     
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 
628                                                                         contentType:nil 
629                                                                     contentEncoding:nil 
630                                                                  contentDisposition:nil 
631                                                                            manifest:nil 
632                                                                             sharing:nil 
633                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
634                                                                            metadata:nil 
635                                                            destinationContainerName:destinationContainerName 
636                                                               destinationObjectName:objectName 
637                                                                  destinationAccount:nil 
638                                                                       sourceVersion:nil];
639             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
640                                       containerName, @"sourceContainerName", 
641                                       objectName, @"sourceObjectName", 
642                                       destinationContainerName, @"destinationContainerName", 
643                                       objectName, @"destinationObjectName", 
644                                       nil];
645             if (sharingAccount)
646                 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
647             [objectRequests addObject:objectRequest];
648         }
649         for (ASIPithosObject *object in objects) {
650             objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos 
651                                                                       containerName:containerName 
652                                                                          objectName:object.name 
653                                                                         contentType:nil 
654                                                                     contentEncoding:nil 
655                                                                  contentDisposition:nil 
656                                                                            manifest:nil 
657                                                                             sharing:nil 
658                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
659                                                                            metadata:nil 
660                                                            destinationContainerName:destinationContainerName 
661                                                               destinationObjectName:object.name 
662                                                                  destinationAccount:nil 
663                                                                       sourceVersion:nil];
664             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
665                                       containerName, @"sourceContainerName", 
666                                       object.name, @"sourceObjectName", 
667                                       destinationContainerName, @"destinationContainerName", 
668                                       object.name, @"destinationObjectName", 
669                                       nil];
670             if (sharingAccount)
671                 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
672             [objectRequests addObject:objectRequest];
673         }
674     } else {
675         if (![objectName hasSuffix:@"/"]) {
676             objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos 
677                                                                       containerName:containerName 
678                                                                          objectName:objectName 
679                                                                         contentType:nil 
680                                                                     contentEncoding:nil 
681                                                                  contentDisposition:nil 
682                                                                            manifest:nil 
683                                                                             sharing:nil 
684                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
685                                                                            metadata:nil 
686                                                            destinationContainerName:destinationContainerName 
687                                                               destinationObjectName:destinationObjectName 
688                                                                  destinationAccount:nil 
689                                                                       sourceVersion:nil];
690             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
691                                       containerName, @"sourceContainerName", 
692                                       objectName, @"sourceObjectName", 
693                                       destinationContainerName, @"destinationContainerName", 
694                                       destinationObjectName, @"destinationObjectName", 
695                                       nil];
696             if (sharingAccount)
697                 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
698             [objectRequests addObject:objectRequest];
699         }
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
706                                                                         range:prefixRange];
707             objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos 
708                                                                       containerName:containerName 
709                                                                          objectName:object.name 
710                                                                         contentType:nil 
711                                                                     contentEncoding:nil 
712                                                                  contentDisposition:nil 
713                                                                            manifest:nil 
714                                                                             sharing:nil 
715                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
716                                                                            metadata:nil 
717                                                            destinationContainerName:destinationContainerName 
718                                                               destinationObjectName:newObjectName 
719                                                                  destinationAccount:nil 
720                                                                       sourceVersion:nil];
721             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
722                                       containerName, @"sourceContainerName", 
723                                       object.name, @"sourceObjectName", 
724                                       destinationContainerName, @"destinationContainerName", 
725                                       newObjectName, @"destinationObjectName", 
726                                       nil];
727             if (sharingAccount)
728                 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
729             [objectRequests addObject:objectRequest];
730         }
731     }
732     
733     if ([objectRequests count] == 0)
734         return nil;
735     return objectRequests;
736 }
737
738 #pragma mark -
739 #pragma mark Move
740
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 
748                                           sharingAccount:nil])
749         return nil;
750     
751     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
752                                                                                       containerName:containerName 
753                                                                                          objectName:objectName 
754                                                                                         contentType:nil 
755                                                                                     contentEncoding:nil 
756                                                                                  contentDisposition:nil 
757                                                                                            manifest:nil 
758                                                                                             sharing:nil 
759                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
760                                                                                            metadata:nil 
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", 
769                               nil];
770     return objectRequest;
771 }
772
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 
780                                           sharingAccount:nil])
781         return nil;
782     
783     NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName 
784                                               delimiter:nil sharingAccount:nil];
785     if (objects == nil)
786         return nil;
787     
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 
795                                                                         contentType:nil 
796                                                                     contentEncoding:nil 
797                                                                  contentDisposition:nil 
798                                                                            manifest:nil 
799                                                                             sharing:nil 
800                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
801                                                                            metadata:nil 
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", 
810                                       nil];
811             [objectRequests addObject:objectRequest];
812         }
813         for (ASIPithosObject *object in objects) {
814             objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
815                                                                       containerName:containerName 
816                                                                          objectName:object.name 
817                                                                         contentType:nil 
818                                                                     contentEncoding:nil 
819                                                                  contentDisposition:nil 
820                                                                            manifest:nil 
821                                                                             sharing:nil 
822                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
823                                                                            metadata:nil 
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", 
832                                       nil];
833             [objectRequests addObject:objectRequest];
834         }
835     } else {
836         if (![objectName hasSuffix:@"/"]) {
837             objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
838                                                                       containerName:containerName 
839                                                                          objectName:objectName 
840                                                                         contentType:nil 
841                                                                     contentEncoding:nil 
842                                                                  contentDisposition:nil 
843                                                                            manifest:nil 
844                                                                             sharing:nil 
845                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
846                                                                            metadata:nil 
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", 
855                                       nil];
856             [objectRequests addObject:objectRequest];
857         }
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
864                                                                         range:prefixRange];
865             objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
866                                                                       containerName:containerName 
867                                                                          objectName:object.name
868                                                                         contentType:nil 
869                                                                     contentEncoding:nil 
870                                                                  contentDisposition:nil 
871                                                                            manifest:nil 
872                                                                             sharing:nil 
873                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
874                                                                            metadata:nil 
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", 
883                                       nil];
884             [objectRequests addObject:objectRequest];
885         }
886     }
887      
888     if ([objectRequests count] == 0)
889         return nil;
890     return objectRequests;
891 }
892
893 #pragma mark -
894 #pragma mark Helper Methods
895
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];
901 }
902
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);
916         }
917         // No UTI yet
918         if (uti == NULL) {
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);
924             }
925         }
926         if (uti != NULL) {
927             CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
928             CFRelease(uti);
929             CFRelease(url);
930             return (__bridge_transfer NSString *)MIMEType;
931         }
932     }
933     CFRelease(url);
934     return nil;
935 }
936
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];
940     BOOL isDirectory;
941     BOOL fileExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory];
942     if (fileExists)
943         return isDirectory;
944     if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:error] || *error)
945         return NO;
946     return YES;
947 }
948
949 // Removes contents of a directory
950 + (void)removeContentsAtPath:(NSString *)dirPath {
951     NSFileManager *fileManager = [NSFileManager defaultManager];
952     NSError *error = nil;
953     BOOL isDirectory;
954     if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory])
955         return;
956     if (isDirectory) {
957         for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:dirPath error:&error]) {
958             if (error) {
959                 [self fileActionFailedAlertWithTitle:@"Directory Contents Error" 
960                                              message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", dirPath] 
961                                                error:error];
962                 break;
963             }
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] 
968                                                error:error];
969             }
970             error = nil;
971         }
972     } else if (![fileManager removeItemAtPath:dirPath error:&error] || error) {
973         [self fileActionFailedAlertWithTitle:@"Remove File Error" 
974                                      message:[NSString stringWithFormat:@"Cannot remove file at '%@'", dirPath] 
975                                        error:error];
976     }
977 }
978
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;"]);
985 }
986
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];
994     if (sharingAccount)
995         [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
996     [self startAndWaitForRequest:objectRequest];
997     if (error != NULL) {
998         *error = [objectRequest error];
999         if (*error) {
1000             [self httpRequestErrorAlertWithRequest:objectRequest];
1001             return NO;
1002         } else if (objectRequest.responseStatusCode == 200) {
1003             *isDirectory = [self isContentTypeDirectory:[objectRequest contentType]];
1004             return YES;
1005         }
1006     }
1007     return NO;
1008 }
1009
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;
1015     BOOL isDirectory;
1016     BOOL objectExists = [self objectExistsAtPithos:pithos containerName:containerName objectName:objectName 
1017                                              error:&error isDirectory:&isDirectory sharingAccount:sharingAccount];
1018     if (error) {
1019         return NO;
1020     } else if (objectExists) {
1021         __block NSInteger choice;
1022         dispatch_sync(dispatch_get_main_queue(), ^{
1023             NSAlert *alert = [[NSAlert alloc] init];
1024             if (isDirectory) {
1025                 [alert setMessageText:@"Directory Exists"];
1026                 if (sharingAccount)
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]];
1028                 else
1029                     [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1030             } else {
1031                 [alert setMessageText:@"Object Exists"];
1032                 if (sharingAccount)
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]];
1034                 else
1035                     [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1036             }
1037             [alert addButtonWithTitle:@"OK"];
1038             [alert addButtonWithTitle:@"Cancel"];
1039             choice = [alert runModal];
1040         });
1041         if (choice == NSAlertSecondButtonReturn)
1042             return NO;
1043     }
1044     return YES;
1045 }
1046
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;
1052     do {
1053         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
1054                                                                                                 containerName:containerName 
1055                                                                                                         limit:0 
1056                                                                                                        marker:marker 
1057                                                                                                        prefix:objectNamePrefix 
1058                                                                                                     delimiter:delimiter 
1059                                                                                                          path:nil 
1060                                                                                                          meta:nil 
1061                                                                                                        shared:NO 
1062                                                                                                         until:nil];
1063         if (sharingAccount)
1064             [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
1065         [self startAndWaitForRequest:containerRequest];
1066         if ([containerRequest error]) {
1067             [self httpRequestErrorAlertWithRequest:containerRequest];
1068             return nil;
1069         }
1070         NSArray *someObjects = [containerRequest objects];
1071         [objects addObjectsFromArray:someObjects];
1072         if ([someObjects count] < 10000)
1073             marker = nil;
1074         else
1075             marker = [[someObjects lastObject] name];
1076     } while (marker);
1077     return objects;
1078 }
1079
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];
1089 }
1090
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 = @"/";
1105     } else {
1106         objectNamePrefix = [NSString stringWithString:objectName];
1107         objectNameExtraSuffix = [NSString string];
1108     }
1109     NSArray *objects = [self objectsWithPithos:pithos containerName:containerName 
1110                               objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent] 
1111                                      delimiter:@"/" sharingAccount:nil];
1112     if (objects == nil)
1113         return nil;
1114     if ([objects count] == 0)
1115         return objectName;
1116     NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1117                                    [[objects objectsAtIndexes:
1118                                      [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1119         if (pithosObject.subdir)
1120             return NO;
1121         return YES;
1122     }]] valueForKey:@"name"]];
1123     for (NSString *name in [[objects objectsAtIndexes:
1124                              [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1125         if (pithosObject.subdir)
1126             return YES;
1127         return NO;
1128     }]] valueForKey:@"name"]) {
1129         [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1130     }
1131     if (![objectNames containsObject:objectName])
1132         return objectName;
1133     NSUInteger objectNameSuffix = 2;
1134     NSString *safeObjectName;
1135     do {
1136         safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix];
1137         objectNameSuffix++;
1138     } while ([objectNames containsObject:safeObjectName]);
1139     return safeObjectName;    
1140 }
1141
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 = @"/";
1152     } else {
1153         subdirNamePrefix = [NSString stringWithString:subdirName];
1154         subdirNameExtraSuffix = [NSString string];
1155     }
1156     NSArray *objects = [self objectsWithPithos:pithos containerName:containerName 
1157                               objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent] 
1158                                      delimiter:@"/" sharingAccount:nil];
1159     if (objects == nil)
1160         return nil;
1161     if ([objects count] == 0)
1162         return subdirName;
1163     NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1164                                    [[objects objectsAtIndexes:
1165                                      [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1166         if (pithosObject.subdir)
1167             return NO;
1168         return YES;
1169     }]] valueForKey:@"name"]];
1170     for (NSString *name in [[objects objectsAtIndexes:
1171                              [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1172         if (pithosObject.subdir)
1173             return YES;
1174         return NO;
1175     }]] valueForKey:@"name"]) {
1176         [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1177     }
1178     if (![objectNames containsObject:subdirNamePrefix])
1179         return subdirName;
1180     NSUInteger subdirNameSuffix = 2;
1181     NSString *safeSubdirName;
1182     do {
1183         safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix];
1184         subdirNameSuffix++;
1185     } while ([objectNames containsObject:safeSubdirName]);
1186     return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix];
1187 }
1188
1189 #pragma mark -
1190 #pragma mark Alerts
1191
1192 + (void)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
1193     if (request.responseStatusCode == 401) {
1194         [self httpAuthenticationError];
1195         return;
1196     }
1197     NSString *message = [NSString stringWithFormat:
1198                          @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", 
1199                          [[request error] localizedDescription], 
1200                          request.requestMethod, 
1201                          request.url, 
1202                          [request requestHeaders], 
1203                          [request responseHeaders], 
1204                          [request responseString]];
1205     DLog(@"%@", message);
1206     dispatch_async(dispatch_get_main_queue(), ^{    
1207         @autoreleasepool {
1208             NSAlert *alert = [[NSAlert alloc] init];
1209             [alert setMessageText:@"HTTP Request Error"];
1210             [alert setInformativeText:message];
1211             [alert addButtonWithTitle:@"OK"];
1212             [alert runModal];
1213         }
1214     });
1215 }
1216
1217 + (void)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
1218     if (request.responseStatusCode == 401) {
1219         [self httpAuthenticationError];
1220         return;
1221     }
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, 
1227                          request.url, 
1228                          [request requestHeaders], 
1229                          [request responseHeaders], 
1230                          [request responseString]];
1231     DLog(@"%@", message);
1232     dispatch_async(dispatch_get_main_queue(), ^{
1233         @autoreleasepool {
1234             NSAlert *alert = [[NSAlert alloc] init];
1235             [alert setMessageText:@"Unexpected Response Status"];
1236             [alert setInformativeText:message];
1237             [alert addButtonWithTitle:@"OK"];
1238             [alert runModal];
1239         }
1240     });
1241 }
1242
1243 + (void)httpAuthenticationError {
1244     dispatch_async(dispatch_get_main_queue(), ^{
1245         @autoreleasepool {
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"];
1250             [alert runModal];
1251         }
1252     });
1253 }
1254
1255 + (void)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error {
1256     dispatch_async(dispatch_get_main_queue(), ^{
1257         @autoreleasepool {
1258             NSAlert *alert = [[NSAlert alloc] init];
1259             [alert setMessageText:title];
1260             if (error)
1261                 [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, [error localizedDescription]]];
1262             else
1263                 [alert setInformativeText:message];
1264             [alert addButtonWithTitle:@"OK"];
1265             [alert runModal];
1266         }
1267     });
1268 }
1269
1270 #pragma mark -
1271 #pragma mark Request Helper Methods
1272
1273 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority {
1274     [request setTimeOutSeconds:60];
1275     request.numberOfTimesToRetryOnTimeout = 10;
1276     [request setQueuePriority:priority];
1277     return request;
1278 }
1279
1280 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request {
1281     return [self prepareRequest:request priority:NSOperationQueuePriorityNormal];
1282 }
1283
1284 + (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request {
1285     NSMutableDictionary *userInfo = (NSMutableDictionary *)request.userInfo;
1286     request.userInfo = nil;
1287     ASIPithosRequest *newRequest = [request copy];
1288     newRequest.userInfo = userInfo;
1289     return newRequest;
1290 }
1291
1292 + (void)startAndWaitForRequest:(ASIPithosRequest *)request {
1293     ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1294     [networkQueue go];
1295     [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:request]] waitUntilFinished:YES];
1296 }
1297
1298 @end