Other fixes and changes.
-Subproject commit 20e74a13c1a904064b0544b398b677cc30f28ae8
+Subproject commit 0a4b9bd3aa71857ebe3439befde0dc5c3b3c96b0
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;
};
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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
PithosActivityCopy,
PithosActivityMove,
PithosActivityCreateDirectory,
- PithosActivityDelete
+ PithosActivityDelete,
+ PithosActivityOther
} PithosActivityType;
@interface PithosActivity : NSObject {
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
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
@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;
- (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])
if ([containerRequest error]) {
[PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
return NO;
- } else if (containerRequest.responseStatusCode != 200) {
+ } else if (containerRequest.responseStatusCode != 204) {
[PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
return NO;
}
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]];
// 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]];
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);
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
@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;
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
</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">
<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>
#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
#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 {
}
[storageURLPrefix retain];
- publicURLPrefix = [[NSUserDefaults standardUserDefaults] stringForKey:@"PithosPublicURLPrefix"];
+ publicURLPrefix = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"PithosPublicURLPrefix"];
if (!publicURLPrefix) {
publicURLPrefix = [NSString stringWithString:@"https://pithos.dev.grnet.gr"];
} else {
}
[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 {
}
[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