// // PithosUtilities.m // pithos-macos // // Copyright 2011-2012 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 "ASINetworkQueue.h" #import "ASIPithos.h" #import "ASIPithosContainerRequest.h" #import "ASIPithosObjectRequest.h" #import "ASIPithosObject.h" #import "HashMapHash.h" @implementation PithosUtilities #pragma mark - #pragma mark Download + (ASIPithosObjectRequest *)objectDataRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName version:(NSString *)version toDirectory:(NSString *)directoryPath withNewFileName:(NSString *)newFileName checkIfExists:(BOOL)ifExists sharingAccount:(NSString *)sharingAccount { NSString *fileName; if (newFileName) { fileName = [NSString stringWithString:newFileName]; } else { 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]) { __block NSInteger choice; dispatch_sync(dispatch_get_main_queue(), ^{ NSAlert *alert = [[NSAlert alloc] init]; [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"]; 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) { DLog(@"Cannot remove existing file '%@': %@", fileName, error); dispatch_async(dispatch_get_main_queue(), ^{ NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Removal Error"]; [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file '%@': %@", fileName, [error localizedDescription]]]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; }); return nil; } ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos containerName:containerName objectName:objectName version:version]; if (sharingAccount) [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; objectRequest.downloadDestinationPath = destinationPath; objectRequest.allowResumeForFileDownloads = YES; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: fileName, @"fileName", destinationPath, @"filePath", nil]; return objectRequest; } + (NSArray *)objectDataRequestsForSubdirWithPithos:(ASIPithos *)pithos containerName:(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]) { __block NSInteger choice; dispatch_sync(dispatch_get_main_queue(), ^{ NSAlert *alert = [[NSAlert alloc] init]; [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"]; choice = [alert runModal]; }); if (choice == NSAlertSecondButtonReturn) return nil; } NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName: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) { DLog(@"Cannot create directory at '%@': %@", directoryPath, error); dispatch_async(dispatch_get_main_queue(), ^{ NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Create Directory Error"]; [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@", directoryPath, [error localizedDescription]]]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; }); } for (ASIPithosObject *object in objects) { if ([self 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) { DLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error); dispatch_async(dispatch_get_main_queue(), ^{ NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Create Directory Error"]; [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@", subdirDirectoryPath, [error localizedDescription]]]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; }); } } else if (!directoryIsDirectory) { [fileManager removeItemAtPath:subdirDirectoryPath error:&error]; if (error) { DLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error); dispatch_async(dispatch_get_main_queue(), ^{ NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Remove File Error"]; [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, [error localizedDescription]]]; [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 objectDataRequestWithPithos:pithos containerName:containerName objectName:object.name version:nil toDirectory:objectDirectoryPath withNewFileName:nil 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 *)objectBlockDataRequestWithPithos:(ASIPithos *)pithos containerName:(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 objectDataRequestWithPithos:pithos containerName: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 *)writeObjectDataRequestWithPithos:(ASIPithos *)pithos containerName:(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 proceedIfObjectExistsAtPithos:pithos containerName: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 writeObjectDataRequestWithPithos:pithos containerName: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 withPithos:pithos]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: fileName, @"fileName", [NSNumber numberWithUnsignedInteger:bytes], @"bytes", nil]; return objectRequest; } + (NSIndexSet *)missingBlocksForHashes:(NSArray *)hashes withMissingHashes:(NSArray *)missingHashes { NSMutableIndexSet *missingBlocks = [NSMutableIndexSet indexSet]; for (NSString *missingHash in missingHashes) { if (![missingHash length]) break; NSUInteger missingBlock = [hashes indexOfObject:missingHash]; if (missingBlock != NSNotFound) [missingBlocks addIndex:missingBlock]; } return missingBlocks; } + (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName blockSize:(NSUInteger)blockSize forFile:(NSString *)filePath hashes:(NSArray *)hashes missingHashes:(NSArray *)missingHashes sharingAccount:(NSString *)sharingAccount { NSIndexSet *missingBlocks = [self missingBlocksForHashes:hashes withMissingHashes:missingHashes]; 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)]; free(tempFileNameCString); if (fileDescriptor == -1) { dispatch_async(dispatch_get_main_queue(), ^{ NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Create Temporary File Error"]; [alert setInformativeText:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; }); return nil; } 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 updateContainerDataRequestWithPithos:pithos containerName:containerName policy:nil metadata:nil update:YES file:tempFilePath]; if (sharingAccount) [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; return containerRequest; } + (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos containerName:(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 updateContainerDataRequestWithPithos:pithos containerName:containerName policy:nil metadata:nil update:YES data:blockData]; if (sharingAccount) [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; return containerRequest; } + (NSArray *)writeObjectDataRequestsWithPithos:(ASIPithos *)pithos containerName:(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 proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName sharingAccount:sharingAccount]) return nil; NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error = nil; NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:directoryPath error:&error]; if (error) { dispatch_async(dispatch_get_main_queue(), ^{ NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Directory Read Error"]; [alert setInformativeText:[NSString stringWithFormat:@"Cannot read contents of directory '%@': %@", [directoryPath lastPathComponent], [error localizedDescription]]]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; }); return nil; } *directoryObjectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]]; ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos containerName: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 withPithos:pithos]; 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] precomposedStringWithCanonicalMapping]; 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 DEBUG_PITHOS if (error) DLog(@"contentType detection error: %@", error); #endif objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos containerName: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 withPithos:pithos]; 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] precomposedStringWithCanonicalMapping]; fileName = [filePath lastPathComponent]; objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos containerName: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 withPithos:pithos]; objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: fileName, @"fileName", nil]; [*directoryObjectRequests addObject:objectRequest]; } } } return objectRequests; } #pragma mark - #pragma mark Delete + (NSArray *)deleteObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName { NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName: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 deleteObjectRequestWithPithos:pithos containerName: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 deleteObjectRequestWithPithos:pithos containerName: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 *)cpyObjectRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName destinationContainerName:(NSString *)destinationContainerName destinationObjectName:(NSString *)destinationObjectName checkIfExists:(BOOL)ifExists sharingAccount:(NSString *)sharingAccount { if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName sharingAccount:nil]) return nil; ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos containerName: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 withPithos:pithos]; return objectRequest; } + (NSArray *)cpyObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName destinationContainerName:(NSString *)destinationContainerName destinationObjectName:(NSString *)destinationObjectName checkIfExists:(BOOL)ifExists sharingAccount:(NSString *)sharingAccount { if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName sharingAccount:nil]) return nil; NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName: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 cpyObjectDataRequestWithPithos:pithos containerName: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 withPithos:pithos]; [objectRequests addObject:objectRequest]; } for (ASIPithosObject *object in objects) { objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos containerName: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 withPithos:pithos]; [objectRequests addObject:objectRequest]; } } else { if (![objectName hasSuffix:@"/"]) { objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos containerName: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 withPithos:pithos]; [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 cpyObjectDataRequestWithPithos:pithos containerName: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 withPithos:pithos]; [objectRequests addObject:objectRequest]; } } if ([objectRequests count] == 0) return nil; return objectRequests; } #pragma mark - #pragma mark Move + (ASIPithosObjectRequest *)moveObjectRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName destinationContainerName:(NSString *)destinationContainerName destinationObjectName:(NSString *)destinationObjectName checkIfExists:(BOOL)ifExists { if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName sharingAccount:nil]) return nil; ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos containerName: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 *)moveObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName destinationContainerName:(NSString *)destinationContainerName destinationObjectName:(NSString *)destinationObjectName checkIfExists:(BOOL)ifExists { if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName sharingAccount:nil]) return nil; NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName: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 moveObjectDataRequestWithPithos:pithos containerName: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 moveObjectDataRequestWithPithos:pithos containerName: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 moveObjectDataRequestWithPithos:pithos containerName: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 moveObjectDataRequestWithPithos:pithos containerName: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] unsignedIntegerValue]; } // Content type of the file or nil if it cannot be determined + (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error { // Based on http://www.ddeville.me/2011/12/mime-to-UTI-cocoa/ // and Apple example ImageBrowserViewAppearance/ImageBrowserController.m LSItemInfoRecord info; CFStringRef uti = NULL; CFURLRef url = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)filePath, kCFURLPOSIXPathStyle, FALSE); if (LSCopyItemInfoForURL(url, kLSRequestExtension | kLSRequestTypeCreator, &info) == noErr) { // Obtain the UTI using the file information. // If there is a file extension, get the UTI. if (info.extension != NULL) { uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, info.extension, kUTTypeData); CFRelease(info.extension); } // No UTI yet if (uti == NULL) { // If there is an OSType, get the UTI. CFStringRef typeString = UTCreateStringForOSType(info.filetype); if ( typeString != NULL) { uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, typeString, kUTTypeData); CFRelease(typeString); } } if (uti != NULL) { CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType); CFRelease(uti); CFRelease(url); return (__bridge_transfer NSString *)MIMEType; } } CFRelease(url); return nil; } // 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; } // Removes contents of a directory and the directory itself if selected + (void)removeContentsAtPath:(NSString *)dirPath andDirectory:(BOOL)removeDirectory { NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error = nil; BOOL isDirectory; if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory]) return; if (isDirectory) { for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:dirPath error:&error]) { if (error) { [self fileActionFailedAlertWithTitle:@"Directory Contents Error" message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", dirPath] error:error]; break; } NSString *subFilePath = [dirPath stringByAppendingPathComponent:subPath]; if (![fileManager removeItemAtPath:subFilePath error:&error] || error) { [self fileActionFailedAlertWithTitle:@"Remove File Error" message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] error:error]; } error = nil; } if (removeDirectory && (![fileManager removeItemAtPath:dirPath error:&error] || error)) { [self fileActionFailedAlertWithTitle:@"Remove Directory Error" message:[NSString stringWithFormat:@"Cannot remove directory at '%@'", dirPath] error:error]; } } else if (![fileManager removeItemAtPath:dirPath error:&error] || error) { [self fileActionFailedAlertWithTitle:@"Remove File Error" message:[NSString stringWithFormat:@"Cannot remove file at '%@'", dirPath] error:error]; } } // Removes contents of a directory + (void)removeContentsAtPath:(NSString *)dirPath { [self removeContentsAtPath:dirPath andDirectory:NO]; } // 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)objectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName error:(NSError **)error isDirectory:(BOOL *)isDirectory sharingAccount:(NSString *)sharingAccount { ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos containerName:containerName objectName:objectName]; if (sharingAccount) [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; [self startAndWaitForRequest:objectRequest]; if (error != NULL) { *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)proceedIfObjectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName sharingAccount:(NSString *)sharingAccount { NSError *error = nil; BOOL isDirectory; BOOL objectExists = [self objectExistsAtPithos:pithos containerName:containerName objectName:objectName error:&error isDirectory:&isDirectory sharingAccount:sharingAccount]; if (error) { return NO; } else if (objectExists) { __block NSInteger choice; dispatch_sync(dispatch_get_main_queue(), ^{ NSAlert *alert = [[NSAlert alloc] init]; 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"]; 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 *)objectsWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount { NSMutableArray *objects = [NSMutableArray array]; NSString *marker = nil; do { ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos containerName:containerName limit:0 marker:marker prefix:objectNamePrefix delimiter:delimiter path:nil meta:nil shared:NO until:nil]; if (sharingAccount) [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; [self startAndWaitForRequest:containerRequest]; 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 *)objectsForSubdirWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount { NSString *subdirNamePrefix = [NSString stringWithString:objectName]; if (![subdirNamePrefix hasSuffix:@"/"]) subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"]; return [self objectsWithPithos:pithos containerName: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 *)safeObjectNameForPithos:(ASIPithos *)pithos containerName:(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 = @"/"; } else { objectNamePrefix = [NSString stringWithString:objectName]; objectNameExtraSuffix = [NSString string]; } NSArray *objects = [self objectsWithPithos:pithos containerName: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 *)safeSubdirNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName subdirName:(NSString *)subdirName { NSString *subdirNamePrefix; NSString *subdirNameExtraSuffix; if ([subdirName hasSuffix:@"/"]) { subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)]; subdirNameExtraSuffix = @"/"; } else { subdirNamePrefix = [NSString stringWithString:subdirName]; subdirNameExtraSuffix = [NSString string]; } NSArray *objects = [self objectsWithPithos:pithos containerName: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 + (void)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request { if (request.responseStatusCode == 401) { [self httpAuthenticationError]; return; } NSString *message = [NSString stringWithFormat: @"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", [[request error] localizedDescription], request.requestMethod, request.url, [request requestHeaders], [request responseHeaders], [request responseString]]; DLog(@"%@", message); dispatch_async(dispatch_get_main_queue(), ^{ @autoreleasepool { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"HTTP Request Error"]; [alert setInformativeText:message]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; } }); } + (void)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request { if (request.responseStatusCode == 401) { [self httpAuthenticationError]; return; } 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]]; DLog(@"%@", message); dispatch_async(dispatch_get_main_queue(), ^{ @autoreleasepool { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Unexpected Response Status"]; [alert setInformativeText:message]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; } }); } + (void)httpAuthenticationError { dispatch_async(dispatch_get_main_queue(), ^{ @autoreleasepool { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Authentication Error"]; [alert setInformativeText:@"Authentication error, please check your token or login again"]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; } }); } + (void)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error { dispatch_async(dispatch_get_main_queue(), ^{ @autoreleasepool { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:title]; if (error) [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, [error localizedDescription]]]; else [alert setInformativeText:message]; [alert addButtonWithTitle:@"OK"]; [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 = (NSMutableDictionary *)request.userInfo; request.userInfo = nil; ASIPithosRequest *newRequest = [request copy]; newRequest.userInfo = userInfo; return newRequest; } + (void)startAndWaitForRequest:(ASIPithosRequest *)request { ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; [networkQueue go]; [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:request]] waitUntilFinished:YES]; } @end