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