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