5 // Copyright 2011 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
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.
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.
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.
38 #import "PithosUtilities.h"
39 #import "ASIPithosContainerRequest.h"
40 #import "ASIPithosObjectRequest.h"
41 #import "ASIPithosObject.h"
42 #import "HashMapHash.h"
44 @implementation PithosUtilities
49 + (ASIPithosObjectRequest *)objectDataRequestWithContainerName:(NSString *)containerName
50 objectName:(NSString *)objectName
51 toDirectory:(NSString *)directoryPath
52 checkIfExists:(BOOL)ifExists
53 sharingAccount:(NSString *)sharingAccount {
54 NSString *fileName = [objectName lastPathComponent];
55 if([objectName hasSuffix:@"/"])
56 fileName = [fileName stringByAppendingString:@"/"];
57 fileName = [fileName stringByReplacingOccurrencesOfString:@"/" withString:@":"];
59 NSFileManager *fileManager = [NSFileManager defaultManager];
61 NSString *destinationPath = [directoryPath stringByAppendingPathComponent:fileName];
62 if (ifExists && [fileManager fileExistsAtPath:destinationPath]) {
63 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
64 [alert setMessageText:@"File Exists"];
65 [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", fileName]];
66 [alert addButtonWithTitle:@"OK"];
67 [alert addButtonWithTitle:@"Cancel"];
68 NSInteger choice = [alert runModal];
69 if (choice == NSAlertSecondButtonReturn)
73 BOOL directoryIsDirectory;
74 BOOL directoryExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&directoryIsDirectory];
76 if (!directoryExists) {
77 [fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
78 } else if (!directoryIsDirectory) {
79 [fileManager removeItemAtPath:directoryPath error:&error];
82 NSLog(@"Cannot remove existing file '%@': %@", fileName, error);
83 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
84 [alert setMessageText:@"Removal Error"];
85 [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file '%@': %@", fileName, error]];
86 [alert addButtonWithTitle:@"OK"];
91 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithContainerName:containerName
92 objectName:objectName];
94 [objectRequest setRequestUserFromDefaultTo:sharingAccount];
95 objectRequest.downloadDestinationPath = destinationPath;
96 objectRequest.allowResumeForFileDownloads = YES;
97 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
98 fileName, @"fileName",
99 destinationPath, @"filePath",
101 return objectRequest;
104 + (NSArray *)objectDataRequestsForSubdirWithContainerName:(NSString *)containerName
105 objectName:(NSString *)objectName
106 toDirectory:(NSString *)directoryPath
107 checkIfExists:(BOOL)ifExists
108 sharingAccount:(NSString *)sharingAccount {
109 NSString *subdirName = [objectName lastPathComponent];
110 NSString *destinationPath = [directoryPath stringByAppendingPathComponent:subdirName];
111 if (ifExists && [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) {
112 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
113 [alert setMessageText:@"File exists"];
114 [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", subdirName]];
115 [alert addButtonWithTitle:@"OK"];
116 [alert addButtonWithTitle:@"Cancel"];
117 NSInteger choice = [alert runModal];
118 if (choice == NSAlertSecondButtonReturn)
122 NSArray *objects = [self objectsForSubdirWithContainerName:containerName objectName:objectName
123 delimiter:nil sharingAccount:sharingAccount];
127 NSFileManager *fileManager = [NSFileManager defaultManager];
128 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[objects count]];
129 NSUInteger subdirPrefixLength = [objectName length];
131 NSError *error = nil;
132 [fileManager createDirectoryAtPath:[directoryPath stringByAppendingPathComponent:subdirName] withIntermediateDirectories:YES attributes:nil error:&error];
134 NSLog(@"Cannot create directory at '%@': %@", directoryPath, error);
135 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
136 [alert setMessageText:@"Create Directory Error"];
137 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@",
138 directoryPath, error]];
139 [alert addButtonWithTitle:@"OK"];
143 for (ASIPithosObject *object in objects) {
144 if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
145 NSString *subdirDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
146 subdirDirectoryPath = [subdirDirectoryPath stringByAppendingPathComponent:[object.name substringFromIndex:subdirPrefixLength]];
148 BOOL directoryIsDirectory;
149 BOOL directoryExists = [fileManager fileExistsAtPath:subdirDirectoryPath isDirectory:&directoryIsDirectory];
150 NSError *error = nil;
151 if (!directoryExists) {
152 [fileManager createDirectoryAtPath:subdirDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error];
154 NSLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error);
155 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
156 [alert setMessageText:@"Create Directory Error"];
157 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@",
158 subdirDirectoryPath, error]];
159 [alert addButtonWithTitle:@"OK"];
162 } else if (!directoryIsDirectory) {
163 [fileManager removeItemAtPath:subdirDirectoryPath error:&error];
165 NSLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error);
166 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
167 [alert setMessageText:@"Remove File Error"];
168 [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file at '%@': %@",
169 subdirDirectoryPath, error]];
170 [alert addButtonWithTitle:@"OK"];
175 NSString *fileName = [object.name lastPathComponent];
176 if([object.name hasSuffix:@"/"])
177 fileName = [fileName stringByAppendingString:@"/"];
179 NSString *objectDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
180 objectDirectoryPath = [objectDirectoryPath stringByAppendingPathComponent:[object.name substringWithRange:NSMakeRange(subdirPrefixLength, [object.name length] - subdirPrefixLength - [fileName length])]];
182 ASIPithosObjectRequest *objectRequest = [self objectDataRequestWithContainerName:containerName
183 objectName:object.name
184 toDirectory:objectDirectoryPath
186 sharingAccount:sharingAccount];
187 [(NSMutableDictionary *)objectRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:object.bytes] forKey:@"bytes"];
188 [objectRequests addObject:objectRequest];
192 return objectRequests;
196 #pragma mark Download Block
198 + (ASIPithosObjectRequest *)objectBlockDataRequestWithContainerName:(NSString *)containerName
199 object:(ASIPithosObject *)object
200 blockIndex:(NSUInteger)blockIndex
201 blockSize:(NSUInteger)blockSize {
202 NSUInteger rangeStart = blockIndex * blockSize;
203 NSUInteger rangeEnd = (rangeStart + blockSize <= object.bytes) ? (rangeStart + blockSize - 1) : (object.bytes - 1);
204 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithContainerName:containerName
205 objectName:object.name
207 range:[NSString stringWithFormat:@"bytes=%lu-%lu", rangeStart, rangeEnd]
208 ifMatch:object.hash];
209 return objectRequest;
212 + (NSIndexSet *)missingBlocksForFile:(NSString *)filePath
213 blockSize:(NSUInteger)blockSize
214 blockHash:(NSString *)blockHash
215 withHashes:(NSArray *)hashes {
216 NSArray *fileHashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
217 return [hashes indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
218 if ((idx >= [fileHashes count]) || ![(NSString *)obj isEqualToString:[fileHashes objectAtIndex:idx]])
227 + (ASIPithosObjectRequest *)writeObjectDataRequestWithContainerName:(NSString *)containerName
228 objectName:(NSString *)objectName
229 contentType:(NSString *)contentType
230 blockSize:(NSUInteger)blockSize
231 blockHash:(NSString *)blockHash
232 forFile:(NSString *)filePath
233 checkIfExists:(BOOL)ifExists
234 hashes:(NSArray **)hashes
235 sharingAccount:(NSString *)sharingAccount {
236 if (ifExists && ![self proceedIfObjectExistsAtContainerName:containerName objectName:objectName
237 sharingAccount:(NSString *)sharingAccount])
241 *hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
244 NSString *fileName = [filePath lastPathComponent];
245 if ([filePath hasSuffix:@"/"])
246 fileName = [fileName stringByAppendingString:@"/"];
247 NSUInteger bytes = [self bytesOfFile:filePath];
248 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName
249 objectName:objectName
250 contentType:contentType
252 contentDisposition:nil
255 isPublic:ASIPithosObjectRequestPublicIgnore
262 [objectRequest setRequestUserFromDefaultTo:sharingAccount];
263 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
264 fileName, @"fileName",
265 [NSNumber numberWithUnsignedInteger:bytes], @"bytes",
267 return objectRequest;
270 + (NSIndexSet *)missingBlocksForHashes:(NSArray *)hashes withMissingHashesResponse:(NSString *)missingHashesResponse {
271 NSArray *responseLines = [missingHashesResponse componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
272 NSMutableIndexSet *missingBlocks = [NSMutableIndexSet indexSet];
273 for (NSString *line in responseLines) {
276 NSUInteger missingBlock = [hashes indexOfObject:line];
277 if (missingBlock != NSNotFound)
278 [missingBlocks addIndex:missingBlock];
280 return missingBlocks;
283 + (ASIPithosContainerRequest *)updateContainerDataRequestWithContainerName:(NSString *)containerName
284 blockSize:(NSUInteger)blockSize
285 forFile:(NSString *)filePath
286 hashes:(NSArray *)hashes
287 missingHashesResponse:(NSString *)missingHashesResponse
288 sharingAccount:(NSString *)sharingAccount {
289 NSIndexSet *missingBlocks = [self missingBlocksForHashes:hashes withMissingHashesResponse:missingHashesResponse];
291 NSFileManager *fileManager = [NSFileManager defaultManager];
292 NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
294 // http://cocoawithlove.com/2009/07/temporary-files-and-folders-in-cocoa.html
295 NSString *tempFileTemplate = NSTemporaryDirectory();
296 if (tempFileTemplate == nil)
297 tempFileTemplate = @"/tmp";
298 tempFileTemplate = [tempFileTemplate stringByAppendingPathComponent:@"pithos-macos.XXXXXX"];
299 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
300 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
301 strcpy(tempFileNameCString, tempFileTemplateCString);
302 int fileDescriptor = mkstemp(tempFileNameCString);
303 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
304 if (fileDescriptor == -1) {
305 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
306 [alert setMessageText:@"Create Temporary File Error"];
307 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]];
308 [alert addButtonWithTitle:@"OK"];
312 free(tempFileNameCString);
313 NSFileHandle *tempFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor closeOnDealloc:YES];
315 [missingBlocks enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
316 [fileHandle seekToFileOffset:(idx*blockSize)];
317 [tempFileHandle writeData:[fileHandle readDataOfLength:blockSize]];
319 [tempFileHandle closeFile];
320 [fileHandle closeFile];
322 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithContainerName:containerName
328 [containerRequest setRequestUserFromDefaultTo:sharingAccount];
329 return containerRequest;
332 + (ASIPithosContainerRequest *)updateContainerDataRequestWithContainerName:(NSString *)containerName
333 blockSize:(NSUInteger)blockSize
334 forFile:(NSString *)filePath
335 missingBlockIndex:(NSUInteger)missingBlockIndex
336 sharingAccount:(NSString *)sharingAccount {
337 NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
338 [fileHandle seekToFileOffset:(missingBlockIndex *blockSize)];
339 NSData *blockData = [fileHandle readDataOfLength:blockSize];
340 [fileHandle closeFile];
341 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithContainerName:containerName
347 [containerRequest setRequestUserFromDefaultTo:sharingAccount];
348 return containerRequest;
351 + (NSArray *)writeObjectDataRequestsWithContainerName:(NSString *)containerName
352 objectName:(NSString *)objectName
353 blockSize:(NSUInteger)blockSize
354 blockHash:(NSString *)blockHash
355 forDirectory:(NSString *)directoryPath
356 checkIfExists:(BOOL)ifExists
357 objectNames:(NSMutableArray **)objectNames
358 contentTypes:(NSMutableArray **)contentTypes
359 filePaths:(NSMutableArray **)filePaths
360 hashesArrays:(NSMutableArray **)hashesArrays
361 directoryObjectRequests:(NSMutableArray **) directoryObjectRequests
362 sharingAccount:(NSString *)sharingAccount {
363 if (ifExists && ![self proceedIfObjectExistsAtContainerName:containerName objectName:objectName sharingAccount:sharingAccount])
366 NSFileManager *fileManager = [NSFileManager defaultManager];
367 NSError *error = nil;
368 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:directoryPath error:&error];
370 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
371 [alert setMessageText:@"Directory Read Error"];
372 [alert setInformativeText:[NSString stringWithFormat:@"Cannot read contents of directory '%@': %@",
373 [directoryPath lastPathComponent], error]];
374 [alert addButtonWithTitle:@"OK"];
379 *directoryObjectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
380 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName
381 objectName:objectName
383 contentType:@"application/directory"
385 contentDisposition:nil
388 isPublic:ASIPithosObjectRequestPublicIgnore
392 [objectRequest setRequestUserFromDefaultTo:sharingAccount];
393 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
394 [directoryPath lastPathComponent], @"fileName",
396 [*directoryObjectRequests addObject:objectRequest];
398 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
399 *objectNames = [NSMutableArray arrayWithCapacity:[subPaths count]];
400 *contentTypes = [NSMutableArray arrayWithCapacity:[subPaths count]];
401 *filePaths = [NSMutableArray arrayWithCapacity:[subPaths count]];
402 *hashesArrays = [NSMutableArray arrayWithCapacity:[subPaths count]];
404 NSString *subObjectName;
407 NSString *contentType;
410 for (NSString *objectNameSuffix in subPaths) {
411 filePath = [directoryPath stringByAppendingPathComponent:objectNameSuffix];
412 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
414 hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
416 subObjectName = [objectName stringByAppendingPathComponent:objectNameSuffix];
417 fileName = [filePath lastPathComponent];
418 if ([filePath hasSuffix:@"/"])
419 fileName = [fileName stringByAppendingString:@"/"];
420 bytes = [self bytesOfFile:filePath];
422 contentType = [self contentTypeOfFile:filePath error:&error];
423 if (contentType == nil)
424 contentType = @"application/octet-stream";
426 NSLog(@"contentType detection error: %@", error);
427 objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName
428 objectName:subObjectName
429 contentType:contentType
431 contentDisposition:nil
434 isPublic:ASIPithosObjectRequestPublicIgnore
441 [objectRequest setRequestUserFromDefaultTo:sharingAccount];
442 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
443 fileName, @"fileName",
444 [NSNumber numberWithUnsignedInteger:bytes], @"bytes",
446 [objectRequests addObject:objectRequest];
447 [*objectNames addObject:subObjectName];
448 [*contentTypes addObject:contentType];
449 [*filePaths addObject:filePath];
450 [*hashesArrays addObject:hashes];
454 subObjectName = [objectName stringByAppendingPathComponent:objectNameSuffix];
455 fileName = [filePath lastPathComponent];
456 objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName
457 objectName:subObjectName
459 contentType:@"application/directory"
461 contentDisposition:nil
464 isPublic:ASIPithosObjectRequestPublicIgnore
468 [objectRequest setRequestUserFromDefaultTo:sharingAccount];
469 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
470 fileName, @"fileName",
472 [*directoryObjectRequests addObject:objectRequest];
477 return objectRequests;
483 + (NSArray *)deleteObjectRequestsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName {
484 NSArray *objects = [self objectsForSubdirWithContainerName:containerName objectName:objectName delimiter:nil sharingAccount:nil];
488 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
489 ASIPithosObjectRequest *objectRequest;
490 if (![objectName hasSuffix:@"/"]) {
491 objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:containerName objectName:objectName];
492 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
493 [objectName lastPathComponent], @"fileName",
495 [objectRequests addObject:objectRequest];
498 for (ASIPithosObject *object in objects) {
499 fileName = [object.name lastPathComponent];
500 if ([object.name hasSuffix:@"/"])
501 fileName = [fileName stringByAppendingString:@"/"];
502 objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:containerName objectName:object.name];
503 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
504 fileName, @"fileName",
506 [objectRequests addObject:objectRequest];
509 if ([objectRequests count] == 0)
511 return objectRequests;
517 + (ASIPithosObjectRequest *)copyObjectRequestWithContainerName:(NSString *)containerName
518 objectName:(NSString *)objectName
519 destinationContainerName:(NSString *)destinationContainerName
520 destinationObjectName:(NSString *)destinationObjectName
521 checkIfExists:(BOOL)ifExists
522 sharingAccount:(NSString *)sharingAccount {
523 if (ifExists && ![self proceedIfObjectExistsAtContainerName:destinationContainerName objectName:destinationObjectName sharingAccount:nil])
526 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithContainerName:containerName
527 objectName:objectName
530 contentDisposition:nil
533 isPublic:ASIPithosObjectRequestPublicIgnore
535 destinationContainerName:destinationContainerName
536 destinationObjectName:destinationObjectName
537 destinationAccount:nil
539 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
540 containerName, @"sourceContainerName",
541 objectName, @"sourceObjectName",
542 destinationContainerName, @"destinationContainerName",
543 destinationObjectName, @"destinationObjectName",
546 [objectRequest setRequestUserFromDefaultTo:sharingAccount];
547 return objectRequest;
550 + (NSArray *)copyObjectRequestsForSubdirWithContainerName:(NSString *)containerName
551 objectName:(NSString *)objectName
552 destinationContainerName:(NSString *)destinationContainerName
553 destinationObjectName:(NSString *)destinationObjectName
554 checkIfExists:(BOOL)ifExists
555 sharingAccount:(NSString *)sharingAccount {
556 if (ifExists && ![self proceedIfObjectExistsAtContainerName:destinationContainerName objectName:destinationObjectName sharingAccount:nil])
559 NSArray *objects = [self objectsForSubdirWithContainerName:containerName objectName:objectName
560 delimiter:nil sharingAccount:sharingAccount];
564 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
565 ASIPithosObjectRequest *objectRequest;
566 if ([objectName isEqualToString:destinationObjectName]) {
567 if (![objectName hasSuffix:@"/"]) {
568 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithContainerName:containerName
569 objectName:objectName
572 contentDisposition:nil
575 isPublic:ASIPithosObjectRequestPublicIgnore
577 destinationContainerName:destinationContainerName
578 destinationObjectName:objectName
579 destinationAccount:nil
581 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
582 containerName, @"sourceContainerName",
583 objectName, @"sourceObjectName",
584 destinationContainerName, @"destinationContainerName",
585 objectName, @"destinationObjectName",
588 [objectRequest setRequestUserFromDefaultTo:sharingAccount];
589 [objectRequests addObject:objectRequest];
591 for (ASIPithosObject *object in objects) {
592 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithContainerName:containerName
593 objectName:object.name
596 contentDisposition:nil
599 isPublic:ASIPithosObjectRequestPublicIgnore
601 destinationContainerName:destinationContainerName
602 destinationObjectName:object.name
603 destinationAccount:nil
605 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
606 containerName, @"sourceContainerName",
607 object.name, @"sourceObjectName",
608 destinationContainerName, @"destinationContainerName",
609 object.name, @"destinationObjectName",
612 [objectRequest setRequestUserFromDefaultTo:sharingAccount];
613 [objectRequests addObject:objectRequest];
616 if (![objectName hasSuffix:@"/"]) {
617 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithContainerName:containerName
618 objectName:objectName
621 contentDisposition:nil
624 isPublic:ASIPithosObjectRequestPublicIgnore
626 destinationContainerName:destinationContainerName
627 destinationObjectName:destinationObjectName
628 destinationAccount:nil
630 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
631 containerName, @"sourceContainerName",
632 objectName, @"sourceObjectName",
633 destinationContainerName, @"destinationContainerName",
634 destinationObjectName, @"destinationObjectName",
637 [objectRequest setRequestUserFromDefaultTo:sharingAccount];
638 [objectRequests addObject:objectRequest];
640 NSRange prefixRange = NSMakeRange(0, [objectName length]);
641 NSString *newObjectName;
642 for (ASIPithosObject *object in objects) {
643 newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
644 withString:destinationObjectName
645 options:NSAnchoredSearch
647 objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithContainerName:containerName
648 objectName:object.name
651 contentDisposition:nil
654 isPublic:ASIPithosObjectRequestPublicIgnore
656 destinationContainerName:destinationContainerName
657 destinationObjectName:newObjectName
658 destinationAccount:nil
660 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
661 containerName, @"sourceContainerName",
662 object.name, @"sourceObjectName",
663 destinationContainerName, @"destinationContainerName",
664 newObjectName, @"destinationObjectName",
667 [objectRequest setRequestUserFromDefaultTo:sharingAccount];
668 [objectRequests addObject:objectRequest];
672 if ([objectRequests count] == 0)
674 return objectRequests;
680 + (ASIPithosObjectRequest *)moveObjectRequestWithContainerName:(NSString *)containerName
681 objectName:(NSString *)objectName
682 destinationContainerName:(NSString *)destinationContainerName
683 destinationObjectName:(NSString *)destinationObjectName
684 checkIfExists:(BOOL)ifExists {
685 if (ifExists && ![self proceedIfObjectExistsAtContainerName:destinationContainerName objectName:destinationObjectName sharingAccount:nil])
688 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:containerName
689 objectName:objectName
692 contentDisposition:nil
695 isPublic:ASIPithosObjectRequestPublicIgnore
697 destinationContainerName:destinationContainerName
698 destinationObjectName:destinationObjectName
699 destinationAccount:nil];
700 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
701 containerName, @"sourceContainerName",
702 objectName, @"sourceObjectName",
703 destinationContainerName, @"destinationContainerName",
704 destinationObjectName, @"destinationObjectName",
706 return objectRequest;
709 + (NSArray *)moveObjectRequestsForSubdirWithContainerName:(NSString *)containerName
710 objectName:(NSString *)objectName
711 destinationContainerName:(NSString *)destinationContainerName
712 destinationObjectName:(NSString *)destinationObjectName
713 checkIfExists:(BOOL)ifExists {
714 if (ifExists && ![self proceedIfObjectExistsAtContainerName:destinationContainerName objectName:destinationObjectName sharingAccount:nil])
717 NSArray *objects = [self objectsForSubdirWithContainerName:containerName objectName:objectName delimiter:nil sharingAccount:nil];
721 ASIPithosObjectRequest *objectRequest;
722 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)];
723 if ([objectName isEqualToString:destinationObjectName]) {
724 if (![objectName hasSuffix:@"/"]) {
725 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:containerName
726 objectName:objectName
729 contentDisposition:nil
732 isPublic:ASIPithosObjectRequestPublicIgnore
734 destinationContainerName:destinationContainerName
735 destinationObjectName:objectName
736 destinationAccount:nil];
737 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
738 containerName, @"sourceContainerName",
739 objectName, @"sourceObjectName",
740 destinationContainerName, @"destinationContainerName",
741 objectName, @"destinationObjectName",
743 [objectRequests addObject:objectRequest];
745 for (ASIPithosObject *object in objects) {
746 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:containerName
747 objectName:object.name
750 contentDisposition:nil
753 isPublic:ASIPithosObjectRequestPublicIgnore
755 destinationContainerName:destinationContainerName
756 destinationObjectName:object.name
757 destinationAccount:nil];
758 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
759 containerName, @"sourceContainerName",
760 object.name, @"sourceObjectName",
761 destinationContainerName, @"destinationContainerName",
762 object.name, @"destinationObjectName",
764 [objectRequests addObject:objectRequest];
767 if (![objectName hasSuffix:@"/"]) {
768 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:containerName
769 objectName:objectName
772 contentDisposition:nil
775 isPublic:ASIPithosObjectRequestPublicIgnore
777 destinationContainerName:destinationContainerName
778 destinationObjectName:destinationObjectName
779 destinationAccount:nil];
780 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
781 containerName, @"sourceContainerName",
782 objectName, @"sourceObjectName",
783 destinationContainerName, @"destinationContainerName",
784 destinationObjectName, @"destinationObjectName",
786 [objectRequests addObject:objectRequest];
788 NSRange prefixRange = NSMakeRange(0, [objectName length]);
789 NSString *newObjectName;
790 for (ASIPithosObject *object in objects) {
791 newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName
792 withString:destinationObjectName
793 options:NSAnchoredSearch
795 objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:containerName
796 objectName:object.name
799 contentDisposition:nil
802 isPublic:ASIPithosObjectRequestPublicIgnore
804 destinationContainerName:destinationContainerName
805 destinationObjectName:newObjectName
806 destinationAccount:nil];
807 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
808 containerName, @"sourceContainerName",
809 object.name, @"sourceObjectName",
810 destinationContainerName, @"destinationContainerName",
811 newObjectName, @"destinationObjectName",
813 [objectRequests addObject:objectRequest];
817 if ([objectRequests count] == 0)
819 return objectRequests;
823 #pragma mark Helper Methods
825 // Size of the file in bytes
826 + (NSUInteger)bytesOfFile:(NSString *)filePath {
827 NSFileManager *fileManager = [NSFileManager defaultManager];
828 NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil];
829 return [[attributes objectForKey:NSFileSize] intValue];
832 // Content type of the file or nil if it cannot be determined
833 + (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error {
834 NSURLResponse *response = nil;
835 [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:filePath]
836 cachePolicy:NSURLCacheStorageNotAllowed
838 returningResponse:&response
840 return [response MIMEType];
843 // Creates a directory if it doesn't exist and returns if successful
844 + (BOOL)safeCreateDirectory:(NSString *)directoryPath error:(NSError **)error {
845 NSFileManager *fileManager = [NSFileManager defaultManager];
847 BOOL fileExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory];
850 if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:error] || *error)
855 // Returns if an object is a directory based on its content type
856 + (BOOL)isContentTypeDirectory:(NSString *)contentType {
857 return ([contentType isEqualToString:@"application/directory"] ||
858 [contentType hasPrefix:@"application/directory;"] ||
859 [contentType isEqualToString:@"application/folder"] ||
860 [contentType hasPrefix:@"application/folder;"]);
863 // Returns if an object exists at the given container/object path and if this object is an application/directory
864 // If an error occured an alert is shown and it is returned so the caller won't proceed
865 + (BOOL)objectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName
866 error:(NSError **)error isDirectory:(BOOL *)isDirectory
867 sharingAccount:(NSString *)sharingAccount {
868 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithContainerName:containerName
869 objectName:objectName];
871 [objectRequest setRequestUserFromDefaultTo:sharingAccount];
872 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
873 while (![objectRequest isFinished]) {
876 *error = [objectRequest error];
878 [self httpRequestErrorAlertWithRequest:objectRequest];
880 } else if (objectRequest.responseStatusCode == 200) {
881 *isDirectory = [self isContentTypeDirectory:[objectRequest contentType]];
887 // Returns if the caller should proceed, after an interactive check if an object exists
888 // at the given container/object path is performed
889 + (BOOL)proceedIfObjectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName
890 sharingAccount:(NSString *)sharingAccount {
891 NSError *error = nil;
893 BOOL objectExists = [self objectExistsAtContainerName:containerName
894 objectName:objectName
896 isDirectory:&isDirectory
897 sharingAccount:sharingAccount];
900 } else if (objectExists) {
901 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
903 [alert setMessageText:@"Directory Exists"];
905 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
907 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
909 [alert setMessageText:@"Object Exists"];
911 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
913 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
915 [alert addButtonWithTitle:@"OK"];
916 [alert addButtonWithTitle:@"Cancel"];
917 NSInteger choice = [alert runModal];
918 if (choice == NSAlertSecondButtonReturn)
924 // List of objects at the given container/object path, with prefix and or delimiter
925 + (NSArray *)objectsWithContainerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix
926 delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
927 NSMutableArray *objects = [NSMutableArray array];
928 NSString *marker = nil;
930 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName
933 prefix:objectNamePrefix
940 [containerRequest setRequestUserFromDefaultTo:sharingAccount];
941 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
942 while (![containerRequest isFinished]) {
945 if ([containerRequest error]) {
946 [self httpRequestErrorAlertWithRequest:containerRequest];
949 NSArray *someObjects = [containerRequest objects];
950 [objects addObjectsFromArray:someObjects];
951 if ([someObjects count] < 10000)
954 marker = [[someObjects lastObject] name];
959 // List of objects at the given container/object path, that may be a subdir or an application/directory,
960 // with prefix and or delimiter
961 + (NSArray *)objectsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName
962 delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
963 NSString *subdirNamePrefix = [NSString stringWithString:objectName];
964 if (![subdirNamePrefix hasSuffix:@"/"])
965 subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"];
966 return [self objectsWithContainerName:containerName objectNamePrefix:subdirNamePrefix
967 delimiter:delimiter sharingAccount:sharingAccount];
970 // A safe object name at the given container/object path
971 // The original name has " %d" appended to it before any ".*" suffix, for the first integer that produces a name that is free to use
972 // If the original name hasn't got a "." but has a "/" suffix, then it is replaced with " %d/" instead
973 // Subdirs are taken into consideration
974 + (NSString *)safeObjectNameForContainerName:(NSString *)containerName objectName:(NSString *)objectName {
975 NSString *objectNamePrefix;
976 NSString *objectNameExtraSuffix;
977 NSRange lastDotRange = [objectName rangeOfString:@"." options:NSBackwardsSearch];
978 if (lastDotRange.length == 1) {
979 objectNamePrefix = [objectName substringToIndex:lastDotRange.location];
980 objectNameExtraSuffix = [objectName substringFromIndex:lastDotRange.location];
981 } else if ([objectName hasSuffix:@"/"]) {
982 objectNamePrefix = [objectName substringToIndex:([objectName length] - 1)];
983 objectNameExtraSuffix = [NSString stringWithString:@"/"];
985 objectNamePrefix = [NSString stringWithString:objectName];
986 objectNameExtraSuffix = [NSString string];
988 NSArray *objects = [self objectsWithContainerName:containerName
989 objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent]
994 if ([objects count] == 0)
996 NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
997 [[objects objectsAtIndexes:
998 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
999 if (pithosObject.subdir)
1002 }]] valueForKey:@"name"]];
1003 for (NSString *name in [[objects objectsAtIndexes:
1004 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1005 if (pithosObject.subdir)
1008 }]] valueForKey:@"name"]) {
1009 [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1011 if (![objectNames containsObject:objectName])
1013 NSUInteger objectNameSuffix = 2;
1014 NSString *safeObjectName;
1016 safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix];
1018 } while ([objectNames containsObject:safeObjectName]);
1019 return safeObjectName;
1022 // A safe object name at the given container/object path that may be a subdir or application/directory
1023 // The original name has " %d" appended to it, for the first integer that produces a name that is free to use
1024 // If the original name has a "/" suffix, then it is replaced with " %d/" instead
1025 // Subdirs are taken into consideration
1026 + (NSString *)safeSubdirNameForContainerName:(NSString *)containerName subdirName:(NSString *)subdirName {
1027 NSString *subdirNamePrefix;
1028 NSString *subdirNameExtraSuffix;
1029 if ([subdirName hasSuffix:@"/"]) {
1030 subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)];
1031 subdirNameExtraSuffix = [NSString stringWithString:@"/"];
1033 subdirNamePrefix = [NSString stringWithString:subdirName];
1034 subdirNameExtraSuffix = [NSString string];
1036 NSArray *objects = [self objectsWithContainerName:containerName
1037 objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent]
1039 sharingAccount:nil];
1042 if ([objects count] == 0)
1044 NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1045 [[objects objectsAtIndexes:
1046 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1047 if (pithosObject.subdir)
1050 }]] valueForKey:@"name"]];
1051 for (NSString *name in [[objects objectsAtIndexes:
1052 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1053 if (pithosObject.subdir)
1056 }]] valueForKey:@"name"]) {
1057 [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1059 if (![objectNames containsObject:subdirNamePrefix])
1061 NSUInteger subdirNameSuffix = 2;
1062 NSString *safeSubdirName;
1064 safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix];
1066 } while ([objectNames containsObject:safeSubdirName]);
1067 return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix];
1073 + (NSInteger)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
1074 NSString *message = [NSString stringWithFormat:
1075 @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@",
1077 request.requestMethod,
1079 [request requestHeaders],
1080 [request responseHeaders],
1081 [request responseString]];
1082 NSLog(@"%@", message);
1083 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1084 [alert setMessageText:@"HTTP Request Error"];
1085 [alert setInformativeText:message];
1086 [alert addButtonWithTitle:@"OK"];
1087 return [alert runModal];
1090 + (NSInteger)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
1091 NSString *message = [NSString stringWithFormat:
1092 @"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@",
1093 request.responseStatusCode,
1094 request.responseStatusMessage,
1095 request.requestMethod,
1097 [request requestHeaders],
1098 [request responseHeaders],
1099 [request responseString]];
1100 NSLog(@"%@", message);
1101 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1102 [alert setMessageText:@"Unexpected Response Status"];
1103 [alert setInformativeText:message];
1104 [alert addButtonWithTitle:@"OK"];
1105 return [alert runModal];
1108 + (NSInteger)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error {
1109 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1110 [alert setMessageText:title];
1112 [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, error]];
1114 [alert setInformativeText:message];
1115 [alert addButtonWithTitle:@"OK"];
1116 return [alert runModal];
1120 #pragma mark Request Helper Methods
1122 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority {
1123 [request setTimeOutSeconds:60];
1124 request.numberOfTimesToRetryOnTimeout = 10;
1125 [request setQueuePriority:priority];
1129 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request {
1130 return [self prepareRequest:request priority:NSOperationQueuePriorityNormal];
1133 + (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request {
1134 NSMutableDictionary *userInfo = [[request.userInfo retain] autorelease];
1135 request.userInfo = nil;
1136 ASIPithosRequest *newRequest = [request copy];
1137 newRequest.userInfo = userInfo;