Add support for syncing multiple containers for an account. Fix sync bugs. Change...
[pithos-macos] / pithos-macos / PithosSyncDaemon.m
index 107d154..da02195 100644 (file)
@@ -2,7 +2,7 @@
 //  PithosSyncDaemon.m
 //  pithos-macos
 //
-// Copyright 2011 GRNET S.A. All rights reserved.
+// 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
 // or implied, of GRNET S.A.
 
 #import "PithosSyncDaemon.h"
+#import "PithosAccount.h"
 #import "PithosLocalObjectState.h"
 #import "PithosActivityFacility.h"
 #import "PithosUtilities.h"
 #import "ASINetworkQueue.h"
 #import "ASIPithosRequest.h"
+#import "ASIPithos.h"
+#import "ASIPithosContainer.h"
 #import "ASIPithosContainerRequest.h"
 #import "ASIPithosObjectRequest.h"
 #import "ASIPithosObject.h"
-#import "FileMD5Hash.h"
-#import "HashMapHash.h"
-
-#define DATA_MODEL_FILE @"localstate.archive"
-#define ARCHIVE_KEY @"Data"
 
 @interface PithosSyncDaemon (Private)
-- (NSString *)pithosStateFilePath;
+- (void)loadLocalState;
+- (void)resetLocalStateWithAll:(BOOL)all;
 - (void)saveLocalState;
-- (BOOL)localStateHasChanged:(PithosLocalObjectState *)storedState currentState:(PithosLocalObjectState *)currentState;
-- (BOOL)serverStateHasChanged:(PithosLocalObjectState *)storedState 
-             remoteObjectHash:(NSString *)remoteObjectHash 
-      remoteObjectIsDirectory:(BOOL)remoteObjectIsDirectory;
-- (void)updateLocalStateWithObject:(ASIPithosObject *)object localFilePath:(NSString *)filePath;
--(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
-                                object:(ASIPithosObject *)object 
-                         localFilePath:(NSString *)filePath;
+
+- (BOOL)moveToTempTrashFile:(NSString *)filePath pithosContainer:(ASIPithosContainer *)pithosContainer;
+- (void)emptyTempTrash;
+- (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath;
+
+- (void)updateLocalStateWithObject:(ASIPithosObject *)object 
+                     localFilePath:(NSString *)filePath 
+                   pithosContainer:(ASIPithosContainer *)pithosContainer;
+- (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
+                                   object:(ASIPithosObject *)object 
+                            localFilePath:(NSString *)filePath 
+                          pithosContainer:(ASIPithosContainer *)pithosContainer;
+- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest;
 - (void)requestFailed:(ASIPithosRequest *)request;
+
+- (void)syncOperationStarted;
+- (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull;
+
 @end
 
 @implementation PithosSyncDaemon
-@synthesize blockHash, blockSize, lastModified, remoteObjects, storedLocalObjectStates;
-@synthesize pithosStateFilePath, tempDownloadsDirPath;
+@synthesize directoryPath, pithosAccount, containersDictionary, pithos;
+@synthesize pithosContainers;
+@synthesize lastCompletedSync, remoteObjects, previousRemoteObjects, storedLocalObjectStates, currentLocalObjectStates;
+@synthesize pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
 
 #pragma mark -
 #pragma Object Lifecycle
 
 - (id)initWithDirectoryPath:(NSString *)aDirectoryPath 
-              containerName:(NSString *)aContainerName 
-               timeInterval:(NSTimeInterval)aTimeInterval {
+              pithosAccount:(PithosAccount *)aPithosAccount 
+       containersDictionary:(NSMutableDictionary *)aContainersDictionary 
+            resetLocalState:(BOOL)resetLocalState {
     if ((self = [super init])) {
         directoryPath = [aDirectoryPath copy];
-        containerName = [aContainerName copy];
-        timeInterval = aTimeInterval;
+        pithosAccount = [aPithosAccount retain];
+        containersDictionary = [aContainersDictionary retain];
+        self.pithos = pithosAccount.pithos;
         
-        syncOperationCount = 0;
-        newSyncRequested = NO;
+        containersCount = [containersDictionary count];
+        self.pithosContainers = [NSMutableArray arrayWithCapacity:containersCount];
+        for (NSString *containerName in [containersDictionary allKeys]) {
+            ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
+            pithosContainer.name = containerName;
+            [pithosContainers addObject:pithosContainer];
+        }
         
         activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
         
-        if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath]) {
-            NSData *data = [NSData dataWithContentsOfFile:self.pithosStateFilePath];
-            NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
-            self.storedLocalObjectStates = [unarchiver decodeObjectForKey:ARCHIVE_KEY];
-            [unarchiver finishDecoding];
-        } else {
-            self.storedLocalObjectStates = [NSMutableDictionary dictionary];
-        }
+        if (resetLocalState)
+            [self resetLocalStateWithAll:YES];
+        else
+            [self resetLocalStateWithAll:NO];
+        
+        networkQueue = [[ASINetworkQueue alloc] init];
+        networkQueue.showAccurateProgress = YES;
+        networkQueue.shouldCancelAllRequestsOnFailure = NO;
+//        networkQueue.maxConcurrentOperationCount = 1;
         
-        queue = [[ASINetworkQueue alloc] init];
-        queue.shouldCancelAllRequestsOnFailure = NO;
-        [queue go];
+        callbackQueue = [[NSOperationQueue alloc] init];
+        [callbackQueue setSuspended:YES];
+        callbackQueue.name = [NSString stringWithFormat:@"gr.grnet.pithos.SyncCallbackQueue.%@", pithosAccount.uniqueName];
+//        callbackQueue.maxConcurrentOperationCount = 1;
         
         [[NSNotificationCenter defaultCenter] addObserver:self
                                                  selector:@selector(applicationWillTerminate:)
                                                      name:NSApplicationWillTerminateNotification
                                                    object:[NSApplication sharedApplication]];
+    }
+    return self;
+}
+
+- (void)loadLocalState {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath])
+        self.storedLocalObjectStates = [NSKeyedUnarchiver unarchiveObjectWithFile:self.pithosStateFilePath];
+    else
+        self.storedLocalObjectStates = [NSMutableDictionary dictionary];
+    if (!storedLocalObjectStates)
+        self.storedLocalObjectStates = [NSMutableDictionary dictionary];
+    for (ASIPithosContainer *pithosContainer in pithosContainers) {
+        if (![storedLocalObjectStates objectForKey:pithosContainer.name]) {
+            [storedLocalObjectStates setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
+        }
+    }
+    [pool drain];
+}
+
+- (void)resetLocalStateWithAll:(BOOL)all {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    self.lastCompletedSync = nil;
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    NSError *error;
+    if (all) {
+        self.storedLocalObjectStates = [NSMutableDictionary dictionary];
+        [self saveLocalState]; // Save an empty dictionary
+        [self loadLocalState]; // Load to populate with containers
+        [self saveLocalState]; // Save again
+        if (self.tempDownloadsDirPath) {
+            error = nil;
+            for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:self.tempDownloadsDirPath error:&error]) {
+                if (error) {
+                    [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
+                                                            message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", self.tempDownloadsDirPath] 
+                                                              error:error];
+                    break;
+                }
+                NSString *subFilePath = [self.tempDownloadsDirPath stringByAppendingPathComponent:subPath];
+                if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
+                    [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
+                                                            message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
+                                                              error:error];
+                }
+                error = nil;
+            }
+        }
+    } else {
+        // Remove containers that don't interest us anymore and save
+        if (!storedLocalObjectStates)
+            [self loadLocalState];
+        for (NSString *containerName in [storedLocalObjectStates allKeys]) {
+            if (![containersDictionary objectForKey:containerName]) {
+                [storedLocalObjectStates removeObjectForKey:containerName];
+                if (self.tempDownloadsDirPath) {
+                    NSString *containerTempDownloadsDirPath = [self.tempDownloadsDirPath stringByAppendingPathComponent:containerName];
+                    BOOL isDirectory;
+                    BOOL fileExists = [fileManager fileExistsAtPath:containerTempDownloadsDirPath isDirectory:&isDirectory];
+                    if (fileExists && isDirectory) {
+                        error = nil;
+                        for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:containerTempDownloadsDirPath error:&error]) {
+                            if (error) {
+                                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
+                                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", 
+                                                                                 containerTempDownloadsDirPath] 
+                                                                          error:error];
+                                break;
+                            }
+                            NSString *subFilePath = [containerTempDownloadsDirPath stringByAppendingPathComponent:subPath];
+                            if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
+                                [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
+                                                                        message:[NSString stringWithFormat:@"Cannot remove file at '%@'", 
+                                                                                 subFilePath] 
+                                                                          error:error];
+                            }
+                            error = nil;
+                        }
+                    } else if (fileExists && !isDirectory) {
+                        error = nil;
+                        if (![fileManager removeItemAtPath:containerTempDownloadsDirPath error:&error] || error) {
+                            [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
+                                                                    message:[NSString stringWithFormat:@"Cannot remove file at '%@'", 
+                                                                             containerTempDownloadsDirPath] 
+                                                                      error:error];
+                        }
+                    }
+                }
+            }
+        }
+        [self saveLocalState];
+    }
+    [pool drain];
+}
+
+- (void)saveLocalState {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    [NSKeyedArchiver archiveRootObject:storedLocalObjectStates toFile:self.pithosStateFilePath];
+    [pool drain];
+}
+
+- (void)resetDaemon {
+    @synchronized(self) {
+        if (!daemonActive)
+            return;
+    }
+    
+    [networkQueue reset];
+    [callbackQueue cancelAllOperations];
+    [callbackQueue setSuspended:YES];
+    [self emptyTempTrash];
     
-        timer = [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(sync) userInfo:nil repeats:YES] retain];
-        [timer fire];
+    syncOperationCount = 0;
+    
+    @synchronized(self) {            
+        daemonActive = NO;
     }
+}
+
+- (void)startDaemon {
+    @synchronized(self) {
+        if (daemonActive)
+            return;
+    }
+
+    // In the improbable case of leftover operations
+    [networkQueue reset];
+    [callbackQueue cancelAllOperations];
     
-    return self;
+    syncOperationCount = 0;
+    newSyncRequested = NO;
+    syncIncomplete = NO;
+    syncLate = NO;
+    
+    [self loadLocalState];
+    
+    [networkQueue go];
+    [callbackQueue setSuspended:NO];
+    
+    @synchronized(self) {
+        daemonActive = YES;
+    }
 }
 
 - (void)dealloc {
     [[NSNotificationCenter defaultCenter] removeObserver:self];
-    [queue cancelAllOperations];
-    [queue release];
-    [timer invalidate];
-    [timer release];
+    [self resetDaemon];
+    [callbackQueue release];
+    [networkQueue release];
+    [tempTrashDirPath release];
     [tempDownloadsDirPath release];
     [pithosStateFilePath release];
+    [currentLocalObjectStates release];
     [storedLocalObjectStates release];
+    [previousRemoteObjects release];
     [remoteObjects release];
     [objects release];
-    [lastModified release];
-    [blockHash release];
-    [containerName release];
+    [lastCompletedSync release];
+    [pithosContainers release];
+    [pithos release];
+    [containersDictionary release];
+    [pithosAccount release];
     [directoryPath release];
     [super dealloc];
 }
 
 - (NSString *)pithosStateFilePath {
     if (!pithosStateFilePath) {
-        pithosStateFilePath = [[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:DATA_MODEL_FILE] retain];
+        pithosStateFilePath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
+                                 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
+                                stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-PithosLocalObjectStates.archive", 
+                                                                pithosAccount.uniqueName]] retain];
+        NSString *pithosStateFileDirPath = [pithosStateFilePath stringByDeletingLastPathComponent];
+        NSFileManager *fileManager = [NSFileManager defaultManager];
+        BOOL isDirectory;
+        BOOL fileExists = [fileManager fileExistsAtPath:pithosStateFileDirPath isDirectory:&isDirectory];
+        NSError *error = nil;
+        if (fileExists && !isDirectory)
+            [fileManager removeItemAtPath:pithosStateFileDirPath error:&error];
+        if (!error && !fileExists)
+            [fileManager createDirectoryAtPath:pithosStateFileDirPath withIntermediateDirectories:YES attributes:nil error:&error];
+        //if (error)
+        //  pithosStateFilePath = nil;
+        // XXX create a dir using mktmps?
     }
-    return [pithosStateFilePath copy];
+    return [[pithosStateFilePath copy] autorelease];
 }
 
 - (NSString *)tempDownloadsDirPath {
-    NSFileManager *fileManager = [NSFileManager defaultManager];
-    if (!tempDownloadsDirPath || ![fileManager fileExistsAtPath:tempDownloadsDirPath]) {
-        // Get the path from user defaults
-        tempDownloadsDirPath = [[NSUserDefaults standardUserDefaults] stringForKey:@"PithosTempDownloadsDirPath"];
-        if (tempDownloadsDirPath) {
-            // Check if the path exists
-            BOOL isDirectory;
-            BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
-            NSError *error = nil;
-            if (fileExists && !isDirectory)
-                [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
-            if (!error & !fileExists)
-                [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
-            if (error)
-                tempDownloadsDirPath = nil;
-            }
-        if (!tempDownloadsDirPath) {
-            NSString *tempDirTemplate = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Temp Downloads XXXXXX"];
-            const char *tempDirTemplateCString = [tempDirTemplate fileSystemRepresentation];
-            char *tempDirNameCString = (char *)malloc(strlen(tempDirTemplateCString) + 1);
-            strcpy(tempDirNameCString, tempDirTemplateCString);
-            tempDirNameCString = mkdtemp(tempDirNameCString);
-            if (tempDirNameCString != NULL)
-                tempDownloadsDirPath = [fileManager stringWithFileSystemRepresentation:tempDirNameCString length:strlen(tempDirNameCString)];
-            free(tempDirNameCString);
+    if (!tempDownloadsDirPath) {
+        tempDownloadsDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
+                                  stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
+                                 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempDownloads", 
+                                                                 pithosAccount.uniqueName]] retain];
+        NSFileManager *fileManager = [NSFileManager defaultManager];
+        BOOL isDirectory;
+        BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
+        NSError *error = nil;
+        if (fileExists && !isDirectory)
+            [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
+        if (!error && !fileExists)
+            [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
+        //if (error)
+        //    tempDownloadsDirPath = nil;
+        // XXX create a dir using mktmps?
+    }
+    return tempDownloadsDirPath;    
+}
+
+- (NSString *)tempTrashDirPath {
+    if (!tempTrashDirPath) {
+        tempTrashDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
+                                  stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
+                                 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempTrash", 
+                                                                 pithosAccount.uniqueName]] retain];
+        NSFileManager *fileManager = [NSFileManager defaultManager];
+        BOOL isDirectory;
+        BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
+        NSError *error = nil;
+        if (fileExists && !isDirectory)
+            [fileManager removeItemAtPath:tempTrashDirPath error:&error];
+        if (!error && !fileExists)
+            [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
+        //if (error)
+        //    tempTrashDirPath = nil;
+        // XXX create a dir using mktmps?
+    }
+    return tempTrashDirPath;
+}
+
+- (void)setDirectoryPath:(NSString *)aDirectoryPath {
+    if (aDirectoryPath && ![aDirectoryPath isEqualToString:directoryPath]) {
+        [self resetDaemon];
+        [self resetLocalStateWithAll:YES];
+        [directoryPath release];
+        directoryPath = [aDirectoryPath copy];
+    }
+}
+
+- (void)setContainersDictionary:(NSDictionary *)aContainersDictionary {
+    if (aContainersDictionary && ![aContainersDictionary isEqualToDictionary:containersDictionary]) {
+        [self resetDaemon];
+        [containersDictionary release];
+        containersDictionary = [aContainersDictionary retain];
+        containersCount = [containersDictionary count];
+        self.pithosContainers = [NSMutableArray arrayWithCapacity:containersCount];
+        for (NSString *containerName in [containersDictionary allKeys]) {
+            ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
+            pithosContainer.name = containerName;
+            [pithosContainers addObject:pithosContainer];
         }
-        if (!tempDownloadsDirPath)
-            [[NSUserDefaults standardUserDefaults] setObject:tempDownloadsDirPath forKey:@"PithosTempDownloadsDirPath"];
-        [tempDownloadsDirPath retain];
+        [self resetLocalStateWithAll:NO];
+    }    
+}
+
+- (void)setPithos:(ASIPithos *)aPithos {
+    if (!pithos) {
+        pithos = [[ASIPithos pithos] retain];
+        pithos.authUser = [[aPithos.authUser copy] autorelease];
+        pithos.authToken = [[aPithos.authToken copy] autorelease];
+        pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
+        pithos.authURL = [[aPithos.authURL copy] autorelease];
+        pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
     }
-    return [tempDownloadsDirPath copy];
+    if (aPithos && 
+        (![aPithos.authUser isEqualToString:pithos.authUser] || 
+         ![aPithos.authToken isEqualToString:pithos.authToken] || 
+         ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])) {
+        [self resetDaemon];
+        if (![aPithos.authUser isEqualToString:pithos.authUser] || 
+            ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])
+            [self resetLocalStateWithAll:YES];
+        pithos.authUser = [[aPithos.authUser copy] autorelease];
+        pithos.authToken = [[aPithos.authToken copy] autorelease];
+        pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
+        pithos.authURL = [[aPithos.authURL copy] autorelease];
+        pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
+    }        
 }
 
 #pragma mark -
 #pragma mark Sync
 
-- (void)saveLocalState {
-    NSMutableData *data = [NSMutableData data];
-    NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
-    [archiver encodeObject:storedLocalObjectStates forKey:ARCHIVE_KEY];
-    [archiver finishEncoding];
-    [data writeToFile:self.pithosStateFilePath atomically:YES];
+- (void)syncOperationStarted {
+    @synchronized(self) {
+        syncOperationCount++;
+    }
+}
+
+- (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
+    @synchronized(self) {
+        if (!operationSuccessfull)
+            syncIncomplete = YES;
+        if (syncOperationCount == 0) {
+            // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
+            NSLog(@"Sync::WARNING: tried to decrease syncOperationCount when 0");
+            return;
+        }
+        syncOperationCount--;
+        if (syncOperationCount == 0) {
+            if (!syncIncomplete) {
+                self.lastCompletedSync = [NSDate date];
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [activityFacility startAndEndActivityWithType:PithosActivityOther 
+                                                          message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync] 
+                                                    pithosAccount:pithosAccount];
+                    
+                });
+            }
+            [self emptyTempTrash];
+            if (newSyncRequested && daemonActive)
+                [self sync];
+        }
+    }
+}
+
+- (BOOL)isSyncing {
+    @synchronized(self) {
+        return ((syncOperationCount > 0) && daemonActive);
+    }
+}
+
+- (void)syncLate {
+    @synchronized(self) {
+        if ([self isSyncing])
+            syncLate = YES;
+    }
 }
 
 - (void)sync {
     @synchronized(self) {
-        if (syncOperationCount) {
+        if ([self isSyncing]) {
             // If at least one operation is running return
             newSyncRequested = YES;
             return;
-        } else {
+        } else if (daemonActive && containersCount) {
             // The first operation is the server listing
-            syncOperationCount = 1;
+            [self syncOperationStarted];
             newSyncRequested = NO;
+            syncIncomplete = NO;
+            syncLate = NO;
+        } else {
+            return;
         }
     }
 
-    NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:containerName];
     NSFileManager *fileManager = [NSFileManager defaultManager];
     BOOL isDirectory;
     NSError *error = nil;
-    if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory] && 
-        (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] || 
-         error)) {
-        [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
-                                                message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", containerDirectoryPath] 
-                                                  error:error];
-        @synchronized(self) {
-            syncOperationCount = 0;
+    if (![fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory]) {
+        if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error] || 
+            error) {
+            [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
+                                                    message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", directoryPath] 
+                                                      error:error];
+            [self syncOperationFinishedWithSuccess:NO];
+            return;
         }
-        return;
     } else if (!isDirectory) {
         [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
-                                                message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", containerDirectoryPath] 
+                                                message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", directoryPath] 
                                                   error:nil];
-        @synchronized(self) {
-            syncOperationCount = 0;
-        }
+        [self syncOperationFinishedWithSuccess:NO];
         return;
     }
-    
-    ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName 
-                                                                                                           limit:0 
-                                                                                                          marker:nil 
-                                                                                                          prefix:nil 
-                                                                                                       delimiter:nil 
-                                                                                                            path:nil 
-                                                                                                            meta:nil 
-                                                                                                          shared:NO 
-                                                                                                           until:nil 
-                                                                                                 ifModifiedSince:lastModified];
+    NSString *containerDirectoryPath;
+    for (ASIPithosContainer *pithosContainer in pithosContainers) {
+        containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
+        error = nil;
+        if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory]) {
+            if (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] || 
+                error) {
+                [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
+                                                        message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", 
+                                                                 containerDirectoryPath] 
+                                                          error:error];
+                [self syncOperationFinishedWithSuccess:NO];
+                return;
+            }
+        } else if (!isDirectory) {
+            [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
+                                                    message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", 
+                                                             containerDirectoryPath] 
+                                                      error:nil];
+            [self syncOperationFinishedWithSuccess:NO];
+            return;
+        }
+    }
+    containersIndex = 0;
+    self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:containersCount];
+    ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
+                                                                                            containerName:[[pithosContainers objectAtIndex:containersIndex] name] 
+                                                                                                    limit:0 
+                                                                                                   marker:nil 
+                                                                                                   prefix:nil 
+                                                                                                delimiter:nil 
+                                                                                                     path:nil 
+                                                                                                     meta:nil 
+                                                                                                   shared:NO 
+                                                                                                    until:nil 
+                                                                                          ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]];
     containerRequest.delegate = self;
-    containerRequest.didFinishSelector = @selector(listRequestFinished:);
-    containerRequest.didFailSelector = @selector(listRequestFailed:);
+    containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+    containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
     PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther 
-                                                               message:@"Sync: Getting server listing"];
+                                                               message:@"Sync: Getting server listing" 
+                                                         pithosAccount:pithosAccount];
     containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                  activity, @"activity", 
+                                 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage", 
+                                 @"Sync: Getting server listing (failed)", @"failedActivityMessage", 
+                                 @"Sync: Getting server listing (finished)", @"finishedActivityMessage", 
+                                 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                                 NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector", 
+                                 NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector", 
                                  nil];
-//    [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
-    [queue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh]];
+    [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
 }
 
-- (BOOL)localStateHasChanged:(PithosLocalObjectState *)storedState currentState:(PithosLocalObjectState *)currentState {
-    if (currentState.isDirectory)
-        // Currently a directory, check previous state
-        return (!storedState.isDirectory);
-    if (storedState.isDirectory)
-        // Previously a directory, currently a file or doesn't exist, state has changed
-        return YES;
-    if ([currentState.md5 isEqualToString:@" "] && [currentState.hashMapHash isEqualToString:@" "] && 
-        (![storedState.md5 isEqualToString:@" "] || ![storedState.hashMapHash isEqualToString:@" "]))
-        // Currently doesn't exist, previously a file, state has changed
-        return YES;
-    if (![storedState.md5 isEqualToString:currentState.md5] && ![storedState.hashMapHash isEqualToString:currentState.hashMapHash])
-        // Neither hash remained the same, different files, state has changed
-        return YES;
-    else
-        // At least one hash remained the same (the other is either the same or emmpty), state hasn't changed
-        return NO;
+- (void)emptyTempTrash {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    NSString *trashDirPath = self.tempTrashDirPath;
+    if (trashDirPath) {
+        NSFileManager *fileManager = [NSFileManager defaultManager];
+        NSError *error = nil;
+//        NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
+        NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
+        if (error) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
+                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", trashDirPath] 
+                                                          error:error];
+            });
+            [pool drain];
+            return;
+        }
+        if ([subPaths count]) {
+//            NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
+//            for (NSString *subPath in subPaths) {
+//                [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
+//            }
+//            [self syncOperationStarted];
+//            [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
+//                if (error) {
+//                    dispatch_async(dispatch_get_main_queue(), ^{
+//                        [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error" 
+//                                                                message:@"Cannot move files to Trash" 
+//                                                                  error:error];
+//                    });
+//                }
+//                [self syncOperationFinishedWithSuccess:YES];
+//            }];
+            for (NSString *subPath in subPaths) {
+                NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
+                error = nil;
+                if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
+                    dispatch_async(dispatch_get_main_queue(), ^{
+                        [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
+                                                                message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
+                                                                  error:error];
+                    });
+            }
+        }
+    }
+    [pool drain];
 }
 
-- (BOOL)serverStateHasChanged:(PithosLocalObjectState *)storedState 
-             remoteObjectHash:(NSString *)remoteObjectHash 
-      remoteObjectIsDirectory:(BOOL)remoteObjectIsDirectory {
-    if (remoteObjectIsDirectory)
-        // Remotely a directory, check previous state
-        return (!storedState.isDirectory);
-    if (storedState.isDirectory)
-        // Previously a directory, remotely a file or doesn't exist, state has changed
-        return YES;
-    if ([remoteObjectHash length] == 32)
-        // Remotely a file, check previous state
-        return ![remoteObjectHash isEqualToString:storedState.md5];
-    else if ([remoteObjectHash length] == 64)
-        // Remotely a file, check previous state
-        return ![remoteObjectHash isEqualToString:storedState.hashMapHash];
-    else if ([remoteObjectHash isEqualToString:@" "]) {
-        // Remotely doesn't exist
-        if ([storedState.md5 isEqualToString:@" "] && [storedState.hashMapHash isEqualToString:@" "])
-            // Previously didn't exist, state hasn't changed
+- (BOOL)moveToTempTrashFile:(NSString *)filePath pithosContainer:(ASIPithosContainer *)pithosContainer {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    if (!self.tempTrashDirPath) {
+        [pool drain];
+        return NO;
+    }
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    BOOL isDirectory;
+    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
+    NSError *error = nil;
+    NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
+    NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath withString:self.tempTrashDirPath];
+    NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
+    if (fileExists && isDirectory) {
+        NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
+        if (error) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
+                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", filePath] 
+                                                          error:error];
+            });
+            [pool drain];
             return NO;
-        else
-            // Previously did exist, state has changed
-            return YES;
+        }
+        if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
+                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
+                                                          error:error];
+            });
+            [pool drain];
+            return NO;
+        }
+        if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
+                                                        message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
+                                                                 filePath, newFilePath] 
+                                                          error:error];
+            });
+            [pool drain];
+            return NO;
+        }
+        PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
+        if (currentState) {
+            currentState.filePath = newFilePath;
+            [currentLocalObjectStates setObject:currentState forKey:newFilePath];
+            [currentLocalObjectStates removeObjectForKey:filePath];        
+        } else {
+            [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
+                                                                                       blockHash:pithosContainer.blockHash 
+                                                                                       blockSize:pithosContainer.blockSize] 
+                                         forKey:newFilePath];
+        }
+        for (NSString *subPath in subPaths) {
+            NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
+            NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath 
+                                                                              withString:self.tempTrashDirPath];
+            currentState = [currentLocalObjectStates objectForKey:subFilePath];
+            if (currentState) {
+                currentState.filePath = newSubFilePath;
+                [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
+                [currentLocalObjectStates removeObjectForKey:subFilePath];
+            } else {
+                [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath 
+                                                                                           blockHash:pithosContainer.blockHash 
+                                                                                           blockSize:pithosContainer.blockSize] 
+                                             forKey:newSubFilePath];
+            }        
+        }
+    } else if (fileExists && !isDirectory) {
+        if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
+                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
+                                                          error:error];
+            });
+            [pool drain];
+            return NO;
+        }
+        if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
+                                                        message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
+                                                                 filePath, newFilePath] 
+                                                          error:error];
+            });
+            [pool drain];
+            return NO;
+        }
+        PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
+        if (currentState) {
+            currentState.filePath = newFilePath;
+            [currentLocalObjectStates setObject:currentState forKey:newFilePath];
+            [currentLocalObjectStates removeObjectForKey:filePath];        
+        } else {
+            [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
+                                                                                       blockHash:pithosContainer.blockHash 
+                                                                                       blockSize:pithosContainer.blockSize] 
+                                         forKey:newFilePath];
+        }
     }
-    // Only if the server doesn't respond properly this will be reached, leave as is for now
+    [pool drain];
+    return YES;
+}
+
+- (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
+    if ([hash length] != 64)
+        return NO;
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    PithosLocalObjectState *localState;
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    BOOL isDirectory;
+    NSError *error = nil;
+    for (NSString *localFilePath in [currentLocalObjectStates allKeys]) {
+        localState = [currentLocalObjectStates objectForKey:localFilePath];
+        if (!localState.isDirectory && [hash isEqualToString:localState.hash] && 
+            [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
+            if ([localFilePath hasPrefix:directoryPath]) {
+                if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
+                    dispatch_async(dispatch_get_main_queue(), ^{
+                        [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error" 
+                                                                message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'", 
+                                                                         localFilePath, filePath] 
+                                                                  error:error];
+                    });
+                } else {
+                    [pool drain];
+                    return YES;
+                }
+            } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
+                if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
+                    dispatch_async(dispatch_get_main_queue(), ^{
+                        [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
+                                                                message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
+                                                                         localFilePath, filePath] 
+                                                                  error:error];
+                    });
+                } else {
+                    localState.filePath = filePath;
+                    [currentLocalObjectStates setObject:localState forKey:filePath];
+                    [currentLocalObjectStates removeObjectForKey:localFilePath];
+                    [pool drain];
+                    return YES;
+                }
+            }
+        }
+    }
+    [pool drain];
     return NO;
 }
 
 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
-                     localFilePath:(NSString *)filePath {
+                     localFilePath:(NSString *)filePath 
+                   pithosContainer:(ASIPithosContainer *)pithosContainer {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
     NSFileManager *fileManager = [NSFileManager defaultManager];
     NSError *error;
     BOOL isDirectory;
     BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
-    PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
-    if ([object.hash isEqualToString:@" "]) {
+    NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
+    NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
+    PithosLocalObjectState *storedState = [containerStoredLocalObjectStates objectForKey:object.name];
+    // Remote updated info
+    NSError *remoteError;
+    BOOL remoteIsDirectory;
+    BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos 
+                                                      containerName:pithosContainer.name 
+                                                         objectName:object.name 
+                                                              error:&remoteError 
+                                                        isDirectory:&remoteIsDirectory 
+                                                     sharingAccount:nil];
+    if (!object || !object.objectHash) {
         // Delete local object
-        // XXX move to local trash instead
-        NSLog(@"Sync::delete local object: %@", filePath);
-        BOOL deleteFailed = NO;
-        if (fileExists) {
-            error = nil;
-            if (![fileManager removeItemAtPath:filePath error:&error] || error) {
-                [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
-                                                        message:[NSString stringWithFormat:@"Cannot remove file at '%@'", filePath] 
-                                                          error:error];
-                deleteFailed = YES;
-            }
+        if (remoteObjectExists) {
+            // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
+            syncIncomplete = YES;
         }
-        if (!deleteFailed) {
-            [activityFacility startAndEndActivityWithType:PithosActivityOther 
-                                                  message:[NSString stringWithFormat:@"Sync: Deleting '%@' locally (finished)", object.name]];
-            [storedLocalObjectStates removeObjectForKey:object.name];
+        NSLog(@"Sync::delete local object: %@", filePath);
+        if (!fileExists || [self moveToTempTrashFile:filePath pithosContainer:pithosContainer]) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility startAndEndActivityWithType:PithosActivityOther 
+                                                      message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@' locally (finished)", 
+                                                               pithosContainer.name, object.name] 
+                                                pithosAccount:pithosAccount];
+            });
+            [containerStoredLocalObjectStates removeObjectForKey:object.name];
             [self saveLocalState];
         }
-    } else if ([object.contentType isEqualToString:@"application/directory"]) {
+    } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
         // Create local directory object
+        if (!remoteObjectExists || !remoteIsDirectory) {
+            // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
+            syncIncomplete = YES;
+            [pool drain];
+            return;
+        }
         NSLog(@"Sync::create local directory object: %@", filePath);
         BOOL directoryCreated = NO;
-        if (fileExists && !isDirectory) {
-            NSLog(@"Sync::delete local file object: %@", filePath);
-            error = nil;
-            if (![fileManager removeItemAtPath:filePath error:&error] || error) {
-                [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
-                                                        message:[NSString stringWithFormat:@"Cannot remove file at '%@'", filePath] 
-                                                          error:error];
-            }
-        }
-        if (![fileManager fileExistsAtPath:filePath]) {
+        if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath pithosContainer:pithosContainer])) {
             NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
             error = nil;
             if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
-                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
-                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath] 
-                                                          error:error];
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
+                                                            message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath] 
+                                                              error:error];
+                });
             } else {
                 directoryCreated = YES;
+                storedState.filePath = filePath;
                 storedState.isDirectory = YES;
                 [self saveLocalState];
             }
-        } else if (isDirectory) {
+        } else {
             NSLog(@"Sync::local directory object exists: %@", filePath);
             directoryCreated = YES;
         }
         if (directoryCreated)
-            [activityFacility startAndEndActivityWithType:PithosActivityOther 
-                                                  message:[NSString stringWithFormat:@"Sync: Creating directory '%@' locally (finished)", object.name]];
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility startAndEndActivityWithType:PithosActivityOther 
+                                                      message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@' locally (finished)", 
+                                                               pithosContainer.name, object.name] 
+                                                pithosAccount:pithosAccount];
+            });
     } else if (object.bytes == 0) {
         // Create local object with zero length
+        if (!remoteObjectExists || remoteIsDirectory) {
+            // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
+            syncIncomplete = YES;
+            [pool drain];
+            return;
+        }
         NSLog(@"Sync::create local zero length object: %@", filePath);
         BOOL fileCreated = NO;
-        if (fileExists && (isDirectory || [PithosUtilities bytesOfFile:filePath])) {
-            NSLog(@"Sync::delete local object: %@", filePath);
+        if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath pithosContainer:pithosContainer])) {
+            NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
+            // Create directory of the file, if it doesn't exist
             error = nil;
-            if (![fileManager removeItemAtPath:filePath error:&error] || error) {
-                [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
-                                                        message:[NSString stringWithFormat:@"Cannot remove file at '%@'", filePath] 
-                                                          error:error];
+            if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
+                                                            message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
+                                                              error:error];
+                });
             }
-        }
-        if (![fileManager fileExistsAtPath:filePath]) {
-            NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
             error = nil;
             if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
-                [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error" 
-                                                        message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath] 
-                                                          error:error];
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error" 
+                                                            message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath] 
+                                                              error:error];
+                });
             } else {
                 fileCreated = YES;
-                if ([object.hash length] == 32) {
-                    storedState.md5 = object.hash;
-                    storedState.hashMapHash = @" ";
-                } else if([object.hash length] == 64) {
-                    storedState.md5 = @" ";
-                    storedState.hashMapHash = object.hash;
-                } else {
-                    storedState.md5 = @" ";
-                    storedState.hashMapHash = @" ";
-                }
-                storedState.tmpDownloadFile = nil;
+                storedState.filePath = filePath;
+                storedState.hash = object.objectHash;
+                storedState.tmpFilePath = nil;
                 [self saveLocalState];
             }
-        } else if (!isDirectory && ![PithosUtilities bytesOfFile:filePath]) {
+        } else {
             NSLog(@"Sync::local zero length object exists: %@", filePath);
             fileCreated = YES;
         }
         if (fileCreated)
-            [activityFacility startAndEndActivityWithType:PithosActivityOther 
-                                                  message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
-    } else if (storedState.tmpDownloadFile == nil) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility startAndEndActivityWithType:PithosActivityOther 
+                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", 
+                                                               pithosContainer.name, object.name] 
+                                                pithosAccount:pithosAccount];
+            });
+    } else if (storedState.tmpFilePath == nil) {
         // Create new local object
-        @synchronized(self) {
-            syncOperationCount++;
+        if (!remoteObjectExists || remoteIsDirectory) {
+            // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
+            syncIncomplete = YES;
+            [pool drain];
+            return;
+        }
+        // Create directory of the file, if it doesn't exist
+        error = nil;
+        if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
+                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
+                                                          error:error];
+            });
+        }
+        // Check first if a local copy exists
+        if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
+            storedState.filePath = filePath;
+            storedState.hash = object.objectHash;
+            [self saveLocalState];
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility startAndEndActivityWithType:PithosActivityOther 
+                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", 
+                                                               pithosContainer.name, object.name] 
+                                                pithosAccount:pithosAccount];
+            });
+        } else {
+            [self syncOperationStarted];
+            __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
+                                                                                                containerName:pithosContainer.name 
+                                                                                                       object:object 
+                                                                                                   blockIndex:0 
+                                                                                                    blockSize:pithosContainer.blockSize];
+            objectRequest.delegate = self;
+            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
+                                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (0%%)", 
+                                                                                pithosContainer.name, object.name] 
+                                                                    totalBytes:object.bytes 
+                                                                  currentBytes:0 
+                                                                 pithosAccount:pithosAccount];
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility updateActivity:activity withMessage:activity.message];
+            });
+            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                      pithosContainer, @"pithosContainer", 
+                                      object, @"pithosObject", 
+                                      [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(pithosContainer.blockSize + 0.0)))], @"missingBlocks", 
+                                      [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex", 
+                                      filePath, @"filePath", 
+                                      activity, @"activity", 
+                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage", 
+                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage", 
+                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", pithosContainer.name, object.name], @"finishedActivityMessage", 
+                                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
+                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                                      NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector", 
+                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                                      nil];
+            [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
+                [activityFacility updateActivity:activity 
+                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", 
+                                                  objectRequest.containerName, 
+                                                  [[objectRequest.userInfo objectForKey:@"pithosObject"] name], 
+                                                  (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
+                                      totalBytes:activity.totalBytes 
+                                    currentBytes:(activity.currentBytes + size)];
+            }];
+            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
         }
-        __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
-                                                                                                          object:object 
-                                                                                                      blockIndex:0 
-                                                                                                       blockSize:blockSize];
-        objectRequest.delegate = self;
-        objectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
-        objectRequest.didFailSelector = @selector(requestFailed:);
-        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
-                                                                   message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name] 
-                                                                totalBytes:object.bytes 
-                                                              currentBytes:0];
-        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                  object, @"pithosObject", 
-                                  [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(blockSize + 0.0)))], @"missingBlocks", 
-                                  [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex", 
-                                  filePath, @"filePath", 
-                                  activity, @"activity", 
-                                  [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
-                                  [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage", 
-                                  [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage", 
-                                  [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
-                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
-                                  nil];
-        [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
-            [activityFacility updateActivity:activity 
-                                 withMessage:[NSString stringWithFormat:@"Downloading '%@' (%.0f%%)", 
-                                              [[objectRequest.userInfo valueForKey:@"pithosObject"] name], 
-                                              (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
-                                  totalBytes:activity.totalBytes 
-                                currentBytes:(activity.currentBytes + size)];
-        }];
-//        [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
-        [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
     } else {
         // Resume local object download
-        @synchronized(self) {
-            syncOperationCount++;
+        if (!remoteObjectExists || remoteIsDirectory) {
+            // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
+            syncIncomplete = YES;
+            [pool drain];
+            return;
+        }
+        // Create directory of the file, if it doesn't exist
+        error = nil;
+        if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
+                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
+                                                          error:error];
+            });
+        }
+        // Check first if a local copy exists
+        if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
+            storedState.filePath = filePath;
+            storedState.hash = object.objectHash;
+            // Delete incomplete temp download
+            storedState.tmpFilePath = nil;
+            [self saveLocalState];
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility startAndEndActivityWithType:PithosActivityOther 
+                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", 
+                                                               pithosContainer.name, object.name] 
+                                                pithosAccount:pithosAccount];
+            });
+        } else {
+            [self syncOperationStarted];
+            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos 
+                                                                                             containerName:pithosContainer.name 
+                                                                                                objectName:object.name];
+            objectRequest.delegate = self;
+            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
+                                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (0%%)", 
+                                                                                pithosContainer.name, object.name] 
+                                                                    totalBytes:object.bytes 
+                                                                  currentBytes:0 
+                                                                 pithosAccount:pithosAccount];
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility updateActivity:activity withMessage:activity.message];
+            });
+            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                      pithosContainer, @"pithosContainer", 
+                                      object, @"pithosObject", 
+                                      filePath, @"filePath", 
+                                      activity, @"activity", 
+                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage", 
+                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage", 
+                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", pithosContainer.name, object.name], @"finishedActivityMessage", 
+                                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
+                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                                      NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector", 
+                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                                      nil];
+            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
         }
-        ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithContainerName:containerName 
-                                                                                                   objectName:object.name];
-        objectRequest.delegate = self;
-        objectRequest.didFinishSelector = @selector(downloadObjectHashMapFinished:);
-        // The fail method for block download does exactly what we want
-        objectRequest.didFailSelector = @selector(requestFailed:);
-        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
-                                                                   message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name] 
-                                                                totalBytes:object.bytes 
-                                                              currentBytes:0];
-        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                  object, @"pithosObject", 
-                                  filePath, @"filePath", 
-                                  activity, @"activity", 
-                                  [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
-                                  [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage", 
-                                  [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage", 
-                                  [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
-                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
-                                  nil];
-//        [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
-        [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
     }
+    [pool drain];
 }
 
 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
                                   object:(ASIPithosObject *)object 
-                           localFilePath:(NSString *)filePath {
+                           localFilePath:(NSString *)filePath 
+                         pithosContainer:(ASIPithosContainer *)pithosContainer {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    [self syncOperationStarted];
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    BOOL isDirectory;
+    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
     if (currentState.isDirectory) {
         // Create remote directory object
-        @synchronized(self) {
-            syncOperationCount++;
+        if (!fileExists || !isDirectory) {
+            // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
+            [self syncOperationFinishedWithSuccess:NO];
+            [pool drain];
+            return;
         }
-        ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName 
-                                                                                                     objectName:object.name 
-                                                                                                           eTag:nil 
-                                                                                                    contentType:@"application/directory" 
-                                                                                                contentEncoding:nil 
-                                                                                             contentDisposition:nil 
-                                                                                                       manifest:nil 
-                                                                                                        sharing:nil 
-                                                                                                       isPublic:ASIPithosObjectRequestPublicIgnore 
-                                                                                                       metadata:nil 
-                                                                                                           data:[NSData data]];
+        ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
+                                                                                           containerName:pithosContainer.name 
+                                                                                              objectName:object.name 
+                                                                                                    eTag:nil 
+                                                                                             contentType:@"application/directory" 
+                                                                                         contentEncoding:nil 
+                                                                                      contentDisposition:nil 
+                                                                                                manifest:nil 
+                                                                                                 sharing:nil 
+                                                                                                isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                                metadata:nil 
+                                                                                                    data:[NSData data]];
         objectRequest.delegate = self;
-        objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
-        objectRequest.didFailSelector = @selector(requestFailed:);
+        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
         PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
-                                                                   message:[NSString stringWithFormat:@"Sync: Creating directory '%@'", object.name]];
+                                                                   message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@'", 
+                                                                            pithosContainer.name, object.name] 
+                                                             pithosAccount:pithosAccount];
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [activityFacility updateActivity:activity withMessage:activity.message];
+        });
         objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                  pithosContainer, @"pithosContainer", 
                                   object, @"pithosObject", 
                                   activity, @"activity", 
-                                  [NSString stringWithFormat:@"Sync: Creating directory '%@' (stopped)", object.name], @"stoppedActivityMessage", 
-                                  [NSString stringWithFormat:@"Sync: Creating directory '%@' (failed)", object.name], @"failedActivityMessage", 
-                                  [NSString stringWithFormat:@"Sync: Creating directory '%@' (finished)", object.name], @"finishedActivityMessage", 
+                                  [NSString stringWithFormat:@"Sync: Creating directory '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage", 
+                                  [NSString stringWithFormat:@"Sync: Creating directory '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage", 
+                                  [NSString stringWithFormat:@"Sync: Creating directory '%@/%@' (finished)", pithosContainer.name, object.name], @"finishedActivityMessage", 
                                   [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
                                   [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                                  NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
+                                  NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
                                   nil];
-//        [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
-        [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
-    } else if ([currentState.md5 isEqualToString:@" "] && [currentState.hashMapHash isEqualToString:@" "]) {
+        [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+    } else if (![currentState exists]) {
         // Delete remote object
-        @synchronized(self) {
-            syncOperationCount++;
+        if (fileExists) {
+            // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
+            syncIncomplete = YES;
+        }
+        if ([pithosContainer.name isEqualToString:@"trash"]) {
+            // Delete
+            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos 
+                                                                                            containerName:pithosContainer.name 
+                                                                                               objectName:object.name];
+            objectRequest.delegate = self;
+            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
+                                                                       message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@'", 
+                                                                                pithosContainer.name, object.name] 
+                                                                 pithosAccount:pithosAccount];
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility updateActivity:activity withMessage:activity.message];
+            });
+            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                      pithosContainer, @"pithosContainer", 
+                                      object, @"pithosObject", 
+                                      activity, @"activity", 
+                                      [NSString stringWithFormat:@"Sync: Deleting '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage", 
+                                      [NSString stringWithFormat:@"Sync: Deleting '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage", 
+                                      [NSString stringWithFormat:@"Sync: Deleting '%@/%@' (finished)", pithosContainer.name, object.name], @"finishedActivityMessage", 
+                                      [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
+                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                                      NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector", 
+                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                                      nil];
+            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+        } else {
+            // Move to container trash
+            NSString *safeName;
+            if ([PithosUtilities isContentTypeDirectory:object.contentType])
+                safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
+            else
+                safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
+            if (safeName) {
+                ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
+                                                                                       containerName:pithosContainer.name 
+                                                                                          objectName:object.name 
+                                                                            destinationContainerName:@"trash" 
+                                                                               destinationObjectName:safeName 
+                                                                                       checkIfExists:NO];
+                if (objectRequest) {
+                    objectRequest.delegate = self;
+                    objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                    objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
+                                                                               message:[NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'", 
+                                                                                        pithosContainer.name, object.name] 
+                                                                         pithosAccount:pithosAccount];
+                    dispatch_async(dispatch_get_main_queue(), ^{
+                        [activityFacility updateActivity:activity withMessage:activity.message];
+                    });
+                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                              pithosContainer, @"pithosContainer", 
+                                              object, @"pithosObject", 
+                                              activity, @"activity", 
+                                              [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage", 
+                                              [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage", 
+                                              [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (finished)", pithosContainer.name, object.name], @"finishedActivityMessage", 
+                                              [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
+                                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                                              NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector", 
+                                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
+                                              nil];
+                    [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+                } else {
+                    [self syncOperationFinishedWithSuccess:NO];
+                }
+            } else {
+                [self syncOperationFinishedWithSuccess:NO];
+            }
         }
-        ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:containerName 
-                                                                                                  objectName:object.name];
-        objectRequest.delegate = self;
-        objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
-        objectRequest.didFailSelector = @selector(requestFailed:);
-        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
-                                                                   message:[NSString stringWithFormat:@"Sync: Deleting '%@'", object.name]];
-        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
-                                  object, @"pithosObject", 
-                                  activity, @"activity", 
-                                  [NSString stringWithFormat:@"Sync: Deleting '%@' (stopped)", object.name], @"stoppedActivityMessage", 
-                                  [NSString stringWithFormat:@"Sync: Deleting '%@' (failed)", object.name], @"failedActivityMessage", 
-                                  [NSString stringWithFormat:@"Sync: Deleting '%@' (finished)", object.name], @"finishedActivityMessage", 
-                                  [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
-                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
-                                  nil];
-//        [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
-        [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
     } else {
         // Upload file to remote object
+        if (!fileExists || isDirectory) {
+            // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
+            [self syncOperationFinishedWithSuccess:NO];
+            [pool drain];
+            return;
+        }
         NSError *error = nil;
         object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
         if (object.contentType == nil)
         if (error)
             NSLog(@"contentType detection error: %@", error);
         NSArray *hashes = nil;
-        ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName 
-                                                                                              objectName:object.name 
-                                                                                             contentType:object.contentType 
-                                                                                               blockSize:blockSize 
-                                                                                               blockHash:blockHash 
-                                                                                                 forFile:filePath 
-                                                                                           checkIfExists:NO 
-                                                                                                  hashes:&hashes 
-                                                                                          sharingAccount:nil];
+        ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
+                                                                                    containerName:pithosContainer.name 
+                                                                                       objectName:object.name 
+                                                                                      contentType:object.contentType 
+                                                                                        blockSize:pithosContainer.blockSize 
+                                                                                        blockHash:pithosContainer.blockHash 
+                                                                                          forFile:filePath 
+                                                                                    checkIfExists:NO 
+                                                                                           hashes:&hashes 
+                                                                                   sharingAccount:nil];
         if (objectRequest) {
-            @synchronized(self) {
-                syncOperationCount++;
-            }
             objectRequest.delegate = self;
-            objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
-            objectRequest.didFailSelector = @selector(requestFailed:);
+            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
             PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
-                                                                       message:[NSString stringWithFormat:@"Sync: Uploading '%@' (0%%)", object.name]
-                                                                    totalBytes:[[objectRequest.userInfo valueForKey:@"bytes"] unsignedIntegerValue]
-                                                                  currentBytes:0];
+                                                                       message:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (0%%)", 
+                                                                                pithosContainer.name, object.name]
+                                                                    totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
+                                                                  currentBytes:0 
+                                                                 pithosAccount:pithosAccount];
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility updateActivity:activity withMessage:activity.message];
+            });
             [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
              [NSDictionary dictionaryWithObjectsAndKeys:
+              pithosContainer, @"pithosContainer", 
               object, @"pithosObject", 
               filePath, @"filePath", 
               hashes, @"hashes", 
               [NSString stringWithFormat:@"Sync: Uploading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
               [NSString stringWithFormat:@"Sync: Uploading '%@' (failed)", object.name], @"failedActivityMessage", 
               [NSString stringWithFormat:@"Sync: Uploading '%@' (100%%)", object.name], @"finishedActivityMessage", 
-              [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
+              [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
               [NSNumber numberWithUnsignedInteger:10], @"retries", 
+              NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", 
+              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
               nil]];
-//            [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
-            [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
+        } else {
+            [self syncOperationFinishedWithSuccess:NO];
         }
     }
-
+    [pool drain];
 }
 
 #pragma mark -
 #pragma mark ASIHTTPRequestDelegate
 
+- (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
+    // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
+    NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
+                                                                             selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) 
+                                                                               object:request] autorelease];
+    operation.completionBlock = ^{
+        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+        if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
+                                  withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+            });
+            [self syncOperationFinishedWithSuccess:NO];
+        }
+        [pool drain];
+    };
+    [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
+    [callbackQueue addOperation:operation];
+}
+
+- (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
+    if (request.isCancelled) {
+        // Request has been cancelled 
+        // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
+        [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
+                   withObject:request];
+    } else {
+        // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
+        NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
+                                                                                 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
+                                                                                   object:request] autorelease];
+        operation.completionBlock = ^{
+            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+            if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
+                                      withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+                });
+                [self syncOperationFinishedWithSuccess:NO];
+            }
+            [pool drain];
+        };
+        [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
+        [callbackQueue addOperation:operation];
+    }
+}
+
 - (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
     NSLog(@"Sync::list request finished: %@", containerRequest.url);
-    if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
+    if (operation.isCancelled) {
+        [self listRequestFailed:containerRequest];
+    } else if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
         if (containerRequest.responseStatusCode == 200) {
             NSArray *someObjects = [containerRequest objects];
             if (objects == nil) {
                 [objects addObjectsFromArray:someObjects];
             }
             if ([someObjects count] < 10000) {
-                self.blockHash = [containerRequest blockHash];
-                self.blockSize = [containerRequest blockSize];
-                self.lastModified = [containerRequest lastModified];
-                self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
+                ASIPithosContainer *pithosContainer = [pithosContainers objectAtIndex:containersIndex];
+                pithosContainer.blockHash = [containerRequest blockHash];
+                pithosContainer.blockSize = [containerRequest blockSize];
+                pithosContainer.lastModified = [containerRequest lastModified];
+                NSMutableDictionary *containerRemoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
                 for (ASIPithosObject *object in objects) {
-                    [remoteObjects setObject:object forKey:object.name];
+                    [containerRemoteObjects setObject:object forKey:object.name];
                 }
+                [remoteObjects setObject:containerRemoteObjects forKey:pithosContainer.name];
                 [objects release];
                 objects = nil;
             } else {
                 // Do an additional request to fetch more objects
-                ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName 
-                                                                                                                          limit:0 
-                                                                                                                         marker:[[someObjects lastObject] name] 
-                                                                                                                         prefix:nil 
-                                                                                                                      delimiter:nil 
-                                                                                                                           path:nil 
-                                                                                                                           meta:nil 
-                                                                                                                         shared:NO 
-                                                                                                                          until:nil 
-                                                                                                                ifModifiedSince:lastModified];
+                ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
+                                                                                                           containerName:[[pithosContainers objectAtIndex:containersIndex] name] 
+                                                                                                                   limit:0 
+                                                                                                                  marker:[[someObjects lastObject] name] 
+                                                                                                                  prefix:nil 
+                                                                                                               delimiter:nil 
+                                                                                                                    path:nil 
+                                                                                                                    meta:nil 
+                                                                                                                  shared:NO 
+                                                                                                                   until:nil 
+                                                                                                         ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]];
                 newContainerRequest.delegate = self;
-                newContainerRequest.didFinishSelector = @selector(listRequestFinished:);
-                newContainerRequest.didFailSelector = @selector(listRequestFailed:);
-                newContainerRequest.userInfo = newContainerRequest.userInfo;
+                newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+                newContainerRequest.userInfo = containerRequest.userInfo;
                 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
-//                [[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
-                [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityHigh]];
+                [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+                [pool drain];
                 return;
             }
+        } else {
+            ASIPithosContainer *pithosContainer = [pithosContainers objectAtIndex:containersIndex];
+            NSMutableDictionary *containerRemoteObjects = [previousRemoteObjects objectForKey:pithosContainer.name];
+            if (containerRemoteObjects) 
+                [remoteObjects setObject:containerRemoteObjects forKey:pithosContainer.name];
         }
-        [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
-                          withMessage:@"Sync: Getting server listing (finished)"];
-        NSFileManager *fileManager = [NSFileManager defaultManager];
-        NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:containerName];
-        NSError *error = nil;
-        NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:containerDirectoryPath error:&error];
-        if (error) {
-            [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
-                                                    message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
-                                                      error:error];
-            [activityFacility startAndEndActivityWithType:PithosActivityOther message:@"Sync: Failed to read contents of sync directory"];
-            @synchronized(self) {
-                // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccesfully
-                syncOperationCount = 0;
-                if (newSyncRequested)
-                    [self sync];
-            }
+        containersIndex++;
+        if (containersIndex < containersCount) {
+            // Do a request for the next container
+            ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
+                                                                                                       containerName:[[pithosContainers objectAtIndex:containersIndex] name] 
+                                                                                                               limit:0 
+                                                                                                              marker:nil 
+                                                                                                              prefix:nil 
+                                                                                                           delimiter:nil 
+                                                                                                                path:nil 
+                                                                                                                meta:nil 
+                                                                                                              shared:NO 
+                                                                                                               until:nil 
+                                                                                                     ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]];
+            newContainerRequest.delegate = self;
+            newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+            newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+            newContainerRequest.userInfo = containerRequest.userInfo;
+            [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+            [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+            [pool drain];
             return;
         }
-        for (NSString *objectName in subPaths) {
-            if (![storedLocalObjectStates objectForKey:objectName]) {
-                [storedLocalObjectStates setObject:[PithosLocalObjectState nullObjectState] forKey:objectName];
-            }
+        self.previousRemoteObjects = remoteObjects;
+        
+        if (operation.isCancelled) {
+            [self listRequestFailed:containerRequest];
+            [pool drain];
+            return;
         }
-        [self saveLocalState];
 
-        for (NSString *objectName in remoteObjects) {
-            if (![storedLocalObjectStates objectForKey:objectName])
-                [storedLocalObjectStates setObject:[PithosLocalObjectState nullObjectState] forKey:objectName];
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
+                              withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+        });
+        NSFileManager *fileManager = [NSFileManager defaultManager];
+        NSError *error;
+        NSMutableDictionary *subPaths = [NSMutableDictionary dictionaryWithCapacity:containersCount];
+        NSUInteger subPathsCount = 0;
+        NSString *containerDirectoryPath;
+        NSArray *containerSubPaths;
+        for (ASIPithosContainer *pithosContainer in pithosContainers) {
+            error = nil;
+            containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
+            containerSubPaths = [fileManager subpathsOfDirectoryAtPath:containerDirectoryPath error:&error];
+            if (error) {
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
+                                                            message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
+                                                              error:error];
+                    [activityFacility startAndEndActivityWithType:PithosActivityOther 
+                                                          message:@"Sync: Failed to read contents of sync directory" 
+                                                    pithosAccount:pithosAccount];
+                });
+                // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccessfully
+                [self syncOperationFinishedWithSuccess:NO];
+                [pool drain];
+                return;
+            }
+            [subPaths setObject:containerSubPaths forKey:pithosContainer.name];
+            subPathsCount += [containerSubPaths count];
+        }        
+        
+        if (operation.isCancelled) {
+            operation.completionBlock = nil;
+            [self syncOperationFinishedWithSuccess:NO];
+            [pool drain];
+            return;
         }
 
-        for (NSString *objectName in [[storedLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
-            NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
-            if ([objectName hasSuffix:@"/"])
-                filePath = [filePath stringByAppendingString:@":"];
-            ASIPithosObject *object = [ASIPithosObject object];
-            object.name = objectName;
-            NSLog(@"Sync::object name: %@", objectName);
-            
-            PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:object.name];
-            PithosLocalObjectState *currentLocalObjectState = [PithosLocalObjectState nullObjectState];
-            BOOL isDirectory;
-            if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
-                if (isDirectory) {
-                    currentLocalObjectState.isDirectory = YES;
+        self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:subPathsCount];
+        NSMutableDictionary *containerStoredLocalObjectStates;
+        for (ASIPithosContainer *pithosContainer in pithosContainers) {
+            containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
+            containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
+            for (NSString *objectName in [subPaths objectForKey:pithosContainer.name]) {
+                if (operation.isCancelled) {
+                    operation.completionBlock = nil;
+                    [self saveLocalState];
+                    [self syncOperationFinishedWithSuccess:NO];
+                    [pool drain];
+                    return;
+                }
+
+                PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
+                NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
+                if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
+                    // New or modified existing local object, compute current state
+                    if (!storedLocalObjectState)
+                        // For new local object, also create empty stored state
+                        [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
+                    [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath 
+                                                                                               blockHash:pithosContainer.blockHash 
+                                                                                               blockSize:pithosContainer.blockSize] 
+                                                          forKey:filePath];
                 } else {
-                    currentLocalObjectState.md5 = (NSString *)FileMD5HashCreateWithPath((CFStringRef)filePath, 
-                                                                                        FileHashDefaultChunkSizeForReadingData);
-                    currentLocalObjectState.hashMapHash = [HashMapHash calculateHashMapHash:[HashMapHash calculateObjectHashMap:filePath 
-                                                                                                                  withBlockHash:blockHash 
-                                                                                                                   andBlockSize:blockSize]];
+                    // Local object hasn't changed, set stored state also to current
+                    [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
                 }
             }
-            if (currentLocalObjectState.isDirectory)
-                object.contentType = @"application/directory";
-            
-            NSString *remoteObjectHash = @" ";
-            BOOL remoteObjectIsDirectory = NO;
-            ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
-            if (remoteObject) {
-                remoteObjectHash = remoteObject.hash;
-                if ([remoteObject.contentType isEqualToString:@"application/directory"]) {
-                    remoteObjectIsDirectory = YES;
-                    object.contentType = @"application/directory";
+            [self saveLocalState];
+        }
+        
+        if (operation.isCancelled) {
+            operation.completionBlock = nil;
+            [self syncOperationFinishedWithSuccess:NO];
+            [pool drain];
+            return;
+        }
+
+        for (ASIPithosContainer *pithosContainer in pithosContainers) {
+            containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
+            for (NSString *objectName in [remoteObjects objectForKey:pithosContainer.name]) {
+                if (operation.isCancelled) {
+                    operation.completionBlock = nil;
+                    [self saveLocalState];
+                    [self syncOperationFinishedWithSuccess:NO];
+                    [pool drain];
+                    return;
                 }
+
+                if (![containerStoredLocalObjectStates objectForKey:objectName])
+                    [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
             }
-            NSLog(@"Sync::remote object is directory: %d", remoteObjectIsDirectory);
+            [self saveLocalState];
+        }
+
+        if (operation.isCancelled) {
+            operation.completionBlock = nil;
+            [self syncOperationFinishedWithSuccess:NO];
+            [pool drain];
+            return;
+        }
+
+        NSMutableDictionary *containerRemoteObjects;
+        for (ASIPithosContainer *pithosContainer in pithosContainers) {
+            containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
+            containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
+            containerRemoteObjects = [remoteObjects objectForKey:pithosContainer.name];
+            for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
+                if (operation.isCancelled) {
+                    operation.completionBlock = nil;
+                    [self syncOperationFinishedWithSuccess:NO];
+                    [pool drain];
+                    return;
+                }
+
+                NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
+                if ([objectName hasSuffix:@"/"])
+                    filePath = [filePath stringByAppendingString:@":"];
+                ASIPithosObject *object = [ASIPithosObject object];
+                object.name = objectName;
+                NSLog(@"Sync::object name: %@", object.name);
+            
+                PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
+                PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
+                if (!currentLocalObjectState)
+                    currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath 
+                                                                                     blockHash:pithosContainer.blockHash 
+                                                                                     blockSize:pithosContainer.blockSize];
+//                if (currentLocalObjectState.isDirectory)
+//                    object.contentType = @"application/directory";
             
-            BOOL localStateHasChanged = [self localStateHasChanged:storedLocalObjectState currentState:currentLocalObjectState];
-            BOOL serverStateHasChanged = [self serverStateHasChanged:storedLocalObjectState 
-                                                    remoteObjectHash:remoteObjectHash 
-                                             remoteObjectIsDirectory:remoteObjectIsDirectory];
-            NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
-            // XXX shouldn't we first do all the deletes? in order not to face a dir that becomes a file and vice versa
-            if (!localStateHasChanged) {
-                // Local state hasn't changed
-                if (serverStateHasChanged) {
-                    // Server state has changed
-                    // Update local state to match that of the server 
-                    object.bytes = [remoteObject bytes];
-                    object.version = [remoteObject version];
-                    object.contentType = [remoteObject contentType];
-                    object.hash = remoteObjectHash;
-                    [self updateLocalStateWithObject:object localFilePath:filePath];
+                PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
+                ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
+                if (remoteObject) {
+                    if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
+                        remoteObjectState.isDirectory = YES;
+//                        object.contentType = @"application/directory";
+                    } else {
+                        remoteObjectState.hash = remoteObject.objectHash;
+                    }
                 }
-            } else {
-                // Local state has changed
-                if (!serverStateHasChanged) {
-                    // Server state hasn't changed
-                    [self updateServerStateWithCurrentState:currentLocalObjectState 
-                                                     object:object 
-                                              localFilePath:filePath];
-                } else {
-                    // Server state has also changed
-                    if (remoteObjectIsDirectory && currentLocalObjectState.isDirectory) {
-                        // Both did the same change (directory)
-                        storedLocalObjectState.isDirectory = YES;
-                        [self saveLocalState];
-                    } else if ([remoteObjectHash isEqualToString:currentLocalObjectState.md5] || 
-                               [remoteObjectHash isEqualToString:currentLocalObjectState.hashMapHash]) {
-                        // Both did the same change (object edit or delete)
-                        if ([remoteObjectHash length] == 32) 
-                            storedLocalObjectState.md5 = remoteObjectHash;
-                        else if ([remoteObjectHash length] == 64)
-                            storedLocalObjectState.hashMapHash = remoteObjectHash;
-                        else if ([remoteObjectHash isEqualToString:@" "])
-                            [storedLocalObjectStates removeObjectForKey:object.name];
+
+                BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
+                BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
+                NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
+                if (!localStateHasChanged) {
+                    // Local state hasn't changed
+                    if (serverStateHasChanged) {
+                        // Server state has changed
+                        // Update local state to match that of the server 
+                        object.bytes = remoteObject.bytes;
+                        object.version = remoteObject.version;
+                        object.contentType = remoteObject.contentType;
+                        object.objectHash = remoteObject.objectHash;
+                        [self updateLocalStateWithObject:object localFilePath:filePath pithosContainer:pithosContainer];
+                    } else if (!remoteObject && ![currentLocalObjectState exists]) {
+                        // Server state hasn't changed
+                        // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
+                        [containerStoredLocalObjectStates removeObjectForKey:objectName];
                         [self saveLocalState];
+                    }
+                } else {
+                    // Local state has changed
+                    if (!serverStateHasChanged) {
+                        // Server state hasn't changed
+                        if (currentLocalObjectState.isDirectory)
+                            object.contentType = @"application/directory";
+                        else
+                            object.objectHash = currentLocalObjectState.hash;
+                        [self updateServerStateWithCurrentState:currentLocalObjectState 
+                                                         object:object 
+                                                  localFilePath:filePath 
+                                                pithosContainer:pithosContainer];
                     } else {
-                        // Conflict, we ask the user which change to keep
-                        NSString *informativeText;
-                        NSString *firstButtonText;
-                        NSString *secondButtonText;
-                        
-                        if ([remoteObjectHash isEqualToString:@" "]) {
-                            // Remote object has been deleted
-                            informativeText = [NSString stringWithFormat:@"'%@' has been modified locally, while it has been deleted from server.", object.name ];
-                            firstButtonText = @"Delete local file";
-                            secondButtonText = @"Upload file to server";
-                        } else if ([currentLocalObjectState.md5 isEqualToString:@" "] && [currentLocalObjectState.hashMapHash isEqualToString:@" "]) {
-                            informativeText = [NSString stringWithFormat:@"'%@' has been modified on the server, while it has been deleted locally.", object.name];
-                            firstButtonText = @"Download file from server";
-                            secondButtonText = @"Delete file on server";
+                        // Server state has also changed
+                        if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
+                            // Both did the same change (directory)
+                            storedLocalObjectState.filePath = filePath;
+                            storedLocalObjectState.isDirectory = YES;
+                            [self saveLocalState];
+                        } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
+                            // Both did the same change (object edit or delete)
+                            if (![remoteObjectState exists]) {
+                                [containerStoredLocalObjectStates removeObjectForKey:object.name];
+                            } else {
+                                storedLocalObjectState.filePath = filePath;
+                                storedLocalObjectState.hash = remoteObjectState.hash;
+                            }
+                            [self saveLocalState];
                         } else {
-                            informativeText = [NSString stringWithFormat:@"'%@' has been modifed both locally and on the server.", object.name];
-                            firstButtonText = @"Keep server version";
-                            secondButtonText = @"Keep local version";
-                        }
-                        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
-                        [alert setMessageText:@"Conflict"];
-                        [alert setInformativeText:informativeText];
-                        [alert addButtonWithTitle:firstButtonText];
-                        [alert addButtonWithTitle:secondButtonText];
-                        [alert addButtonWithTitle:@"Do nothing"];
-                        NSInteger choice = [alert runModal];
-                        if (choice == NSAlertFirstButtonReturn) {
-                            object.bytes = [remoteObject bytes];
-                            object.version = [remoteObject version];
-                            object.contentType = [remoteObject contentType];
-                            object.hash = remoteObjectHash;
-                            [self updateLocalStateWithObject:object localFilePath:filePath];
-                        } if (choice == NSAlertSecondButtonReturn) {
-                            [self updateServerStateWithCurrentState:currentLocalObjectState 
-                                                             object:object 
-                                                      localFilePath:filePath];
+                            // Conflict, we ask the user which change to keep
+                            NSString *informativeText;
+                            NSString *firstButtonText;
+                            NSString *secondButtonText;
+                            
+                            if (![remoteObjectState exists]) {
+                                // Remote object has been deleted
+                                informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified locally, while it has been deleted from server.", 
+                                                   pithosContainer.name, object.name ];
+                                firstButtonText = @"Delete local file";
+                                secondButtonText = @"Upload file to server";
+                            } else if (![currentLocalObjectState exists]) {
+                                informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified on the server, while it has been deleted locally.", 
+                                                   pithosContainer.name, object.name];
+                                firstButtonText = @"Download file from server";
+                                secondButtonText = @"Delete file on server";
+                            } else {
+                                informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified both locally and on the server.", 
+                                                   pithosContainer.name, object.name];
+                                firstButtonText = @"Keep server version";
+                                secondButtonText = @"Keep local version";
+                            }
+                            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+                            [alert setMessageText:@"Conflict"];
+                            [alert setInformativeText:informativeText];
+                            [alert addButtonWithTitle:firstButtonText];
+                            [alert addButtonWithTitle:secondButtonText];
+                            [alert addButtonWithTitle:@"Do nothing"];
+                            NSInteger choice = [alert runModal];
+                            if (choice == NSAlertFirstButtonReturn) {
+                                object.bytes = remoteObject.bytes;
+                                object.version = remoteObject.version;
+                                object.contentType = remoteObject.contentType;
+                                object.objectHash = remoteObject.objectHash;
+                                [self updateLocalStateWithObject:object localFilePath:filePath pithosContainer:pithosContainer];
+                            } if (choice == NSAlertSecondButtonReturn) {
+                                if (currentLocalObjectState.isDirectory)
+                                    object.contentType = @"application/directory";
+                                else
+                                    object.objectHash = currentLocalObjectState.hash;
+                                [self updateServerStateWithCurrentState:currentLocalObjectState 
+                                                                 object:object 
+                                                          localFilePath:filePath 
+                                                        pithosContainer:pithosContainer];
+                            }
                         }
                     }
                 }
             }
         }
-        @synchronized(self) {
-            syncOperationCount--;
-            if (newSyncRequested && !syncOperationCount)
-                [self sync];
-        }
+        [self syncOperationFinishedWithSuccess:YES];
     } else {
-        NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
-        if (retries > 0) {
-            ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
-            [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
-//            [[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
-            [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityHigh]];
-        } else {
-            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
-                              withMessage:@"Sync: Getting server listing (failed)"];
-            @synchronized(self) {
-                // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
-                syncOperationCount = 0;
-                if (newSyncRequested)
-                    [self sync];
-            }
-        }
+        [self listRequestFailed:containerRequest];
     }
+    [pool drain];
 }
 
 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
-    if ([containerRequest isCancelled]) {
-        [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
-                          withMessage:@"Sync: Getting server listing (stopped)"];
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
+    if (operation.isCancelled) {
         [objects release];
         objects = nil;
-        @synchronized(self) {
-            syncOperationCount = 0;
-        }
+        [pool drain];
+        return;        
+    }
+    if (containerRequest.isCancelled) {
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
+                              withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
+        });
+        [objects release];
+        objects = nil;
+        [self syncOperationFinishedWithSuccess:NO];
+        [pool drain];
         return;
     }
     // If the server listing fails, the sync should start over, so just retrying is enough
     NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
     if (retries > 0) {
-        ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
+        ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
         [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
-//        [[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
-        [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityVeryHigh]];
+        [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
     } else {
-        [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
-                          withMessage:@"Sync: Getting server listing (failed)"];
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
+                              withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
+        });
         [objects release];
         objects = nil;
-        @synchronized(self) {
-            // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
-            syncOperationCount = 0;
-            if (newSyncRequested)
-                [self sync];
-        }
+        // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
+        [self syncOperationFinishedWithSuccess:NO];
     }
+    [pool drain];
 }
 
 - (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
     NSLog(@"Sync::download object block finished: %@", objectRequest.url);
-    if (objectRequest.responseStatusCode == 206) {
+    if (operation.isCancelled) {
+        [self requestFailed:objectRequest];
+    } else if (objectRequest.responseStatusCode == 206) {
+        ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
         ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
         NSFileManager *fileManager = [NSFileManager defaultManager];
         NSError *error;
         
         NSString *downloadsDirPath = self.tempDownloadsDirPath;
         if (!downloadsDirPath) {
-            [activityFacility endActivity:activity 
-                              withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
-            @synchronized(self) {
-                syncOperationCount--;
-                if (newSyncRequested && !syncOperationCount)
-                    [self sync];
-            }
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility endActivity:activity 
+                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+            });
+            [self syncOperationFinishedWithSuccess:NO];
+            [pool drain];
             return;
         }
         
-        PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
-        if ((storedState.tmpDownloadFile == nil) || ![fileManager fileExistsAtPath:storedState.tmpDownloadFile]) {
+        PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
+        if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
             NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
             const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
             char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
             NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
             free(tempFileNameCString);
             if (fileDescriptor == -1) {
-                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error" 
-                                                        message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", storedState.tmpDownloadFile] 
-                                                          error:nil];
-                [activityFacility endActivity:activity 
-                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
-                @synchronized(self) {
-                    syncOperationCount--;
-                    if (newSyncRequested && !syncOperationCount)
-                        [self sync];
-                }
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error" 
+                                                            message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath] 
+                                                              error:nil];
+                    [activityFacility endActivity:activity 
+                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+                });
+                [self syncOperationFinishedWithSuccess:NO];
+                [pool drain];
                 return;
             }
             close(fileDescriptor);
-            storedState.tmpDownloadFile = tempFilePath;
+            storedState.tmpFilePath = tempFilePath;
             [self saveLocalState];
         }
         
 
         NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
-        NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpDownloadFile];
-        [tempFileHandle seekToFileOffset:missingBlockIndex*blockSize];
+        NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
+        [tempFileHandle seekToFileOffset:missingBlockIndex*pithosContainer.blockSize];
         [tempFileHandle writeData:[objectRequest responseData]];
         [tempFileHandle closeFile];
 
         if (missingBlockIndex == NSNotFound) {
             NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
             NSString *dirPath = [filePath stringByDeletingLastPathComponent];
-            if ([fileManager fileExistsAtPath:filePath]) {
-                error = nil;
-                // XXX don't delete but move to local trash instead
-                [fileManager removeItemAtPath:filePath error:&error];
-                if (error != nil) {
-                    [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
-                                                            message:[NSString stringWithFormat:@"Cannot remove file at '%@'", filePath] 
-                                                              error:error];
+            if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath pithosContainer:pithosContainer]) {
+                dispatch_async(dispatch_get_main_queue(), ^{
                     [activityFacility endActivity:activity 
                                       withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
-                    @synchronized(self) {
-                        syncOperationCount--;
-                        if (newSyncRequested && !syncOperationCount)
-                            [self sync];
-                    }
-                    return;
-                }
+                });
+                [self syncOperationFinishedWithSuccess:NO];
+                [pool drain];
+                return;
             } else if (![fileManager fileExistsAtPath:dirPath]) {
                 // File doesn't exist but also the containing directory doesn't exist
                 // In most cases this should have been resolved as an update of the corresponding local object,
                 error = nil;
                 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
                 if (error != nil) {
-                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
-                                                            message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath] 
-                                                              error:error];
-                    [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
-                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
-                    @synchronized(self) {
-                        syncOperationCount--;
-                        if (newSyncRequested && !syncOperationCount)
-                            [self sync];
-                    }
+                    dispatch_async(dispatch_get_main_queue(), ^{
+                        [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
+                                                                message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath] 
+                                                                  error:error];
+                        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                                          withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+                    });
+                    [self syncOperationFinishedWithSuccess:NO];
+                    [pool drain];
                     return;
                 }
             }
             // Move file from tmp download
             error = nil;
-            [fileManager moveItemAtPath:storedState.tmpDownloadFile toPath:filePath error:&error];
+            [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
             if (error != nil) {
-                [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
-                                                        message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpDownloadFile, filePath] 
-                                                          error:error];
-                [activityFacility endActivity:activity 
-                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
-                @synchronized(self) {
-                    syncOperationCount--;
-                    if (newSyncRequested && !syncOperationCount)
-                        [self sync];
-                }
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
+                                                            message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath] 
+                                                              error:error];
+                    [activityFacility endActivity:activity 
+                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+                });
+                [self syncOperationFinishedWithSuccess:NO];
+                [pool drain];
                 return;
             }
-            [activityFacility endActivity:activity 
-                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
-                               totalBytes:activity.totalBytes 
-                             currentBytes:activity.totalBytes];
-
-            if ([object.hash length] == 32) {
-                storedState.md5 = object.hash;
-                storedState.hashMapHash = @" ";
-            } else if([object.hash length] == 64) {
-                storedState.md5 = @" ";
-                storedState.hashMapHash = object.hash;
-            } else {
-                storedState.md5 = @" ";
-                storedState.hashMapHash = @" ";
-            }
-            
-            storedState.tmpDownloadFile = nil;
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility endActivity:activity 
+                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
+                                   totalBytes:activity.totalBytes 
+                                 currentBytes:activity.totalBytes];
+            });
+
+            storedState.filePath = filePath;
+            storedState.hash = object.objectHash;
+            storedState.tmpFilePath = nil;
             [self saveLocalState];
-            
-            @synchronized(self) {
-                syncOperationCount--;
-                if (newSyncRequested && !syncOperationCount)
-                    [self sync];
-            }
+            [self syncOperationFinishedWithSuccess:YES];
+            [pool drain];
             return;
         } else {
-            if (newSyncRequested) {
-                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
-                                  withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
-                @synchronized(self) {
-                    syncOperationCount--;
-                    if (!syncOperationCount)
-                        [self sync];
-                }
-                return;
+            if (newSyncRequested || syncLate || operation.isCancelled) {
+                [self requestFailed:objectRequest];
             } else {
-                __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
-                                                                                                                     object:object 
-                                                                                                                 blockIndex:missingBlockIndex 
-                                                                                                                  blockSize:blockSize];
+                __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
+                                                                                                       containerName:pithosContainer.name 
+                                                                                                              object:object 
+                                                                                                          blockIndex:missingBlockIndex 
+                                                                                                           blockSize:pithosContainer.blockSize];
                 newObjectRequest.delegate = self;
-                newObjectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
-                newObjectRequest.didFailSelector = @selector(downloadObjectBlockOrHashMapFailed:);
+                newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
                 newObjectRequest.userInfo = objectRequest.userInfo;
                 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
                 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
                 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
                     [activityFacility updateActivity:activity 
-                                         withMessage:[NSString stringWithFormat:@"Downloading '%@' (%.0f%%)", 
-                                                      object.name, 
+                                         withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", 
+                                                      newObjectRequest.containerName, object.name, 
                                                       (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
                                           totalBytes:activity.totalBytes 
                                         currentBytes:(activity.currentBytes + size)];
                 }];
-//                [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
-                [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityHigh]];
+                [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
             }
         }
     } else if (objectRequest.responseStatusCode == 412) {
         // The object has changed on the server
-        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
-                          withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
-        @synchronized(self) {
-            syncOperationCount--;
-            if (newSyncRequested && !syncOperationCount)
-                [self sync];
-        }
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                              withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
+        });
+        [self syncOperationFinishedWithSuccess:NO];
     } else {
         [self requestFailed:objectRequest];
     }
+    [pool drain];
 }
 
 - (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
     NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
-    if (objectRequest.responseStatusCode == 200) {
-        if (newSyncRequested) {
-            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
-                              withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
-            @synchronized(self) {
-                syncOperationCount--;
-                if (!syncOperationCount)
-                    [self sync];
-            }
-            return;
+    if (operation.isCancelled) {
+        [self requestFailed:objectRequest];
+    } else if (objectRequest.responseStatusCode == 200) {
+        if (newSyncRequested || syncLate || operation.isCancelled) {
+            [self requestFailed:objectRequest];
         } else {
+            ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
             ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
-            PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
-            if ([PithosUtilities bytesOfFile:storedState.tmpDownloadFile] > object.bytes)
-                [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpDownloadFile] truncateFileAtOffset:object.bytes];
+            PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
+            if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
+                [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
             PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
-            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpDownloadFile 
-                                                                    blockSize:blockSize 
-                                                                    blockHash:blockHash 
+            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath 
+                                                                    blockSize:pithosContainer.blockSize 
+                                                                    blockHash:pithosContainer.blockHash 
                                                                    withHashes:[objectRequest hashes]];
             NSUInteger missingBlockIndex = [missingBlocks firstIndex];
-            [activityFacility endActivity:activity 
-                              withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
-                                           object.name, 
-                                           (100*(activity.totalBytes - [missingBlocks count]*blockSize + 0.0)/(activity.totalBytes + 0.0))] 
-                               totalBytes:activity.totalBytes 
-                             currentBytes:(activity.totalBytes - [missingBlocks count]*blockSize)];
-
-            __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
-                                                                                                                 object:object 
-                                                                                                             blockIndex:missingBlockIndex 
-                                                                                                              blockSize:blockSize];
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility updateActivity:activity 
+                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", 
+                                                  pithosContainer.name, object.name, 
+                                                  (100*(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize + 0.0)/(activity.totalBytes + 0.0))] 
+                                      totalBytes:activity.totalBytes 
+                                    currentBytes:(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize)];
+            });
+
+            __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
+                                                                                                   containerName:pithosContainer.name 
+                                                                                                          object:object 
+                                                                                                      blockIndex:missingBlockIndex 
+                                                                                                       blockSize:pithosContainer.blockSize];
             newObjectRequest.delegate = self;
-            newObjectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
-            newObjectRequest.didFailSelector = @selector(downloadObjectBlockOrHashMapFailed:);
+            newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+            newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
             newObjectRequest.userInfo = objectRequest.userInfo;
             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
             [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
                 [activityFacility updateActivity:activity 
-                                     withMessage:[NSString stringWithFormat:@"Downloading '%@' (%.0f%%)", 
-                                                  object.name, 
+                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", 
+                                                  newObjectRequest.containerName, object.name, 
                                                   (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
                                       totalBytes:activity.totalBytes 
                                     currentBytes:(activity.currentBytes + size)];
             }];
-//            [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
-            [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityHigh]];
+            [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
         }
     } else {
         [self requestFailed:objectRequest];
     }
+    [pool drain];
 }
 
 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
     NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
-    if (objectRequest.responseStatusCode == 201) {
-        PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
+    if (operation.isCancelled) {
+        [self requestFailed:objectRequest];
+    } else if (objectRequest.responseStatusCode == 201) {
+        PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] 
+                                               objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
         storedState.isDirectory = YES;
         [self saveLocalState];
-        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
-                          withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
-        @synchronized(self) {
-            syncOperationCount--;
-            if (newSyncRequested && !syncOperationCount)
-                [self sync];
-        }
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+        });
+        [self syncOperationFinishedWithSuccess:YES];
+    } else {
+        [self requestFailed:objectRequest];
+    }
+    [pool drain];
+}
+
+- (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+    NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
+    if (operation.isCancelled) {
+        [self requestFailed:objectRequest];
+    } else if (objectRequest.responseStatusCode == 201) {
+        [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] 
+         removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
+        [self saveLocalState];
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+        });
+        [self syncOperationFinishedWithSuccess:YES];
     } else {
         [self requestFailed:objectRequest];
     }
+    [pool drain];
 }
 
 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
     NSLog(@"Sync::delete object finished: %@", objectRequest.url);
-    if (objectRequest.responseStatusCode == 204) {
-        [storedLocalObjectStates removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
+    if (operation.isCancelled) {
+        [self requestFailed:objectRequest];
+    } else if (objectRequest.responseStatusCode == 204) {
+        [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] 
+         removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
         [self saveLocalState];
-        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
-                          withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
-        @synchronized(self) {
-            syncOperationCount--;
-            if (newSyncRequested && !syncOperationCount)
-                [self sync];
-        }
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+        });
+        [self syncOperationFinishedWithSuccess:YES];
     } else {
         [self requestFailed:objectRequest];
     }
+    [pool drain];
 }
 
 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
     NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
+    ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
     ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
-    PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
+    PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
     PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
     NSUInteger totalBytes = activity.totalBytes;
     NSUInteger currentBytes = activity.currentBytes;
-    if (objectRequest.responseStatusCode == 201) {
+    if (operation.isCancelled) {
+        [self requestFailed:objectRequest];
+    } else if (objectRequest.responseStatusCode == 201) {
         NSLog(@"Sync::object created: %@", objectRequest.url);
-        NSString *eTag = [objectRequest eTag];
-        if ([eTag length] == 32)
-            storedState.md5 = eTag;
-        else if([eTag length] == 64)
-            storedState.hashMapHash = eTag;
+        storedState.filePath = [objectRequest.userInfo objectForKey:@"filePath"];
+        storedState.hash = object.objectHash;
         [self saveLocalState];
-        [activityFacility endActivity:activity 
-                          withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
-                           totalBytes:totalBytes 
-                         currentBytes:totalBytes];
-        @synchronized(self) {
-            syncOperationCount--;
-            if (newSyncRequested && !syncOperationCount)
-                [self sync];
-        }
-    } else if (objectRequest.responseStatusCode == 409) {
-        if (newSyncRequested) {
+        dispatch_async(dispatch_get_main_queue(), ^{
             [activityFacility endActivity:activity 
-                              withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
-            @synchronized(self) {
-                syncOperationCount--;
-                if (!syncOperationCount)
-                    [self sync];
-            }
-            return;
+                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
+                               totalBytes:totalBytes 
+                             currentBytes:totalBytes];
+        });
+        [self syncOperationFinishedWithSuccess:YES];
+    } else if (objectRequest.responseStatusCode == 409) {
+        if (newSyncRequested || syncLate || operation.isCancelled) {
+            [self requestFailed:objectRequest];
         } else {
             NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
             if (iteration == 0) {
                 NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
-                [activityFacility endActivity:activity 
-                                  withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]]; 
-                @synchronized(self) {
-                    syncOperationCount--;
-                }
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
+                });
+                [self syncOperationFinishedWithSuccess:NO];
+                [pool drain];
                 return;
             }
             NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
             NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
-                                                      withMissingHashesResponse:[objectRequest responseString]];
-            if (totalBytes >= [missingBlocks count]*blockSize)
-                currentBytes = totalBytes - [missingBlocks count]*blockSize;
-            [activityFacility updateActivity:activity 
-                                 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
-                                  totalBytes:totalBytes 
-                                currentBytes:currentBytes];
+                                                              withMissingHashes:[objectRequest hashes]];
+            if (totalBytes >= [missingBlocks count]*pithosContainer.blockSize)
+                currentBytes = totalBytes - [missingBlocks count]*pithosContainer.blockSize;
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [activityFacility updateActivity:activity 
+                                     withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)", 
+                                                  pithosContainer.name, object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
+                                      totalBytes:totalBytes 
+                                    currentBytes:currentBytes];
+            });
             NSUInteger missingBlockIndex = [missingBlocks firstIndex];
-            __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName 
-                                                                                                                        blockSize:blockSize 
-                                                                                                                          forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
-                                                                                                                missingBlockIndex:missingBlockIndex 
-                                                                                                                   sharingAccount:nil];
+            __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
+                                                                                                             containerName:pithosContainer.name 
+                                                                                                                 blockSize:pithosContainer.blockSize 
+                                                                                                                   forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
+                                                                                                         missingBlockIndex:missingBlockIndex 
+                                                                                                            sharingAccount:nil];
             newContainerRequest.delegate = self;
-            newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
-            newContainerRequest.didFailSelector = @selector(requestFailed:);
+            newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+            newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
             newContainerRequest.userInfo = objectRequest.userInfo;
             [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
             [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
             [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
             [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
+            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
             [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
                 [activityFacility updateActivity:activity 
-                                     withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
+                                     withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)", 
+                                                  newContainerRequest.containerName, object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
                                       totalBytes:activity.totalBytes 
                                     currentBytes:(activity.currentBytes + size)];
             }];
-//            [[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
-            [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+            [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
         }
     } else {
         [self requestFailed:objectRequest];
     }
+    [pool drain];
 }
 
 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
     NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
-    if (containerRequest.responseStatusCode == 202) {
+    if (operation.isCancelled) {
+        [self requestFailed:containerRequest];
+    } else if (containerRequest.responseStatusCode == 202) {
+        ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"];
         ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
         PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
-        NSUInteger totalBytes = activity.totalBytes;
-        NSUInteger currentBytes = activity.currentBytes + blockSize;
-        if (currentBytes > totalBytes)
-            currentBytes = totalBytes;
-        [activityFacility updateActivity:activity 
-                             withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
-                              totalBytes:totalBytes 
-                            currentBytes:currentBytes];
         NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
         NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
         missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
-        if (missingBlockIndex == NSNotFound) {
+        if (operation.isCancelled) {
+            [self requestFailed:containerRequest];
+        } else if (missingBlockIndex == NSNotFound) {
             NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
-            ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName 
-                                                                                                     objectName:object.name 
-                                                                                                    contentType:object.contentType 
-                                                                                                      blockSize:blockSize 
-                                                                                                      blockHash:blockHash
-                                                                                                        forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
-                                                                                                  checkIfExists:NO 
-                                                                                                         hashes:&hashes 
-                                                                                                 sharingAccount:nil];
+            ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
+                                                                                           containerName:pithosContainer.name 
+                                                                                              objectName:object.name 
+                                                                                             contentType:object.contentType 
+                                                                                               blockSize:pithosContainer.blockSize 
+                                                                                               blockHash:pithosContainer.blockHash
+                                                                                                 forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
+                                                                                           checkIfExists:NO 
+                                                                                                  hashes:&hashes 
+                                                                                          sharingAccount:nil];
             newObjectRequest.delegate = self;
-            newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
-            newObjectRequest.didFailSelector = @selector(requestFailed:);
+            newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+            newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
             newObjectRequest.userInfo = containerRequest.userInfo;
             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
-//            [[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
-            [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
+            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
+            [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
         } else {
-            if (newSyncRequested) {
-                [activityFacility endActivity:activity 
-                                  withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
-                @synchronized(self) {
-                    syncOperationCount--;
-                    if (!syncOperationCount)
-                        [self sync];
-                }
-                return;
+            if (newSyncRequested || syncLate || operation.isCancelled) {
+                [self requestFailed:containerRequest];
             } else {
-                __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName
-                                                                                                                            blockSize:blockSize
-                                                                                                                              forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
-                                                                                                                    missingBlockIndex:missingBlockIndex 
-                                                                                                                       sharingAccount:nil];
+                __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
+                                                                                                                 containerName:pithosContainer.name
+                                                                                                                     blockSize:pithosContainer.blockSize
+                                                                                                                       forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
+                                                                                                             missingBlockIndex:missingBlockIndex 
+                                                                                                                sharingAccount:nil];
                 newContainerRequest.delegate = self;
-                newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
-                newContainerRequest.didFailSelector = @selector(requestFailed:);
+                newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+                newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
                 newContainerRequest.userInfo = containerRequest.userInfo;
                 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
                 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
                 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
                     [activityFacility updateActivity:activity 
-                                         withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
+                                         withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)", 
+                                                      newContainerRequest.containerName, object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
                                           totalBytes:activity.totalBytes 
                                         currentBytes:(activity.currentBytes + size)];
                 }];
-//                [[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
-                [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+                [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
             }
         }
     } else {
         [self requestFailed:containerRequest];
     }
+    [pool drain];
 }
 
 - (void)requestFailed:(ASIPithosRequest *)request {
-    if ([request isCancelled]) {
-        [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
-                          withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
-        @synchronized(self) {
-            syncOperationCount--;
-        }
-        return;
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    NSOperation *operation = [request.userInfo objectForKey:@"operation"];
+    NSLog(@"Sync::request failed: %@", request.url);
+    if (operation.isCancelled) {
+        [pool drain];
+        return;        
     }
-    if (newSyncRequested) {
-        [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
-                          withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
-        @synchronized(self) {
-            syncOperationCount--;
-            if (!syncOperationCount)
-                [self sync];
-        }
+    if (request.isCancelled || newSyncRequested || syncLate) {
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
+                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+        });
+        [self syncOperationFinishedWithSuccess:NO];
+        [pool drain];
         return;
     }
     NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
     if (retries > 0) {
-        ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
+        ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
         [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
-//        [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
-        [queue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
+        [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
     } else {
-        [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
-                          withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
-        @synchronized(self) {
-            syncOperationCount--;
-        }
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
+                              withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
+        });
+        [self syncOperationFinishedWithSuccess:NO];
     }
+    [pool drain];
 }
 
-
 @end