//
//  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 
                                            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 objectDataRequestWithPithos:pithos 
                                                                                  containerName:containerName 
                                                                                     objectName:objectName];
    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]) {
        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 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) {
        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 objectDataRequestWithPithos:pithos 
                                                                        containerName: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 *)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)];
    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 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) {
        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 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];
                    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 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];
                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 *)copyObjectRequestWithPithos:(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 copyObjectDataRequestWithPithos: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 *)copyObjectRequestsForSubdirWithPithos:(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 copyObjectDataRequestWithPithos: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 copyObjectDataRequestWithPithos: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 copyObjectDataRequestWithPithos: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 copyObjectDataRequestWithPithos: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] 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)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];
    ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
    [networkQueue go];
    [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:objectRequest]] waitUntilFinished:YES];
    *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) {
        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 *)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];
        ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
        [networkQueue go];
        [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
        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 = [NSString stringWithString:@"/"];
    } 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 = [NSString stringWithString:@"/"];
    } 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

+ (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 = (NSMutableDictionary *)[[request.userInfo retain] autorelease];
    request.userInfo = nil;
    ASIPithosRequest *newRequest = [request copy];
    newRequest.userInfo = userInfo;
    return newRequest;
}

@end
