// // PithosUtilities.m // pithos-macos // // Copyright 2011 GRNET S.A. All rights reserved. // // Redistribution and use in source and binary forms, with or // without modification, are permitted provided that the following // conditions are met: // // 1. Redistributions of source code must retain the above // copyright notice, this list of conditions and the following // disclaimer. // // 2. Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials // provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // The views and conclusions contained in the software and // documentation are those of the authors and should not be // interpreted as representing official policies, either expressed // or implied, of GRNET S.A. #import "PithosUtilities.h" #import "ASIPithosContainerRequest.h" #import "ASIPithosObjectRequest.h" #import "ASIPithosObject.h" #import "HashMapHash.h" @implementation PithosUtilities #pragma mark - #pragma mark Download + (ASIPithosObjectRequest *)objectDataRequestWithContainerName:(NSString *)containerName objectName:(NSString *)objectName toDirectory:(NSString *)directoryPath checkIfExists:(BOOL)ifExists sharingAccount:(NSString *)sharingAccount { NSString *fileName = [objectName lastPathComponent]; if([objectName hasSuffix:@"/"]) fileName = [fileName stringByAppendingString:@"/"]; fileName = [fileName stringByReplacingOccurrencesOfString:@"/" withString:@":"]; NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *destinationPath = [directoryPath stringByAppendingPathComponent:fileName]; if (ifExists && [fileManager fileExistsAtPath:destinationPath]) { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"File Exists"]; [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", fileName]]; [alert addButtonWithTitle:@"OK"]; [alert addButtonWithTitle:@"Cancel"]; NSInteger choice = [alert runModal]; if (choice == NSAlertSecondButtonReturn) return nil; } BOOL directoryIsDirectory; BOOL directoryExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&directoryIsDirectory]; NSError *error = nil; if (!directoryExists) { [fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error]; } else if (!directoryIsDirectory) { [fileManager removeItemAtPath:directoryPath error:&error]; } if (error) { NSLog(@"Cannot remove existing file '%@': %@", fileName, error); NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Removal Error"]; [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file '%@': %@", fileName, error]]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; return nil; } ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithContainerName:containerName objectName:objectName]; if (sharingAccount) [objectRequest setRequestUserFromDefaultTo:sharingAccount]; objectRequest.downloadDestinationPath = destinationPath; objectRequest.allowResumeForFileDownloads = YES; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: fileName, @"fileName", destinationPath, @"filePath", nil]; return objectRequest; } + (NSArray *)objectDataRequestsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName toDirectory:(NSString *)directoryPath checkIfExists:(BOOL)ifExists sharingAccount:(NSString *)sharingAccount { NSString *subdirName = [objectName lastPathComponent]; NSString *destinationPath = [directoryPath stringByAppendingPathComponent:subdirName]; if (ifExists && [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"File exists"]; [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", subdirName]]; [alert addButtonWithTitle:@"OK"]; [alert addButtonWithTitle:@"Cancel"]; NSInteger choice = [alert runModal]; if (choice == NSAlertSecondButtonReturn) return nil; } NSArray *objects = [self objectsForSubdirWithContainerName:containerName objectName:objectName delimiter:nil sharingAccount:sharingAccount]; if (objects == nil) return nil; NSFileManager *fileManager = [NSFileManager defaultManager]; NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[objects count]]; NSUInteger subdirPrefixLength = [objectName length]; NSError *error = nil; [fileManager createDirectoryAtPath:[directoryPath stringByAppendingPathComponent:subdirName] withIntermediateDirectories:YES attributes:nil error:&error]; if (error) { NSLog(@"Cannot create directory at '%@': %@", directoryPath, error); NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Create Directory Error"]; [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@", directoryPath, error]]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; } for (ASIPithosObject *object in objects) { if ([PithosUtilities isContentTypeDirectory:object.contentType]) { NSString *subdirDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName]; subdirDirectoryPath = [subdirDirectoryPath stringByAppendingPathComponent:[object.name substringFromIndex:subdirPrefixLength]]; BOOL directoryIsDirectory; BOOL directoryExists = [fileManager fileExistsAtPath:subdirDirectoryPath isDirectory:&directoryIsDirectory]; NSError *error = nil; if (!directoryExists) { [fileManager createDirectoryAtPath:subdirDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error]; if (error) { NSLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error); NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Create Directory Error"]; [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@", subdirDirectoryPath, error]]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; } } else if (!directoryIsDirectory) { [fileManager removeItemAtPath:subdirDirectoryPath error:&error]; if (error) { NSLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error); NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Remove File Error"]; [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error]]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; } } } else { NSString *fileName = [object.name lastPathComponent]; if([object.name hasSuffix:@"/"]) fileName = [fileName stringByAppendingString:@"/"]; NSString *objectDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName]; objectDirectoryPath = [objectDirectoryPath stringByAppendingPathComponent:[object.name substringWithRange:NSMakeRange(subdirPrefixLength, [object.name length] - subdirPrefixLength - [fileName length])]]; ASIPithosObjectRequest *objectRequest = [self objectDataRequestWithContainerName:containerName objectName:object.name toDirectory:objectDirectoryPath checkIfExists:NO sharingAccount:sharingAccount]; [(NSMutableDictionary *)objectRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:object.bytes] forKey:@"bytes"]; [objectRequests addObject:objectRequest]; } } return objectRequests; } #pragma mark - #pragma mark Download Block + (ASIPithosObjectRequest *)objectBlockDataRequestWithContainerName:(NSString *)containerName object:(ASIPithosObject *)object blockIndex:(NSUInteger)blockIndex blockSize:(NSUInteger)blockSize { NSUInteger rangeStart = blockIndex * blockSize; NSUInteger rangeEnd = (rangeStart + blockSize <= object.bytes) ? (rangeStart + blockSize - 1) : (object.bytes - 1); ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithContainerName:containerName objectName:object.name version:nil range:[NSString stringWithFormat:@"bytes=%lu-%lu", rangeStart, rangeEnd] ifMatch:object.hash]; return objectRequest; } + (NSIndexSet *)missingBlocksForFile:(NSString *)filePath blockSize:(NSUInteger)blockSize blockHash:(NSString *)blockHash withHashes:(NSArray *)hashes { NSArray *fileHashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize]; return [hashes indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) { if ((idx >= [fileHashes count]) || ![(NSString *)obj isEqualToString:[fileHashes objectAtIndex:idx]]) return YES; return NO; }]; } #pragma mark - #pragma mark Upload + (ASIPithosObjectRequest *)writeObjectDataRequestWithContainerName:(NSString *)containerName objectName:(NSString *)objectName contentType:(NSString *)contentType blockSize:(NSUInteger)blockSize blockHash:(NSString *)blockHash forFile:(NSString *)filePath checkIfExists:(BOOL)ifExists hashes:(NSArray **)hashes sharingAccount:(NSString *)sharingAccount { if (ifExists && ![self proceedIfObjectExistsAtContainerName:containerName objectName:objectName sharingAccount:(NSString *)sharingAccount]) return nil; if (*hashes == nil) *hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize]; if (*hashes == nil) return nil; NSString *fileName = [filePath lastPathComponent]; if ([filePath hasSuffix:@"/"]) fileName = [fileName stringByAppendingString:@"/"]; NSUInteger bytes = [self bytesOfFile:filePath]; ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName objectName:objectName contentType:contentType contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil blockSize:blockSize blockHash:blockHash hashes:*hashes bytes:bytes]; if (sharingAccount) [objectRequest setRequestUserFromDefaultTo:sharingAccount]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: fileName, @"fileName", [NSNumber numberWithUnsignedInteger:bytes], @"bytes", nil]; return objectRequest; } + (NSIndexSet *)missingBlocksForHashes:(NSArray *)hashes withMissingHashesResponse:(NSString *)missingHashesResponse { NSArray *responseLines = [missingHashesResponse componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; NSMutableIndexSet *missingBlocks = [NSMutableIndexSet indexSet]; for (NSString *line in responseLines) { if (![line length]) break; NSUInteger missingBlock = [hashes indexOfObject:line]; if (missingBlock != NSNotFound) [missingBlocks addIndex:missingBlock]; } return missingBlocks; } + (ASIPithosContainerRequest *)updateContainerDataRequestWithContainerName:(NSString *)containerName blockSize:(NSUInteger)blockSize forFile:(NSString *)filePath hashes:(NSArray *)hashes missingHashesResponse:(NSString *)missingHashesResponse sharingAccount:(NSString *)sharingAccount { NSIndexSet *missingBlocks = [self missingBlocksForHashes:hashes withMissingHashesResponse:missingHashesResponse]; NSFileManager *fileManager = [NSFileManager defaultManager]; NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath]; // http://cocoawithlove.com/2009/07/temporary-files-and-folders-in-cocoa.html NSString *tempFileTemplate = NSTemporaryDirectory(); if (tempFileTemplate == nil) tempFileTemplate = @"/tmp"; tempFileTemplate = [tempFileTemplate stringByAppendingPathComponent:@"pithos-macos.XXXXXX"]; const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation]; char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1); strcpy(tempFileNameCString, tempFileTemplateCString); int fileDescriptor = mkstemp(tempFileNameCString); NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)]; if (fileDescriptor == -1) { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Create Temporary File Error"]; [alert setInformativeText:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; return nil; } free(tempFileNameCString); NSFileHandle *tempFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor closeOnDealloc:YES]; [missingBlocks enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){ [fileHandle seekToFileOffset:(idx*blockSize)]; [tempFileHandle writeData:[fileHandle readDataOfLength:blockSize]]; }]; [tempFileHandle closeFile]; [fileHandle closeFile]; ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithContainerName:containerName policy:nil metadata:nil update:YES file:tempFilePath]; if (sharingAccount) [containerRequest setRequestUserFromDefaultTo:sharingAccount]; return containerRequest; } + (ASIPithosContainerRequest *)updateContainerDataRequestWithContainerName:(NSString *)containerName blockSize:(NSUInteger)blockSize forFile:(NSString *)filePath missingBlockIndex:(NSUInteger)missingBlockIndex sharingAccount:(NSString *)sharingAccount { NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath]; [fileHandle seekToFileOffset:(missingBlockIndex *blockSize)]; NSData *blockData = [fileHandle readDataOfLength:blockSize]; [fileHandle closeFile]; ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithContainerName:containerName policy:nil metadata:nil update:YES data:blockData]; if (sharingAccount) [containerRequest setRequestUserFromDefaultTo:sharingAccount]; return containerRequest; } + (NSArray *)writeObjectDataRequestsWithContainerName:(NSString *)containerName objectName:(NSString *)objectName blockSize:(NSUInteger)blockSize blockHash:(NSString *)blockHash forDirectory:(NSString *)directoryPath checkIfExists:(BOOL)ifExists objectNames:(NSMutableArray **)objectNames contentTypes:(NSMutableArray **)contentTypes filePaths:(NSMutableArray **)filePaths hashesArrays:(NSMutableArray **)hashesArrays directoryObjectRequests:(NSMutableArray **) directoryObjectRequests sharingAccount:(NSString *)sharingAccount { if (ifExists && ![self proceedIfObjectExistsAtContainerName:containerName objectName:objectName sharingAccount:sharingAccount]) return nil; NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error = nil; NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:directoryPath error:&error]; if (error) { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Directory Read Error"]; [alert setInformativeText:[NSString stringWithFormat:@"Cannot read contents of directory '%@': %@", [directoryPath lastPathComponent], error]]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; return nil; } *directoryObjectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]]; ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName objectName:objectName eTag:nil contentType:@"application/directory" contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil data:[NSData data]]; if (sharingAccount) [objectRequest setRequestUserFromDefaultTo:sharingAccount]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: [directoryPath lastPathComponent], @"fileName", nil]; [*directoryObjectRequests addObject:objectRequest]; NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]]; *objectNames = [NSMutableArray arrayWithCapacity:[subPaths count]]; *contentTypes = [NSMutableArray arrayWithCapacity:[subPaths count]]; *filePaths = [NSMutableArray arrayWithCapacity:[subPaths count]]; *hashesArrays = [NSMutableArray arrayWithCapacity:[subPaths count]]; BOOL isDirectory; NSString *subObjectName; NSArray *hashes; NSUInteger bytes; NSString *contentType; NSString *filePath; NSString *fileName; for (NSString *objectNameSuffix in subPaths) { filePath = [directoryPath stringByAppendingPathComponent:objectNameSuffix]; if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) { if (!isDirectory) { hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize]; if (hashes) { subObjectName = [objectName stringByAppendingPathComponent:objectNameSuffix]; fileName = [filePath lastPathComponent]; if ([filePath hasSuffix:@"/"]) fileName = [fileName stringByAppendingString:@"/"]; bytes = [self bytesOfFile:filePath]; error = nil; contentType = [self contentTypeOfFile:filePath error:&error]; if (contentType == nil) contentType = @"application/octet-stream"; if (error) NSLog(@"contentType detection error: %@", error); objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName objectName:subObjectName contentType:contentType contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil blockSize:blockSize blockHash:blockHash hashes:hashes bytes:bytes]; if (sharingAccount) [objectRequest setRequestUserFromDefaultTo:sharingAccount]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: fileName, @"fileName", [NSNumber numberWithUnsignedInteger:bytes], @"bytes", nil]; [objectRequests addObject:objectRequest]; [*objectNames addObject:subObjectName]; [*contentTypes addObject:contentType]; [*filePaths addObject:filePath]; [*hashesArrays addObject:hashes]; } } else { subObjectName = [objectName stringByAppendingPathComponent:objectNameSuffix]; fileName = [filePath lastPathComponent]; objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName objectName:subObjectName eTag:nil contentType:@"application/directory" contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil data:[NSData data]]; if (sharingAccount) [objectRequest setRequestUserFromDefaultTo:sharingAccount]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: fileName, @"fileName", nil]; [*directoryObjectRequests addObject:objectRequest]; } } } return objectRequests; } #pragma mark - #pragma mark Delete + (NSArray *)deleteObjectRequestsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName { NSArray *objects = [self objectsForSubdirWithContainerName:containerName objectName:objectName delimiter:nil sharingAccount:nil]; if (objects == nil) return nil; NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)]; ASIPithosObjectRequest *objectRequest; if (![objectName hasSuffix:@"/"]) { objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:containerName objectName:objectName]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: [objectName lastPathComponent], @"fileName", nil]; [objectRequests addObject:objectRequest]; } NSString *fileName; for (ASIPithosObject *object in objects) { fileName = [object.name lastPathComponent]; if ([object.name hasSuffix:@"/"]) fileName = [fileName stringByAppendingString:@"/"]; objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:containerName objectName:object.name]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: fileName, @"fileName", nil]; [objectRequests addObject:objectRequest]; } if ([objectRequests count] == 0) return nil; return objectRequests; } #pragma mark - #pragma mark Copy + (ASIPithosObjectRequest *)copyObjectRequestWithContainerName:(NSString *)containerName objectName:(NSString *)objectName destinationContainerName:(NSString *)destinationContainerName destinationObjectName:(NSString *)destinationObjectName checkIfExists:(BOOL)ifExists sharingAccount:(NSString *)sharingAccount { if (ifExists && ![self proceedIfObjectExistsAtContainerName:destinationContainerName objectName:destinationObjectName sharingAccount:nil]) return nil; ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithContainerName:containerName objectName:objectName contentType:nil contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil destinationContainerName:destinationContainerName destinationObjectName:destinationObjectName destinationAccount:nil sourceVersion:nil]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: containerName, @"sourceContainerName", objectName, @"sourceObjectName", destinationContainerName, @"destinationContainerName", destinationObjectName, @"destinationObjectName", nil]; if (sharingAccount) [objectRequest setRequestUserFromDefaultTo:sharingAccount]; return objectRequest; } + (NSArray *)copyObjectRequestsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName destinationContainerName:(NSString *)destinationContainerName destinationObjectName:(NSString *)destinationObjectName checkIfExists:(BOOL)ifExists sharingAccount:(NSString *)sharingAccount { if (ifExists && ![self proceedIfObjectExistsAtContainerName:destinationContainerName objectName:destinationObjectName sharingAccount:nil]) return nil; NSArray *objects = [self objectsForSubdirWithContainerName:containerName objectName:objectName delimiter:nil sharingAccount:sharingAccount]; if (objects == nil) return nil; NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)]; ASIPithosObjectRequest *objectRequest; if ([objectName isEqualToString:destinationObjectName]) { if (![objectName hasSuffix:@"/"]) { objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithContainerName:containerName objectName:objectName contentType:nil contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil destinationContainerName:destinationContainerName destinationObjectName:objectName destinationAccount:nil sourceVersion:nil]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: containerName, @"sourceContainerName", objectName, @"sourceObjectName", destinationContainerName, @"destinationContainerName", objectName, @"destinationObjectName", nil]; if (sharingAccount) [objectRequest setRequestUserFromDefaultTo:sharingAccount]; [objectRequests addObject:objectRequest]; } for (ASIPithosObject *object in objects) { objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithContainerName:containerName objectName:object.name contentType:nil contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil destinationContainerName:destinationContainerName destinationObjectName:object.name destinationAccount:nil sourceVersion:nil]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: containerName, @"sourceContainerName", object.name, @"sourceObjectName", destinationContainerName, @"destinationContainerName", object.name, @"destinationObjectName", nil]; if (sharingAccount) [objectRequest setRequestUserFromDefaultTo:sharingAccount]; [objectRequests addObject:objectRequest]; } } else { if (![objectName hasSuffix:@"/"]) { objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithContainerName:containerName objectName:objectName contentType:nil contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil destinationContainerName:destinationContainerName destinationObjectName:destinationObjectName destinationAccount:nil sourceVersion:nil]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: containerName, @"sourceContainerName", objectName, @"sourceObjectName", destinationContainerName, @"destinationContainerName", destinationObjectName, @"destinationObjectName", nil]; if (sharingAccount) [objectRequest setRequestUserFromDefaultTo:sharingAccount]; [objectRequests addObject:objectRequest]; } NSRange prefixRange = NSMakeRange(0, [objectName length]); NSString *newObjectName; for (ASIPithosObject *object in objects) { newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName withString:destinationObjectName options:NSAnchoredSearch range:prefixRange]; objectRequest = [ASIPithosObjectRequest copyObjectDataRequestWithContainerName:containerName objectName:object.name contentType:nil contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil destinationContainerName:destinationContainerName destinationObjectName:newObjectName destinationAccount:nil sourceVersion:nil]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: containerName, @"sourceContainerName", object.name, @"sourceObjectName", destinationContainerName, @"destinationContainerName", newObjectName, @"destinationObjectName", nil]; if (sharingAccount) [objectRequest setRequestUserFromDefaultTo:sharingAccount]; [objectRequests addObject:objectRequest]; } } if ([objectRequests count] == 0) return nil; return objectRequests; } #pragma mark - #pragma mark Move + (ASIPithosObjectRequest *)moveObjectRequestWithContainerName:(NSString *)containerName objectName:(NSString *)objectName destinationContainerName:(NSString *)destinationContainerName destinationObjectName:(NSString *)destinationObjectName checkIfExists:(BOOL)ifExists { if (ifExists && ![self proceedIfObjectExistsAtContainerName:destinationContainerName objectName:destinationObjectName sharingAccount:nil]) return nil; ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:containerName objectName:objectName contentType:nil contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil destinationContainerName:destinationContainerName destinationObjectName:destinationObjectName destinationAccount:nil]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: containerName, @"sourceContainerName", objectName, @"sourceObjectName", destinationContainerName, @"destinationContainerName", destinationObjectName, @"destinationObjectName", nil]; return objectRequest; } + (NSArray *)moveObjectRequestsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName destinationContainerName:(NSString *)destinationContainerName destinationObjectName:(NSString *)destinationObjectName checkIfExists:(BOOL)ifExists { if (ifExists && ![self proceedIfObjectExistsAtContainerName:destinationContainerName objectName:destinationObjectName sharingAccount:nil]) return nil; NSArray *objects = [self objectsForSubdirWithContainerName:containerName objectName:objectName delimiter:nil sharingAccount:nil]; if (objects == nil) return nil; ASIPithosObjectRequest *objectRequest; NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)]; if ([objectName isEqualToString:destinationObjectName]) { if (![objectName hasSuffix:@"/"]) { objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:containerName objectName:objectName contentType:nil contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil destinationContainerName:destinationContainerName destinationObjectName:objectName destinationAccount:nil]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: containerName, @"sourceContainerName", objectName, @"sourceObjectName", destinationContainerName, @"destinationContainerName", objectName, @"destinationObjectName", nil]; [objectRequests addObject:objectRequest]; } for (ASIPithosObject *object in objects) { objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:containerName objectName:object.name contentType:nil contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil destinationContainerName:destinationContainerName destinationObjectName:object.name destinationAccount:nil]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: containerName, @"sourceContainerName", object.name, @"sourceObjectName", destinationContainerName, @"destinationContainerName", object.name, @"destinationObjectName", nil]; [objectRequests addObject:objectRequest]; } } else { if (![objectName hasSuffix:@"/"]) { objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:containerName objectName:objectName contentType:nil contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil destinationContainerName:destinationContainerName destinationObjectName:destinationObjectName destinationAccount:nil]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: containerName, @"sourceContainerName", objectName, @"sourceObjectName", destinationContainerName, @"destinationContainerName", destinationObjectName, @"destinationObjectName", nil]; [objectRequests addObject:objectRequest]; } NSRange prefixRange = NSMakeRange(0, [objectName length]); NSString *newObjectName; for (ASIPithosObject *object in objects) { newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName withString:destinationObjectName options:NSAnchoredSearch range:prefixRange]; objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:containerName objectName:object.name contentType:nil contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil destinationContainerName:destinationContainerName destinationObjectName:newObjectName destinationAccount:nil]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: containerName, @"sourceContainerName", object.name, @"sourceObjectName", destinationContainerName, @"destinationContainerName", newObjectName, @"destinationObjectName", nil]; [objectRequests addObject:objectRequest]; } } if ([objectRequests count] == 0) return nil; return objectRequests; } #pragma mark - #pragma mark Helper Methods // Size of the file in bytes + (NSUInteger)bytesOfFile:(NSString *)filePath { NSFileManager *fileManager = [NSFileManager defaultManager]; NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil]; return [[attributes objectForKey:NSFileSize] intValue]; } // Content type of the file or nil if it cannot be determined + (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error { NSURLResponse *response = nil; [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:filePath] cachePolicy:NSURLCacheStorageNotAllowed timeoutInterval:.1] returningResponse:&response error:error]; return [response MIMEType]; } // Creates a directory if it doesn't exist and returns if successful + (BOOL)safeCreateDirectory:(NSString *)directoryPath error:(NSError **)error { NSFileManager *fileManager = [NSFileManager defaultManager]; BOOL isDirectory; BOOL fileExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory]; if (fileExists) return isDirectory; if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:error] || *error) return NO; return YES; } // Returns if an object is a directory based on its content type + (BOOL)isContentTypeDirectory:(NSString *)contentType { return ([contentType isEqualToString:@"application/directory"] || [contentType hasPrefix:@"application/directory;"] || [contentType isEqualToString:@"application/folder"] || [contentType hasPrefix:@"application/folder;"]); } // Returns if an object exists at the given container/object path and if this object is an application/directory // If an error occured an alert is shown and it is returned so the caller won't proceed + (BOOL)objectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName error:(NSError **)error isDirectory:(BOOL *)isDirectory sharingAccount:(NSString *)sharingAccount { ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithContainerName:containerName objectName:objectName]; if (sharingAccount) [objectRequest setRequestUserFromDefaultTo:sharingAccount]; [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous]; while (![objectRequest isFinished]) { usleep(1); } *error = [objectRequest error]; if (*error) { [self httpRequestErrorAlertWithRequest:objectRequest]; return NO; } else if (objectRequest.responseStatusCode == 200) { *isDirectory = [self isContentTypeDirectory:[objectRequest contentType]]; return YES; } return NO; } // Returns if the caller should proceed, after an interactive check if an object exists // at the given container/object path is performed + (BOOL)proceedIfObjectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName sharingAccount:(NSString *)sharingAccount { NSError *error = nil; BOOL isDirectory; BOOL objectExists = [self objectExistsAtContainerName:containerName objectName:objectName error:&error isDirectory:&isDirectory sharingAccount:sharingAccount]; if (error) { return NO; } else if (objectExists) { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; if (isDirectory) { [alert setMessageText:@"Directory Exists"]; if (sharingAccount) [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]]; else [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]]; } else { [alert setMessageText:@"Object Exists"]; if (sharingAccount) [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]]; else [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]]; } [alert addButtonWithTitle:@"OK"]; [alert addButtonWithTitle:@"Cancel"]; NSInteger choice = [alert runModal]; if (choice == NSAlertSecondButtonReturn) return NO; } return YES; } // List of objects at the given container/object path, with prefix and or delimiter + (NSArray *)objectsWithContainerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount { NSMutableArray *objects = [NSMutableArray array]; NSString *marker = nil; do { ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName limit:0 marker:marker prefix:objectNamePrefix delimiter:delimiter path:nil meta:nil shared:NO until:nil]; if (sharingAccount) [containerRequest setRequestUserFromDefaultTo:sharingAccount]; [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous]; while (![containerRequest isFinished]) { usleep(1); } if ([containerRequest error]) { [self httpRequestErrorAlertWithRequest:containerRequest]; return nil; } NSArray *someObjects = [containerRequest objects]; [objects addObjectsFromArray:someObjects]; if ([someObjects count] < 10000) marker = nil; else marker = [[someObjects lastObject] name]; } while (marker); return objects; } // List of objects at the given container/object path, that may be a subdir or an application/directory, // with prefix and or delimiter + (NSArray *)objectsForSubdirWithContainerName:(NSString *)containerName objectName:(NSString *)objectName delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount { NSString *subdirNamePrefix = [NSString stringWithString:objectName]; if (![subdirNamePrefix hasSuffix:@"/"]) subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"]; return [self objectsWithContainerName:containerName objectNamePrefix:subdirNamePrefix delimiter:delimiter sharingAccount:sharingAccount]; } // A safe object name at the given container/object path // The original name has " %d" appended to it before any ".*" suffix, for the first integer that produces a name that is free to use // If the original name hasn't got a "." but has a "/" suffix, then it is replaced with " %d/" instead // Subdirs are taken into consideration + (NSString *)safeObjectNameForContainerName:(NSString *)containerName objectName:(NSString *)objectName { NSString *objectNamePrefix; NSString *objectNameExtraSuffix; NSRange lastDotRange = [objectName rangeOfString:@"." options:NSBackwardsSearch]; if (lastDotRange.length == 1) { objectNamePrefix = [objectName substringToIndex:lastDotRange.location]; objectNameExtraSuffix = [objectName substringFromIndex:lastDotRange.location]; } else if ([objectName hasSuffix:@"/"]) { objectNamePrefix = [objectName substringToIndex:([objectName length] - 1)]; objectNameExtraSuffix = [NSString stringWithString:@"/"]; } else { objectNamePrefix = [NSString stringWithString:objectName]; objectNameExtraSuffix = [NSString string]; } NSArray *objects = [self objectsWithContainerName:containerName objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent] delimiter:@"/" sharingAccount:nil]; if (objects == nil) return nil; if ([objects count] == 0) return objectName; NSMutableArray *objectNames = [NSMutableArray arrayWithArray: [[objects objectsAtIndexes: [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){ if (pithosObject.subdir) return NO; return YES; }]] valueForKey:@"name"]]; for (NSString *name in [[objects objectsAtIndexes: [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){ if (pithosObject.subdir) return YES; return NO; }]] valueForKey:@"name"]) { [objectNames addObject:[name substringToIndex:([name length] - 1)]]; } if (![objectNames containsObject:objectName]) return objectName; NSUInteger objectNameSuffix = 2; NSString *safeObjectName; do { safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix]; objectNameSuffix++; } while ([objectNames containsObject:safeObjectName]); return safeObjectName; } // A safe object name at the given container/object path that may be a subdir or application/directory // The original name has " %d" appended to it, for the first integer that produces a name that is free to use // If the original name has a "/" suffix, then it is replaced with " %d/" instead // Subdirs are taken into consideration + (NSString *)safeSubdirNameForContainerName:(NSString *)containerName subdirName:(NSString *)subdirName { NSString *subdirNamePrefix; NSString *subdirNameExtraSuffix; if ([subdirName hasSuffix:@"/"]) { subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)]; subdirNameExtraSuffix = [NSString stringWithString:@"/"]; } else { subdirNamePrefix = [NSString stringWithString:subdirName]; subdirNameExtraSuffix = [NSString string]; } NSArray *objects = [self objectsWithContainerName:containerName objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent] delimiter:@"/" sharingAccount:nil]; if (objects == nil) return nil; if ([objects count] == 0) return subdirName; NSMutableArray *objectNames = [NSMutableArray arrayWithArray: [[objects objectsAtIndexes: [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){ if (pithosObject.subdir) return NO; return YES; }]] valueForKey:@"name"]]; for (NSString *name in [[objects objectsAtIndexes: [objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){ if (pithosObject.subdir) return YES; return NO; }]] valueForKey:@"name"]) { [objectNames addObject:[name substringToIndex:([name length] - 1)]]; } if (![objectNames containsObject:subdirNamePrefix]) return subdirName; NSUInteger subdirNameSuffix = 2; NSString *safeSubdirName; do { safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix]; subdirNameSuffix++; } while ([objectNames containsObject:safeSubdirName]); return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix]; } #pragma mark - #pragma mark Alerts + (NSInteger)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request { NSString *message = [NSString stringWithFormat: @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", [request error], request.requestMethod, request.url, [request requestHeaders], [request responseHeaders], [request responseString]]; NSLog(@"%@", message); NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"HTTP Request Error"]; [alert setInformativeText:message]; [alert addButtonWithTitle:@"OK"]; return [alert runModal]; } + (NSInteger)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request { NSString *message = [NSString stringWithFormat: @"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", request.responseStatusCode, request.responseStatusMessage, request.requestMethod, request.url, [request requestHeaders], [request responseHeaders], [request responseString]]; NSLog(@"%@", message); NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Unexpected Response Status"]; [alert setInformativeText:message]; [alert addButtonWithTitle:@"OK"]; return [alert runModal]; } + (NSInteger)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:title]; if (error) [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, error]]; else [alert setInformativeText:message]; [alert addButtonWithTitle:@"OK"]; return [alert runModal]; } #pragma mark - #pragma mark Request Helper Methods + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority { [request setTimeOutSeconds:60]; request.numberOfTimesToRetryOnTimeout = 10; [request setQueuePriority:priority]; return request; } + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request { return [self prepareRequest:request priority:NSOperationQueuePriorityNormal]; } + (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request { NSMutableDictionary *userInfo = [[request.userInfo retain] autorelease]; request.userInfo = nil; ASIPithosRequest *newRequest = [request copy]; newRequest.userInfo = userInfo; return newRequest; } @end