Initial implementation of the syncing algorithm.
authorMiltiadis Vasilakis <mvasilak@gmail.com>
Thu, 20 Oct 2011 08:26:51 +0000 (11:26 +0300)
committerMiltiadis Vasilakis <mvasilak@gmail.com>
Thu, 20 Oct 2011 08:26:51 +0000 (11:26 +0300)
Other fixes and changes.

18 files changed:
pithos-apple-common
pithos-macos.xcodeproj/project.pbxproj
pithos-macos/FileMD5Hash.c [new file with mode: 0644]
pithos-macos/FileMD5Hash.h [new file with mode: 0644]
pithos-macos/PithosActivity.h
pithos-macos/PithosActivityFacility.h
pithos-macos/PithosActivityFacility.m
pithos-macos/PithosBrowserController.m
pithos-macos/PithosLocalObjectState.h [new file with mode: 0644]
pithos-macos/PithosLocalObjectState.m [new file with mode: 0644]
pithos-macos/PithosSyncDaemon.h [new file with mode: 0644]
pithos-macos/PithosSyncDaemon.m [new file with mode: 0644]
pithos-macos/PithosUtilities.h
pithos-macos/PithosUtilities.m
pithos-macos/en.lproj/MainMenu.xib
pithos-macos/pithos-macos-Info.plist
pithos-macos/pithos_macosAppDelegate.h
pithos-macos/pithos_macosAppDelegate.m

index 20e74a1..0a4b9bd 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 20e74a13c1a904064b0544b398b677cc30f28ae8
+Subproject commit 0a4b9bd3aa71857ebe3439befde0dc5c3b3c96b0
index 54aac14..e45a1c5 100644 (file)
@@ -69,6 +69,9 @@
                61C65AE31428D41C002597C2 /* PolicyVersioningTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 61C65AE21428D41C002597C2 /* PolicyVersioningTransformer.m */; };
                61C65AE6142918DD002597C2 /* PithosObjectNodeInfoController.m in Sources */ = {isa = PBXBuildFile; fileRef = 61C65AE5142918DD002597C2 /* PithosObjectNodeInfoController.m */; };
                61E99D9413EC348500E48DA5 /* 145-persondot.png in Resources */ = {isa = PBXBuildFile; fileRef = 61E99D9313EC348500E48DA5 /* 145-persondot.png */; };
+               61F040F31448547000A0C788 /* FileMD5Hash.c in Sources */ = {isa = PBXBuildFile; fileRef = 61F040F11448547000A0C788 /* FileMD5Hash.c */; };
+               61F04132144DB97200A0C788 /* PithosLocalObjectState.m in Sources */ = {isa = PBXBuildFile; fileRef = 61F040EE144757B500A0C788 /* PithosLocalObjectState.m */; };
+               61F04133144DB97600A0C788 /* PithosSyncDaemon.m in Sources */ = {isa = PBXBuildFile; fileRef = 61F040EA144724F500A0C788 /* PithosSyncDaemon.m */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
                61C65AE4142918DC002597C2 /* PithosObjectNodeInfoController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PithosObjectNodeInfoController.h; sourceTree = "<group>"; };
                61C65AE5142918DD002597C2 /* PithosObjectNodeInfoController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PithosObjectNodeInfoController.m; sourceTree = "<group>"; };
                61E99D9313EC348500E48DA5 /* 145-persondot.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "145-persondot.png"; sourceTree = "<group>"; };
+               61F040E9144724F500A0C788 /* PithosSyncDaemon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PithosSyncDaemon.h; sourceTree = "<group>"; };
+               61F040EA144724F500A0C788 /* PithosSyncDaemon.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PithosSyncDaemon.m; sourceTree = "<group>"; };
+               61F040ED144757B500A0C788 /* PithosLocalObjectState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PithosLocalObjectState.h; sourceTree = "<group>"; };
+               61F040EE144757B500A0C788 /* PithosLocalObjectState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PithosLocalObjectState.m; sourceTree = "<group>"; };
+               61F040F11448547000A0C788 /* FileMD5Hash.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = FileMD5Hash.c; path = "pithos-macos/FileMD5Hash.c"; sourceTree = "<group>"; };
+               61F040F21448547000A0C788 /* FileMD5Hash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FileMD5Hash.h; path = "pithos-macos/FileMD5Hash.h"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
                610DD2FB13E6BB2000ED982F /* pithos-macos */ = {
                        isa = PBXGroup;
                        children = (
+                               61F040E71447218F00A0C788 /* PithosBrowser */,
+                               61F040E51447217100A0C788 /* PithosSyncDaemon */,
+                               61F040E8144721B200A0C788 /* PithosActivityFacility */,
                                61F1C5DB1444A8CF00C1E6EB /* PithosNodes */,
                                61F1C5DD1444A92B00C1E6EB /* PithosNodeInfoControllers */,
                                610DD2FC13E6BB2000ED982F /* Supporting Files */,
                                610DD30713E6BB2000ED982F /* pithos_macosAppDelegate.h */,
                                610DD30813E6BB2000ED982F /* pithos_macosAppDelegate.m */,
                                610DD30A13E6BB2000ED982F /* MainMenu.xib */,
-                               610DD34C13E6BEF400ED982F /* PithosBrowserController.h */,
-                               610DD34D13E6BEF400ED982F /* PithosBrowserController.m */,
-                               610DD34F13E6C00E00ED982F /* PithosBrowserController.xib */,
-                               6121250813F033F400063041 /* PithosBrowserPreviewController.xib */,
-                               619B85D213F8076F00C9371F /* PithosPreferencesController.h */,
-                               619B85D313F8077100C9371F /* PithosPreferencesController.m */,
-                               619B85D413F8077300C9371F /* PithosPreferencesController.xib */,
                                61C24BEA14161EC0007004DC /* PithosUtilities.h */,
                                61C24BEB14161EC3007004DC /* PithosUtilities.m */,
-                               618A7FD61438CE5D0040F043 /* PithosActivityFacility.h */,
-                               618A7FD71438CE5D0040F043 /* PithosActivityFacility.m */,
-                               618A7FF4143A20830040F043 /* PithosActivity.h */,
-                               618A7FF5143A20830040F043 /* PithosActivity.m */,
                        );
                        path = "pithos-macos";
                        sourceTree = "<group>";
                6139837313F01CFC004CE444 /* Utilities */ = {
                        isa = PBXGroup;
                        children = (
-                               61433BC7141BA1CE00CD978D /* HashMapHash.h */,
-                               61433BC8141BA1CE00CD978D /* HashMapHash.m */,
+                               61F040F41448547B00A0C788 /* Hashing */,
                                615A442E140E5ECA00308614 /* Formatters */,
                                6180C22713FAED1D00BCA40B /* Cells */,
                                6180C22813FAED4B00BCA40B /* Value Transformers */,
                        name = "Value Transformers";
                        sourceTree = "<group>";
                };
+               61F040E51447217100A0C788 /* PithosSyncDaemon */ = {
+                       isa = PBXGroup;
+                       children = (
+                               61F040E9144724F500A0C788 /* PithosSyncDaemon.h */,
+                               61F040EA144724F500A0C788 /* PithosSyncDaemon.m */,
+                               61F040ED144757B500A0C788 /* PithosLocalObjectState.h */,
+                               61F040EE144757B500A0C788 /* PithosLocalObjectState.m */,
+                       );
+                       name = PithosSyncDaemon;
+                       sourceTree = "<group>";
+               };
+               61F040E71447218F00A0C788 /* PithosBrowser */ = {
+                       isa = PBXGroup;
+                       children = (
+                               610DD34C13E6BEF400ED982F /* PithosBrowserController.h */,
+                               610DD34D13E6BEF400ED982F /* PithosBrowserController.m */,
+                               610DD34F13E6C00E00ED982F /* PithosBrowserController.xib */,
+                               6121250813F033F400063041 /* PithosBrowserPreviewController.xib */,
+                               619B85D213F8076F00C9371F /* PithosPreferencesController.h */,
+                               619B85D313F8077100C9371F /* PithosPreferencesController.m */,
+                               619B85D413F8077300C9371F /* PithosPreferencesController.xib */,
+                       );
+                       name = PithosBrowser;
+                       sourceTree = "<group>";
+               };
+               61F040E8144721B200A0C788 /* PithosActivityFacility */ = {
+                       isa = PBXGroup;
+                       children = (
+                               618A7FD61438CE5D0040F043 /* PithosActivityFacility.h */,
+                               618A7FD71438CE5D0040F043 /* PithosActivityFacility.m */,
+                               618A7FF4143A20830040F043 /* PithosActivity.h */,
+                               618A7FF5143A20830040F043 /* PithosActivity.m */,
+                       );
+                       name = PithosActivityFacility;
+                       sourceTree = "<group>";
+               };
+               61F040F41448547B00A0C788 /* Hashing */ = {
+                       isa = PBXGroup;
+                       children = (
+                               61433BC7141BA1CE00CD978D /* HashMapHash.h */,
+                               61433BC8141BA1CE00CD978D /* HashMapHash.m */,
+                               61F040F11448547000A0C788 /* FileMD5Hash.c */,
+                               61F040F21448547000A0C788 /* FileMD5Hash.h */,
+                       );
+                       name = Hashing;
+                       sourceTree = "<group>";
+               };
                61F1C5DB1444A8CF00C1E6EB /* PithosNodes */ = {
                        isa = PBXGroup;
                        children = (
                                618A7FF6143A20830040F043 /* PithosActivity.m in Sources */,
                                613629B5143E0F8B00363787 /* GroupMembersDictionaryTransformer.m in Sources */,
                                613629B9143E51E000363787 /* GroupAndGroupMemberFormatter.m in Sources */,
+                               61F040F31448547000A0C788 /* FileMD5Hash.c in Sources */,
+                               61F04132144DB97200A0C788 /* PithosLocalObjectState.m in Sources */,
+                               61F04133144DB97600A0C788 /* PithosSyncDaemon.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
diff --git a/pithos-macos/FileMD5Hash.c b/pithos-macos/FileMD5Hash.c
new file mode 100644 (file)
index 0000000..a889e51
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ *  FileMD5Hash.c
+ *  FileMD5Hash
+ * 
+ *  Copyright © 2010 Joel Lopes Da Silva. All rights reserved.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ * 
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+//---------------------------------------------------------
+// Includes
+//---------------------------------------------------------
+
+// Header file
+#include "FileMD5Hash.h"
+
+// Standard library
+#include <stdint.h>
+#include <stdio.h>
+
+// Core Foundation
+#include <CoreFoundation/CoreFoundation.h>
+
+// Cryptography
+#include <CommonCrypto/CommonDigest.h>
+
+
+//---------------------------------------------------------
+// Function definition
+//---------------------------------------------------------
+
+CFStringRef FileMD5HashCreateWithPath(CFStringRef filePath,
+                                      size_t chunkSizeForReadingData) {
+    
+    // Declare needed variables
+    CFStringRef result = NULL;
+    CFReadStreamRef readStream = NULL;
+    
+    // Get the file URL
+    CFURLRef fileURL =
+    CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
+                                  (CFStringRef)filePath,
+                                  kCFURLPOSIXPathStyle,
+                                  (Boolean)false);
+    if (!fileURL) goto done;
+    
+    // Create and open the read stream
+    readStream = CFReadStreamCreateWithFile(kCFAllocatorDefault,
+                                            (CFURLRef)fileURL);
+    if (!readStream) goto done;
+    bool didSucceed = (bool)CFReadStreamOpen(readStream);
+    if (!didSucceed) goto done;
+    
+    // Initialize the hash object
+    CC_MD5_CTX hashObject;
+    CC_MD5_Init(&hashObject);
+    
+    // Make sure chunkSizeForReadingData is valid
+    if (!chunkSizeForReadingData) {
+        chunkSizeForReadingData = FileHashDefaultChunkSizeForReadingData;
+    }
+    
+    // Feed the data to the hash object
+    bool hasMoreData = true;
+    while (hasMoreData) {
+        uint8_t buffer[chunkSizeForReadingData];
+        CFIndex readBytesCount = CFReadStreamRead(readStream,
+                                                  (UInt8 *)buffer,
+                                                  (CFIndex)sizeof(buffer));
+        if (readBytesCount == -1) break;
+        if (readBytesCount == 0) {
+            hasMoreData = false;
+            continue;
+        }
+        CC_MD5_Update(&hashObject,
+                      (const void *)buffer,
+                      (CC_LONG)readBytesCount);
+    }
+    
+    // Check if the read operation succeeded
+    didSucceed = !hasMoreData;
+    
+    // Compute the hash digest
+    unsigned char digest[CC_MD5_DIGEST_LENGTH];
+    CC_MD5_Final(digest, &hashObject);
+    
+    // Abort if the read operation failed
+    if (!didSucceed) goto done;
+    
+    // Compute the string result
+    char hash[2 * sizeof(digest) + 1];
+    for (size_t i = 0; i < sizeof(digest); ++i) {
+        snprintf(hash + (2 * i), 3, "%02x", (int)(digest[i]));
+    }
+    result = CFStringCreateWithCString(kCFAllocatorDefault,
+                                       (const char *)hash,
+                                       kCFStringEncodingUTF8);
+    
+done:
+    
+    if (readStream) {
+        CFReadStreamClose(readStream);
+        CFRelease(readStream);
+    }
+    if (fileURL) {
+        CFRelease(fileURL);
+    }
+    return result;
+}
diff --git a/pithos-macos/FileMD5Hash.h b/pithos-macos/FileMD5Hash.h
new file mode 100644 (file)
index 0000000..5e9de69
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ *  FileMD5Hash.h
+ *  FileMD5Hash
+ * 
+ *  Copyright © 2010 Joel Lopes Da Silva. All rights reserved.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ * 
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+#ifndef FILEMD5HASH_H
+#define FILEMD5HASH_H
+
+//---------------------------------------------------------
+// Includes
+//---------------------------------------------------------
+
+// Core Foundation
+#include <CoreFoundation/CoreFoundation.h>
+
+
+//---------------------------------------------------------
+// Constant declaration
+//---------------------------------------------------------
+
+// In bytes
+#define FileHashDefaultChunkSizeForReadingData 4096
+
+
+//---------------------------------------------------------
+// Function declaration
+//---------------------------------------------------------
+
+#include <TargetConditionals.h>
+
+// General imports for Objective-C
+#ifdef __OBJC__
+#if TARGET_OS_IPHONE
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+#elif TARGET_OS_MAC
+#import <Cocoa/Cocoa.h>
+#endif
+#endif
+
+
+//---------------------------------------------------------
+// Macros
+//---------------------------------------------------------
+
+// Extern
+#if defined(__cplusplus)
+#define FILEMD5HASH_EXTERN extern "C"
+#else
+#define FILEMD5HASH_EXTERN extern
+#endif
+
+
+FILEMD5HASH_EXTERN CFStringRef FileMD5HashCreateWithPath(CFStringRef filePath, 
+                                                         size_t chunkSizeForReadingData);
+
+
+#endif
index c94b751..6569d69 100644 (file)
@@ -43,7 +43,8 @@ typedef enum  {
     PithosActivityCopy, 
     PithosActivityMove, 
     PithosActivityCreateDirectory, 
-    PithosActivityDelete
+    PithosActivityDelete, 
+    PithosActivityOther
 } PithosActivityType;
 
 @interface PithosActivity : NSObject {
index 8006962..912f246 100644 (file)
@@ -68,6 +68,7 @@
                              currentBytes:(NSUInteger)currentBytes;
 - (PithosActivity *)startActivityWithType:(PithosActivityType)type 
                                   message:(NSString *)message;
+- (PithosActivity *)startAndEndActivityWithType:(PithosActivityType)type message:(NSString *)message;
 - (void)updateActivity:(PithosActivity *)activity 
            withMessage:(NSString *)message 
             totalBytes:(NSUInteger)totalBytes 
index 168476c..a11ab79 100644 (file)
@@ -228,6 +228,19 @@ static PithosActivityFacility *defaultPithosActivityFacility = nil;
     return [self startActivityWithType:type message:message totalBytes:0 currentBytes:0];
 }
 
+- (PithosActivity *)startAndEndActivityWithType:(PithosActivityType)type message:(NSString *)message {
+    PithosActivity *activity = [[[PithosActivity alloc] initWithType:type] autorelease];
+    activity.message = message;
+    activity.totalBytes = 0;
+    activity.currentBytes = 0;
+    @synchronized(self) {
+        [endingActivities addObject:activity];
+    }
+    NSLog(@"PithosActivityFacility startedAndEndedActivity %@", activity);
+    
+    return activity;
+}
+
 - (void)updateActivity:(PithosActivity *)activity 
            withMessage:(NSString *)message 
             totalBytes:(NSUInteger)totalBytes 
index 0b42490..6bffdca 100644 (file)
@@ -97,7 +97,7 @@
 
 @end
 
-@interface PithosBrowserController (Private) {}
+@interface PithosBrowserController (Private)
 - (void)resetContainers:(NSNotification *)notification;
 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode;
 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
@@ -741,7 +741,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
     if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
         return NO;
-    NSFileManager *defaultManager = [NSFileManager defaultManager];
+    NSFileManager *fileManager = [NSFileManager defaultManager];
     NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
     NSString *objectNamePrefix;
     if ([destinationNode class] == [PithosSubdirNode class])
@@ -757,7 +757,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         if ([containerRequest error]) {
             [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
             return NO;
-        } else if (containerRequest.responseStatusCode != 200) {
+        } else if (containerRequest.responseStatusCode != 204) {
             [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
             return NO;
         }
@@ -769,7 +769,7 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     
     for (NSString *filePath in filenames) {
         BOOL isDirectory;
-        if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
+        if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
             if (!isDirectory) {
                 // Upload file
                 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
@@ -1088,9 +1088,9 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
         // XXX change contentLength to objectContentLength if it is fixed in the server
         if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
             NSLog(@"Downloaded  0 bytes");
-            NSFileManager *defaultManager = [NSFileManager defaultManager];
-            if (![defaultManager fileExistsAtPath:filePath]) {
-                if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
+            NSFileManager *fileManager = [NSFileManager defaultManager];
+            if (![fileManager fileExistsAtPath:filePath]) {
+                if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
                     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
                     [alert setMessageText:@"Create File Error"];
                     [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
@@ -1900,20 +1900,22 @@ forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
     for (PithosNode *node in ((NSArray *)[sender representedObject])) {
         if (([node class] == [PithosObjectNode class]) || 
             (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
+            NSString *fileName = [node.pithosObject.name lastPathComponent];
+            if ([node.pithosObject.name hasSuffix:@"/"])
+                fileName = [fileName stringByAppendingString:@"/"];
             ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name 
                                                                                                       objectName:node.pithosObject.name];
             objectRequest.delegate = self;
             objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
             objectRequest.didFailSelector = @selector(deleteObjectFailed:);
             PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
-                                                                       message:[NSString stringWithFormat:@"Deleting '%@'", 
-                                                                                [objectRequest.userInfo objectForKey:@"fileName"]]];
-            [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
-             [NSDictionary dictionaryWithObjectsAndKeys:
-              [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
-              activity, @"activity", 
-              [NSNumber numberWithUnsignedInteger:10], @"retries", 
-              nil]];
+                                                                       message:[NSString stringWithFormat:@"Deleting '%@'", fileName]];
+            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                      fileName, @"fileName", 
+                                      [NSArray arrayWithObject:node.parent], @"forceRefreshNodes", 
+                                      activity, @"activity", 
+                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                                      nil];
             [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
         } else if ([node class] == [PithosSubdirNode class]) {
             dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
diff --git a/pithos-macos/PithosLocalObjectState.h b/pithos-macos/PithosLocalObjectState.h
new file mode 100644 (file)
index 0000000..50a8688
--- /dev/null
@@ -0,0 +1,54 @@
+//
+//  PithosLocalObjectState.h
+//  pithos-macos
+//
+// Copyright 2011 GRNET S.A. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or
+// without modification, are permitted provided that the following
+// conditions are met:
+// 
+//   1. Redistributions of source code must retain the above
+//      copyright notice, this list of conditions and the following
+//      disclaimer.
+// 
+//   2. Redistributions in binary form must reproduce the above
+//      copyright notice, this list of conditions and the following
+//      disclaimer in the documentation and/or other materials
+//      provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+// 
+// The views and conclusions contained in the software and
+// documentation are those of the authors and should not be
+// interpreted as representing official policies, either expressed
+// or implied, of GRNET S.A.
+
+#import <Foundation/Foundation.h>
+
+@interface PithosLocalObjectState : NSObject <NSCoding> {
+    NSString *md5;
+    NSString *hashMapHash;
+    NSString *tmpDownloadFile;
+    BOOL isDirectory; 
+}
+
++ (id)nullObjectState;
+
+@property (nonatomic, retain) NSString *md5;
+@property (nonatomic, retain) NSString *hashMapHash;
+@property (nonatomic, retain) NSString *tmpDownloadFile;
+@property (nonatomic, assign) BOOL isDirectory;
+
+@end
diff --git a/pithos-macos/PithosLocalObjectState.m b/pithos-macos/PithosLocalObjectState.m
new file mode 100644 (file)
index 0000000..091cd19
--- /dev/null
@@ -0,0 +1,112 @@
+//
+//  PithosLocalObjectState.m
+//  pithos-macos
+//
+// Copyright 2011 GRNET S.A. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or
+// without modification, are permitted provided that the following
+// conditions are met:
+// 
+//   1. Redistributions of source code must retain the above
+//      copyright notice, this list of conditions and the following
+//      disclaimer.
+// 
+//   2. Redistributions in binary form must reproduce the above
+//      copyright notice, this list of conditions and the following
+//      disclaimer in the documentation and/or other materials
+//      provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+// 
+// The views and conclusions contained in the software and
+// documentation are those of the authors and should not be
+// interpreted as representing official policies, either expressed
+// or implied, of GRNET S.A.
+
+#import "PithosLocalObjectState.h"
+#import "PithosUtilities.h"
+
+@implementation PithosLocalObjectState
+@synthesize md5, hashMapHash, tmpDownloadFile, isDirectory;
+
+#pragma mark -
+#pragma mark Object Lifecycle
+
++ (id)nullObjectState {
+       PithosLocalObjectState *localObjectState = [[[self alloc] init] autorelease];
+    localObjectState.md5 = @" ";
+    localObjectState.hashMapHash = @" ";
+    localObjectState.tmpDownloadFile = nil;
+    localObjectState.isDirectory = NO;
+    return localObjectState;
+}
+
+- (void)dealloc {
+    self.tmpDownloadFile = nil;
+    [hashMapHash release];
+    [md5 release];
+    [super dealloc];    
+}
+
+#pragma mark -
+#pragma mark Properties
+
+- (void)setIsDirectory:(BOOL)anIsDirectory {
+    isDirectory = anIsDirectory;
+    if (isDirectory) {
+        self.md5 = @" ";
+        self.hashMapHash = @" ";
+        self.tmpDownloadFile = nil;
+    }
+}
+
+- (void)setTmpDownloadFile:(NSString *)aTmpDownloadFile {
+    if (![tmpDownloadFile isEqualToString:aTmpDownloadFile]) {
+        if (!aTmpDownloadFile) {
+            NSFileManager *fileManager = [NSFileManager defaultManager];
+            if ([fileManager fileExistsAtPath:tmpDownloadFile]) {
+                NSError *error = nil;
+                if (![fileManager removeItemAtPath:tmpDownloadFile error:&error] || error)
+                    [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
+                                                            message:[NSString stringWithFormat:@"Cannot remove file at '%@'", tmpDownloadFile] 
+                                                              error:error];
+            }
+        }
+        [tmpDownloadFile release];
+        tmpDownloadFile = [aTmpDownloadFile retain];
+    }
+}
+
+#pragma mark -
+#pragma mark NSCoding
+
+- (id)initWithCoder:(NSCoder *)decoder {
+    if ((self = [super init])) {
+        self.md5 = [decoder decodeObjectForKey:@"md5"];
+        self.hashMapHash = [decoder decodeObjectForKey:@"hashMapHash"];
+        self.tmpDownloadFile = [decoder decodeObjectForKey:@"tmpDownloadFile"];
+        self.isDirectory = [decoder decodeBoolForKey:@"isDirectory"];
+    }
+    return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)encoder {
+    [encoder encodeObject:md5 forKey:@"md5"];
+    [encoder encodeObject:hashMapHash forKey:@"hashMapHash"];
+    [encoder encodeObject:tmpDownloadFile forKey:@"tmpDownloadFile"];
+    [encoder encodeBool:isDirectory forKey:@"isDirectory"];
+}
+
+@end
diff --git a/pithos-macos/PithosSyncDaemon.h b/pithos-macos/PithosSyncDaemon.h
new file mode 100644 (file)
index 0000000..8b454a3
--- /dev/null
@@ -0,0 +1,80 @@
+//
+//  PithosSyncDaemon.h
+//  pithos-macos
+//
+// Copyright 2011 GRNET S.A. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or
+// without modification, are permitted provided that the following
+// conditions are met:
+// 
+//   1. Redistributions of source code must retain the above
+//      copyright notice, this list of conditions and the following
+//      disclaimer.
+// 
+//   2. Redistributions in binary form must reproduce the above
+//      copyright notice, this list of conditions and the following
+//      disclaimer in the documentation and/or other materials
+//      provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+// 
+// The views and conclusions contained in the software and
+// documentation are those of the authors and should not be
+// interpreted as representing official policies, either expressed
+// or implied, of GRNET S.A.
+
+#import <Foundation/Foundation.h>
+@class PithosActivityFacility;
+@class ASINetworkQueue;
+
+@interface PithosSyncDaemon : NSObject {
+    NSString *directoryPath;
+    NSString *containerName;
+    NSTimeInterval timeInterval;
+
+    NSString *blockHash;
+    NSUInteger blockSize;
+    NSDate *lastModified;
+    NSMutableArray *objects;
+    NSMutableDictionary *remoteObjects;
+    NSMutableDictionary *storedLocalObjectStates;
+    
+    NSString *pithosStateFilePath;
+    NSString *tempDownloadsDirPath;
+    
+    NSUInteger syncOperationCount;
+    BOOL newSyncRequested;
+    
+    ASINetworkQueue *queue;
+    NSTimer *timer;
+    
+    PithosActivityFacility *activityFacility;
+}
+
+@property (nonatomic, retain) NSString *blockHash;
+@property (nonatomic, assign) NSUInteger blockSize;
+@property (nonatomic, retain) NSDate *lastModified;
+@property (nonatomic, retain) NSMutableDictionary *remoteObjects;
+@property (nonatomic, retain) NSMutableDictionary *storedLocalObjectStates;
+
+@property (nonatomic, readonly) NSString *pithosStateFilePath;
+@property (nonatomic, readonly) NSString *tempDownloadsDirPath;
+
+- (id)initWithDirectoryPath:(NSString *)aDirectoryPath 
+              containerName:(NSString *)aContainerName 
+               timeInterval:(NSTimeInterval)aTimeInterval;
+- (void)sync;
+
+@end
diff --git a/pithos-macos/PithosSyncDaemon.m b/pithos-macos/PithosSyncDaemon.m
new file mode 100644 (file)
index 0000000..107d154
--- /dev/null
@@ -0,0 +1,1293 @@
+//
+//  PithosSyncDaemon.m
+//  pithos-macos
+//
+// Copyright 2011 GRNET S.A. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or
+// without modification, are permitted provided that the following
+// conditions are met:
+// 
+//   1. Redistributions of source code must retain the above
+//      copyright notice, this list of conditions and the following
+//      disclaimer.
+// 
+//   2. Redistributions in binary form must reproduce the above
+//      copyright notice, this list of conditions and the following
+//      disclaimer in the documentation and/or other materials
+//      provided with the distribution.
+// 
+// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+// 
+// The views and conclusions contained in the software and
+// documentation are those of the authors and should not be
+// interpreted as representing official policies, either expressed
+// or implied, of GRNET S.A.
+
+#import "PithosSyncDaemon.h"
+#import "PithosLocalObjectState.h"
+#import "PithosActivityFacility.h"
+#import "PithosUtilities.h"
+#import "ASINetworkQueue.h"
+#import "ASIPithosRequest.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)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;
+- (void)requestFailed:(ASIPithosRequest *)request;
+@end
+
+@implementation PithosSyncDaemon
+@synthesize blockHash, blockSize, lastModified, remoteObjects, storedLocalObjectStates;
+@synthesize pithosStateFilePath, tempDownloadsDirPath;
+
+#pragma mark -
+#pragma Object Lifecycle
+
+- (id)initWithDirectoryPath:(NSString *)aDirectoryPath 
+              containerName:(NSString *)aContainerName 
+               timeInterval:(NSTimeInterval)aTimeInterval {
+    if ((self = [super init])) {
+        directoryPath = [aDirectoryPath copy];
+        containerName = [aContainerName copy];
+        timeInterval = aTimeInterval;
+        
+        syncOperationCount = 0;
+        newSyncRequested = NO;
+        
+        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];
+        }
+        
+        queue = [[ASINetworkQueue alloc] init];
+        queue.shouldCancelAllRequestsOnFailure = NO;
+        [queue go];
+        
+        [[NSNotificationCenter defaultCenter] addObserver:self
+                                                 selector:@selector(applicationWillTerminate:)
+                                                     name:NSApplicationWillTerminateNotification
+                                                   object:[NSApplication sharedApplication]];
+    
+        timer = [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(sync) userInfo:nil repeats:YES] retain];
+        [timer fire];
+    }
+    
+    return self;
+}
+
+- (void)dealloc {
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+    [queue cancelAllOperations];
+    [queue release];
+    [timer invalidate];
+    [timer release];
+    [tempDownloadsDirPath release];
+    [pithosStateFilePath release];
+    [storedLocalObjectStates release];
+    [remoteObjects release];
+    [objects release];
+    [lastModified release];
+    [blockHash release];
+    [containerName release];
+    [directoryPath release];
+    [super dealloc];
+}
+
+#pragma mark -
+#pragma mark Observers
+
+- (void)applicationWillTerminate:(NSNotification *)notification {
+    [self saveLocalState];
+}
+
+#pragma mark -
+#pragma mark Properties
+
+- (NSString *)pithosStateFilePath {
+    if (!pithosStateFilePath) {
+        pithosStateFilePath = [[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:DATA_MODEL_FILE] retain];
+    }
+    return [pithosStateFilePath copy];
+}
+
+- (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)
+            [[NSUserDefaults standardUserDefaults] setObject:tempDownloadsDirPath forKey:@"PithosTempDownloadsDirPath"];
+        [tempDownloadsDirPath retain];
+    }
+    return [tempDownloadsDirPath copy];
+}
+
+#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)sync {
+    @synchronized(self) {
+        if (syncOperationCount) {
+            // If at least one operation is running return
+            newSyncRequested = YES;
+            return;
+        } else {
+            // The first operation is the server listing
+            syncOperationCount = 1;
+            newSyncRequested = NO;
+        }
+    }
+
+    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;
+        }
+        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];
+        @synchronized(self) {
+            syncOperationCount = 0;
+        }
+        return;
+    }
+    
+    ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName 
+                                                                                                           limit:0 
+                                                                                                          marker:nil 
+                                                                                                          prefix:nil 
+                                                                                                       delimiter:nil 
+                                                                                                            path:nil 
+                                                                                                            meta:nil 
+                                                                                                          shared:NO 
+                                                                                                           until:nil 
+                                                                                                 ifModifiedSince:lastModified];
+    containerRequest.delegate = self;
+    containerRequest.didFinishSelector = @selector(listRequestFinished:);
+    containerRequest.didFailSelector = @selector(listRequestFailed:);
+    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther 
+                                                               message:@"Sync: Getting server listing"];
+    containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                 activity, @"activity", 
+                                 [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                                 nil];
+//    [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
+    [queue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh]];
+}
+
+- (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;
+}
+
+- (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
+            return NO;
+        else
+            // Previously did exist, state has changed
+            return YES;
+    }
+    // Only if the server doesn't respond properly this will be reached, leave as is for now
+    return NO;
+}
+
+- (void)updateLocalStateWithObject:(ASIPithosObject *)object
+                     localFilePath:(NSString *)filePath {
+    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:@" "]) {
+        // 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 (!deleteFailed) {
+            [activityFacility startAndEndActivityWithType:PithosActivityOther 
+                                                  message:[NSString stringWithFormat:@"Sync: Deleting '%@' locally (finished)", object.name]];
+            [storedLocalObjectStates removeObjectForKey:object.name];
+            [self saveLocalState];
+        }
+    } else if ([object.contentType isEqualToString:@"application/directory"]) {
+        // Create local directory object
+        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]) {
+            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];
+            } else {
+                directoryCreated = YES;
+                storedState.isDirectory = YES;
+                [self saveLocalState];
+            }
+        } else if (isDirectory) {
+            NSLog(@"Sync::local directory object exists: %@", filePath);
+            directoryCreated = YES;
+        }
+        if (directoryCreated)
+            [activityFacility startAndEndActivityWithType:PithosActivityOther 
+                                                  message:[NSString stringWithFormat:@"Sync: Creating directory '%@' locally (finished)", object.name]];
+    } else if (object.bytes == 0) {
+        // Create local object with zero length
+        NSLog(@"Sync::create local zero length object: %@", filePath);
+        BOOL fileCreated = NO;
+        if (fileExists && (isDirectory || [PithosUtilities bytesOfFile:filePath])) {
+            NSLog(@"Sync::delete local 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]) {
+            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];
+            } 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;
+                [self saveLocalState];
+            }
+        } else if (!isDirectory && ![PithosUtilities bytesOfFile:filePath]) {
+            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) {
+        // Create new local object
+        @synchronized(self) {
+            syncOperationCount++;
+        }
+        __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++;
+        }
+        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]];
+    }
+}
+
+-(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
+                                  object:(ASIPithosObject *)object 
+                           localFilePath:(NSString *)filePath {
+    if (currentState.isDirectory) {
+        // Create remote directory object
+        @synchronized(self) {
+            syncOperationCount++;
+        }
+        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]];
+        objectRequest.delegate = self;
+        objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
+        objectRequest.didFailSelector = @selector(requestFailed:);
+        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
+                                                                   message:[NSString stringWithFormat:@"Sync: Creating directory '%@'", object.name]];
+        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                  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", 
+                                  [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
+                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
+                                  nil];
+//        [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
+        [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+    } else if ([currentState.md5 isEqualToString:@" "] && [currentState.hashMapHash isEqualToString:@" "]) {
+        // Delete remote object
+        @synchronized(self) {
+            syncOperationCount++;
+        }
+        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
+        NSError *error = nil;
+        object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
+        if (object.contentType == nil)
+            object.contentType = @"application/octet-stream";
+        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];
+        if (objectRequest) {
+            @synchronized(self) {
+                syncOperationCount++;
+            }
+            objectRequest.delegate = self;
+            objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
+            objectRequest.didFailSelector = @selector(requestFailed:);
+            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
+                                                                       message:[NSString stringWithFormat:@"Sync: Uploading '%@' (0%%)", object.name]
+                                                                    totalBytes:[[objectRequest.userInfo valueForKey:@"bytes"] unsignedIntegerValue]
+                                                                  currentBytes:0];
+            [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
+             [NSDictionary dictionaryWithObjectsAndKeys:
+              object, @"pithosObject", 
+              filePath, @"filePath", 
+              hashes, @"hashes", 
+              [NSNumber numberWithUnsignedInteger:10], @"iteration", 
+              activity, @"activity", 
+              [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 numberWithUnsignedInteger:10], @"retries", 
+              nil]];
+//            [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
+            [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+        }
+    }
+
+}
+
+#pragma mark -
+#pragma mark ASIHTTPRequestDelegate
+
+- (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
+    NSLog(@"Sync::list request finished: %@", containerRequest.url);
+    if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
+        if (containerRequest.responseStatusCode == 200) {
+            NSArray *someObjects = [containerRequest objects];
+            if (objects == nil) {
+                objects = [[NSMutableArray alloc] initWithArray:someObjects];
+            } else {
+                [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]];
+                for (ASIPithosObject *object in objects) {
+                    [remoteObjects setObject:object forKey:object.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];
+                newContainerRequest.delegate = self;
+                newContainerRequest.didFinishSelector = @selector(listRequestFinished:);
+                newContainerRequest.didFailSelector = @selector(listRequestFailed:);
+                newContainerRequest.userInfo = newContainerRequest.userInfo;
+                [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+//                [[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
+                [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityHigh]];
+                return;
+            }
+        }
+        [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];
+            }
+            return;
+        }
+        for (NSString *objectName in subPaths) {
+            if (![storedLocalObjectStates objectForKey:objectName]) {
+                [storedLocalObjectStates setObject:[PithosLocalObjectState nullObjectState] forKey:objectName];
+            }
+        }
+        [self saveLocalState];
+
+        for (NSString *objectName in remoteObjects) {
+            if (![storedLocalObjectStates objectForKey:objectName])
+                [storedLocalObjectStates setObject:[PithosLocalObjectState nullObjectState] forKey:objectName];
+        }
+
+        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;
+                } else {
+                    currentLocalObjectState.md5 = (NSString *)FileMD5HashCreateWithPath((CFStringRef)filePath, 
+                                                                                        FileHashDefaultChunkSizeForReadingData);
+                    currentLocalObjectState.hashMapHash = [HashMapHash calculateHashMapHash:[HashMapHash calculateObjectHashMap:filePath 
+                                                                                                                  withBlockHash:blockHash 
+                                                                                                                   andBlockSize:blockSize]];
+                }
+            }
+            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";
+                }
+            }
+            NSLog(@"Sync::remote object is directory: %d", remoteObjectIsDirectory);
+            
+            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];
+                }
+            } 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];
+                        [self saveLocalState];
+                    } 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";
+                        } 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];
+                        }
+                    }
+                }
+            }
+        }
+        @synchronized(self) {
+            syncOperationCount--;
+            if (newSyncRequested && !syncOperationCount)
+                [self sync];
+        }
+    } 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];
+            }
+        }
+    }
+}
+
+- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
+    if ([containerRequest isCancelled]) {
+        [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
+                          withMessage:@"Sync: Getting server listing (stopped)"];
+        [objects release];
+        objects = nil;
+        @synchronized(self) {
+            syncOperationCount = 0;
+        }
+        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];
+        [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+//        [[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
+        [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityVeryHigh]];
+    } else {
+        [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
+                          withMessage:@"Sync: Getting server listing (failed)"];
+        [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];
+        }
+    }
+}
+
+- (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Sync::download object block finished: %@", objectRequest.url);
+    if (objectRequest.responseStatusCode == 206) {
+        ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
+        NSFileManager *fileManager = [NSFileManager defaultManager];
+        NSError *error;
+        PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
+        
+        NSString *downloadsDirPath = self.tempDownloadsDirPath;
+        if (!downloadsDirPath) {
+            [activityFacility endActivity:activity 
+                              withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+            @synchronized(self) {
+                syncOperationCount--;
+                if (newSyncRequested && !syncOperationCount)
+                    [self sync];
+            }
+            return;
+        }
+        
+        PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
+        if ((storedState.tmpDownloadFile == nil) || ![fileManager fileExistsAtPath:storedState.tmpDownloadFile]) {
+            NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
+            const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
+            char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
+            strcpy(tempFileNameCString, tempFileTemplateCString);
+            int fileDescriptor = mkstemp(tempFileNameCString);
+            NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
+            free(tempFileNameCString);
+            if (fileDescriptor == -1) {
+                [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];
+                }
+                return;
+            }
+            close(fileDescriptor);
+            storedState.tmpDownloadFile = tempFilePath;
+            [self saveLocalState];
+        }
+        
+
+        NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
+        NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpDownloadFile];
+        [tempFileHandle seekToFileOffset:missingBlockIndex*blockSize];
+        [tempFileHandle writeData:[objectRequest responseData]];
+        [tempFileHandle closeFile];
+
+        NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
+        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
+        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];
+                    [activityFacility endActivity:activity 
+                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+                    @synchronized(self) {
+                        syncOperationCount--;
+                        if (newSyncRequested && !syncOperationCount)
+                            [self sync];
+                    }
+                    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,
+                // but it never hurts to check
+                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];
+                    }
+                    return;
+                }
+            }
+            // Move file from tmp download
+            error = nil;
+            [fileManager moveItemAtPath:storedState.tmpDownloadFile 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];
+                }
+                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;
+            [self saveLocalState];
+            
+            @synchronized(self) {
+                syncOperationCount--;
+                if (newSyncRequested && !syncOperationCount)
+                    [self sync];
+            }
+            return;
+        } else {
+            if (newSyncRequested) {
+                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
+                                  withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
+                @synchronized(self) {
+                    syncOperationCount--;
+                    if (!syncOperationCount)
+                        [self sync];
+                }
+                return;
+            } else {
+                __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
+                                                                                                                     object:object 
+                                                                                                                 blockIndex:missingBlockIndex 
+                                                                                                                  blockSize:blockSize];
+                newObjectRequest.delegate = self;
+                newObjectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
+                newObjectRequest.didFailSelector = @selector(downloadObjectBlockOrHashMapFailed:);
+                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, 
+                                                      (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]];
+            }
+        }
+    } 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];
+        }
+    } else {
+        [self requestFailed:objectRequest];
+    }
+}
+
+- (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
+    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;
+        } else {
+            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];
+            PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
+            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpDownloadFile 
+                                                                    blockSize:blockSize 
+                                                                    blockHash: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];
+            newObjectRequest.delegate = self;
+            newObjectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
+            newObjectRequest.didFailSelector = @selector(downloadObjectBlockOrHashMapFailed:);
+            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"];
+            [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
+                [activityFacility updateActivity:activity 
+                                     withMessage:[NSString stringWithFormat:@"Downloading '%@' (%.0f%%)", 
+                                                  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]];
+        }
+    } else {
+        [self requestFailed:objectRequest];
+    }
+}
+
+- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
+    if (objectRequest.responseStatusCode == 201) {
+        PithosLocalObjectState *storedState = [storedLocalObjectStates 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];
+        }
+    } else {
+        [self requestFailed:objectRequest];
+    }
+}
+
+- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Sync::delete object finished: %@", objectRequest.url);
+    if (objectRequest.responseStatusCode == 204) {
+        [storedLocalObjectStates 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];
+        }
+    } else {
+        [self requestFailed:objectRequest];
+    }
+}
+
+- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
+    NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
+    ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
+    PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
+    PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
+    NSUInteger totalBytes = activity.totalBytes;
+    NSUInteger currentBytes = activity.currentBytes;
+    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;
+        [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) {
+            [activityFacility endActivity:activity 
+                              withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
+            @synchronized(self) {
+                syncOperationCount--;
+                if (!syncOperationCount)
+                    [self sync];
+            }
+            return;
+        } 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--;
+                }
+                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];
+            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
+            __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName 
+                                                                                                                        blockSize:blockSize 
+                                                                                                                          forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
+                                                                                                                missingBlockIndex:missingBlockIndex 
+                                                                                                                   sharingAccount:nil];
+            newContainerRequest.delegate = self;
+            newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
+            newContainerRequest.didFailSelector = @selector(requestFailed:);
+            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"];
+            [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))] 
+                                      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]]];
+        }
+    } else {
+        [self requestFailed:objectRequest];
+    }
+}
+
+- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
+    NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
+    if (containerRequest.responseStatusCode == 202) {
+        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) {
+            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];
+            newObjectRequest.delegate = self;
+            newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
+            newObjectRequest.didFailSelector = @selector(requestFailed:);
+            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]]];
+        } else {
+            if (newSyncRequested) {
+                [activityFacility endActivity:activity 
+                                  withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
+                @synchronized(self) {
+                    syncOperationCount--;
+                    if (!syncOperationCount)
+                        [self sync];
+                }
+                return;
+            } else {
+                __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName
+                                                                                                                            blockSize:blockSize
+                                                                                                                              forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
+                                                                                                                    missingBlockIndex:missingBlockIndex 
+                                                                                                                       sharingAccount:nil];
+                newContainerRequest.delegate = self;
+                newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
+                newContainerRequest.didFailSelector = @selector(requestFailed:);
+                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))] 
+                                          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]]];
+            }
+        }
+    } else {
+        [self requestFailed:containerRequest];
+    }
+}
+
+- (void)requestFailed:(ASIPithosRequest *)request {
+    if ([request isCancelled]) {
+        [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
+                          withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+        @synchronized(self) {
+            syncOperationCount--;
+        }
+        return;
+    }
+    if (newSyncRequested) {
+        [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
+                          withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+        @synchronized(self) {
+            syncOperationCount--;
+            if (!syncOperationCount)
+                [self sync];
+        }
+        return;
+    }
+    NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+    if (retries > 0) {
+        ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
+        [(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]]];
+    } else {
+        [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
+                          withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
+        @synchronized(self) {
+            syncOperationCount--;
+        }
+    }
+}
+
+
+@end
index ff7fcc0..483afb3 100644 (file)
@@ -38,6 +38,7 @@
 @class ASIPithosRequest;
 @class ASIPithosContainerRequest;
 @class ASIPithosObjectRequest;
+@class ASIPithosObject;
 
 @interface PithosUtilities : NSObject
 
                                             checkIfExists:(BOOL)ifExists 
                                            sharingAccount:(NSString *)sharingAccount;
 
+
++ (ASIPithosObjectRequest *)objectBlockDataRequestWithContainerName:(NSString *)containerName 
+                                                             object:(ASIPithosObject *)object 
+                                                         blockIndex:(NSUInteger)blockIndex 
+                                                          blockSize:(NSUInteger)blockSize;
++ (NSIndexSet *)missingBlocksForFile:(NSString *)filePath
+                           blockSize:(NSUInteger)blockSize 
+                           blockHash:(NSString *)blockHash 
+                          withHashes:(NSArray *)hashes;
+
 + (ASIPithosObjectRequest *)writeObjectDataRequestWithContainerName:(NSString *)containerName
                                                          objectName:(NSString *)objectName
                                                         contentType:(NSString *)contentType 
 
 + (NSInteger)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request;
 + (NSInteger)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request;
++ (NSInteger)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error;
 
 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority;
 + (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request;
index 62db1e5..8fecb7b 100644 (file)
         fileName = [fileName stringByAppendingString:@"/"];    
     fileName = [fileName stringByReplacingOccurrencesOfString:@"/" withString:@":"];
     
-    NSFileManager *defaultManager = [NSFileManager defaultManager];
+    NSFileManager *fileManager = [NSFileManager defaultManager];
     
     NSString *destinationPath = [directoryPath stringByAppendingPathComponent:fileName];
-    if (ifExists && [defaultManager fileExistsAtPath:destinationPath]) {
+    if (ifExists && [fileManager fileExistsAtPath:destinationPath]) {
         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
         [alert setMessageText:@"File Exists"];
         [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", fileName]];
     }
     
     BOOL directoryIsDirectory;
-    BOOL directoryExists = [defaultManager fileExistsAtPath:directoryPath isDirectory:&directoryIsDirectory];
+    BOOL directoryExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&directoryIsDirectory];
     NSError *error = nil;
     if (!directoryExists) {
-        [defaultManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
+        [fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
     } else if (!directoryIsDirectory) {
-        [defaultManager removeItemAtPath:directoryPath error:&error];
+        [fileManager removeItemAtPath:directoryPath error:&error];
     }
     if (error) {
         NSLog(@"Cannot remove existing file '%@': %@", fileName, error);
     if (objects == nil)
         return nil;
     
-    NSFileManager *defaultManager = [NSFileManager defaultManager];
+    NSFileManager *fileManager = [NSFileManager defaultManager];
     NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[objects count]];
     NSUInteger subdirPrefixLength = [objectName length];
 
     NSError *error = nil;
-    [defaultManager createDirectoryAtPath:[directoryPath stringByAppendingPathComponent:subdirName] withIntermediateDirectories:YES attributes:nil error:&error];
+    [fileManager createDirectoryAtPath:[directoryPath stringByAppendingPathComponent:subdirName] withIntermediateDirectories:YES attributes:nil error:&error];
     if (error) {
         NSLog(@"Cannot create directory at '%@': %@", directoryPath, error);
         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
             subdirDirectoryPath = [subdirDirectoryPath stringByAppendingPathComponent:[object.name substringFromIndex:subdirPrefixLength]];
             
             BOOL directoryIsDirectory;
-            BOOL directoryExists = [defaultManager fileExistsAtPath:subdirDirectoryPath isDirectory:&directoryIsDirectory];
+            BOOL directoryExists = [fileManager fileExistsAtPath:subdirDirectoryPath isDirectory:&directoryIsDirectory];
             NSError *error = nil;
             if (!directoryExists) {
-                [defaultManager createDirectoryAtPath:subdirDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error];
+                [fileManager createDirectoryAtPath:subdirDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error];
                 if (error) {
                     NSLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error);
                     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
                     [alert runModal];
                 }
             } else if (!directoryIsDirectory) {
-                [defaultManager removeItemAtPath:subdirDirectoryPath error:&error];
+                [fileManager removeItemAtPath:subdirDirectoryPath error:&error];
                 if (error) {
                     NSLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error);
                     NSAlert *alert = [[[NSAlert alloc] init] autorelease];
 }
 
 #pragma mark -
+#pragma mark Download Block
+
++ (ASIPithosObjectRequest *)objectBlockDataRequestWithContainerName:(NSString *)containerName 
+                                                             object:(ASIPithosObject *)object 
+                                                         blockIndex:(NSUInteger)blockIndex 
+                                                          blockSize:(NSUInteger)blockSize {
+    NSUInteger rangeStart = blockIndex * blockSize;
+    NSUInteger rangeEnd = (rangeStart + blockSize <= object.bytes) ? (rangeStart + blockSize - 1) : (object.bytes - 1);
+    ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithContainerName:containerName
+                                                                                            objectName:object.name
+                                                                                               version:nil
+                                                                                                 range:[NSString stringWithFormat:@"bytes=%lu-%lu", rangeStart, rangeEnd]
+                                                                                               ifMatch:object.hash];
+    return objectRequest;
+}
+
++ (NSIndexSet *)missingBlocksForFile:(NSString *)filePath
+                           blockSize:(NSUInteger)blockSize 
+                           blockHash:(NSString *)blockHash 
+                          withHashes:(NSArray *)hashes {
+    NSArray *fileHashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
+    return [hashes indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
+        if ((idx >= [fileHashes count]) || ![(NSString *)obj isEqualToString:[fileHashes objectAtIndex:idx]])
+            return YES;
+        return NO;
+    }];
+}
+
+#pragma mark -
 #pragma mark Upload
 
 + (ASIPithosObjectRequest *)writeObjectDataRequestWithContainerName:(NSString *)containerName
                                                             sharingAccount:(NSString *)sharingAccount {
     NSIndexSet *missingBlocks = [self missingBlocksForHashes:hashes withMissingHashesResponse:missingHashesResponse];
     
-    NSFileManager *defaultManager = [NSFileManager defaultManager];
+    NSFileManager *fileManager = [NSFileManager defaultManager];
     NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
     
     // http://cocoawithlove.com/2009/07/temporary-files-and-folders-in-cocoa.html
     char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
     strcpy(tempFileNameCString, tempFileTemplateCString);
     int fileDescriptor = mkstemp(tempFileNameCString);
-    NSString *tempFilePath = [defaultManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
+    NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
     if (fileDescriptor == -1) {
         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
         [alert setMessageText:@"Create Temporary File Error"];
     if (ifExists && ![self proceedIfObjectExistsAtContainerName:containerName objectName:objectName sharingAccount:sharingAccount])
         return nil;
 
-    NSFileManager *defaultManager = [NSFileManager defaultManager];
+    NSFileManager *fileManager = [NSFileManager defaultManager];
     NSError *error = nil;
-    NSArray *subPaths = [defaultManager subpathsOfDirectoryAtPath:directoryPath error:&error];
+    NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:directoryPath error:&error];
     if (error) {
         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
         [alert setMessageText:@"Directory Read Error"];
     NSString *fileName;
     for (NSString *objectNameSuffix in subPaths) {
         filePath = [directoryPath stringByAppendingPathComponent:objectNameSuffix];
-        if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
+        if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
             if (!isDirectory) {
                 hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize];
                 if (hashes) {
 
 // Size of the file in bytes
 + (NSUInteger)bytesOfFile:(NSString *)filePath {
-    NSFileManager *defaultManager = [NSFileManager defaultManager];
-    NSDictionary *attributes = [defaultManager attributesOfItemAtPath:filePath error:nil];
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil];
     return [[attributes objectForKey:NSFileSize] intValue];
 }
 
     return [alert runModal];
 }
 
++ (NSInteger)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error {
+    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+    [alert setMessageText:title];
+    if (error)
+        [alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, error]];
+    else
+        [alert setInformativeText:message];
+    [alert addButtonWithTitle:@"OK"];
+    return [alert runModal];
+}
+
 #pragma mark -
 #pragma mark Request Helper Methods
 
index 98f82bd..fdd2fc2 100644 (file)
                                        </object>
                                        <int key="connectionID">559</int>
                                </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">syncNow:</string>
+                                               <reference key="source" ref="976324537"/>
+                                               <reference key="destination" ref="575253026"/>
+                                       </object>
+                                       <int key="connectionID">561</int>
+                               </object>
                        </object>
                        <object class="IBMutableOrderedSet" key="objectRecords">
                                <object class="NSArray" key="orderedObjects">
                                <reference key="dict.values" ref="0"/>
                        </object>
                        <nil key="sourceID"/>
-                       <int key="maxID">560</int>
-               </object>
-               <object class="IBClassDescriber" key="IBDocument.Classes">
-                       <object class="NSMutableArray" key="referencedPartialClassDescriptions">
-                               <bool key="EncodedWithXMLCoder">YES</bool>
-                               <object class="IBPartialClassDescription">
-                                       <string key="className">NSDocument</string>
-                                       <object class="NSMutableDictionary" key="actions">
-                                               <bool key="EncodedWithXMLCoder">YES</bool>
-                                               <object class="NSArray" key="dict.sortedKeys">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>printDocument:</string>
-                                                       <string>revertDocumentToSaved:</string>
-                                                       <string>runPageLayout:</string>
-                                                       <string>saveDocument:</string>
-                                                       <string>saveDocumentAs:</string>
-                                                       <string>saveDocumentTo:</string>
-                                               </object>
-                                               <object class="NSMutableArray" key="dict.values">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>id</string>
-                                                       <string>id</string>
-                                                       <string>id</string>
-                                                       <string>id</string>
-                                                       <string>id</string>
-                                                       <string>id</string>
-                                               </object>
-                                       </object>
-                                       <object class="NSMutableDictionary" key="actionInfosByName">
-                                               <bool key="EncodedWithXMLCoder">YES</bool>
-                                               <object class="NSArray" key="dict.sortedKeys">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>printDocument:</string>
-                                                       <string>revertDocumentToSaved:</string>
-                                                       <string>runPageLayout:</string>
-                                                       <string>saveDocument:</string>
-                                                       <string>saveDocumentAs:</string>
-                                                       <string>saveDocumentTo:</string>
-                                               </object>
-                                               <object class="NSMutableArray" key="dict.values">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <object class="IBActionInfo">
-                                                               <string key="name">printDocument:</string>
-                                                               <string key="candidateClassName">id</string>
-                                                       </object>
-                                                       <object class="IBActionInfo">
-                                                               <string key="name">revertDocumentToSaved:</string>
-                                                               <string key="candidateClassName">id</string>
-                                                       </object>
-                                                       <object class="IBActionInfo">
-                                                               <string key="name">runPageLayout:</string>
-                                                               <string key="candidateClassName">id</string>
-                                                       </object>
-                                                       <object class="IBActionInfo">
-                                                               <string key="name">saveDocument:</string>
-                                                               <string key="candidateClassName">id</string>
-                                                       </object>
-                                                       <object class="IBActionInfo">
-                                                               <string key="name">saveDocumentAs:</string>
-                                                               <string key="candidateClassName">id</string>
-                                                       </object>
-                                                       <object class="IBActionInfo">
-                                                               <string key="name">saveDocumentTo:</string>
-                                                               <string key="candidateClassName">id</string>
-                                                       </object>
-                                               </object>
-                                       </object>
-                                       <object class="IBClassDescriptionSource" key="sourceIdentifier">
-                                               <string key="majorKey">IBProjectSource</string>
-                                               <string key="minorKey">./Classes/NSDocument.h</string>
-                                       </object>
-                               </object>
-                               <object class="IBPartialClassDescription">
-                                       <string key="className">PithosBrowserController</string>
-                                       <string key="superclassName">NSWindowController</string>
-                                       <object class="NSMutableDictionary" key="actions">
-                                               <string key="NS.key.0">refresh:</string>
-                                               <string key="NS.object.0">id</string>
-                                       </object>
-                                       <object class="NSMutableDictionary" key="actionInfosByName">
-                                               <string key="NS.key.0">refresh:</string>
-                                               <object class="IBActionInfo" key="NS.object.0">
-                                                       <string key="name">refresh:</string>
-                                                       <string key="candidateClassName">id</string>
-                                               </object>
-                                       </object>
-                                       <object class="NSMutableDictionary" key="outlets">
-                                               <bool key="EncodedWithXMLCoder">YES</bool>
-                                               <object class="NSArray" key="dict.sortedKeys">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>activityProgressIndicator</string>
-                                                       <string>activityTextField</string>
-                                                       <string>browser</string>
-                                                       <string>horizontalSplitView</string>
-                                                       <string>leftBottomView</string>
-                                                       <string>leftTopView</string>
-                                                       <string>outlineView</string>
-                                                       <string>verticalSplitView</string>
-                                               </object>
-                                               <object class="NSMutableArray" key="dict.values">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>NSProgressIndicator</string>
-                                                       <string>NSTextField</string>
-                                                       <string>NSBrowser</string>
-                                                       <string>NSSplitView</string>
-                                                       <string>NSView</string>
-                                                       <string>NSView</string>
-                                                       <string>NSOutlineView</string>
-                                                       <string>NSSplitView</string>
-                                               </object>
-                                       </object>
-                                       <object class="NSMutableDictionary" key="toOneOutletInfosByName">
-                                               <bool key="EncodedWithXMLCoder">YES</bool>
-                                               <object class="NSArray" key="dict.sortedKeys">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>activityProgressIndicator</string>
-                                                       <string>activityTextField</string>
-                                                       <string>browser</string>
-                                                       <string>horizontalSplitView</string>
-                                                       <string>leftBottomView</string>
-                                                       <string>leftTopView</string>
-                                                       <string>outlineView</string>
-                                                       <string>verticalSplitView</string>
-                                               </object>
-                                               <object class="NSMutableArray" key="dict.values">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">activityProgressIndicator</string>
-                                                               <string key="candidateClassName">NSProgressIndicator</string>
-                                                       </object>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">activityTextField</string>
-                                                               <string key="candidateClassName">NSTextField</string>
-                                                       </object>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">browser</string>
-                                                               <string key="candidateClassName">NSBrowser</string>
-                                                       </object>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">horizontalSplitView</string>
-                                                               <string key="candidateClassName">NSSplitView</string>
-                                                       </object>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">leftBottomView</string>
-                                                               <string key="candidateClassName">NSView</string>
-                                                       </object>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">leftTopView</string>
-                                                               <string key="candidateClassName">NSView</string>
-                                                       </object>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">outlineView</string>
-                                                               <string key="candidateClassName">NSOutlineView</string>
-                                                       </object>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">verticalSplitView</string>
-                                                               <string key="candidateClassName">NSSplitView</string>
-                                                       </object>
-                                               </object>
-                                       </object>
-                                       <object class="IBClassDescriptionSource" key="sourceIdentifier">
-                                               <string key="majorKey">IBProjectSource</string>
-                                               <string key="minorKey">./Classes/PithosBrowserController.h</string>
-                                       </object>
-                               </object>
-                               <object class="IBPartialClassDescription">
-                                       <string key="className">PithosPreferencesController</string>
-                                       <string key="superclassName">NSWindowController</string>
-                                       <object class="NSMutableDictionary" key="actions">
-                                               <bool key="EncodedWithXMLCoder">YES</bool>
-                                               <object class="NSArray" key="dict.sortedKeys">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>authenticationLogin:</string>
-                                                       <string>toolbarItemSelected:</string>
-                                               </object>
-                                               <object class="NSMutableArray" key="dict.values">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>id</string>
-                                                       <string>id</string>
-                                               </object>
-                                       </object>
-                                       <object class="NSMutableDictionary" key="actionInfosByName">
-                                               <bool key="EncodedWithXMLCoder">YES</bool>
-                                               <object class="NSArray" key="dict.sortedKeys">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>authenticationLogin:</string>
-                                                       <string>toolbarItemSelected:</string>
-                                               </object>
-                                               <object class="NSMutableArray" key="dict.values">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <object class="IBActionInfo">
-                                                               <string key="name">authenticationLogin:</string>
-                                                               <string key="candidateClassName">id</string>
-                                                       </object>
-                                                       <object class="IBActionInfo">
-                                                               <string key="name">toolbarItemSelected:</string>
-                                                               <string key="candidateClassName">id</string>
-                                                       </object>
-                                               </object>
-                                       </object>
-                                       <object class="NSMutableDictionary" key="outlets">
-                                               <bool key="EncodedWithXMLCoder">YES</bool>
-                                               <object class="NSArray" key="dict.sortedKeys">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>authenticationRenewCheckBox</string>
-                                                       <string>authenticationTokenTextField</string>
-                                                       <string>authenticationUserTextField</string>
-                                                       <string>pithosBrowserController</string>
-                                                       <string>userDefaultsController</string>
-                                               </object>
-                                               <object class="NSMutableArray" key="dict.values">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>NSButton</string>
-                                                       <string>NSTextField</string>
-                                                       <string>NSTextField</string>
-                                                       <string>PithosBrowserController</string>
-                                                       <string>NSUserDefaultsController</string>
-                                               </object>
-                                       </object>
-                                       <object class="NSMutableDictionary" key="toOneOutletInfosByName">
-                                               <bool key="EncodedWithXMLCoder">YES</bool>
-                                               <object class="NSArray" key="dict.sortedKeys">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>authenticationRenewCheckBox</string>
-                                                       <string>authenticationTokenTextField</string>
-                                                       <string>authenticationUserTextField</string>
-                                                       <string>pithosBrowserController</string>
-                                                       <string>userDefaultsController</string>
-                                               </object>
-                                               <object class="NSMutableArray" key="dict.values">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">authenticationRenewCheckBox</string>
-                                                               <string key="candidateClassName">NSButton</string>
-                                                       </object>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">authenticationTokenTextField</string>
-                                                               <string key="candidateClassName">NSTextField</string>
-                                                       </object>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">authenticationUserTextField</string>
-                                                               <string key="candidateClassName">NSTextField</string>
-                                                       </object>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">pithosBrowserController</string>
-                                                               <string key="candidateClassName">PithosBrowserController</string>
-                                                       </object>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">userDefaultsController</string>
-                                                               <string key="candidateClassName">NSUserDefaultsController</string>
-                                                       </object>
-                                               </object>
-                                       </object>
-                                       <object class="IBClassDescriptionSource" key="sourceIdentifier">
-                                               <string key="majorKey">IBProjectSource</string>
-                                               <string key="minorKey">./Classes/PithosPreferencesController.h</string>
-                                       </object>
-                               </object>
-                               <object class="IBPartialClassDescription">
-                                       <string key="className">pithos_macosAppDelegate</string>
-                                       <string key="superclassName">NSObject</string>
-                                       <object class="NSMutableDictionary" key="actions">
-                                               <bool key="EncodedWithXMLCoder">YES</bool>
-                                               <object class="NSArray" key="dict.sortedKeys">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>aboutPithos:</string>
-                                                       <string>showPithosBrowser:</string>
-                                                       <string>showPithosPreferences:</string>
-                                               </object>
-                                               <object class="NSMutableArray" key="dict.values">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>id</string>
-                                                       <string>id</string>
-                                                       <string>id</string>
-                                               </object>
-                                       </object>
-                                       <object class="NSMutableDictionary" key="actionInfosByName">
-                                               <bool key="EncodedWithXMLCoder">YES</bool>
-                                               <object class="NSArray" key="dict.sortedKeys">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>aboutPithos:</string>
-                                                       <string>showPithosBrowser:</string>
-                                                       <string>showPithosPreferences:</string>
-                                               </object>
-                                               <object class="NSMutableArray" key="dict.values">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <object class="IBActionInfo">
-                                                               <string key="name">aboutPithos:</string>
-                                                               <string key="candidateClassName">id</string>
-                                                       </object>
-                                                       <object class="IBActionInfo">
-                                                               <string key="name">showPithosBrowser:</string>
-                                                               <string key="candidateClassName">id</string>
-                                                       </object>
-                                                       <object class="IBActionInfo">
-                                                               <string key="name">showPithosPreferences:</string>
-                                                               <string key="candidateClassName">id</string>
-                                                       </object>
-                                               </object>
-                                       </object>
-                                       <object class="NSMutableDictionary" key="outlets">
-                                               <bool key="EncodedWithXMLCoder">YES</bool>
-                                               <object class="NSArray" key="dict.sortedKeys">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>pithosBrowserController</string>
-                                                       <string>pithosPreferencesController</string>
-                                                       <string>statusMenu</string>
-                                                       <string>userDefaultsController</string>
-                                               </object>
-                                               <object class="NSMutableArray" key="dict.values">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>PithosBrowserController</string>
-                                                       <string>PithosPreferencesController</string>
-                                                       <string>NSMenu</string>
-                                                       <string>NSUserDefaultsController</string>
-                                               </object>
-                                       </object>
-                                       <object class="NSMutableDictionary" key="toOneOutletInfosByName">
-                                               <bool key="EncodedWithXMLCoder">YES</bool>
-                                               <object class="NSArray" key="dict.sortedKeys">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <string>pithosBrowserController</string>
-                                                       <string>pithosPreferencesController</string>
-                                                       <string>statusMenu</string>
-                                                       <string>userDefaultsController</string>
-                                               </object>
-                                               <object class="NSMutableArray" key="dict.values">
-                                                       <bool key="EncodedWithXMLCoder">YES</bool>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">pithosBrowserController</string>
-                                                               <string key="candidateClassName">PithosBrowserController</string>
-                                                       </object>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">pithosPreferencesController</string>
-                                                               <string key="candidateClassName">PithosPreferencesController</string>
-                                                       </object>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">statusMenu</string>
-                                                               <string key="candidateClassName">NSMenu</string>
-                                                       </object>
-                                                       <object class="IBToOneOutletInfo">
-                                                               <string key="name">userDefaultsController</string>
-                                                               <string key="candidateClassName">NSUserDefaultsController</string>
-                                                       </object>
-                                               </object>
-                                       </object>
-                                       <object class="IBClassDescriptionSource" key="sourceIdentifier">
-                                               <string key="majorKey">IBProjectSource</string>
-                                               <string key="minorKey">./Classes/pithos_macosAppDelegate.h</string>
-                                       </object>
-                               </object>
-                       </object>
+                       <int key="maxID">561</int>
                </object>
+               <object class="IBClassDescriber" key="IBDocument.Classes"/>
                <int key="IBDocument.localizationMode">0</int>
                <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
                <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
index 2796712..3fafe29 100644 (file)
        <string>MainMenu</string>
        <key>NSPrincipalClass</key>
        <string>NSApplication</string>
-       <key>PithosStorageURLPrefix</key>
-       <string>https://pithos.dev.grnet.gr/v1</string>
-       <key>PithosPublicURLPrefix</key>
-       <string>https://pithos.dev.grnet.gr</string>
-       <key>PithosLoginURLPrefix</key>
-       <string>https://pithos.dev.grnet.gr/login</string>
        <key>PithosAboutURL</key>
        <string>https://pithos.dev.grnet.gr/docs</string>
+       <key>PithosLoginURLPrefix</key>
+       <string>https://pithos.dev.grnet.gr/login</string>
+       <key>PithosPublicURLPrefix</key>
+       <string>https://pithos.dev.grnet.gr</string>
+       <key>PithosStorageURLPrefix</key>
+       <string>https://pithos.dev.grnet.gr/v1</string>
+       <key>PithosSyncDirectoryPath</key>
+       <string></string>
+       <key>PithosSyncContainerName</key>
+       <string>pithos</string>
+       <key>PithosSyncTimeInterval</key>
+       <real>180</real>
 </dict>
 </plist>
index 7d999ef..c684274 100644 (file)
 #import <Cocoa/Cocoa.h>
 @class PithosBrowserController;
 @class PithosPreferencesController;
+@class PithosSyncDaemon;
 
 @interface pithos_macosAppDelegate : NSObject <NSApplicationDelegate> {
     IBOutlet PithosBrowserController *pithosBrowserController;
     IBOutlet PithosPreferencesController *pithosPreferencesController;
+    
+    PithosSyncDaemon *pithosSyncDaemon;
 
     IBOutlet NSMenu *statusMenu;
     NSStatusItem *statusItem;
     NSString *publicURLPrefix;
     NSString *loginURLPrefix;
     NSString *aboutURL;
+    
+    NSString *syncDirectoryPath;
+    NSString *syncContainerName;
+    NSTimeInterval syncTimeInterval;
 }
 
 - (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent;
 - (IBAction)showPithosBrowser:(id)sender;
 - (IBAction)showPithosPreferences:(id)sender;
 - (IBAction)aboutPithos:(id)sender;
+- (IBAction)syncNow:(id)sender;
 
 - (void)authenticateWithAuthUser:(NSString *)authUser authToken:(NSString *)authToken;
+- (void)startSyncWithDirectoryPath:(NSString *)directoryPath containerName:(NSString *)containerName;
 
 @property (nonatomic, readonly) NSString *storageURLPrefix;
 @property (nonatomic, readonly) NSString *publicURLPrefix;
 @property (nonatomic, readonly) NSString *loginURLPrefix;
 @property (nonatomic, readonly) NSString *aboutURL;
+@property (nonatomic, readonly) NSString *syncDirectoryPath;
+@property (nonatomic, readonly) NSString *syncContainerName;
+@property (nonatomic, assign) NSTimeInterval syncTimeInterval;
 
 @end
index 5188f15..235a9f7 100644 (file)
 #import "pithos_macosAppDelegate.h"
 #import "PithosBrowserController.h"
 #import "PithosPreferencesController.h"
+#import "PithosSyncDaemon.h"
 #import "ASIPithosRequest.h"
 #import "ASIDownloadCache.h"
 
 @implementation pithos_macosAppDelegate
 @synthesize storageURLPrefix, publicURLPrefix, loginURLPrefix, aboutURL;
+@synthesize syncDirectoryPath, syncContainerName, syncTimeInterval;
 
 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
     NSURL *testURL;
-    storageURLPrefix = [[NSUserDefaults standardUserDefaults] stringForKey:@"PithosStorageURLPrefix"];
+    storageURLPrefix = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"PithosStorageURLPrefix"];
     if (!storageURLPrefix) {
         storageURLPrefix = [NSString stringWithString:@"https://pithos.dev.grnet.gr/v1"];
     } else {
@@ -56,7 +58,7 @@
     }
     [storageURLPrefix retain];
     
-    publicURLPrefix = [[NSUserDefaults standardUserDefaults] stringForKey:@"PithosPublicURLPrefix"];
+    publicURLPrefix = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"PithosPublicURLPrefix"];
     if (!publicURLPrefix) {
         publicURLPrefix = [NSString stringWithString:@"https://pithos.dev.grnet.gr"];
     } else {
@@ -66,7 +68,7 @@
     }
     [publicURLPrefix retain];
     
-    loginURLPrefix = [[NSUserDefaults standardUserDefaults] stringForKey:@"PithosLoginURLPrefix"];
+    loginURLPrefix = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"PithosLoginURLPrefix"];
     if (!loginURLPrefix) {
         loginURLPrefix = [NSString stringWithString:@"https://pithos.dev.grnet.gr/login"];
     } else {
@@ -76,7 +78,7 @@
     }
     [loginURLPrefix retain];
 
-    aboutURL = [[NSUserDefaults standardUserDefaults] stringForKey:@"PithosAboutURL"];
+    aboutURL = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"PithosAboutURL"];
     if (!aboutURL) {
         aboutURL = [NSString stringWithString:@"https://pithos.dev.grnet.gr/docs"];
     } else {
     }
     [aboutURL retain];
     
+    syncDirectoryPath = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"PithosSyncDirectoryPath"];
+    if (!syncDirectoryPath || ![syncDirectoryPath length]) {
+        syncDirectoryPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"Pithos"];
+    } else {
+        BOOL isDirectory;
+        if ([[NSFileManager defaultManager] fileExistsAtPath:syncDirectoryPath isDirectory:&isDirectory] && !isDirectory)
+            syncDirectoryPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"Pithos"];
+    }
+    [syncDirectoryPath retain];
+    
+    syncContainerName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"PithosSyncContainerName"];
+    if (!syncContainerName || ![syncContainerName length]) {
+        syncContainerName = [NSString stringWithString:@"pithos"];
+    }
+    [syncContainerName retain];
+
+    syncTimeInterval = [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"PithosSyncTimeInterval"] doubleValue];
+    if (syncTimeInterval <= 0) {
+        syncTimeInterval = 180.0;
+    }
+    
     [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self 
                                                        andSelector:@selector(handleAppleEvent:withReplyEvent:) 
                                                      forEventClass:kInternetEventClass 
     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:self.aboutURL]];
 }
 
+- (IBAction)syncNow:(id)sender {
+    [pithosSyncDaemon sync];
+}
+
 #pragma mark -
 #pragma Authentication
 
 - (void)authenticateWithAuthUser:(NSString *)authUser authToken:(NSString *)authToken {
     NSLog(@"Authentication - storageURLPrefix:%@, authUser:%@, authToken:%@", storageURLPrefix, authUser, authToken);
-    if ([authUser length] && [authToken length]) {
+    if ([authUser length] && [authToken length] && 
+        (![[ASIPithosRequest authUser] isEqualToString:authUser] || ![[ASIPithosRequest authToken] isEqualToString:authToken])) {
         [[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
         [[ASIPithosRequest sharedQueue] cancelAllOperations];
+        
         [ASIPithosRequest setAuthURL:storageURLPrefix];
         [ASIPithosRequest setStorageURLPrefix:storageURLPrefix];
         [ASIPithosRequest setAuthUser:authUser];
         [ASIPithosRequest setAuthToken:authToken];
         [ASIPithosRequest setPublicURLPrefix:publicURLPrefix];
         
+        [self startSyncWithDirectoryPath:syncDirectoryPath containerName:syncContainerName];
+        
         [[NSNotificationCenter defaultCenter] postNotificationName:@"PithosAuthenticationCredentialsUpdated" object:self];
     }
 }
 
+- (void)startSyncWithDirectoryPath:(NSString *)directoryPath containerName:(NSString *)containerName {
+    [pithosSyncDaemon release];
+    pithosSyncDaemon = [[PithosSyncDaemon alloc] initWithDirectoryPath:syncDirectoryPath 
+                                                         containerName:syncContainerName 
+                                                          timeInterval:syncTimeInterval];
+}
+
 @end