BOOL isDirectory;
NSString *hash;
NSString *tmpFilePath;
-
- BOOL exists;
+ NSDate *fileModificationDate;
+ NSNumber *fileSize;
}
- (id)initWithFile:(NSString *)aFilePath blockHash:(NSString *)blockHash blockSize:(NSUInteger)blockSize;
@property (nonatomic, assign) BOOL isDirectory;
@property (nonatomic, retain) NSString *hash;
@property (nonatomic, retain) NSString *tmpFilePath;
+@property (nonatomic, retain) NSDate *fileModificationDate;
+@property (nonatomic, retain) NSNumber *fileSize;
-@property (nonatomic, readonly) BOOL exists;
+- (BOOL)exists;
+- (BOOL)isModified;
@end
#import "HashMapHash.h"
@implementation PithosLocalObjectState
-@synthesize filePath, isDirectory, exists, hash, tmpFilePath;
+@synthesize filePath, isDirectory, hash, tmpFilePath, fileModificationDate, fileSize;
#pragma mark -
#pragma mark Object Lifecycle
if ((self = [self init])) {
self.filePath = aFilePath;
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory] && !isDirectory) {
- self.hash = [HashMapHash calculateHashMapHash:[HashMapHash calculateObjectHashMap:aFilePath
+ self.hash = [HashMapHash calculateHashMapHash:[HashMapHash calculateObjectHashMap:filePath
withBlockHash:blockHash
andBlockSize:blockSize]];
}
}
- (void)dealloc {
+ [fileSize release];
+ [fileModificationDate release];
self.tmpFilePath = nil;
[filePath release];
[hash release];
}
- (NSString *)description {
- return [NSString stringWithFormat:@"hash: %@, filePath: %@, isDirectory: %d",
- hash, filePath, isDirectory];
+ return [NSString stringWithFormat:@"hash: %@, filePath: %@, isDirectory: %d, fileModificationDate: %@, fileSize: %@",
+ hash, filePath, isDirectory, fileModificationDate, fileSize];
}
- (BOOL)isEqualToState:(PithosLocalObjectState *)aState {
else if (aState.isDirectory)
// Object is not a directory, while the other is
return NO;
- else if (!self.exists)
+ else if (![self exists])
// Object doesn't exist, check the other
- return (!aState.exists);
- else if (!aState.exists)
+ return (![aState exists]);
+ else if (![aState exists])
// Object exists, while the other doesn't
return NO;
else
}
}
-- (BOOL)exists {
- return (isDirectory || hash);
-}
-
- (void)setHash:(NSString *)aHash {
[hash release];
if ([aHash length] == 64)
hash = [aHash retain];
else
hash = nil;
+ if (filePath) {
+ NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
+ self.fileModificationDate = [attributes objectForKey:NSFileModificationDate];
+ self.fileSize = [attributes objectForKey:NSFileSize];
+ } else {
+ self.fileModificationDate = nil;
+ self.fileSize = nil;
+ }
+}
+
+#pragma mark -
+#pragma mark Methods
+
+- (BOOL)exists {
+ return (isDirectory || hash);
+}
+
+- (BOOL)isModified {
+ NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
+ return (![fileSize isEqualToNumber:[attributes objectForKey:NSFileSize]] ||
+ ![fileModificationDate isEqualToDate:[attributes objectForKey:NSFileModificationDate]]);
}
#pragma mark -
self.filePath = [decoder decodeObjectForKey:@"filePath"];
self.isDirectory = [decoder decodeBoolForKey:@"isDirectory"];
self.tmpFilePath = [decoder decodeObjectForKey:@"tmpFilePath"];
+ self.fileModificationDate = [decoder decodeObjectForKey:@"fileModificationDate"];
+ self.fileSize = [decoder decodeObjectForKey:@"fileSize"];
}
return self;
}
[encoder encodeObject:filePath forKey:@"filePath"];
[encoder encodeBool:isDirectory forKey:@"isDirectory"];
[encoder encodeObject:tmpFilePath forKey:@"tmpFilePath"];
+ [encoder encodeObject:fileModificationDate forKey:@"fileModificationDate"];
+ [encoder encodeObject:fileSize forKey:@"fileSize"];
}
@end
});
} else {
directoryCreated = YES;
+ storedState.filePath = filePath;
storedState.isDirectory = YES;
[self saveLocalState];
}
});
} else {
fileCreated = YES;
+ storedState.filePath = filePath;
storedState.hash = object.objectHash;
storedState.tmpFilePath = nil;
[self saveLocalState];
}
// Check first if a local copy exists
if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
+ storedState.filePath = filePath;
+ storedState.hash = object.objectHash;
+ [self saveLocalState];
dispatch_async(dispatch_get_main_queue(), ^{
[activityFacility startAndEndActivityWithType:PithosActivityOther
message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
}
// Check first if a local copy exists
if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
+ storedState.filePath = filePath;
+ storedState.hash = object.objectHash;
+ // Delete incomplete temp download
+ storedState.tmpFilePath = nil;
+ [self saveLocalState];
dispatch_async(dispatch_get_main_queue(), ^{
[activityFacility startAndEndActivityWithType:PithosActivityOther
message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
});
- // Delete incomplete temp download
- error = nil;
- storedState.tmpFilePath = nil;
} else {
[self increaseSyncOperationCount];
ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos
NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
nil];
[networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
- } else if (!currentState.exists) {
+ } else if (![currentState exists]) {
// Delete remote object
if (fileExists) {
// Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
}
self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:[subPaths count]];
for (NSString *objectName in subPaths) {
- if (![storedLocalObjectStates objectForKey:objectName]) {
- [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
- }
+ PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:objectName];
NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
- [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
- blockHash:blockHash
- blockSize:blockSize]
- forKey:filePath];
+ if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
+ // New or modified existing local object, compute current state
+ if (!storedLocalObjectState)
+ // For new local object, also create empty stored state
+ [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
+ [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
+ blockHash:blockHash
+ blockSize:blockSize]
+ forKey:filePath];
+ } else {
+ // Local object hasn't changed, set stored state also to current
+ [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
+ }
}
[self saveLocalState];
object.contentType = remoteObject.contentType;
object.objectHash = remoteObject.objectHash;
[self updateLocalStateWithObject:object localFilePath:filePath];
- } else if (!remoteObject && !currentLocalObjectState.exists) {
+ } else if (!remoteObject && ![currentLocalObjectState exists]) {
// Server state hasn't changed
// If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
[storedLocalObjectStates removeObjectForKey:objectName];
// Server state has also changed
if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
// Both did the same change (directory)
+ storedLocalObjectState.filePath = filePath;
storedLocalObjectState.isDirectory = YES;
[self saveLocalState];
} else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
// Both did the same change (object edit or delete)
- if (!remoteObjectState.exists)
+ if (![remoteObjectState exists]) {
[storedLocalObjectStates removeObjectForKey:object.name];
- else
+ } else {
+ storedLocalObjectState.filePath = filePath;
storedLocalObjectState.hash = remoteObjectState.hash;
+ }
[self saveLocalState];
} else {
// Conflict, we ask the user which change to keep
NSString *firstButtonText;
NSString *secondButtonText;
- if (!remoteObjectState.exists) {
+ if (![remoteObjectState exists]) {
// 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.exists) {
+ } else if (![currentLocalObjectState exists]) {
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";
currentBytes:activity.totalBytes];
});
+ storedState.filePath = filePath;
storedState.hash = object.objectHash;
storedState.tmpFilePath = nil;
[self saveLocalState];
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
<data>
- <int key="IBDocument.SystemTarget">1060</int>
- <string key="IBDocument.SystemVersion">10K549</string>
+ <int key="IBDocument.SystemTarget">1070</int>
+ <string key="IBDocument.SystemVersion">11C74</string>
<string key="IBDocument.InterfaceBuilderVersion">1938</string>
- <string key="IBDocument.AppKitVersion">1038.36</string>
- <string key="IBDocument.HIToolboxVersion">461.00</string>
+ <string key="IBDocument.AppKitVersion">1138.23</string>
+ <string key="IBDocument.HIToolboxVersion">567.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="NS.object.0">1938</string>
</object>
<object class="NSArray" key="IBDocument.IntegratedClassDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
+ <string>NSUserDefaultsController</string>
<string>NSMenu</string>
<string>NSMenuItem</string>
<string>NSCustomObject</string>
</object>
</object>
</object>
+ <object class="NSUserDefaultsController" id="796122129">
+ <bool key="NSSharedInstance">YES</bool>
+ </object>
</object>
<object class="IBObjectContainer" key="IBDocument.Objects">
<object class="NSMutableArray" key="connectionRecords">
</object>
<object class="IBConnectionRecord">
<object class="IBBindingConnection" key="connection">
+ <string key="label">title: delegate.aboutVersion</string>
+ <reference key="source" ref="1057169541"/>
+ <reference key="destination" ref="1050"/>
+ <object class="NSNibBindingConnector" key="connector">
+ <reference key="NSSource" ref="1057169541"/>
+ <reference key="NSDestination" ref="1050"/>
+ <string key="NSLabel">title: delegate.aboutVersion</string>
+ <string key="NSBinding">title</string>
+ <string key="NSKeyPath">delegate.aboutVersion</string>
+ <int key="NSNibBindingConnectorVersion">2</int>
+ </object>
+ </object>
+ <int key="connectionID">580</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBBindingConnection" key="connection">
<string key="label">enabled: delegate.alwaysNo</string>
<reference key="source" ref="629835270"/>
<reference key="destination" ref="1050"/>
<reference key="parent" ref="0"/>
<string key="objectName">PithosPreferencesController</string>
</object>
+ <object class="IBObjectRecord">
+ <int key="objectID">576</int>
+ <reference key="object" ref="796122129"/>
+ <reference key="parent" ref="0"/>
+ </object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
<string>546.IBPluginDependency</string>
<string>56.IBPluginDependency</string>
<string>57.IBPluginDependency</string>
+ <string>576.IBPluginDependency</string>
<string>58.IBPluginDependency</string>
<string>72.IBPluginDependency</string>
<string>73.IBPluginDependency</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
<reference key="dict.values" ref="0"/>
</object>
<nil key="sourceID"/>
- <int key="maxID">575</int>
+ <int key="maxID">580</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes"/>
<int key="IBDocument.localizationMode">0</int>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
- <string>1.0</string>
+ <string>0.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
</dict>
</array>
<key>CFBundleVersion</key>
- <string>1</string>
+ <string>20120206a</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
+ <key>LSUIElement</key>
+ <true/>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
- <key>LSUIElement</key>
- <true/>
</dict>
</plist>
IBOutlet NSMenu *statusMenu;
NSStatusItem *statusItem;
+ NSString *aboutVersion;
+
NSUserDefaults *userDefaults;
}
@property (nonatomic, retain) PithosBrowserController *pithosBrowserController;
@property (nonatomic, retain) PithosSyncDaemon *pithosSyncDaemon;
@property (nonatomic, assign) BOOL alwaysNo;
+@property (nonatomic, readonly) NSString *aboutVersion;
@end
#import "ASIDownloadCache.h"
@implementation pithos_macosAppDelegate
-@synthesize pithos, pithosBrowserController, pithosSyncDaemon, alwaysNo;
+@synthesize pithos, pithosBrowserController, pithosSyncDaemon, alwaysNo, aboutVersion;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
userDefaults = [[NSUserDefaults standardUserDefaults] retain];
}
#pragma mark -
+#pragma Properties
+
+- (NSString *)aboutVersion {
+ return [NSString stringWithFormat:@"About Pithos+ %@ (%@)",
+ [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
+ [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]];
+}
+
+#pragma mark -
#pragma Actions
- (IBAction)showPithosBrowser:(id)sender {