Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosUtilities.m @ 9990166e

History | View | Annotate | Download (77.1 kB)

1
//
2
//  PithosUtilities.m
3
//  pithos-macos
4
//
5
// Copyright 2011-2012 GRNET S.A. All rights reserved.
6
//
7
// Redistribution and use in source and binary forms, with or
8
// without modification, are permitted provided that the following
9
// conditions are met:
10
// 
11
//   1. Redistributions of source code must retain the above
12
//      copyright notice, this list of conditions and the following
13
//      disclaimer.
14
// 
15
//   2. Redistributions in binary form must reproduce the above
16
//      copyright notice, this list of conditions and the following
17
//      disclaimer in the documentation and/or other materials
18
//      provided with the distribution.
19
// 
20
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
// POSSIBILITY OF SUCH DAMAGE.
32
// 
33
// The views and conclusions contained in the software and
34
// documentation are those of the authors and should not be
35
// interpreted as representing official policies, either expressed
36
// or implied, of GRNET S.A.
37

    
38
#import "PithosUtilities.h"
39
#import "ASINetworkQueue.h"
40
#import "ASIPithos.h"
41
#import "ASIPithosContainerRequest.h"
42
#import "ASIPithosObjectRequest.h"
43
#import "ASIPithosObject.h"
44
#import "HashMapHash.h"
45

    
46
@implementation PithosUtilities
47

    
48
#pragma mark -
49
#pragma mark Download
50

    
51
+ (ASIPithosObjectRequest *)objectDataRequestWithPithos:(ASIPithos *)pithos 
52
                                          containerName:(NSString *)containerName 
53
                                             objectName:(NSString *)objectName 
54
                                                version:(NSString *)version 
55
                                            toDirectory:(NSString *)directoryPath 
56
                                        withNewFileName:(NSString *)newFileName 
57
                                          checkIfExists:(BOOL)ifExists 
58
                                         sharingAccount:(NSString *)sharingAccount {
59
    NSString *fileName;
60
    if (newFileName) {
61
        fileName = [NSString stringWithString:newFileName];
62
    } else {
63
        fileName = [objectName lastPathComponent];
64
        if ([objectName hasSuffix:@"/"])
65
            fileName = [fileName stringByAppendingString:@"/"];    
66
    }
67
    fileName = [fileName stringByReplacingOccurrencesOfString:@"/" withString:@":"];
68
    
69
    NSFileManager *fileManager = [NSFileManager defaultManager];
70
    
71
    NSString *destinationPath = [directoryPath stringByAppendingPathComponent:fileName];
72
    if (ifExists && [fileManager fileExistsAtPath:destinationPath]) {
73
        __block NSInteger choice;
74
        dispatch_sync(dispatch_get_main_queue(), ^{
75
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
76
            [alert setMessageText:@"File Exists"];
77
            [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", fileName]];
78
            [alert addButtonWithTitle:@"OK"];
79
            [alert addButtonWithTitle:@"Cancel"];
80
            choice = [alert runModal];
81
        });
82
        if (choice == NSAlertSecondButtonReturn)
83
            return nil;
84
    }
85
    
86
    BOOL directoryIsDirectory;
87
    BOOL directoryExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&directoryIsDirectory];
88
    NSError *error = nil;
89
    if (!directoryExists) {
90
        [fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
91
    } else if (!directoryIsDirectory) {
92
        [fileManager removeItemAtPath:directoryPath error:&error];
93
    }
94
    if (error) {
95
        DLog(@"Cannot remove existing file '%@': %@", fileName, error);
96
        dispatch_async(dispatch_get_main_queue(), ^{
97
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
98
            [alert setMessageText:@"Removal Error"];
99
            [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file '%@': %@", 
100
                                       fileName, [error localizedDescription]]];
101
            [alert addButtonWithTitle:@"OK"];
102
            [alert runModal];
103
        });
104
        return nil;
105
    }
106

    
107
    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos 
108
                                                                                  containerName:containerName 
109
                                                                                     objectName:objectName 
110
                                                                                        version:version];
111
    if (sharingAccount)
112
        [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
113
    objectRequest.downloadDestinationPath = destinationPath;
114
    objectRequest.allowResumeForFileDownloads = YES;
115
    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
116
                              fileName, @"fileName", 
117
                              destinationPath, @"filePath", 
118
                              nil];
119
    return objectRequest;
120
}
121

    
122
+ (NSArray *)objectDataRequestsForSubdirWithPithos:(ASIPithos *)pithos 
123
                                     containerName:(NSString *)containerName 
124
                                        objectName:(NSString *)objectName 
125
                                       toDirectory:(NSString *)directoryPath 
126
                                     checkIfExists:(BOOL)ifExists 
127
                                    sharingAccount:(NSString *)sharingAccount {
128
    NSString *subdirName = [objectName lastPathComponent];
129
    NSString *destinationPath = [directoryPath stringByAppendingPathComponent:subdirName];
130
    if (ifExists && [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) {
131
        __block NSInteger choice;
132
        dispatch_sync(dispatch_get_main_queue(), ^{
133
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
134
            [alert setMessageText:@"File exists"];
135
            [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", subdirName]];
136
            [alert addButtonWithTitle:@"OK"];
137
            [alert addButtonWithTitle:@"Cancel"];
138
            choice = [alert runModal];
139
        });
140
        if (choice == NSAlertSecondButtonReturn)
141
            return nil;
142
    }
143
    
144
    NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName 
145
                                              delimiter:nil sharingAccount:sharingAccount];
146
    if (objects == nil)
147
        return nil;
148
    
149
    NSFileManager *fileManager = [NSFileManager defaultManager];
150
    NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[objects count]];
151
    NSUInteger subdirPrefixLength = [objectName length];
152

    
153
    NSError *error = nil;
154
    [fileManager createDirectoryAtPath:[directoryPath stringByAppendingPathComponent:subdirName] withIntermediateDirectories:YES attributes:nil error:&error];
155
    if (error) {
156
        DLog(@"Cannot create directory at '%@': %@", directoryPath, error);
157
        dispatch_async(dispatch_get_main_queue(), ^{
158
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
159
            [alert setMessageText:@"Create Directory Error"];
160
            [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@", 
161
                                       directoryPath, [error localizedDescription]]];
162
            [alert addButtonWithTitle:@"OK"];
163
            [alert runModal];
164
        });
165
    }
166
    
167
    for (ASIPithosObject *object in objects) {
168
        if ([self isContentTypeDirectory:object.contentType]) {
169
            NSString *subdirDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
170
            subdirDirectoryPath = [subdirDirectoryPath stringByAppendingPathComponent:[object.name substringFromIndex:subdirPrefixLength]];
171
            
172
            BOOL directoryIsDirectory;
173
            BOOL directoryExists = [fileManager fileExistsAtPath:subdirDirectoryPath isDirectory:&directoryIsDirectory];
174
            NSError *error = nil;
175
            if (!directoryExists) {
176
                [fileManager createDirectoryAtPath:subdirDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error];
177
                if (error) {
178
                    DLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error);
179
                    dispatch_async(dispatch_get_main_queue(), ^{
180
                        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
181
                        [alert setMessageText:@"Create Directory Error"];
182
                        [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@", 
183
                                                   subdirDirectoryPath, [error localizedDescription]]];
184
                        [alert addButtonWithTitle:@"OK"];
185
                        [alert runModal];
186
                    });
187
                }
188
            } else if (!directoryIsDirectory) {
189
                [fileManager removeItemAtPath:subdirDirectoryPath error:&error];
190
                if (error) {
191
                    DLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error);
192
                    dispatch_async(dispatch_get_main_queue(), ^{
193
                        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
194
                        [alert setMessageText:@"Remove File Error"];
195
                        [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file at '%@': %@", 
196
                                                   subdirDirectoryPath, [error localizedDescription]]];
197
                        [alert addButtonWithTitle:@"OK"];
198
                        [alert runModal];
199
                    });
200
                }
201
            }
202
        } else {
203
            NSString *fileName = [object.name lastPathComponent];
204
            if([object.name hasSuffix:@"/"])
205
                fileName = [fileName stringByAppendingString:@"/"];
206
            
207
            NSString *objectDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
208
            objectDirectoryPath = [objectDirectoryPath stringByAppendingPathComponent:[object.name substringWithRange:NSMakeRange(subdirPrefixLength, [object.name length] - subdirPrefixLength - [fileName length])]];
209
            
210
            ASIPithosObjectRequest *objectRequest = [self objectDataRequestWithPithos:pithos 
211
                                                                        containerName:containerName 
212
                                                                           objectName:object.name 
213
                                                                              version:nil 
214
                                                                          toDirectory:objectDirectoryPath 
215
                                                                      withNewFileName:nil 
216
                                                                        checkIfExists:NO 
217
                                                                       sharingAccount:sharingAccount];
218
            [(NSMutableDictionary *)objectRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:object.bytes] forKey:@"bytes"];
219
            [objectRequests addObject:objectRequest];
220
        }
221
    }
222
    
223
    return objectRequests;
224
}
225

    
226
#pragma mark -
227
#pragma mark Download Block
228

    
229
+ (ASIPithosObjectRequest *)objectBlockDataRequestWithPithos:(ASIPithos *)pithos 
230
                                               containerName:(NSString *)containerName 
231
                                                      object:(ASIPithosObject *)object 
232
                                                  blockIndex:(NSUInteger)blockIndex 
233
                                                   blockSize:(NSUInteger)blockSize {
234
    NSUInteger rangeStart = blockIndex * blockSize;
235
    NSUInteger rangeEnd = (rangeStart + blockSize <= object.bytes) ? (rangeStart + blockSize - 1) : (object.bytes - 1);
236
    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos 
237
                                                                                  containerName:containerName
238
                                                                                     objectName:object.name
239
                                                                                        version:nil
240
                                                                                          range:[NSString stringWithFormat:@"bytes=%lu-%lu", rangeStart, rangeEnd]
241
                                                                                        ifMatch:object.hash];
242
    return objectRequest;
243
}
244

    
245
+ (NSIndexSet *)missingBlocksForFile:(NSString *)filePath
246
                           blockSize:(NSUInteger)blockSize 
247
                           blockHash:(NSString *)blockHash 
248
                          withHashes:(NSArray *)hashes {
249
    NSArray *fileHashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
250
    return [hashes indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
251
        if ((idx >= [fileHashes count]) || ![(NSString *)obj isEqualToString:[fileHashes objectAtIndex:idx]])
252
            return YES;
253
        return NO;
254
    }];
255
}
256

    
257
#pragma mark -
258
#pragma mark Upload
259

    
260
+ (ASIPithosObjectRequest *)writeObjectDataRequestWithPithos:(ASIPithos *)pithos 
261
                                               containerName:(NSString *)containerName
262
                                                  objectName:(NSString *)objectName
263
                                                 contentType:(NSString *)contentType 
264
                                                   blockSize:(NSUInteger)blockSize 
265
                                                   blockHash:(NSString *)blockHash 
266
                                                     forFile:(NSString *)filePath 
267
                                               checkIfExists:(BOOL)ifExists 
268
                                                      hashes:(NSArray **)hashes 
269
                                              sharingAccount:(NSString *)sharingAccount {
270
    if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName 
271
                                          sharingAccount:(NSString *)sharingAccount])
272
        return nil;
273
    
274
    if (*hashes == nil)
275
        *hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
276
    if (*hashes == nil)
277
        return nil;
278
    NSString *fileName = [filePath lastPathComponent];
279
    if ([filePath hasSuffix:@"/"])
280
        fileName = [fileName stringByAppendingString:@"/"];
281
    NSUInteger bytes = [self bytesOfFile:filePath];
282
    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
283
                                                                                       containerName:containerName 
284
                                                                                          objectName:objectName 
285
                                                                                         contentType:contentType 
286
                                                                                     contentEncoding:nil 
287
                                                                                  contentDisposition:nil 
288
                                                                                            manifest:nil 
289
                                                                                             sharing:nil 
290
                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
291
                                                                                            metadata:nil
292
                                                                                           blockSize:blockSize
293
                                                                                           blockHash:blockHash 
294
                                                                                              hashes:*hashes 
295
                                                                                               bytes:bytes];
296
    if (sharingAccount) 
297
        [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
298
    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
299
                              fileName, @"fileName", 
300
                              [NSNumber numberWithUnsignedInteger:bytes], @"bytes", 
301
                              nil];
302
    return objectRequest;
303
}
304

    
305
+ (NSIndexSet *)missingBlocksForHashes:(NSArray *)hashes withMissingHashes:(NSArray *)missingHashes {
306
    NSMutableIndexSet *missingBlocks = [NSMutableIndexSet indexSet];
307
    for (NSString *missingHash in missingHashes) {
308
        if (![missingHash length])
309
            break;
310
        NSUInteger missingBlock = [hashes indexOfObject:missingHash];
311
        if (missingBlock != NSNotFound)
312
            [missingBlocks addIndex:missingBlock];
313
    }
314
    return missingBlocks;
315
}
316

    
317
+ (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos 
318
                                                      containerName:(NSString *)containerName 
319
                                                          blockSize:(NSUInteger)blockSize 
320
                                                            forFile:(NSString *)filePath 
321
                                                             hashes:(NSArray *)hashes 
322
                                                      missingHashes:(NSArray *)missingHashes 
323
                                                     sharingAccount:(NSString *)sharingAccount {
324
    NSIndexSet *missingBlocks = [self missingBlocksForHashes:hashes withMissingHashes:missingHashes];
325
    
326
    NSFileManager *fileManager = [NSFileManager defaultManager];
327
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
328
    
329
    // http://cocoawithlove.com/2009/07/temporary-files-and-folders-in-cocoa.html
330
    NSString *tempFileTemplate = NSTemporaryDirectory();
331
    if (tempFileTemplate == nil)
332
        tempFileTemplate = @"/tmp";
333
    tempFileTemplate = [tempFileTemplate stringByAppendingPathComponent:@"pithos-macos.XXXXXX"];
334
    const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
335
    char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
336
    strcpy(tempFileNameCString, tempFileTemplateCString);
337
    int fileDescriptor = mkstemp(tempFileNameCString);
338
    NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
339
    if (fileDescriptor == -1) {
340
        dispatch_async(dispatch_get_main_queue(), ^{
341
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
342
            [alert setMessageText:@"Create Temporary File Error"];
343
            [alert setInformativeText:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]];
344
            [alert addButtonWithTitle:@"OK"];
345
            [alert runModal];
346
        });
347
        return nil;
348
    }
349
    free(tempFileNameCString);
350
    NSFileHandle *tempFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor closeOnDealloc:YES];
351

    
352
    [missingBlocks enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
353
        [fileHandle seekToFileOffset:(idx*blockSize)];
354
        [tempFileHandle writeData:[fileHandle readDataOfLength:blockSize]];
355
    }];
356
    [tempFileHandle closeFile];
357
    [fileHandle closeFile];
358

    
359
    ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos 
360
                                                                                                    containerName:containerName 
361
                                                                                                           policy:nil 
362
                                                                                                         metadata:nil 
363
                                                                                                           update:YES 
364
                                                                                                             file:tempFilePath];
365
    if (sharingAccount)
366
        [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
367
    return containerRequest;
368
}
369

    
370
+ (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos 
371
                                                      containerName:(NSString *)containerName 
372
                                                          blockSize:(NSUInteger)blockSize 
373
                                                            forFile:(NSString *)filePath 
374
                                                  missingBlockIndex:(NSUInteger)missingBlockIndex 
375
                                                     sharingAccount:(NSString *)sharingAccount {
376
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
377
    [fileHandle seekToFileOffset:(missingBlockIndex *blockSize)];
378
    NSData *blockData = [fileHandle readDataOfLength:blockSize];
379
    [fileHandle closeFile];
380
    ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos 
381
                                                                                                    containerName:containerName 
382
                                                                                                           policy:nil 
383
                                                                                                         metadata:nil 
384
                                                                                                           update:YES 
385
                                                                                                             data:blockData];
386
    if (sharingAccount)
387
        [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
388
    return containerRequest;
389
}
390

    
391
+ (NSArray *)writeObjectDataRequestsWithPithos:(ASIPithos *)pithos 
392
                                 containerName:(NSString *)containerName
393
                                    objectName:(NSString *)objectName
394
                                     blockSize:(NSUInteger)blockSize 
395
                                     blockHash:(NSString *)blockHash 
396
                                  forDirectory:(NSString *)directoryPath 
397
                                 checkIfExists:(BOOL)ifExists 
398
                                   objectNames:(NSMutableArray **)objectNames
399
                                  contentTypes:(NSMutableArray **)contentTypes
400
                                     filePaths:(NSMutableArray **)filePaths 
401
                                  hashesArrays:(NSMutableArray **)hashesArrays 
402
                       directoryObjectRequests:(NSMutableArray **) directoryObjectRequests 
403
                                sharingAccount:(NSString *)sharingAccount {
404
    if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName 
405
                                          sharingAccount:sharingAccount])
406
        return nil;
407

    
408
    NSFileManager *fileManager = [NSFileManager defaultManager];
409
    NSError *error = nil;
410
    NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:directoryPath error:&error];
411
    if (error) {
412
        dispatch_async(dispatch_get_main_queue(), ^{
413
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
414
            [alert setMessageText:@"Directory Read Error"];
415
            [alert setInformativeText:[NSString stringWithFormat:@"Cannot read contents of directory '%@': %@", 
416
                                       [directoryPath lastPathComponent], [error localizedDescription]]];
417
            [alert addButtonWithTitle:@"OK"];
418
            [alert runModal];
419
        });
420
        return nil;
421
    }
422
    
423
    *directoryObjectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
424
    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
425
                                                                                       containerName:containerName 
426
                                                                                          objectName:objectName 
427
                                                                                                eTag:nil 
428
                                                                                         contentType:@"application/directory" 
429
                                                                                     contentEncoding:nil 
430
                                                                                  contentDisposition:nil 
431
                                                                                            manifest:nil 
432
                                                                                             sharing:nil 
433
                                                                                            isPublic:ASIPithosObjectRequestPublicIgnore 
434
                                                                                            metadata:nil 
435
                                                                                                data:[NSData data]];
436
    if (sharingAccount)
437
        [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
438
    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
439
                              [directoryPath lastPathComponent], @"fileName", 
440
                              nil];
441
    [*directoryObjectRequests addObject:objectRequest];
442
    
443
    NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
444
    *objectNames = [NSMutableArray arrayWithCapacity:[subPaths count]];
445
    *contentTypes = [NSMutableArray arrayWithCapacity:[subPaths count]];
446
    *filePaths = [NSMutableArray arrayWithCapacity:[subPaths count]];
447
    *hashesArrays = [NSMutableArray arrayWithCapacity:[subPaths count]];
448
    BOOL isDirectory;
449
    NSString *subObjectName;
450
    NSArray *hashes;
451
    NSUInteger bytes;
452
    NSString *contentType;
453
    NSString *filePath;
454
    NSString *fileName;
455
    for (NSString *objectNameSuffix in subPaths) {
456
        filePath = [directoryPath stringByAppendingPathComponent:objectNameSuffix];
457
        if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
458
            if (!isDirectory) {
459
                hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
460
                if (hashes) {
461
                    subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping];
462
                    fileName = [filePath lastPathComponent];
463
                    if ([filePath hasSuffix:@"/"])
464
                        fileName = [fileName stringByAppendingString:@"/"];
465
                    bytes = [self bytesOfFile:filePath];
466
                    error = nil;
467
                    contentType = [self contentTypeOfFile:filePath error:&error];
468
                    if (contentType == nil)
469
                        contentType = @"application/octet-stream";
470
                    #if DEBUG_PITHOS
471
                    if (error)
472
                        DLog(@"contentType detection error: %@", error);
473
                    #endif
474
                    objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
475
                                                                               containerName:containerName 
476
                                                                                  objectName:subObjectName 
477
                                                                                 contentType:contentType 
478
                                                                             contentEncoding:nil 
479
                                                                          contentDisposition:nil 
480
                                                                                    manifest:nil 
481
                                                                                     sharing:nil 
482
                                                                                    isPublic:ASIPithosObjectRequestPublicIgnore 
483
                                                                                    metadata:nil
484
                                                                                   blockSize:blockSize
485
                                                                                   blockHash:blockHash 
486
                                                                                      hashes:hashes 
487
                                                                                       bytes:bytes];
488
                    if (sharingAccount)
489
                        [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
490
                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
491
                                              fileName, @"fileName", 
492
                                              [NSNumber numberWithUnsignedInteger:bytes], @"bytes", 
493
                                              nil];
494
                    [objectRequests addObject:objectRequest];
495
                    [*objectNames addObject:subObjectName];
496
                    [*contentTypes addObject:contentType];
497
                    [*filePaths addObject:filePath];
498
                    [*hashesArrays addObject:hashes];
499
                }
500
                
501
            } else {
502
                subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping];
503
                fileName = [filePath lastPathComponent];
504
                objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
505
                                                                           containerName:containerName 
506
                                                                              objectName:subObjectName 
507
                                                                                    eTag:nil 
508
                                                                             contentType:@"application/directory" 
509
                                                                         contentEncoding:nil 
510
                                                                      contentDisposition:nil 
511
                                                                                manifest:nil 
512
                                                                                 sharing:nil 
513
                                                                                isPublic:ASIPithosObjectRequestPublicIgnore 
514
                                                                                metadata:nil 
515
                                                                                    data:[NSData data]];
516
                if (sharingAccount)
517
                    [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
518
                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
519
                                          fileName, @"fileName", 
520
                                          nil];
521
                [*directoryObjectRequests addObject:objectRequest];
522
            }
523
        }
524
    }
525
    
526
    return objectRequests;
527
}
528

    
529
#pragma mark -
530
#pragma mark Delete
531

    
532
+ (NSArray *)deleteObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos 
533
                                       containerName:(NSString *)containerName 
534
                                          objectName:(NSString *)objectName {
535
    NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName delimiter:nil 
536
                                         sharingAccount:nil];
537
    if (objects == nil)
538
        return nil;
539

    
540
    NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
541
    ASIPithosObjectRequest *objectRequest;
542
    if (![objectName hasSuffix:@"/"]) {
543
        objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:objectName];
544
        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
545
                                  [objectName lastPathComponent], @"fileName", 
546
                                  nil];
547
        [objectRequests addObject:objectRequest];
548
    }
549
    NSString *fileName;
550
    for (ASIPithosObject *object in objects) {
551
        fileName = [object.name lastPathComponent];
552
        if ([object.name hasSuffix:@"/"])
553
            fileName = [fileName stringByAppendingString:@"/"];
554
        objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:object.name];
555
        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
556
                                  fileName, @"fileName", 
557
                                  nil];
558
        [objectRequests addObject:objectRequest];
559
    }
560
    
561
    if ([objectRequests count] == 0)
562
        return nil;
563
    return objectRequests;
564
}
565

    
566
#pragma mark -
567
#pragma mark Copy
568

    
569
+ (ASIPithosObjectRequest *)copyObjectRequestWithPithos:(ASIPithos *)pithos 
570
                                          containerName:(NSString *)containerName 
571
                                             objectName:(NSString *)objectName 
572
                               destinationContainerName:(NSString *)destinationContainerName 
573
                                  destinationObjectName:(NSString *)destinationObjectName 
574
                                          checkIfExists:(BOOL)ifExists 
575
                                         sharingAccount:(NSString *)sharingAccount {
576
    if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName 
577
                                          sharingAccount:nil])
578
        return nil;
579
    
580
    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos 
581
                                                                                      containerName:containerName 
582
                                                                                         objectName:objectName 
583
                                                                                        contentType:nil 
584
                                                                                    contentEncoding:nil 
585
                                                                                 contentDisposition:nil 
586
                                                                                           manifest:nil 
587
                                                                                            sharing:nil 
588
                                                                                           isPublic:ASIPithosObjectRequestPublicIgnore 
589
                                                                                           metadata:nil 
590
                                                                           destinationContainerName:destinationContainerName 
591
                                                                              destinationObjectName:destinationObjectName 
592
                                                                                 destinationAccount:nil
593
                                                                                      sourceVersion:nil];
594
    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
595
                              containerName, @"sourceContainerName", 
596
                              objectName, @"sourceObjectName", 
597
                              destinationContainerName, @"destinationContainerName", 
598
                              destinationObjectName, @"destinationObjectName", 
599
                              nil];
600
    if (sharingAccount) 
601
        [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
602
    return objectRequest;
603
}
604

    
605
+ (NSArray *)copyObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos 
606
                                     containerName:(NSString *)containerName 
607
                                        objectName:(NSString *)objectName 
608
                          destinationContainerName:(NSString *)destinationContainerName 
609
                             destinationObjectName:(NSString *)destinationObjectName 
610
                                     checkIfExists:(BOOL)ifExists 
611
                                    sharingAccount:(NSString *)sharingAccount {
612
    if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName 
613
                                          sharingAccount:nil])
614
        return nil;
615
    
616
    NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName 
617
                                              delimiter:nil sharingAccount:sharingAccount];
618
    if (objects == nil)
619
        return nil;
620
    
621
    NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
622
    ASIPithosObjectRequest *objectRequest;
623
    if ([objectName isEqualToString:destinationObjectName]) {
624
        if (![objectName hasSuffix:@"/"]) {
625
            objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos 
626
                                                                      containerName:containerName 
627
                                                                         objectName:objectName 
628
                                                                        contentType:nil 
629
                                                                    contentEncoding:nil 
630
                                                                 contentDisposition:nil 
631
                                                                           manifest:nil 
632
                                                                            sharing:nil 
633
                                                                           isPublic:ASIPithosObjectRequestPublicIgnore 
634
                                                                           metadata:nil 
635
                                                           destinationContainerName:destinationContainerName 
636
                                                              destinationObjectName:objectName 
637
                                                                 destinationAccount:nil 
638
                                                                      sourceVersion:nil];
639
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
640
                                      containerName, @"sourceContainerName", 
641
                                      objectName, @"sourceObjectName", 
642
                                      destinationContainerName, @"destinationContainerName", 
643
                                      objectName, @"destinationObjectName", 
644
                                      nil];
645
            if (sharingAccount)
646
                [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
647
            [objectRequests addObject:objectRequest];
648
        }
649
        for (ASIPithosObject *object in objects) {
650
            objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos 
651
                                                                      containerName: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
                                                                      sourceVersion:nil];
664
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
665
                                      containerName, @"sourceContainerName", 
666
                                      object.name, @"sourceObjectName", 
667
                                      destinationContainerName, @"destinationContainerName", 
668
                                      object.name, @"destinationObjectName", 
669
                                      nil];
670
            if (sharingAccount)
671
                [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
672
            [objectRequests addObject:objectRequest];
673
        }
674
    } else {
675
        if (![objectName hasSuffix:@"/"]) {
676
            objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos 
677
                                                                      containerName:containerName 
678
                                                                         objectName:objectName 
679
                                                                        contentType:nil 
680
                                                                    contentEncoding:nil 
681
                                                                 contentDisposition:nil 
682
                                                                           manifest:nil 
683
                                                                            sharing:nil 
684
                                                                           isPublic:ASIPithosObjectRequestPublicIgnore 
685
                                                                           metadata:nil 
686
                                                           destinationContainerName:destinationContainerName 
687
                                                              destinationObjectName:destinationObjectName 
688
                                                                 destinationAccount:nil 
689
                                                                      sourceVersion:nil];
690
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
691
                                      containerName, @"sourceContainerName", 
692
                                      objectName, @"sourceObjectName", 
693
                                      destinationContainerName, @"destinationContainerName", 
694
                                      destinationObjectName, @"destinationObjectName", 
695
                                      nil];
696
            if (sharingAccount)
697
                [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
698
            [objectRequests addObject:objectRequest];
699
        }
700
        NSRange prefixRange = NSMakeRange(0, [objectName length]);
701
        NSString *newObjectName;
702
        for (ASIPithosObject *object in objects) {
703
            newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
704
                                                                   withString:destinationObjectName
705
                                                                      options:NSAnchoredSearch
706
                                                                        range:prefixRange];
707
            objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithPithos:pithos 
708
                                                                      containerName:containerName 
709
                                                                         objectName:object.name 
710
                                                                        contentType:nil 
711
                                                                    contentEncoding:nil 
712
                                                                 contentDisposition:nil 
713
                                                                           manifest:nil 
714
                                                                            sharing:nil 
715
                                                                           isPublic:ASIPithosObjectRequestPublicIgnore 
716
                                                                           metadata:nil 
717
                                                           destinationContainerName:destinationContainerName 
718
                                                              destinationObjectName:newObjectName 
719
                                                                 destinationAccount:nil 
720
                                                                      sourceVersion:nil];
721
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
722
                                      containerName, @"sourceContainerName", 
723
                                      object.name, @"sourceObjectName", 
724
                                      destinationContainerName, @"destinationContainerName", 
725
                                      newObjectName, @"destinationObjectName", 
726
                                      nil];
727
            if (sharingAccount)
728
                [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
729
            [objectRequests addObject:objectRequest];
730
        }
731
    }
732
    
733
    if ([objectRequests count] == 0)
734
        return nil;
735
    return objectRequests;
736
}
737

    
738
#pragma mark -
739
#pragma mark Move
740

    
741
+ (ASIPithosObjectRequest *)moveObjectRequestWithPithos:(ASIPithos *)pithos 
742
                                          containerName:(NSString *)containerName 
743
                                             objectName:(NSString *)objectName 
744
                               destinationContainerName:(NSString *)destinationContainerName 
745
                                  destinationObjectName:(NSString *)destinationObjectName 
746
                                          checkIfExists:(BOOL)ifExists {
747
    if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName 
748
                                          sharingAccount:nil])
749
        return nil;
750
    
751
    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
752
                                                                                      containerName:containerName 
753
                                                                                         objectName:objectName 
754
                                                                                        contentType:nil 
755
                                                                                    contentEncoding:nil 
756
                                                                                 contentDisposition:nil 
757
                                                                                           manifest:nil 
758
                                                                                            sharing:nil 
759
                                                                                           isPublic:ASIPithosObjectRequestPublicIgnore 
760
                                                                                           metadata:nil 
761
                                                                           destinationContainerName:destinationContainerName 
762
                                                                              destinationObjectName:destinationObjectName 
763
                                                                                 destinationAccount:nil];
764
    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
765
                              containerName, @"sourceContainerName", 
766
                              objectName, @"sourceObjectName", 
767
                              destinationContainerName, @"destinationContainerName", 
768
                              destinationObjectName, @"destinationObjectName", 
769
                              nil];
770
    return objectRequest;
771
}
772

    
773
+ (NSArray *)moveObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos 
774
                                     containerName:(NSString *)containerName 
775
                                        objectName:(NSString *)objectName 
776
                          destinationContainerName:(NSString *)destinationContainerName 
777
                             destinationObjectName:(NSString *)destinationObjectName 
778
                                     checkIfExists:(BOOL)ifExists {
779
    if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName 
780
                                          sharingAccount:nil])
781
        return nil;
782
    
783
    NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName 
784
                                              delimiter:nil sharingAccount:nil];
785
    if (objects == nil)
786
        return nil;
787
    
788
    ASIPithosObjectRequest *objectRequest;
789
    NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
790
    if ([objectName isEqualToString:destinationObjectName]) {
791
        if (![objectName hasSuffix:@"/"]) {
792
            objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
793
                                                                      containerName:containerName 
794
                                                                         objectName:objectName 
795
                                                                        contentType:nil 
796
                                                                    contentEncoding:nil 
797
                                                                 contentDisposition:nil 
798
                                                                           manifest:nil 
799
                                                                            sharing:nil 
800
                                                                           isPublic:ASIPithosObjectRequestPublicIgnore 
801
                                                                           metadata:nil 
802
                                                           destinationContainerName:destinationContainerName 
803
                                                              destinationObjectName:objectName 
804
                                                                 destinationAccount:nil];
805
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
806
                                      containerName, @"sourceContainerName", 
807
                                      objectName, @"sourceObjectName", 
808
                                      destinationContainerName, @"destinationContainerName", 
809
                                      objectName, @"destinationObjectName", 
810
                                      nil];
811
            [objectRequests addObject:objectRequest];
812
        }
813
        for (ASIPithosObject *object in objects) {
814
            objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
815
                                                                      containerName:containerName 
816
                                                                         objectName:object.name 
817
                                                                        contentType:nil 
818
                                                                    contentEncoding:nil 
819
                                                                 contentDisposition:nil 
820
                                                                           manifest:nil 
821
                                                                            sharing:nil 
822
                                                                           isPublic:ASIPithosObjectRequestPublicIgnore 
823
                                                                           metadata:nil 
824
                                                           destinationContainerName:destinationContainerName 
825
                                                              destinationObjectName:object.name 
826
                                                                 destinationAccount:nil];
827
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
828
                                      containerName, @"sourceContainerName", 
829
                                      object.name, @"sourceObjectName", 
830
                                      destinationContainerName, @"destinationContainerName", 
831
                                      object.name, @"destinationObjectName", 
832
                                      nil];
833
            [objectRequests addObject:objectRequest];
834
        }
835
    } else {
836
        if (![objectName hasSuffix:@"/"]) {
837
            objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
838
                                                                      containerName:containerName 
839
                                                                         objectName:objectName 
840
                                                                        contentType:nil 
841
                                                                    contentEncoding:nil 
842
                                                                 contentDisposition:nil 
843
                                                                           manifest:nil 
844
                                                                            sharing:nil 
845
                                                                           isPublic:ASIPithosObjectRequestPublicIgnore 
846
                                                                           metadata:nil 
847
                                                           destinationContainerName:destinationContainerName 
848
                                                              destinationObjectName:destinationObjectName 
849
                                                                 destinationAccount:nil];
850
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
851
                                      containerName, @"sourceContainerName", 
852
                                      objectName, @"sourceObjectName", 
853
                                      destinationContainerName, @"destinationContainerName", 
854
                                      destinationObjectName, @"destinationObjectName", 
855
                                      nil];
856
            [objectRequests addObject:objectRequest];
857
        }
858
        NSRange prefixRange = NSMakeRange(0, [objectName length]);
859
        NSString *newObjectName;
860
        for (ASIPithosObject *object in objects) {
861
            newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
862
                                                                   withString:destinationObjectName
863
                                                                      options:NSAnchoredSearch
864
                                                                        range:prefixRange];
865
            objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos 
866
                                                                      containerName:containerName 
867
                                                                         objectName:object.name
868
                                                                        contentType:nil 
869
                                                                    contentEncoding:nil 
870
                                                                 contentDisposition:nil 
871
                                                                           manifest:nil 
872
                                                                            sharing:nil 
873
                                                                           isPublic:ASIPithosObjectRequestPublicIgnore 
874
                                                                           metadata:nil 
875
                                                           destinationContainerName:destinationContainerName 
876
                                                              destinationObjectName:newObjectName 
877
                                                                 destinationAccount:nil];
878
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
879
                                      containerName, @"sourceContainerName", 
880
                                      object.name, @"sourceObjectName", 
881
                                      destinationContainerName, @"destinationContainerName", 
882
                                      newObjectName, @"destinationObjectName", 
883
                                      nil];
884
            [objectRequests addObject:objectRequest];
885
        }
886
    }
887
     
888
    if ([objectRequests count] == 0)
889
        return nil;
890
    return objectRequests;
891
}
892

    
893
#pragma mark -
894
#pragma mark Helper Methods
895

    
896
// Size of the file in bytes
897
+ (NSUInteger)bytesOfFile:(NSString *)filePath {
898
    NSFileManager *fileManager = [NSFileManager defaultManager];
899
    NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil];
900
    return [[attributes objectForKey:NSFileSize] unsignedIntegerValue];
901
}
902

    
903
// Content type of the file or nil if it cannot be determined
904
+ (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error {
905
    // Based on http://www.ddeville.me/2011/12/mime-to-UTI-cocoa/
906
    // and Apple example ImageBrowserViewAppearance/ImageBrowserController.m
907
    LSItemInfoRecord info;
908
    CFStringRef uti = NULL;
909
    CFURLRef url = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)filePath, kCFURLPOSIXPathStyle, FALSE);
910
    if (LSCopyItemInfoForURL(url, kLSRequestExtension | kLSRequestTypeCreator, &info) == noErr) {
911
        // Obtain the UTI using the file information.
912
        // If there is a file extension, get the UTI.
913
        if (info.extension != NULL) {
914
            uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, info.extension, kUTTypeData);
915
            CFRelease(info.extension);
916
        }
917
        // No UTI yet
918
        if (uti == NULL) {
919
            // If there is an OSType, get the UTI.
920
            CFStringRef typeString = UTCreateStringForOSType(info.filetype);
921
            if ( typeString != NULL) {
922
                uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, typeString, kUTTypeData);
923
                CFRelease(typeString);
924
            }
925
        }
926
        if (uti != NULL) {
927
            CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
928
            CFRelease(uti);
929
            return (NSString *)MIMEType;
930
        }
931
    }
932
    return nil;
933
}
934

    
935
// Creates a directory if it doesn't exist and returns if successful
936
+ (BOOL)safeCreateDirectory:(NSString *)directoryPath error:(NSError **)error {
937
    NSFileManager *fileManager = [NSFileManager defaultManager];
938
    BOOL isDirectory;
939
    BOOL fileExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory];
940
    if (fileExists)
941
        return isDirectory;
942
    if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:error] || *error)
943
        return NO;
944
    return YES;
945
}
946

    
947
// Removes contents of a directory
948
+ (void)removeContentsAtPath:(NSString *)dirPath {
949
    NSFileManager *fileManager = [NSFileManager defaultManager];
950
    NSError *error = nil;
951
    BOOL isDirectory;
952
    if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory])
953
        return;
954
    if (isDirectory) {
955
        for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:dirPath error:&error]) {
956
            if (error) {
957
                [self fileActionFailedAlertWithTitle:@"Directory Contents Error" 
958
                                             message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", dirPath] 
959
                                               error:error];
960
                break;
961
            }
962
            NSString *subFilePath = [dirPath stringByAppendingPathComponent:subPath];
963
            if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
964
                [self fileActionFailedAlertWithTitle:@"Remove File Error" 
965
                                             message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
966
                                               error:error];
967
            }
968
            error = nil;
969
        }
970
    } else if (![fileManager removeItemAtPath:dirPath error:&error] || error) {
971
        [self fileActionFailedAlertWithTitle:@"Remove File Error" 
972
                                     message:[NSString stringWithFormat:@"Cannot remove file at '%@'", dirPath] 
973
                                       error:error];
974
    }
975
}
976

    
977
// Returns if an object is a directory based on its content type
978
+ (BOOL)isContentTypeDirectory:(NSString *)contentType {
979
    return ([contentType isEqualToString:@"application/directory"] ||
980
            [contentType hasPrefix:@"application/directory;"] ||
981
            [contentType isEqualToString:@"application/folder"] ||
982
            [contentType hasPrefix:@"application/folder;"]);
983
}
984

    
985
// Returns if an object exists at the given container/object path and if this object is an application/directory
986
// If an error occured an alert is shown and it is returned so the caller won't proceed
987
+ (BOOL)objectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName 
988
                       error:(NSError **)error isDirectory:(BOOL *)isDirectory sharingAccount:(NSString *)sharingAccount {
989
    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos 
990
                                                                                      containerName:containerName 
991
                                                                                         objectName:objectName];
992
    if (sharingAccount)
993
        [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
994
    ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
995
    [networkQueue go];
996
    [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:objectRequest]] waitUntilFinished:YES];
997
    *error = [objectRequest error];
998
    if (*error) {
999
        [self httpRequestErrorAlertWithRequest:objectRequest];
1000
        return NO;
1001
    } else if (objectRequest.responseStatusCode == 200) {
1002
        *isDirectory = [self isContentTypeDirectory:[objectRequest contentType]];
1003
        return YES;
1004
    }
1005
    return NO;
1006
}
1007

    
1008
// Returns if the caller should proceed, after an interactive check if an object exists 
1009
// at the given container/object path is performed
1010
+ (BOOL)proceedIfObjectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName 
1011
                       sharingAccount:(NSString *)sharingAccount {
1012
    NSError *error = nil;
1013
    BOOL isDirectory;
1014
    BOOL objectExists = [self objectExistsAtPithos:pithos containerName:containerName objectName:objectName 
1015
                                             error:&error isDirectory:&isDirectory sharingAccount:sharingAccount];
1016
    if (error) {
1017
        return NO;
1018
    } else if (objectExists) {
1019
        __block NSInteger choice;
1020
        dispatch_sync(dispatch_get_main_queue(), ^{
1021
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1022
            if (isDirectory) {
1023
                [alert setMessageText:@"Directory Exists"];
1024
                if (sharingAccount)
1025
                    [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
1026
                else
1027
                    [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1028
            } else {
1029
                [alert setMessageText:@"Object Exists"];
1030
                if (sharingAccount)
1031
                    [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
1032
                else
1033
                    [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
1034
            }
1035
            [alert addButtonWithTitle:@"OK"];
1036
            [alert addButtonWithTitle:@"Cancel"];
1037
            choice = [alert runModal];
1038
        });
1039
        if (choice == NSAlertSecondButtonReturn)
1040
            return NO;
1041
    }
1042
    return YES;
1043
}
1044

    
1045
// List of objects at the given container/object path, with prefix and or delimiter
1046
+ (NSArray *)objectsWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix 
1047
                     delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1048
    NSMutableArray *objects = [NSMutableArray array];
1049
    NSString *marker = nil;
1050
    do {
1051
        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
1052
                                                                                                containerName:containerName 
1053
                                                                                                        limit:0 
1054
                                                                                                       marker:marker 
1055
                                                                                                       prefix:objectNamePrefix 
1056
                                                                                                    delimiter:delimiter 
1057
                                                                                                         path:nil 
1058
                                                                                                         meta:nil 
1059
                                                                                                       shared:NO 
1060
                                                                                                        until:nil];
1061
        if (sharingAccount)
1062
            [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
1063
        ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1064
        [networkQueue go];
1065
        [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:containerRequest]] waitUntilFinished:YES];
1066
        if ([containerRequest error]) {
1067
            [self httpRequestErrorAlertWithRequest:containerRequest];
1068
            return nil;
1069
        }
1070
        NSArray *someObjects = [containerRequest objects];
1071
        [objects addObjectsFromArray:someObjects];
1072
        if ([someObjects count] < 10000)
1073
            marker = nil;
1074
        else
1075
            marker = [[someObjects lastObject] name];
1076
    } while (marker);
1077
    return objects;
1078
}
1079

    
1080
// List of objects at the given container/object path, that may be a subdir or an application/directory, 
1081
// with prefix and or delimiter
1082
+ (NSArray *)objectsForSubdirWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName 
1083
                              delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1084
    NSString *subdirNamePrefix = [NSString stringWithString:objectName];
1085
    if (![subdirNamePrefix hasSuffix:@"/"])
1086
        subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"];
1087
    return [self objectsWithPithos:pithos containerName:containerName objectNamePrefix:subdirNamePrefix 
1088
                         delimiter:delimiter sharingAccount:sharingAccount];
1089
}
1090

    
1091
// A safe object name at the given container/object path
1092
// The original name has " %d" appended to it before any ".*" suffix, for the first integer that produces a name that is free to use
1093
// If the original name hasn't got a "." but has a "/" suffix, then it is replaced with " %d/" instead
1094
// Subdirs are taken into consideration
1095
+ (NSString *)safeObjectNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName {
1096
    NSString *objectNamePrefix;
1097
    NSString *objectNameExtraSuffix;
1098
    NSRange lastDotRange = [objectName rangeOfString:@"." options:NSBackwardsSearch];
1099
    if (lastDotRange.length == 1) {
1100
        objectNamePrefix = [objectName substringToIndex:lastDotRange.location];
1101
        objectNameExtraSuffix = [objectName substringFromIndex:lastDotRange.location];
1102
    } else if ([objectName hasSuffix:@"/"]) {
1103
        objectNamePrefix = [objectName substringToIndex:([objectName length] - 1)];
1104
        objectNameExtraSuffix = [NSString stringWithString:@"/"];
1105
    } else {
1106
        objectNamePrefix = [NSString stringWithString:objectName];
1107
        objectNameExtraSuffix = [NSString string];
1108
    }
1109
    NSArray *objects = [self objectsWithPithos:pithos containerName:containerName 
1110
                              objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent] 
1111
                                     delimiter:@"/" sharingAccount:nil];
1112
    if (objects == nil)
1113
        return nil;
1114
    if ([objects count] == 0)
1115
        return objectName;
1116
    NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1117
                                   [[objects objectsAtIndexes:
1118
                                     [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1119
        if (pithosObject.subdir)
1120
            return NO;
1121
        return YES;
1122
    }]] valueForKey:@"name"]];
1123
    for (NSString *name in [[objects objectsAtIndexes:
1124
                             [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1125
        if (pithosObject.subdir)
1126
            return YES;
1127
        return NO;
1128
    }]] valueForKey:@"name"]) {
1129
        [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1130
    }
1131
    if (![objectNames containsObject:objectName])
1132
        return objectName;
1133
    NSUInteger objectNameSuffix = 2;
1134
    NSString *safeObjectName;
1135
    do {
1136
        safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix];
1137
        objectNameSuffix++;
1138
    } while ([objectNames containsObject:safeObjectName]);
1139
    return safeObjectName;    
1140
}
1141

    
1142
// A safe object name at the given container/object path that may be a subdir or application/directory
1143
// The original name has " %d" appended to it, for the first integer that produces a name that is free to use
1144
// If the original name has a "/" suffix, then it is replaced with " %d/" instead
1145
// Subdirs are taken into consideration
1146
+ (NSString *)safeSubdirNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName subdirName:(NSString *)subdirName {
1147
    NSString *subdirNamePrefix;
1148
    NSString *subdirNameExtraSuffix;
1149
    if ([subdirName hasSuffix:@"/"]) {
1150
        subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)];
1151
        subdirNameExtraSuffix = [NSString stringWithString:@"/"];
1152
    } else {
1153
        subdirNamePrefix = [NSString stringWithString:subdirName];
1154
        subdirNameExtraSuffix = [NSString string];
1155
    }
1156
    NSArray *objects = [self objectsWithPithos:pithos containerName:containerName 
1157
                              objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent] 
1158
                                     delimiter:@"/" sharingAccount:nil];
1159
    if (objects == nil)
1160
        return nil;
1161
    if ([objects count] == 0)
1162
        return subdirName;
1163
    NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1164
                                   [[objects objectsAtIndexes:
1165
                                     [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1166
        if (pithosObject.subdir)
1167
            return NO;
1168
        return YES;
1169
    }]] valueForKey:@"name"]];
1170
    for (NSString *name in [[objects objectsAtIndexes:
1171
                             [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1172
        if (pithosObject.subdir)
1173
            return YES;
1174
        return NO;
1175
    }]] valueForKey:@"name"]) {
1176
        [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1177
    }
1178
    if (![objectNames containsObject:subdirNamePrefix])
1179
        return subdirName;
1180
    NSUInteger subdirNameSuffix = 2;
1181
    NSString *safeSubdirName;
1182
    do {
1183
        safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix];
1184
        subdirNameSuffix++;
1185
    } while ([objectNames containsObject:safeSubdirName]);
1186
    return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix];
1187
}
1188

    
1189
#pragma mark -
1190
#pragma mark Alerts
1191

    
1192
+ (void)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
1193
    if (request.responseStatusCode == 401) {
1194
        [self httpAuthenticationError];
1195
        return;
1196
    }
1197
    NSString *message = [NSString stringWithFormat:
1198
                         @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", 
1199
                         [[request error] localizedDescription], 
1200
                         request.requestMethod, 
1201
                         request.url, 
1202
                         [request requestHeaders], 
1203
                         [request responseHeaders], 
1204
                         [request responseString]];
1205
    DLog(@"%@", message);
1206
    dispatch_async(dispatch_get_main_queue(), ^{    
1207
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1208
        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1209
        [alert setMessageText:@"HTTP Request Error"];
1210
        [alert setInformativeText:message];
1211
        [alert addButtonWithTitle:@"OK"];
1212
        [alert runModal];
1213
        [pool drain];
1214
    });
1215
}
1216

    
1217
+ (void)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
1218
    if (request.responseStatusCode == 401) {
1219
        [self httpAuthenticationError];
1220
        return;
1221
    }
1222
    NSString *message = [NSString stringWithFormat:
1223
                         @"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", 
1224
                         request.responseStatusCode, 
1225
                         request.responseStatusMessage, 
1226
                         request.requestMethod, 
1227
                         request.url, 
1228
                         [request requestHeaders], 
1229
                         [request responseHeaders], 
1230
                         [request responseString]];
1231
    DLog(@"%@", message);
1232
    dispatch_async(dispatch_get_main_queue(), ^{
1233
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1234
        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1235
        [alert setMessageText:@"Unexpected Response Status"];
1236
        [alert setInformativeText:message];
1237
        [alert addButtonWithTitle:@"OK"];
1238
        [alert runModal];
1239
        [pool drain];
1240
    });
1241
}
1242

    
1243
+ (void)httpAuthenticationError {
1244
    dispatch_async(dispatch_get_main_queue(), ^{
1245
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1246
        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1247
        [alert setMessageText:@"Authentication Error"];
1248
        [alert setInformativeText:@"Authentication error, please check your token or login again"];
1249
        [alert addButtonWithTitle:@"OK"];
1250
        [alert runModal];
1251
        [pool drain];
1252
    });
1253
}
1254

    
1255
+ (void)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error {
1256
    dispatch_async(dispatch_get_main_queue(), ^{
1257
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1258
        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1259
        [alert setMessageText:title];
1260
        if (error)
1261
            [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, [error localizedDescription]]];
1262
        else
1263
            [alert setInformativeText:message];
1264
        [alert addButtonWithTitle:@"OK"];
1265
        [alert runModal];
1266
        [pool drain];
1267
    });
1268
}
1269

    
1270
#pragma mark -
1271
#pragma mark Request Helper Methods
1272

    
1273
+ (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority {
1274
    [request setTimeOutSeconds:60];
1275
    request.numberOfTimesToRetryOnTimeout = 10;
1276
    [request setQueuePriority:priority];
1277
    return request;
1278
}
1279

    
1280
+ (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request {
1281
    return [self prepareRequest:request priority:NSOperationQueuePriorityNormal];
1282
}
1283

    
1284
+ (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request {
1285
    NSMutableDictionary *userInfo = (NSMutableDictionary *)[[request.userInfo retain] autorelease];
1286
    request.userInfo = nil;
1287
    ASIPithosRequest *newRequest = [request copy];
1288
    newRequest.userInfo = userInfo;
1289
    return newRequest;
1290
}
1291

    
1292
@end