Bug fix in sync where copy/move of files failed due to missing directory.
[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 ([PithosUtilities isContentTypeDirectory:object.contentType]) {
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 // Creates a directory if it doesn't exist and returns if successful
844 + (BOOL)safeCreateDirectory:(NSString *)directoryPath error:(NSError **)error {
845     NSFileManager *fileManager = [NSFileManager defaultManager];
846     BOOL isDirectory;
847     BOOL fileExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory];
848     if (fileExists)
849         return isDirectory;
850     if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:error] || *error)
851         return NO;
852     return YES;
853 }
854
855 // Returns if an object is a directory based on its content type
856 + (BOOL)isContentTypeDirectory:(NSString *)contentType {
857     return ([contentType isEqualToString:@"application/directory"] ||
858             [contentType hasPrefix:@"application/directory;"] ||
859             [contentType isEqualToString:@"application/folder"] ||
860             [contentType hasPrefix:@"application/folder;"]);
861 }
862
863 // Returns if an object exists at the given container/object path and if this object is an application/directory
864 // If an error occured an alert is shown and it is returned so the caller won't proceed
865 + (BOOL)objectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName 
866                               error:(NSError **)error isDirectory:(BOOL *)isDirectory 
867                      sharingAccount:(NSString *)sharingAccount {
868     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithContainerName:containerName 
869                                                                                                 objectName:objectName];
870     if (sharingAccount)
871         [objectRequest setRequestUserFromDefaultTo:sharingAccount];
872     [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
873     while (![objectRequest isFinished]) {
874         usleep(1);
875     }
876     *error = [objectRequest error];
877     if (*error) {
878         [self httpRequestErrorAlertWithRequest:objectRequest];
879         return NO;
880     } else if (objectRequest.responseStatusCode == 200) {
881         *isDirectory = [self isContentTypeDirectory:[objectRequest contentType]];
882         return YES;
883     }
884     return NO;
885 }
886
887 // Returns if the caller should proceed, after an interactive check if an object exists 
888 // at the given container/object path is performed
889 + (BOOL)proceedIfObjectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName 
890                               sharingAccount:(NSString *)sharingAccount {
891     NSError *error = nil;
892     BOOL isDirectory;
893     BOOL objectExists = [self objectExistsAtContainerName:containerName 
894                                                objectName:objectName
895                                                     error:&error 
896                                               isDirectory:&isDirectory 
897                                            sharingAccount:sharingAccount];
898     if (error) {
899         return NO;
900     } else if (objectExists) {
901         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
902         if (isDirectory) {
903             [alert setMessageText:@"Directory Exists"];
904             if (sharingAccount)
905                 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
906             else
907                 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
908         } else {
909             [alert setMessageText:@"Object Exists"];
910             if (sharingAccount)
911                 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
912             else
913                 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
914         }
915         [alert addButtonWithTitle:@"OK"];
916         [alert addButtonWithTitle:@"Cancel"];
917         NSInteger choice = [alert runModal];
918         if (choice == NSAlertSecondButtonReturn)
919             return NO;
920     }
921     return YES;
922 }
923
924 // List of objects at the given container/object path, with prefix and or delimiter
925 + (NSArray *)objectsWithContainerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix 
926                             delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
927     NSMutableArray *objects = [NSMutableArray array];
928     NSString *marker = nil;
929     do {
930         ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName 
931                                                                                                                limit:0 
932                                                                                                               marker:marker 
933                                                                                                               prefix:objectNamePrefix 
934                                                                                                            delimiter:delimiter 
935                                                                                                                 path:nil 
936                                                                                                                 meta:nil 
937                                                                                                               shared:NO 
938                                                                                                                until:nil];
939         if (sharingAccount)
940             [containerRequest setRequestUserFromDefaultTo:sharingAccount];
941         [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
942         while (![containerRequest isFinished]) {
943             usleep(1);
944         }
945         if ([containerRequest error]) {
946             [self httpRequestErrorAlertWithRequest:containerRequest];
947             return nil;
948         }
949         NSArray *someObjects = [containerRequest objects];
950         [objects addObjectsFromArray:someObjects];
951         if ([someObjects count] < 10000)
952             marker = nil;
953         else
954             marker = [[someObjects lastObject] name];
955     } while (marker);
956     return objects;
957 }
958
959 // List of objects at the given container/object path, that may be a subdir or an application/directory, 
960 // with prefix and or delimiter
961 + (NSArray *)objectsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName 
962                                      delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
963     NSString *subdirNamePrefix = [NSString stringWithString:objectName];
964     if (![subdirNamePrefix hasSuffix:@"/"])
965         subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"];
966     return [self objectsWithContainerName:containerName objectNamePrefix:subdirNamePrefix 
967                                 delimiter:delimiter sharingAccount:sharingAccount];
968 }
969
970 // A safe object name at the given container/object path
971 // The original name has " %d" appended to it before any ".*" suffix, for the first integer that produces a name that is free to use
972 // If the original name hasn't got a "." but has a "/" suffix, then it is replaced with " %d/" instead
973 // Subdirs are taken into consideration
974 + (NSString *)safeObjectNameForContainerName:(NSString *)containerName objectName:(NSString *)objectName {
975     NSString *objectNamePrefix;
976     NSString *objectNameExtraSuffix;
977     NSRange lastDotRange = [objectName rangeOfString:@"." options:NSBackwardsSearch];
978     if (lastDotRange.length == 1) {
979         objectNamePrefix = [objectName substringToIndex:lastDotRange.location];
980         objectNameExtraSuffix = [objectName substringFromIndex:lastDotRange.location];
981     } else if ([objectName hasSuffix:@"/"]) {
982         objectNamePrefix = [objectName substringToIndex:([objectName length] - 1)];
983         objectNameExtraSuffix = [NSString stringWithString:@"/"];
984     } else {
985         objectNamePrefix = [NSString stringWithString:objectName];
986         objectNameExtraSuffix = [NSString string];
987     }
988     NSArray *objects = [self objectsWithContainerName:containerName 
989                                      objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent] 
990                                             delimiter:@"/" 
991                                        sharingAccount:nil];
992     if (objects == nil)
993         return nil;
994     if ([objects count] == 0)
995         return objectName;
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:objectName])
1012         return objectName;
1013     NSUInteger objectNameSuffix = 2;
1014     NSString *safeObjectName;
1015     do {
1016         safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix];
1017         objectNameSuffix++;
1018     } while ([objectNames containsObject:safeObjectName]);
1019     return safeObjectName;    
1020 }
1021
1022 // A safe object name at the given container/object path that may be a subdir or application/directory
1023 // The original name has " %d" appended to it, for the first integer that produces a name that is free to use
1024 // If the original name has a "/" suffix, then it is replaced with " %d/" instead
1025 // Subdirs are taken into consideration
1026 + (NSString *)safeSubdirNameForContainerName:(NSString *)containerName subdirName:(NSString *)subdirName {
1027     NSString *subdirNamePrefix;
1028     NSString *subdirNameExtraSuffix;
1029     if ([subdirName hasSuffix:@"/"]) {
1030         subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)];
1031         subdirNameExtraSuffix = [NSString stringWithString:@"/"];
1032     } else {
1033         subdirNamePrefix = [NSString stringWithString:subdirName];
1034         subdirNameExtraSuffix = [NSString string];
1035     }
1036     NSArray *objects = [self objectsWithContainerName:containerName 
1037                                      objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent] 
1038                                             delimiter:@"/" 
1039                                        sharingAccount:nil];
1040     if (objects == nil)
1041         return nil;
1042     if ([objects count] == 0)
1043         return subdirName;
1044     NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1045                                    [[objects objectsAtIndexes:
1046                                      [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1047         if (pithosObject.subdir)
1048             return NO;
1049         return YES;
1050     }]] valueForKey:@"name"]];
1051     for (NSString *name in [[objects objectsAtIndexes:
1052                              [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1053         if (pithosObject.subdir)
1054             return YES;
1055         return NO;
1056     }]] valueForKey:@"name"]) {
1057         [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1058     }
1059     if (![objectNames containsObject:subdirNamePrefix])
1060         return subdirName;
1061     NSUInteger subdirNameSuffix = 2;
1062     NSString *safeSubdirName;
1063     do {
1064         safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix];
1065         subdirNameSuffix++;
1066     } while ([objectNames containsObject:safeSubdirName]);
1067     return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix];
1068 }
1069
1070 #pragma mark -
1071 #pragma mark Alerts
1072
1073 + (NSInteger)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
1074     NSString *message = [NSString stringWithFormat:
1075                          @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", 
1076                          [request error], 
1077                          request.requestMethod, 
1078                          request.url, 
1079                          [request requestHeaders], 
1080                          [request responseHeaders], 
1081                          [request responseString]];
1082     NSLog(@"%@", message);
1083     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1084     [alert setMessageText:@"HTTP Request Error"];
1085     [alert setInformativeText:message];
1086     [alert addButtonWithTitle:@"OK"];
1087     return [alert runModal];
1088 }
1089
1090 + (NSInteger)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
1091     NSString *message = [NSString stringWithFormat:
1092                          @"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", 
1093                          request.responseStatusCode, 
1094                          request.responseStatusMessage, 
1095                          request.requestMethod, 
1096                          request.url, 
1097                          [request requestHeaders], 
1098                          [request responseHeaders], 
1099                          [request responseString]];
1100     NSLog(@"%@", message);
1101     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1102     [alert setMessageText:@"Unexpected Response Status"];
1103     [alert setInformativeText:message];
1104     [alert addButtonWithTitle:@"OK"];
1105     return [alert runModal];
1106 }
1107
1108 + (NSInteger)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error {
1109     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1110     [alert setMessageText:title];
1111     if (error)
1112         [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, error]];
1113     else
1114         [alert setInformativeText:message];
1115     [alert addButtonWithTitle:@"OK"];
1116     return [alert runModal];
1117 }
1118
1119 #pragma mark -
1120 #pragma mark Request Helper Methods
1121
1122 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority {
1123     [request setTimeOutSeconds:60];
1124     request.numberOfTimesToRetryOnTimeout = 10;
1125     [request setQueuePriority:priority];
1126     return request;
1127 }
1128
1129 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request {
1130     return [self prepareRequest:request priority:NSOperationQueuePriorityNormal];
1131 }
1132
1133 + (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request {
1134     NSMutableDictionary *userInfo = [[request.userInfo retain] autorelease];
1135     request.userInfo = nil;
1136     ASIPithosRequest *newRequest = [request copy];
1137     newRequest.userInfo = userInfo;
1138     return newRequest;
1139 }
1140
1141 @end