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