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