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