Move main thread dispatch of alerts just before the alert creation. Use localized...
[pithos-macos] / pithos-macos / PithosUtilities.m
1 //
2 //  PithosUtilities.m
3 //  pithos-macos
4 //
5 // Copyright 2011-2012 GRNET S.A. All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
9 // conditions are met:
10 // 
11 //   1. Redistributions of source code must retain the above
12 //      copyright notice, this list of conditions and the following
13 //      disclaimer.
14 // 
15 //   2. Redistributions in binary form must reproduce the above
16 //      copyright notice, this list of conditions and the following
17 //      disclaimer in the documentation and/or other materials
18 //      provided with the distribution.
19 // 
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 // 
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
37
38 #import "PithosUtilities.h"
39 #import "ASINetworkQueue.h"
40 #import "ASIPithos.h"
41 #import "ASIPithosContainerRequest.h"
42 #import "ASIPithosObjectRequest.h"
43 #import "ASIPithosObject.h"
44 #import "HashMapHash.h"
45
46 @implementation PithosUtilities
47
48 #pragma mark -
49 #pragma mark Download
50
51 + (ASIPithosObjectRequest *)objectDataRequestWithPithos:(ASIPithos *)pithos 
52                                           containerName:(NSString *)containerName 
53                                              objectName:(NSString *)objectName 
54                                                 version:(NSString *)version 
55                                             toDirectory:(NSString *)directoryPath 
56                                         withNewFileName:(NSString *)newFileName 
57                                           checkIfExists:(BOOL)ifExists 
58                                          sharingAccount:(NSString *)sharingAccount {
59     NSString *fileName;
60     if (newFileName) {
61         fileName = [NSString stringWithString:newFileName];
62     } else {
63         fileName = [objectName lastPathComponent];
64         if ([objectName hasSuffix:@"/"])
65             fileName = [fileName stringByAppendingString:@"/"];    
66     }
67     fileName = [fileName stringByReplacingOccurrencesOfString:@"/" withString:@":"];
68     
69     NSFileManager *fileManager = [NSFileManager defaultManager];
70     
71     NSString *destinationPath = [directoryPath stringByAppendingPathComponent:fileName];
72     if (ifExists && [fileManager fileExistsAtPath:destinationPath]) {
73         __block NSInteger choice;
74         dispatch_sync(dispatch_get_main_queue(), ^{
75             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
76             [alert setMessageText:@"File Exists"];
77             [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", fileName]];
78             [alert addButtonWithTitle:@"OK"];
79             [alert addButtonWithTitle:@"Cancel"];
80             choice = [alert runModal];
81         });
82         if (choice == NSAlertSecondButtonReturn)
83             return nil;
84     }
85     
86     BOOL directoryIsDirectory;
87     BOOL directoryExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&directoryIsDirectory];
88     NSError *error = nil;
89     if (!directoryExists) {
90         [fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
91     } else if (!directoryIsDirectory) {
92         [fileManager removeItemAtPath:directoryPath error:&error];
93     }
94     if (error) {
95         NSLog(@"Cannot remove existing file '%@': %@", fileName, error);
96         dispatch_async(dispatch_get_main_queue(), ^{
97             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
98             [alert setMessageText:@"Removal Error"];
99             [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file '%@': %@", 
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] autorelease];
134             [alert setMessageText:@"File exists"];
135             [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", subdirName]];
136             [alert addButtonWithTitle:@"OK"];
137             [alert addButtonWithTitle:@"Cancel"];
138             choice = [alert runModal];
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         NSLog(@"Cannot create directory at '%@': %@", directoryPath, error);
157         dispatch_async(dispatch_get_main_queue(), ^{
158             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
159             [alert setMessageText:@"Create Directory Error"];
160             [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@", 
161                                        directoryPath, [error localizedDescription]]];
162             [alert addButtonWithTitle:@"OK"];
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                     NSLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error);
179                     dispatch_async(dispatch_get_main_queue(), ^{
180                         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
181                         [alert setMessageText:@"Create Directory Error"];
182                         [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@", 
183                                                    subdirDirectoryPath, [error localizedDescription]]];
184                         [alert addButtonWithTitle:@"OK"];
185                         [alert runModal];
186                     });
187                 }
188             } else if (!directoryIsDirectory) {
189                 [fileManager removeItemAtPath:subdirDirectoryPath error:&error];
190                 if (error) {
191                     NSLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error);
192                     dispatch_async(dispatch_get_main_queue(), ^{
193                         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
194                         [alert setMessageText:@"Remove File Error"];
195                         [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file at '%@': %@", 
196                                                    subdirDirectoryPath, [error localizedDescription]]];
197                         [alert addButtonWithTitle:@"OK"];
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     if (fileDescriptor == -1) {
340         dispatch_async(dispatch_get_main_queue(), ^{
341             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
342             [alert setMessageText:@"Create Temporary File Error"];
343             [alert setInformativeText:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]];
344             [alert addButtonWithTitle:@"OK"];
345             [alert runModal];
346         });
347         return nil;
348     }
349     free(tempFileNameCString);
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] autorelease];
414             [alert setMessageText:@"Directory Read Error"];
415             [alert setInformativeText:[NSString stringWithFormat:@"Cannot read contents of directory '%@': %@", 
416                                        [directoryPath lastPathComponent], [error localizedDescription]]];
417             [alert addButtonWithTitle:@"OK"];
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 (error)
471                         NSLog(@"contentType detection error: %@", error);
472                     objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
473                                                                                containerName:containerName 
474                                                                                   objectName:subObjectName 
475                                                                                  contentType:contentType 
476                                                                              contentEncoding:nil 
477                                                                           contentDisposition:nil 
478                                                                                     manifest:nil 
479                                                                                      sharing:nil 
480                                                                                     isPublic:ASIPithosObjectRequestPublicIgnore 
481                                                                                     metadata:nil
482                                                                                    blockSize:blockSize
483                                                                                    blockHash:blockHash 
484                                                                                       hashes:hashes 
485                                                                                        bytes:bytes];
486                     if (sharingAccount)
487                         [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
488                     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
489                                               fileName, @"fileName", 
490                                               [NSNumber numberWithUnsignedInteger:bytes], @"bytes", 
491                                               nil];
492                     [objectRequests addObject:objectRequest];
493                     [*objectNames addObject:subObjectName];
494                     [*contentTypes addObject:contentType];
495                     [*filePaths addObject:filePath];
496                     [*hashesArrays addObject:hashes];
497                 }
498                 
499             } else {
500                 subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping];
501                 fileName = [filePath lastPathComponent];
502                 objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
503                                                                            containerName:containerName 
504                                                                               objectName:subObjectName 
505                                                                                     eTag:nil 
506                                                                              contentType:@"application/directory" 
507                                                                          contentEncoding:nil 
508                                                                       contentDisposition:nil 
509                                                                                 manifest:nil 
510                                                                                  sharing:nil 
511                                                                                 isPublic:ASIPithosObjectRequestPublicIgnore 
512                                                                                 metadata:nil 
513                                                                                     data:[NSData data]];
514                 if (sharingAccount)
515                     [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
516                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
517                                           fileName, @"fileName", 
518                                           nil];
519                 [*directoryObjectRequests addObject:objectRequest];
520             }
521         }
522     }
523     
524     return objectRequests;
525 }
526
527 #pragma mark -
528 #pragma mark Delete
529
530 + (NSArray *)deleteObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos 
531                                        containerName:(NSString *)containerName 
532                                           objectName:(NSString *)objectName {
533     NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName delimiter:nil 
534                                          sharingAccount:nil];
535     if (objects == nil)
536         return nil;
537
538     NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
539     ASIPithosObjectRequest *objectRequest;
540     if (![objectName hasSuffix:@"/"]) {
541         objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:objectName];
542         objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
543                                   [objectName lastPathComponent], @"fileName", 
544                                   nil];
545         [objectRequests addObject:objectRequest];
546     }
547     NSString *fileName;
548     for (ASIPithosObject *object in objects) {
549         fileName = [object.name lastPathComponent];
550         if ([object.name hasSuffix:@"/"])
551             fileName = [fileName stringByAppendingString:@"/"];
552         objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:object.name];
553         objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
554                                   fileName, @"fileName", 
555                                   nil];
556         [objectRequests addObject:objectRequest];
557     }
558     
559     if ([objectRequests count] == 0)
560         return nil;
561     return objectRequests;
562 }
563
564 #pragma mark -
565 #pragma mark Copy
566
567 + (ASIPithosObjectRequest *)copyObjectRequestWithPithos:(ASIPithos *)pithos 
568                                           containerName:(NSString *)containerName 
569                                              objectName:(NSString *)objectName 
570                                destinationContainerName:(NSString *)destinationContainerName 
571                                   destinationObjectName:(NSString *)destinationObjectName 
572                                           checkIfExists:(BOOL)ifExists 
573                                          sharingAccount:(NSString *)sharingAccount {
574     if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName 
575                                           sharingAccount:nil])
576         return nil;
577     
578     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos 
579                                                                                       containerName:containerName 
580                                                                                          objectName:objectName 
581                                                                                         contentType:nil 
582                                                                                     contentEncoding:nil 
583                                                                                  contentDisposition:nil 
584                                                                                            manifest:nil 
585                                                                                             sharing:nil 
586                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
587                                                                                            metadata:nil 
588                                                                            destinationContainerName:destinationContainerName 
589                                                                               destinationObjectName:destinationObjectName 
590                                                                                  destinationAccount:nil
591                                                                                       sourceVersion:nil];
592     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
593                               containerName, @"sourceContainerName", 
594                               objectName, @"sourceObjectName", 
595                               destinationContainerName, @"destinationContainerName", 
596                               destinationObjectName, @"destinationObjectName", 
597                               nil];
598     if (sharingAccount) 
599         [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
600     return objectRequest;
601 }
602
603 + (NSArray *)copyObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos 
604                                      containerName:(NSString *)containerName 
605                                         objectName:(NSString *)objectName 
606                           destinationContainerName:(NSString *)destinationContainerName 
607                              destinationObjectName:(NSString *)destinationObjectName 
608                                      checkIfExists:(BOOL)ifExists 
609                                     sharingAccount:(NSString *)sharingAccount {
610     if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName 
611                                           sharingAccount:nil])
612         return nil;
613     
614     NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName 
615                                               delimiter:nil sharingAccount:sharingAccount];
616     if (objects == nil)
617         return nil;
618     
619     NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
620     ASIPithosObjectRequest *objectRequest;
621     if ([objectName isEqualToString:destinationObjectName]) {
622         if (![objectName hasSuffix:@"/"]) {
623             objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos 
624                                                                       containerName:containerName 
625                                                                          objectName:objectName 
626                                                                         contentType:nil 
627                                                                     contentEncoding:nil 
628                                                                  contentDisposition:nil 
629                                                                            manifest:nil 
630                                                                             sharing:nil 
631                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
632                                                                            metadata:nil 
633                                                            destinationContainerName:destinationContainerName 
634                                                               destinationObjectName:objectName 
635                                                                  destinationAccount:nil 
636                                                                       sourceVersion:nil];
637             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
638                                       containerName, @"sourceContainerName", 
639                                       objectName, @"sourceObjectName", 
640                                       destinationContainerName, @"destinationContainerName", 
641                                       objectName, @"destinationObjectName", 
642                                       nil];
643             if (sharingAccount)
644                 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
645             [objectRequests addObject:objectRequest];
646         }
647         for (ASIPithosObject *object in objects) {
648             objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos 
649                                                                       containerName:containerName 
650                                                                          objectName:object.name 
651                                                                         contentType:nil 
652                                                                     contentEncoding:nil 
653                                                                  contentDisposition:nil 
654                                                                            manifest:nil 
655                                                                             sharing:nil 
656                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
657                                                                            metadata:nil 
658                                                            destinationContainerName:destinationContainerName 
659                                                               destinationObjectName:object.name 
660                                                                  destinationAccount:nil 
661                                                                       sourceVersion:nil];
662             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
663                                       containerName, @"sourceContainerName", 
664                                       object.name, @"sourceObjectName", 
665                                       destinationContainerName, @"destinationContainerName", 
666                                       object.name, @"destinationObjectName", 
667                                       nil];
668             if (sharingAccount)
669                 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
670             [objectRequests addObject:objectRequest];
671         }
672     } else {
673         if (![objectName hasSuffix:@"/"]) {
674             objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos 
675                                                                       containerName:containerName 
676                                                                          objectName:objectName 
677                                                                         contentType:nil 
678                                                                     contentEncoding:nil 
679                                                                  contentDisposition:nil 
680                                                                            manifest:nil 
681                                                                             sharing:nil 
682                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
683                                                                            metadata:nil 
684                                                            destinationContainerName:destinationContainerName 
685                                                               destinationObjectName:destinationObjectName 
686                                                                  destinationAccount:nil 
687                                                                       sourceVersion:nil];
688             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
689                                       containerName, @"sourceContainerName", 
690                                       objectName, @"sourceObjectName", 
691                                       destinationContainerName, @"destinationContainerName", 
692                                       destinationObjectName, @"destinationObjectName", 
693                                       nil];
694             if (sharingAccount)
695                 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
696             [objectRequests addObject:objectRequest];
697         }
698         NSRange prefixRange = NSMakeRange(0, [objectName length]);
699         NSString *newObjectName;
700         for (ASIPithosObject *object in objects) {
701             newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
702                                                                    withString:destinationObjectName
703                                                                       options:NSAnchoredSearch
704                                                                         range:prefixRange];
705             objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos 
706                                                                       containerName:containerName 
707                                                                          objectName:object.name 
708                                                                         contentType:nil 
709                                                                     contentEncoding:nil 
710                                                                  contentDisposition:nil 
711                                                                            manifest:nil 
712                                                                             sharing:nil 
713                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
714                                                                            metadata:nil 
715                                                            destinationContainerName:destinationContainerName 
716                                                               destinationObjectName:newObjectName 
717                                                                  destinationAccount:nil 
718                                                                       sourceVersion:nil];
719             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
720                                       containerName, @"sourceContainerName", 
721                                       object.name, @"sourceObjectName", 
722                                       destinationContainerName, @"destinationContainerName", 
723                                       newObjectName, @"destinationObjectName", 
724                                       nil];
725             if (sharingAccount)
726                 [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
727             [objectRequests addObject:objectRequest];
728         }
729     }
730     
731     if ([objectRequests count] == 0)
732         return nil;
733     return objectRequests;
734 }
735
736 #pragma mark -
737 #pragma mark Move
738
739 + (ASIPithosObjectRequest *)moveObjectRequestWithPithos:(ASIPithos *)pithos 
740                                           containerName:(NSString *)containerName 
741                                              objectName:(NSString *)objectName 
742                                destinationContainerName:(NSString *)destinationContainerName 
743                                   destinationObjectName:(NSString *)destinationObjectName 
744                                           checkIfExists:(BOOL)ifExists {
745     if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName 
746                                           sharingAccount:nil])
747         return nil;
748     
749     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
750                                                                                       containerName:containerName 
751                                                                                          objectName:objectName 
752                                                                                         contentType:nil 
753                                                                                     contentEncoding:nil 
754                                                                                  contentDisposition:nil 
755                                                                                            manifest:nil 
756                                                                                             sharing:nil 
757                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
758                                                                                            metadata:nil 
759                                                                            destinationContainerName:destinationContainerName 
760                                                                               destinationObjectName:destinationObjectName 
761                                                                                  destinationAccount:nil];
762     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
763                               containerName, @"sourceContainerName", 
764                               objectName, @"sourceObjectName", 
765                               destinationContainerName, @"destinationContainerName", 
766                               destinationObjectName, @"destinationObjectName", 
767                               nil];
768     return objectRequest;
769 }
770
771 + (NSArray *)moveObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos 
772                                      containerName:(NSString *)containerName 
773                                         objectName:(NSString *)objectName 
774                           destinationContainerName:(NSString *)destinationContainerName 
775                              destinationObjectName:(NSString *)destinationObjectName 
776                                      checkIfExists:(BOOL)ifExists {
777     if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName 
778                                           sharingAccount:nil])
779         return nil;
780     
781     NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName 
782                                               delimiter:nil sharingAccount:nil];
783     if (objects == nil)
784         return nil;
785     
786     ASIPithosObjectRequest *objectRequest;
787     NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
788     if ([objectName isEqualToString:destinationObjectName]) {
789         if (![objectName hasSuffix:@"/"]) {
790             objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
791                                                                       containerName:containerName 
792                                                                          objectName:objectName 
793                                                                         contentType:nil 
794                                                                     contentEncoding:nil 
795                                                                  contentDisposition:nil 
796                                                                            manifest:nil 
797                                                                             sharing:nil 
798                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
799                                                                            metadata:nil 
800                                                            destinationContainerName:destinationContainerName 
801                                                               destinationObjectName:objectName 
802                                                                  destinationAccount:nil];
803             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
804                                       containerName, @"sourceContainerName", 
805                                       objectName, @"sourceObjectName", 
806                                       destinationContainerName, @"destinationContainerName", 
807                                       objectName, @"destinationObjectName", 
808                                       nil];
809             [objectRequests addObject:objectRequest];
810         }
811         for (ASIPithosObject *object in objects) {
812             objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
813                                                                       containerName:containerName 
814                                                                          objectName:object.name 
815                                                                         contentType:nil 
816                                                                     contentEncoding:nil 
817                                                                  contentDisposition:nil 
818                                                                            manifest:nil 
819                                                                             sharing:nil 
820                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
821                                                                            metadata:nil 
822                                                            destinationContainerName:destinationContainerName 
823                                                               destinationObjectName:object.name 
824                                                                  destinationAccount:nil];
825             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
826                                       containerName, @"sourceContainerName", 
827                                       object.name, @"sourceObjectName", 
828                                       destinationContainerName, @"destinationContainerName", 
829                                       object.name, @"destinationObjectName", 
830                                       nil];
831             [objectRequests addObject:objectRequest];
832         }
833     } else {
834         if (![objectName hasSuffix:@"/"]) {
835             objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
836                                                                       containerName:containerName 
837                                                                          objectName:objectName 
838                                                                         contentType:nil 
839                                                                     contentEncoding:nil 
840                                                                  contentDisposition:nil 
841                                                                            manifest:nil 
842                                                                             sharing:nil 
843                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
844                                                                            metadata:nil 
845                                                            destinationContainerName:destinationContainerName 
846                                                               destinationObjectName:destinationObjectName 
847                                                                  destinationAccount:nil];
848             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
849                                       containerName, @"sourceContainerName", 
850                                       objectName, @"sourceObjectName", 
851                                       destinationContainerName, @"destinationContainerName", 
852                                       destinationObjectName, @"destinationObjectName", 
853                                       nil];
854             [objectRequests addObject:objectRequest];
855         }
856         NSRange prefixRange = NSMakeRange(0, [objectName length]);
857         NSString *newObjectName;
858         for (ASIPithosObject *object in objects) {
859             newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
860                                                                    withString:destinationObjectName
861                                                                       options:NSAnchoredSearch
862                                                                         range:prefixRange];
863             objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
864                                                                       containerName:containerName 
865                                                                          objectName:object.name
866                                                                         contentType:nil 
867                                                                     contentEncoding:nil 
868                                                                  contentDisposition:nil 
869                                                                            manifest:nil 
870                                                                             sharing:nil 
871                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
872                                                                            metadata:nil 
873                                                            destinationContainerName:destinationContainerName 
874                                                               destinationObjectName:newObjectName 
875                                                                  destinationAccount:nil];
876             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
877                                       containerName, @"sourceContainerName", 
878                                       object.name, @"sourceObjectName", 
879                                       destinationContainerName, @"destinationContainerName", 
880                                       newObjectName, @"destinationObjectName", 
881                                       nil];
882             [objectRequests addObject:objectRequest];
883         }
884     }
885      
886     if ([objectRequests count] == 0)
887         return nil;
888     return objectRequests;
889 }
890
891 #pragma mark -
892 #pragma mark Helper Methods
893
894 // Size of the file in bytes
895 + (NSUInteger)bytesOfFile:(NSString *)filePath {
896     NSFileManager *fileManager = [NSFileManager defaultManager];
897     NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil];
898     return [[attributes objectForKey:NSFileSize] unsignedIntegerValue];
899 }
900
901 // Content type of the file or nil if it cannot be determined
902 + (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error {
903     // Based on http://www.ddeville.me/2011/12/mime-to-UTI-cocoa/
904     // and Apple example ImageBrowserViewAppearance/ImageBrowserController.m
905     LSItemInfoRecord info;
906     CFStringRef uti = NULL;
907     CFURLRef url = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)filePath, kCFURLPOSIXPathStyle, FALSE);
908     if (LSCopyItemInfoForURL(url, kLSRequestExtension | kLSRequestTypeCreator, &info) == noErr) {
909         // Obtain the UTI using the file information.
910         // If there is a file extension, get the UTI.
911         if (info.extension != NULL) {
912             uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, info.extension, kUTTypeData);
913             CFRelease(info.extension);
914         }
915         // No UTI yet
916         if (uti == NULL) {
917             // If there is an OSType, get the UTI.
918             CFStringRef typeString = UTCreateStringForOSType(info.filetype);
919             if ( typeString != NULL) {
920                 uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, typeString, kUTTypeData);
921                 CFRelease(typeString);
922             }
923         }
924         if (uti != NULL) {
925             CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
926             CFRelease(uti);
927             return (NSString *)MIMEType;
928         }
929     }
930     return nil;
931 }
932
933 // Creates a directory if it doesn't exist and returns if successful
934 + (BOOL)safeCreateDirectory:(NSString *)directoryPath error:(NSError **)error {
935     NSFileManager *fileManager = [NSFileManager defaultManager];
936     BOOL isDirectory;
937     BOOL fileExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory];
938     if (fileExists)
939         return isDirectory;
940     if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:error] || *error)
941         return NO;
942     return YES;
943 }
944
945 // Removes contents of a directory
946 + (void)removeContentsAtPath:(NSString *)dirPath {
947     NSFileManager *fileManager = [NSFileManager defaultManager];
948     NSError *error = nil;
949     BOOL isDirectory;
950     if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory])
951         return;
952     if (isDirectory) {
953         for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:dirPath error:&error]) {
954             if (error) {
955                 [self fileActionFailedAlertWithTitle:@"Directory Contents Error" 
956                                              message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", dirPath] 
957                                                error:error];
958                 break;
959             }
960             NSString *subFilePath = [dirPath stringByAppendingPathComponent:subPath];
961             if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
962                 [self fileActionFailedAlertWithTitle:@"Remove File Error" 
963                                              message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
964                                                error:error];
965             }
966             error = nil;
967         }
968     } else if (![fileManager removeItemAtPath:dirPath error:&error] || error) {
969         [self fileActionFailedAlertWithTitle:@"Remove File Error" 
970                                      message:[NSString stringWithFormat:@"Cannot remove file at '%@'", dirPath] 
971                                        error:error];
972     }
973 }
974
975 // Returns if an object is a directory based on its content type
976 + (BOOL)isContentTypeDirectory:(NSString *)contentType {
977     return ([contentType isEqualToString:@"application/directory"] ||
978             [contentType hasPrefix:@"application/directory;"] ||
979             [contentType isEqualToString:@"application/folder"] ||
980             [contentType hasPrefix:@"application/folder;"]);
981 }
982
983 // Returns if an object exists at the given container/object path and if this object is an application/directory
984 // If an error occured an alert is shown and it is returned so the caller won't proceed
985 + (BOOL)objectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName 
986                        error:(NSError **)error isDirectory:(BOOL *)isDirectory sharingAccount:(NSString *)sharingAccount {
987     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos 
988                                                                                       containerName:containerName 
989                                                                                          objectName:objectName];
990     if (sharingAccount)
991         [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
992     ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
993     [networkQueue go];
994     [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:objectRequest]] waitUntilFinished:YES];
995     *error = [objectRequest error];
996     if (*error) {
997         [self httpRequestErrorAlertWithRequest:objectRequest];
998         return NO;
999     } else if (objectRequest.responseStatusCode == 200) {
1000         *isDirectory = [self isContentTypeDirectory:[objectRequest contentType]];
1001         return YES;
1002     }
1003     return NO;
1004 }
1005
1006 // Returns if the caller should proceed, after an interactive check if an object exists 
1007 // at the given container/object path is performed
1008 + (BOOL)proceedIfObjectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName 
1009                        sharingAccount:(NSString *)sharingAccount {
1010     NSError *error = nil;
1011     BOOL isDirectory;
1012     BOOL objectExists = [self objectExistsAtPithos:pithos containerName:containerName objectName:objectName 
1013                                              error:&error isDirectory:&isDirectory sharingAccount:sharingAccount];
1014     if (error) {
1015         return NO;
1016     } else if (objectExists) {
1017         __block NSInteger choice;
1018         dispatch_sync(dispatch_get_main_queue(), ^{
1019             NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1020             if (isDirectory) {
1021                 [alert setMessageText:@"Directory Exists"];
1022                 if (sharingAccount)
1023                     [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
1024                 else
1025                     [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1026             } else {
1027                 [alert setMessageText:@"Object Exists"];
1028                 if (sharingAccount)
1029                     [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
1030                 else
1031                     [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1032             }
1033             [alert addButtonWithTitle:@"OK"];
1034             [alert addButtonWithTitle:@"Cancel"];
1035             choice = [alert runModal];
1036         });
1037         if (choice == NSAlertSecondButtonReturn)
1038             return NO;
1039     }
1040     return YES;
1041 }
1042
1043 // List of objects at the given container/object path, with prefix and or delimiter
1044 + (NSArray *)objectsWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix 
1045                      delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1046     NSMutableArray *objects = [NSMutableArray array];
1047     NSString *marker = nil;
1048     do {
1049         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
1050                                                                                                 containerName:containerName 
1051                                                                                                         limit:0 
1052                                                                                                        marker:marker 
1053                                                                                                        prefix:objectNamePrefix 
1054                                                                                                     delimiter:delimiter 
1055                                                                                                          path:nil 
1056                                                                                                          meta:nil 
1057                                                                                                        shared:NO 
1058                                                                                                         until:nil];
1059         if (sharingAccount)
1060             [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
1061         ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1062         [networkQueue go];
1063         [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:containerRequest]] waitUntilFinished:YES];
1064         if ([containerRequest error]) {
1065             [self httpRequestErrorAlertWithRequest:containerRequest];
1066             return nil;
1067         }
1068         NSArray *someObjects = [containerRequest objects];
1069         [objects addObjectsFromArray:someObjects];
1070         if ([someObjects count] < 10000)
1071             marker = nil;
1072         else
1073             marker = [[someObjects lastObject] name];
1074     } while (marker);
1075     return objects;
1076 }
1077
1078 // List of objects at the given container/object path, that may be a subdir or an application/directory, 
1079 // with prefix and or delimiter
1080 + (NSArray *)objectsForSubdirWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName 
1081                               delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1082     NSString *subdirNamePrefix = [NSString stringWithString:objectName];
1083     if (![subdirNamePrefix hasSuffix:@"/"])
1084         subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"];
1085     return [self objectsWithPithos:pithos containerName:containerName objectNamePrefix:subdirNamePrefix 
1086                          delimiter:delimiter sharingAccount:sharingAccount];
1087 }
1088
1089 // A safe object name at the given container/object path
1090 // The original name has " %d" appended to it before any ".*" suffix, for the first integer that produces a name that is free to use
1091 // If the original name hasn't got a "." but has a "/" suffix, then it is replaced with " %d/" instead
1092 // Subdirs are taken into consideration
1093 + (NSString *)safeObjectNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName {
1094     NSString *objectNamePrefix;
1095     NSString *objectNameExtraSuffix;
1096     NSRange lastDotRange = [objectName rangeOfString:@"." options:NSBackwardsSearch];
1097     if (lastDotRange.length == 1) {
1098         objectNamePrefix = [objectName substringToIndex:lastDotRange.location];
1099         objectNameExtraSuffix = [objectName substringFromIndex:lastDotRange.location];
1100     } else if ([objectName hasSuffix:@"/"]) {
1101         objectNamePrefix = [objectName substringToIndex:([objectName length] - 1)];
1102         objectNameExtraSuffix = [NSString stringWithString:@"/"];
1103     } else {
1104         objectNamePrefix = [NSString stringWithString:objectName];
1105         objectNameExtraSuffix = [NSString string];
1106     }
1107     NSArray *objects = [self objectsWithPithos:pithos containerName:containerName 
1108                               objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent] 
1109                                      delimiter:@"/" sharingAccount:nil];
1110     if (objects == nil)
1111         return nil;
1112     if ([objects count] == 0)
1113         return objectName;
1114     NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1115                                    [[objects objectsAtIndexes:
1116                                      [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1117         if (pithosObject.subdir)
1118             return NO;
1119         return YES;
1120     }]] valueForKey:@"name"]];
1121     for (NSString *name in [[objects objectsAtIndexes:
1122                              [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1123         if (pithosObject.subdir)
1124             return YES;
1125         return NO;
1126     }]] valueForKey:@"name"]) {
1127         [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1128     }
1129     if (![objectNames containsObject:objectName])
1130         return objectName;
1131     NSUInteger objectNameSuffix = 2;
1132     NSString *safeObjectName;
1133     do {
1134         safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix];
1135         objectNameSuffix++;
1136     } while ([objectNames containsObject:safeObjectName]);
1137     return safeObjectName;    
1138 }
1139
1140 // A safe object name at the given container/object path that may be a subdir or application/directory
1141 // The original name has " %d" appended to it, for the first integer that produces a name that is free to use
1142 // If the original name has a "/" suffix, then it is replaced with " %d/" instead
1143 // Subdirs are taken into consideration
1144 + (NSString *)safeSubdirNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName subdirName:(NSString *)subdirName {
1145     NSString *subdirNamePrefix;
1146     NSString *subdirNameExtraSuffix;
1147     if ([subdirName hasSuffix:@"/"]) {
1148         subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)];
1149         subdirNameExtraSuffix = [NSString stringWithString:@"/"];
1150     } else {
1151         subdirNamePrefix = [NSString stringWithString:subdirName];
1152         subdirNameExtraSuffix = [NSString string];
1153     }
1154     NSArray *objects = [self objectsWithPithos:pithos containerName:containerName 
1155                               objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent] 
1156                                      delimiter:@"/" sharingAccount:nil];
1157     if (objects == nil)
1158         return nil;
1159     if ([objects count] == 0)
1160         return subdirName;
1161     NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1162                                    [[objects objectsAtIndexes:
1163                                      [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1164         if (pithosObject.subdir)
1165             return NO;
1166         return YES;
1167     }]] valueForKey:@"name"]];
1168     for (NSString *name in [[objects objectsAtIndexes:
1169                              [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1170         if (pithosObject.subdir)
1171             return YES;
1172         return NO;
1173     }]] valueForKey:@"name"]) {
1174         [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1175     }
1176     if (![objectNames containsObject:subdirNamePrefix])
1177         return subdirName;
1178     NSUInteger subdirNameSuffix = 2;
1179     NSString *safeSubdirName;
1180     do {
1181         safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix];
1182         subdirNameSuffix++;
1183     } while ([objectNames containsObject:safeSubdirName]);
1184     return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix];
1185 }
1186
1187 #pragma mark -
1188 #pragma mark Alerts
1189
1190 + (void)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
1191     NSString *message = [NSString stringWithFormat:
1192                          @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", 
1193                          [[request error] localizedDescription], 
1194                          request.requestMethod, 
1195                          request.url, 
1196                          [request requestHeaders], 
1197                          [request responseHeaders], 
1198                          [request responseString]];
1199     NSLog(@"%@", message);
1200     dispatch_async(dispatch_get_main_queue(), ^{    
1201         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1202         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1203         [alert setMessageText:@"HTTP Request Error"];
1204         [alert setInformativeText:message];
1205         [alert addButtonWithTitle:@"OK"];
1206         [alert runModal];
1207         [pool drain];
1208     });
1209 }
1210
1211 + (void)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
1212     NSString *message = [NSString stringWithFormat:
1213                          @"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", 
1214                          request.responseStatusCode, 
1215                          request.responseStatusMessage, 
1216                          request.requestMethod, 
1217                          request.url, 
1218                          [request requestHeaders], 
1219                          [request responseHeaders], 
1220                          [request responseString]];
1221     NSLog(@"%@", message);
1222     dispatch_async(dispatch_get_main_queue(), ^{
1223         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1224         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1225         [alert setMessageText:@"Unexpected Response Status"];
1226         [alert setInformativeText:message];
1227         [alert addButtonWithTitle:@"OK"];
1228         [alert runModal];
1229         [pool drain];
1230     });
1231 }
1232
1233 + (void)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error {
1234     dispatch_async(dispatch_get_main_queue(), ^{
1235         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1236         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1237         [alert setMessageText:title];
1238         if (error)
1239             [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, [error localizedDescription]]];
1240         else
1241             [alert setInformativeText:message];
1242         [alert addButtonWithTitle:@"OK"];
1243         [alert runModal];
1244         [pool drain];
1245     });
1246 }
1247
1248 #pragma mark -
1249 #pragma mark Request Helper Methods
1250
1251 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority {
1252     [request setTimeOutSeconds:60];
1253     request.numberOfTimesToRetryOnTimeout = 10;
1254     [request setQueuePriority:priority];
1255     return request;
1256 }
1257
1258 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request {
1259     return [self prepareRequest:request priority:NSOperationQueuePriorityNormal];
1260 }
1261
1262 + (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request {
1263     NSMutableDictionary *userInfo = (NSMutableDictionary *)[[request.userInfo retain] autorelease];
1264     request.userInfo = nil;
1265     ASIPithosRequest *newRequest = [request copy];
1266     newRequest.userInfo = userInfo;
1267     return newRequest;
1268 }
1269
1270 @end