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 // Returns if an object is a directory based on its content type
844 + (BOOL)isContentTypeDirectory:(NSString *)contentType {
845 return ([contentType isEqualToString:@"application/directory"] ||
846 [contentType hasPrefix:@"application/directory;"] ||
847 [contentType isEqualToString:@"application/folder"] ||
848 [contentType hasPrefix:@"application/folder;"]);
851 // Returns if an object exists at the given container/object path and if this object is an application/directory
852 // If an error occured an alert is shown and it is returned so the caller won't proceed
853 + (BOOL)objectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName
854 error:(NSError **)error isDirectory:(BOOL *)isDirectory
855 sharingAccount:(NSString *)sharingAccount {
856 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithContainerName:containerName
857 objectName:objectName];
859 [objectRequest setRequestUserFromDefaultTo:sharingAccount];
860 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
861 while (![objectRequest isFinished]) {
864 *error = [objectRequest error];
866 [self httpRequestErrorAlertWithRequest:objectRequest];
868 } else if (objectRequest.responseStatusCode == 200) {
869 *isDirectory = [self isContentTypeDirectory:[objectRequest contentType]];
875 // Returns if the caller should proceed, after an interactive check if an object exists
876 // at the given container/object path is performed
877 + (BOOL)proceedIfObjectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName
878 sharingAccount:(NSString *)sharingAccount {
879 NSError *error = nil;
881 BOOL objectExists = [self objectExistsAtContainerName:containerName
882 objectName:objectName
884 isDirectory:&isDirectory
885 sharingAccount:sharingAccount];
888 } else if (objectExists) {
889 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
891 [alert setMessageText:@"Directory Exists"];
893 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
895 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
897 [alert setMessageText:@"Object Exists"];
899 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]];
901 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
903 [alert addButtonWithTitle:@"OK"];
904 [alert addButtonWithTitle:@"Cancel"];
905 NSInteger choice = [alert runModal];
906 if (choice == NSAlertSecondButtonReturn)
912 // List of objects at the given container/object path, with prefix and or delimiter
913 + (NSArray *)objectsWithContainerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix
914 delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
915 NSMutableArray *objects = [NSMutableArray array];
916 NSString *marker = nil;
918 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName
921 prefix:objectNamePrefix
928 [containerRequest setRequestUserFromDefaultTo:sharingAccount];
929 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
930 while (![containerRequest isFinished]) {
933 if ([containerRequest error]) {
934 [self httpRequestErrorAlertWithRequest:containerRequest];
937 NSArray *someObjects = [containerRequest objects];
938 [objects addObjectsFromArray:someObjects];
939 if ([someObjects count] < 10000)
942 marker = [[someObjects lastObject] name];
947 // List of objects at the given container/object path, that may be a subdir or an application/directory,
948 // with prefix and or delimiter
949 + (NSArray *)objectsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName
950 delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount {
951 NSString *subdirNamePrefix = [NSString stringWithString:objectName];
952 if (![subdirNamePrefix hasSuffix:@"/"])
953 subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"];
954 return [self objectsWithContainerName:containerName objectNamePrefix:subdirNamePrefix
955 delimiter:delimiter sharingAccount:sharingAccount];
958 // A safe object name at the given container/object path
959 // The original name has " %d" appended to it before any ".*" suffix, for the first integer that produces a name that is free to use
960 // If the original name hasn't got a "." but has a "/" suffix, then it is replaced with " %d/" instead
961 // Subdirs are taken into consideration
962 + (NSString *)safeObjectNameForContainerName:(NSString *)containerName objectName:(NSString *)objectName {
963 NSString *objectNamePrefix;
964 NSString *objectNameExtraSuffix;
965 NSRange lastDotRange = [objectName rangeOfString:@"." options:NSBackwardsSearch];
966 if (lastDotRange.length == 1) {
967 objectNamePrefix = [objectName substringToIndex:lastDotRange.location];
968 objectNameExtraSuffix = [objectName substringFromIndex:lastDotRange.location];
969 } else if ([objectName hasSuffix:@"/"]) {
970 objectNamePrefix = [objectName substringToIndex:([objectName length] - 1)];
971 objectNameExtraSuffix = [NSString stringWithString:@"/"];
973 objectNamePrefix = [NSString stringWithString:objectName];
974 objectNameExtraSuffix = [NSString string];
976 NSArray *objects = [self objectsWithContainerName:containerName
977 objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent]
982 if ([objects count] == 0)
984 NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
985 [[objects objectsAtIndexes:
986 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
987 if (pithosObject.subdir)
990 }]] valueForKey:@"name"]];
991 for (NSString *name in [[objects objectsAtIndexes:
992 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
993 if (pithosObject.subdir)
996 }]] valueForKey:@"name"]) {
997 [objectNames addObject:[name substringToIndex:([name length] - 1)]];
999 if (![objectNames containsObject:objectName])
1001 NSUInteger objectNameSuffix = 2;
1002 NSString *safeObjectName;
1004 safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix];
1006 } while ([objectNames containsObject:safeObjectName]);
1007 return safeObjectName;
1010 // A safe object name at the given container/object path that may be a subdir or application/directory
1011 // The original name has " %d" appended to it, for the first integer that produces a name that is free to use
1012 // If the original name has a "/" suffix, then it is replaced with " %d/" instead
1013 // Subdirs are taken into consideration
1014 + (NSString *)safeSubdirNameForContainerName:(NSString *)containerName subdirName:(NSString *)subdirName {
1015 NSString *subdirNamePrefix;
1016 NSString *subdirNameExtraSuffix;
1017 if ([subdirName hasSuffix:@"/"]) {
1018 subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)];
1019 subdirNameExtraSuffix = [NSString stringWithString:@"/"];
1021 subdirNamePrefix = [NSString stringWithString:subdirName];
1022 subdirNameExtraSuffix = [NSString string];
1024 NSArray *objects = [self objectsWithContainerName:containerName
1025 objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent]
1027 sharingAccount:nil];
1030 if ([objects count] == 0)
1032 NSMutableArray *objectNames = [NSMutableArray arrayWithArray:
1033 [[objects objectsAtIndexes:
1034 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1035 if (pithosObject.subdir)
1038 }]] valueForKey:@"name"]];
1039 for (NSString *name in [[objects objectsAtIndexes:
1040 [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){
1041 if (pithosObject.subdir)
1044 }]] valueForKey:@"name"]) {
1045 [objectNames addObject:[name substringToIndex:([name length] - 1)]];
1047 if (![objectNames containsObject:subdirNamePrefix])
1049 NSUInteger subdirNameSuffix = 2;
1050 NSString *safeSubdirName;
1052 safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix];
1054 } while ([objectNames containsObject:safeSubdirName]);
1055 return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix];
1061 + (NSInteger)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
1062 NSString *message = [NSString stringWithFormat:
1063 @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@",
1065 request.requestMethod,
1067 [request requestHeaders],
1068 [request responseHeaders],
1069 [request responseString]];
1070 NSLog(@"%@", message);
1071 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1072 [alert setMessageText:@"HTTP Request Error"];
1073 [alert setInformativeText:message];
1074 [alert addButtonWithTitle:@"OK"];
1075 return [alert runModal];
1078 + (NSInteger)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
1079 NSString *message = [NSString stringWithFormat:
1080 @"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@",
1081 request.responseStatusCode,
1082 request.responseStatusMessage,
1083 request.requestMethod,
1085 [request requestHeaders],
1086 [request responseHeaders],
1087 [request responseString]];
1088 NSLog(@"%@", message);
1089 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1090 [alert setMessageText:@"Unexpected Response Status"];
1091 [alert setInformativeText:message];
1092 [alert addButtonWithTitle:@"OK"];
1093 return [alert runModal];
1096 + (NSInteger)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error {
1097 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1098 [alert setMessageText:title];
1100 [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, error]];
1102 [alert setInformativeText:message];
1103 [alert addButtonWithTitle:@"OK"];
1104 return [alert runModal];
1108 #pragma mark Request Helper Methods
1110 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority {
1111 [request setTimeOutSeconds:60];
1112 request.numberOfTimesToRetryOnTimeout = 10;
1113 [request setQueuePriority:priority];
1117 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request {
1118 return [self prepareRequest:request priority:NSOperationQueuePriorityNormal];
1121 + (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request {
1122 NSMutableDictionary *userInfo = [[request.userInfo retain] autorelease];
1123 request.userInfo = nil;
1124 ASIPithosRequest *newRequest = [request copy];
1125 newRequest.userInfo = userInfo;