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