Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosUtilities.m @ ef59aa75

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
        NSLog(@"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
        NSLog(@"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
                    NSLog(@"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
                    NSLog(@"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 (error)
471
                        NSLog(@"contentType detection error: %@", error);
472
                    objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
473
                                                                               containerName:containerName 
474
                                                                                  objectName:subObjectName 
475
                                                                                 contentType:contentType 
476
                                                                             contentEncoding:nil 
477
                                                                          contentDisposition:nil 
478
                                                                                    manifest:nil 
479
                                                                                     sharing:nil 
480
                                                                                    isPublic:ASIPithosObjectRequestPublicIgnore 
481
                                                                                    metadata:nil
482
                                                                                   blockSize:blockSize
483
                                                                                   blockHash:blockHash 
484
                                                                                      hashes:hashes 
485
                                                                                       bytes:bytes];
486
                    if (sharingAccount)
487
                        [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
488
                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
489
                                              fileName, @"fileName", 
490
                                              [NSNumber numberWithUnsignedInteger:bytes], @"bytes", 
491
                                              nil];
492
                    [objectRequests addObject:objectRequest];
493
                    [*objectNames addObject:subObjectName];
494
                    [*contentTypes addObject:contentType];
495
                    [*filePaths addObject:filePath];
496
                    [*hashesArrays addObject:hashes];
497
                }
498
                
499
            } else {
500
                subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping];
501
                fileName = [filePath lastPathComponent];
502
                objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
503
                                                                           containerName:containerName 
504
                                                                              objectName:subObjectName 
505
                                                                                    eTag:nil 
506
                                                                             contentType:@"application/directory" 
507
                                                                         contentEncoding:nil 
508
                                                                      contentDisposition:nil 
509
                                                                                manifest:nil 
510
                                                                                 sharing:nil 
511
                                                                                isPublic:ASIPithosObjectRequestPublicIgnore 
512
                                                                                metadata:nil 
513
                                                                                    data:[NSData data]];
514
                if (sharingAccount)
515
                    [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
516
                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
517
                                          fileName, @"fileName", 
518
                                          nil];
519
                [*directoryObjectRequests addObject:objectRequest];
520
            }
521
        }
522
    }
523
    
524
    return objectRequests;
525
}
526

    
527
#pragma mark -
528
#pragma mark Delete
529

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

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

    
564
#pragma mark -
565
#pragma mark Copy
566

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

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

    
736
#pragma mark -
737
#pragma mark Move
738

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

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

    
891
#pragma mark -
892
#pragma mark Helper Methods
893

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

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

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

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

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

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

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

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

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

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

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

    
1187
#pragma mark -
1188
#pragma mark Alerts
1189

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

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

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

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

    
1268
#pragma mark -
1269
#pragma mark Request Helper Methods
1270

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

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

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

    
1290
@end