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