Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosUtilities.m @ 6d9d5dce

History | View | Annotate | Download (78.9 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 "PithosAccount.h"
45
#import "HashMapHash.h"
46

    
47
@implementation PithosUtilities
48

    
49
#pragma mark -
50
#pragma mark Download
51

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

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

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

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

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

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

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

    
258
#pragma mark -
259
#pragma mark Upload
260

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

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

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

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

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

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

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

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

    
530
#pragma mark -
531
#pragma mark Delete
532

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

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

    
567
#pragma mark -
568
#pragma mark Copy
569

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

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

    
739
#pragma mark -
740
#pragma mark Move
741

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

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

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

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

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

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

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

    
985
// Removes contents of a directory
986
+ (void)removeContentsAtPath:(NSString *)dirPath {
987
    [self removeContentsAtPath:dirPath andDirectory:NO];
988
}
989

    
990
// Returns if an object is a directory based on its content type
991
+ (BOOL)isContentTypeDirectory:(NSString *)contentType {
992
    return ([contentType isEqualToString:@"application/directory"] ||
993
            [contentType hasPrefix:@"application/directory;"] ||
994
            [contentType isEqualToString:@"application/folder"] ||
995
            [contentType hasPrefix:@"application/folder;"]);
996
}
997

    
998
// Returns if an object exists at the given container/object path and if this object is an application/directory
999
// If an error occured an alert is shown and it is returned so the caller won't proceed
1000
+ (BOOL)objectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName 
1001
                       error:(NSError **)error isDirectory:(BOOL *)isDirectory sharingAccount:(NSString *)sharingAccount {
1002
    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos 
1003
                                                                                      containerName:containerName 
1004
                                                                                         objectName:objectName];
1005
    if (sharingAccount)
1006
        [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
1007
    [self startAndWaitForRequest:objectRequest];
1008
    if (error != NULL) {
1009
        *error = [objectRequest error];
1010
        if (*error) {
1011
            [self httpRequestErrorAlertWithRequest:objectRequest];
1012
            return NO;
1013
        } else if (objectRequest.responseStatusCode == 200) {
1014
            *isDirectory = [self isContentTypeDirectory:[objectRequest contentType]];
1015
            return YES;
1016
        }
1017
    }
1018
    return NO;
1019
}
1020

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

    
1058
// List of objects at the given container/object path, with prefix and or delimiter
1059
+ (NSArray *)objectsWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix 
1060
                     delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1061
    NSMutableArray *objects = [NSMutableArray array];
1062
    NSString *marker = nil;
1063
    do {
1064
        ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
1065
                                                                                                containerName:containerName 
1066
                                                                                                        limit:0 
1067
                                                                                                       marker:marker 
1068
                                                                                                       prefix:objectNamePrefix 
1069
                                                                                                    delimiter:delimiter 
1070
                                                                                                         path:nil 
1071
                                                                                                         meta:nil 
1072
                                                                                                       shared:NO 
1073
                                                                                                        until:nil];
1074
        if (sharingAccount)
1075
            [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
1076
        [self startAndWaitForRequest:containerRequest];
1077
        if ([containerRequest error]) {
1078
            [self httpRequestErrorAlertWithRequest:containerRequest];
1079
            return nil;
1080
        }
1081
        NSArray *someObjects = [containerRequest objects];
1082
        [objects addObjectsFromArray:someObjects];
1083
        if ([someObjects count] < 10000)
1084
            marker = nil;
1085
        else
1086
            marker = [[someObjects lastObject] name];
1087
    } while (marker);
1088
    return objects;
1089
}
1090

    
1091
// List of objects at the given container/object path, that may be a subdir or an application/directory, 
1092
// with prefix and or delimiter
1093
+ (NSArray *)objectsForSubdirWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName 
1094
                              delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
1095
    NSString *subdirNamePrefix = [NSString stringWithString:objectName];
1096
    if (![subdirNamePrefix hasSuffix:@"/"])
1097
        subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"];
1098
    return [self objectsWithPithos:pithos containerName:containerName objectNamePrefix:subdirNamePrefix 
1099
                         delimiter:delimiter sharingAccount:sharingAccount];
1100
}
1101

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

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

    
1200
#pragma mark -
1201
#pragma mark Alerts
1202

    
1203
+ (void)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
1204
    if (request.responseStatusCode == 401) {
1205
        [self httpAuthenticationError];
1206
        return;
1207
    }
1208
    NSString *message = [NSString stringWithFormat:
1209
                         @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", 
1210
                         [[request error] localizedDescription], 
1211
                         request.requestMethod, 
1212
                         request.url, 
1213
                         [request requestHeaders], 
1214
                         [request responseHeaders], 
1215
                         [request responseString]];
1216
    DLog(@"%@", message);
1217
    dispatch_async(dispatch_get_main_queue(), ^{    
1218
        @autoreleasepool {
1219
            NSAlert *alert = [[NSAlert alloc] init];
1220
            [alert setMessageText:@"HTTP Request Error"];
1221
            [alert setInformativeText:message];
1222
            [alert addButtonWithTitle:@"OK"];
1223
            [alert runModal];
1224
        }
1225
    });
1226
}
1227

    
1228
+ (void)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
1229
    if (request.responseStatusCode == 401) {
1230
        [self httpAuthenticationError];
1231
        return;
1232
    }
1233
    NSString *message = [NSString stringWithFormat:
1234
                         @"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", 
1235
                         request.responseStatusCode, 
1236
                         request.responseStatusMessage, 
1237
                         request.requestMethod, 
1238
                         request.url, 
1239
                         [request requestHeaders], 
1240
                         [request responseHeaders], 
1241
                         [request responseString]];
1242
    DLog(@"%@", message);
1243
    dispatch_async(dispatch_get_main_queue(), ^{
1244
        @autoreleasepool {
1245
            NSAlert *alert = [[NSAlert alloc] init];
1246
            [alert setMessageText:@"Unexpected Response Status"];
1247
            [alert setInformativeText:message];
1248
            [alert addButtonWithTitle:@"OK"];
1249
            [alert runModal];
1250
        }
1251
    });
1252
}
1253

    
1254
+ (void)httpAuthenticationError {
1255
    dispatch_async(dispatch_get_main_queue(), ^{
1256
        @autoreleasepool {
1257
            NSAlert *alert = [[NSAlert alloc] init];
1258
            [alert setMessageText:@"Authentication Error"];
1259
            [alert setInformativeText:@"Authentication error, please check your token or login again"];
1260
            [alert addButtonWithTitle:@"OK"];
1261
            [alert runModal];
1262
        }
1263
    });
1264
}
1265

    
1266
+ (void)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error {
1267
    dispatch_async(dispatch_get_main_queue(), ^{
1268
        @autoreleasepool {
1269
            NSAlert *alert = [[NSAlert alloc] init];
1270
            [alert setMessageText:title];
1271
            if (error)
1272
                [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, [error localizedDescription]]];
1273
            else
1274
                [alert setInformativeText:message];
1275
            [alert addButtonWithTitle:@"OK"];
1276
            [alert runModal];
1277
        }
1278
    });
1279
}
1280

    
1281
#pragma mark -
1282
#pragma mark Request Helper Methods
1283

    
1284
+ (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority {
1285
    request.timeOutSeconds = 60;
1286
    request.numberOfTimesToRetryOnTimeout = 10;
1287
    request.queuePriority = priority;
1288
    return request;
1289
}
1290

    
1291
+ (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request {
1292
    return [self prepareRequest:request priority:NSOperationQueuePriorityNormal];
1293
}
1294

    
1295
+ (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request {
1296
    NSMutableDictionary *userInfo = (NSMutableDictionary *)request.userInfo;
1297
    request.userInfo = nil;
1298
    ASIPithosRequest *newRequest = [request copy];
1299
    newRequest.userInfo = userInfo;
1300
    return newRequest;
1301
}
1302

    
1303
+ (void)startAndWaitForRequest:(ASIPithosRequest *)request {
1304
    ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1305
    [networkQueue go];
1306
    [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:request]] waitUntilFinished:YES];
1307
}
1308

    
1309
+ (ASIPithosRequest *)retryWithUpdatedURLRequest:(ASIPithosRequest *)request andPithosAccountManager:(PithosAccount *)pithosAccountManager {
1310
    ASIPithosRequest *newRequest = [PithosUtilities copyRequest:request];
1311
    if (((request.responseStatusCode == 401) || (request.responseStatusCode == 404) || (request.responseStatusCode == 0)) &&
1312
        request.retryBaseURLString) {
1313
        ASIPithosRequest *serviceCatalogRequest = [ASIPithosRequest serviceCatalogRequestWithPithos:pithosAccountManager.pithos];
1314
        [PithosUtilities startAndWaitForRequest:serviceCatalogRequest];
1315
        [pithosAccountManager updateServicesFromServiceCatalogRequest:serviceCatalogRequest];
1316

    
1317
        if (newRequest.retryType == ASIPithosRequestTypeStorage) {
1318
            NSString *URLPrefix = pithosAccountManager.pithos.storageURLPrefix;
1319
            NSString *URLSuffix = [[newRequest.url description] substringFromIndex:[newRequest.retryBaseURLString length]];
1320
            newRequest.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", URLPrefix, URLSuffix]];
1321
        } else if (newRequest.retryType == ASIPithosRequestTypeUserCatalog) {
1322
            NSString *URLPrefix = pithosAccountManager.pithos.userCatalogURL;
1323
            NSString *URLSuffix = [[newRequest.url description] substringFromIndex:[newRequest.retryBaseURLString length]];
1324
            newRequest.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", URLPrefix, URLSuffix]];
1325
        }
1326
    }
1327

    
1328
    return newRequest;
1329
}
1330

    
1331
@end