# HG changeset patch # User Jens Alfke # Date 1253854026 25200 # Node ID f2cd752db494b1174ff2c2227ca7da753cdd0bdd # Parent 4c10b7956435d6106ee1facc9d77865729fd29fe Initial Cocoa (Objective-C) API. diff -r 4c10b7956435 -r f2cd752db494 Ottoman.xcodeproj/project.pbxproj --- a/Ottoman.xcodeproj/project.pbxproj Thu Sep 24 21:46:17 2009 -0700 +++ b/Ottoman.xcodeproj/project.pbxproj Thu Sep 24 21:47:06 2009 -0700 @@ -8,6 +8,11 @@ /* Begin PBXBuildFile section */ 27156CAA104C9C44009EBD39 /* gtest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27156CA9104C9C44009EBD39 /* gtest.framework */; }; + 2754DD87106BCBCF00365FAA /* MYOttoman_test.m in Sources */ = {isa = PBXBuildFile; fileRef = 2754DD86106BCBCF00365FAA /* MYOttoman_test.m */; }; + 2754DDC5106BD20A00365FAA /* Test.m in Sources */ = {isa = PBXBuildFile; fileRef = 2754DDC4106BD20A00365FAA /* Test.m */; }; + 2754DDEE106BD3BD00365FAA /* Logging.m in Sources */ = {isa = PBXBuildFile; fileRef = 2754DDED106BD3BD00365FAA /* Logging.m */; }; + 2754DDF3106BD3D300365FAA /* ExceptionUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 2754DDF2106BD3D300365FAA /* ExceptionUtils.m */; }; + 2754DE2D106BD65600365FAA /* CollectionUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 2754DE2C106BD65600365FAA /* CollectionUtils.m */; }; 27603901105AC81200D931A7 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27603900105AC81200D931A7 /* CoreFoundation.framework */; }; 276E5BCD1066D13D008A2171 /* Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 276E5BC41066D13D008A2171 /* Base.cpp */; }; 276E5BCE1066D13D008A2171 /* Chunk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 276E5BC51066D13D008A2171 /* Chunk.cpp */; }; @@ -41,8 +46,21 @@ 276E5CC4106731C7008A2171 /* File.h in Headers */ = {isa = PBXBuildFile; fileRef = 276E5BBD1066D135008A2171 /* File.h */; }; 276E5CC5106731C8008A2171 /* Ottoman.h in Headers */ = {isa = PBXBuildFile; fileRef = 276E5BC11066D135008A2171 /* Ottoman.h */; }; 276E5CC6106731C8008A2171 /* VersionDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 276E5BC21066D135008A2171 /* VersionDictionary.h */; }; + 276E5D141067D2AD008A2171 /* MYOttoman.mm in Sources */ = {isa = PBXBuildFile; fileRef = 276E5D0A1067D24A008A2171 /* MYOttoman.mm */; }; + 276E5D151067D2B2008A2171 /* libOttoman.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 276E5CAE1067315D008A2171 /* libOttoman.a */; }; + 276E5D2E1067F86E008A2171 /* MYVersionDictionary.mm in Sources */ = {isa = PBXBuildFile; fileRef = 276E5D2D1067F86E008A2171 /* MYVersionDictionary.mm */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 276E5D7810692FA7008A2171 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 276E5CAD1067315D008A2171; + remoteInfo = "Static Library"; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 8DD76F690486A84900D96B5E /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; @@ -57,6 +75,15 @@ /* Begin PBXFileReference section */ 27156CA9104C9C44009EBD39 /* gtest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = gtest.framework; path = /Library/Frameworks/gtest.framework; sourceTree = ""; }; + 2754DD86106BCBCF00365FAA /* MYOttoman_test.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYOttoman_test.m; sourceTree = ""; }; + 2754DDC3106BD20A00365FAA /* Test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Test.h; sourceTree = ""; }; + 2754DDC4106BD20A00365FAA /* Test.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Test.m; sourceTree = ""; }; + 2754DDEC106BD3BD00365FAA /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logging.h; sourceTree = ""; }; + 2754DDED106BD3BD00365FAA /* Logging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Logging.m; sourceTree = ""; }; + 2754DDF1106BD3D300365FAA /* ExceptionUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExceptionUtils.h; sourceTree = ""; }; + 2754DDF2106BD3D300365FAA /* ExceptionUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExceptionUtils.m; sourceTree = ""; }; + 2754DE2B106BD65600365FAA /* CollectionUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CollectionUtils.h; sourceTree = ""; }; + 2754DE2C106BD65600365FAA /* CollectionUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CollectionUtils.m; sourceTree = ""; }; 27603900105AC81200D931A7 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; 276E5BBA1066D135008A2171 /* Base.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Base.h; sourceTree = ""; }; 276E5BBB1066D135008A2171 /* Chunk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Chunk.h; sourceTree = ""; }; @@ -83,6 +110,12 @@ 276E5BDB1066D142008A2171 /* TestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestUtils.h; sourceTree = ""; }; 276E5BDC1066D142008A2171 /* VersionDictionary_test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VersionDictionary_test.cpp; sourceTree = ""; }; 276E5CAE1067315D008A2171 /* libOttoman.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libOttoman.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 276E5D091067D24A008A2171 /* MYOttoman.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYOttoman.h; sourceTree = ""; }; + 276E5D0A1067D24A008A2171 /* MYOttoman.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MYOttoman.mm; sourceTree = ""; }; + 276E5D101067D27E008A2171 /* OttomanCocoa */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = OttomanCocoa; sourceTree = BUILT_PRODUCTS_DIR; }; + 276E5D2C1067F86E008A2171 /* MYVersionDictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYVersionDictionary.h; sourceTree = ""; }; + 276E5D2D1067F86E008A2171 /* MYVersionDictionary.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MYVersionDictionary.mm; sourceTree = ""; }; + 276E5D451069246E008A2171 /* MYOttoman_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYOttoman_internal.h; sourceTree = ""; }; 8DD76F6C0486A84900D96B5E /* OttomanTest */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = OttomanTest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -94,6 +127,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 276E5D0E1067D27E008A2171 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 276E5D151067D2B2008A2171 /* libOttoman.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8DD76F660486A84900D96B5E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -109,6 +150,7 @@ 08FB7794FE84155DC02AAC07 /* BPlusTree */ = { isa = PBXGroup; children = ( + 276E5D081067D20B008A2171 /* Cocoa */, 276E5BB91066D135008A2171 /* include */, 276E5BC31066D13D008A2171 /* src */, 276E5BD61066D142008A2171 /* test */, @@ -124,10 +166,27 @@ children = ( 8DD76F6C0486A84900D96B5E /* OttomanTest */, 276E5CAE1067315D008A2171 /* libOttoman.a */, + 276E5D101067D27E008A2171 /* OttomanCocoa */, ); name = Products; sourceTree = ""; }; + 2754DDEB106BD38600365FAA /* MYUtilities */ = { + isa = PBXGroup; + children = ( + 2754DE2B106BD65600365FAA /* CollectionUtils.h */, + 2754DE2C106BD65600365FAA /* CollectionUtils.m */, + 2754DDF1106BD3D300365FAA /* ExceptionUtils.h */, + 2754DDF2106BD3D300365FAA /* ExceptionUtils.m */, + 2754DDEC106BD3BD00365FAA /* Logging.h */, + 2754DDED106BD3BD00365FAA /* Logging.m */, + 2754DDC3106BD20A00365FAA /* Test.h */, + 2754DDC4106BD20A00365FAA /* Test.m */, + ); + name = MYUtilities; + path = /Volumes/snoog/Code/MYUtilities; + sourceTree = ""; + }; 276E5BB91066D135008A2171 /* include */ = { isa = PBXGroup; children = ( @@ -173,6 +232,21 @@ path = test; sourceTree = ""; }; + 276E5D081067D20B008A2171 /* Cocoa */ = { + isa = PBXGroup; + children = ( + 276E5D091067D24A008A2171 /* MYOttoman.h */, + 276E5D0A1067D24A008A2171 /* MYOttoman.mm */, + 276E5D2C1067F86E008A2171 /* MYVersionDictionary.h */, + 276E5D2D1067F86E008A2171 /* MYVersionDictionary.mm */, + 276E5D451069246E008A2171 /* MYOttoman_internal.h */, + 2754DD86106BCBCF00365FAA /* MYOttoman_test.m */, + 2754DDEB106BD38600365FAA /* MYUtilities */, + ); + name = Cocoa; + path = bindings/Cocoa; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -212,6 +286,23 @@ productReference = 276E5CAE1067315D008A2171 /* libOttoman.a */; productType = "com.apple.product-type.library.static"; }; + 276E5D0F1067D27E008A2171 /* Cocoa */ = { + isa = PBXNativeTarget; + buildConfigurationList = 276E5D161067D2D1008A2171 /* Build configuration list for PBXNativeTarget "Cocoa" */; + buildPhases = ( + 276E5D0D1067D27E008A2171 /* Sources */, + 276E5D0E1067D27E008A2171 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 276E5D7910692FA7008A2171 /* PBXTargetDependency */, + ); + name = Cocoa; + productName = Cocoa; + productReference = 276E5D101067D27E008A2171 /* OttomanCocoa */; + productType = "com.apple.product-type.tool"; + }; 8DD76F620486A84900D96B5E /* OttomanTest */ = { isa = PBXNativeTarget; buildConfigurationList = 1DEB923108733DC60010E9CD /* Build configuration list for PBXNativeTarget "OttomanTest" */; @@ -244,6 +335,7 @@ targets = ( 8DD76F620486A84900D96B5E /* OttomanTest */, 276E5CAD1067315D008A2171 /* Static Library */, + 276E5D0F1067D27E008A2171 /* Cocoa */, ); }; /* End PBXProject section */ @@ -265,6 +357,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 276E5D0D1067D27E008A2171 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 276E5D141067D2AD008A2171 /* MYOttoman.mm in Sources */, + 276E5D2E1067F86E008A2171 /* MYVersionDictionary.mm in Sources */, + 2754DD87106BCBCF00365FAA /* MYOttoman_test.m in Sources */, + 2754DDC5106BD20A00365FAA /* Test.m in Sources */, + 2754DDEE106BD3BD00365FAA /* Logging.m in Sources */, + 2754DDF3106BD3D300365FAA /* ExceptionUtils.m in Sources */, + 2754DE2D106BD65600365FAA /* CollectionUtils.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8DD76F640486A84900D96B5E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -288,6 +394,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 276E5D7910692FA7008A2171 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 276E5CAD1067315D008A2171 /* Static Library */; + targetProxy = 276E5D7810692FA7008A2171 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ 1DEB923208733DC60010E9CD /* Debug */ = { isa = XCBuildConfiguration; @@ -320,6 +434,7 @@ buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; GCC_C_LANGUAGE_STANDARD = c99; + GCC_INLINES_ARE_PRIVATE_EXTERN = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREPROCESSOR_DEFINITIONS = ""; @@ -345,6 +460,7 @@ GCC_C_LANGUAGE_STANDARD = c99; GCC_DYNAMIC_NO_PIC = YES; GCC_ENABLE_CPP_RTTI = NO; + GCC_INLINES_ARE_PRIVATE_EXTERN = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREPROCESSOR_DEFINITIONS = NDEBUG; GCC_TREAT_WARNINGS_AS_ERRORS = YES; @@ -380,12 +496,43 @@ buildSettings = { COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GENERATE_MASTER_OBJECT_FILE = YES; INSTALL_PATH = /usr/local/lib; PRODUCT_NAME = Ottoman; }; name = Release; }; + 276E5D121067D27F008A2171 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_PRECOMPILE_PREFIX_HEADER = NO; + GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/Foundation.framework/Headers/Foundation.h"; + GCC_PREPROCESSOR_DEFINITIONS = DEBUG; + INSTALL_PATH = /usr/local/bin; + OTHER_LDFLAGS = ( + "-framework", + Foundation, + ); + PRODUCT_NAME = OttomanCocoa; + }; + name = Debug; + }; + 276E5D131067D27F008A2171 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + GCC_PRECOMPILE_PREFIX_HEADER = NO; + GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/Foundation.framework/Headers/Foundation.h"; + GCC_PREPROCESSOR_DEFINITIONS = DEBUG; + INSTALL_PATH = /usr/local/bin; + OTHER_LDFLAGS = ( + "-framework", + Foundation, + ); + PRODUCT_NAME = OttomanCocoa; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -416,6 +563,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 276E5D161067D2D1008A2171 /* Build configuration list for PBXNativeTarget "Cocoa" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 276E5D121067D27F008A2171 /* Debug */, + 276E5D131067D27F008A2171 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; diff -r 4c10b7956435 -r f2cd752db494 bindings/Cocoa/MYOttoman.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bindings/Cocoa/MYOttoman.h Thu Sep 24 21:47:06 2009 -0700 @@ -0,0 +1,81 @@ +// +// MYOttoman.h +// Ottoman +// +// Created by Jens Alfke on 9/21/09. +// Copyright 2009 Jens Alfke. All rights reserved. +// + +#import + +@class MYVersionDictionary, MYCurrentVersionDictionary; + + +/** A version-controlled persistent dictionary. + Each version is stored as a MYVersionDictionary, + unsaved changes as an MYCurrentVersionDictionary. */ +@interface MYOttoman : NSObject +{ + void *_ottoman; + MYVersionDictionary *_lastVersion; + MYCurrentVersionDictionary *_currentVersion; +} + +/** Creates an "untitled" Ottoman with no file and no lastVersion. + After adding values to the currentVersion, use saveAs to save it. */ +- (id) init; + +/** Opens an existing Ottoman file. */ +- (id) initWithURL: (NSURL*)fileURL writeable: (BOOL)writeable error: (NSError**)outError; + +/** Closes an Ottoman. */ +- (void) close; + +/** The current file, or nil if the receiver is still in the "untitled" state. */ +@property (readonly) NSURL *URL; + +/** The latest saved version of the dictionary. + Earlier versions can be accessed through its previousVersion property. + This will be nil if the receiver is still in the "untitled" state. */ +@property (readonly) MYVersionDictionary* lastVersion; + +/** A mutable overlay representing the current state of the dictionary. + Changes are made in memory until -save is called. + This will be nil if the receiver was opened read-only (writeable=NO). */ +@property (readonly) MYCurrentVersionDictionary* currentVersion; + +/** Has the on-disk file been updated with newer revisions than what I know about? */ +@property (readonly) BOOL needsSync; + +/** Reads any newer versions from disk. + Returns YES if new versions were read, NO if there were none or on error. + Afterwards, -lastVersion will return the latest version in the file. + (The old lastVersion dictionary is still around, so you can save a pointer to it + before the call and use it later to see what changed.) + Changes made to the -currentVersion dictionary are not lost, but are now relative + to the new lastVersion. You may want to scan them and resolve conflicts. */ +- (BOOL) sync: (NSError**)outError; + +/** Saves the current version to the file, by appending. + Returns NO if there is a version conflict; in that case you need to call -sync, + possibly resolve conflicts, and then call -save again. */ +- (BOOL) save: (NSError**)outError; + +/** Saves the current version to the file, by writing to a new temporary file and + then atomically replacing the original. + Older versions, and older copies of the data, are not preserved, so this + will typically shrink the file quite a bit. + Returns NO if there is a version conflict. */ +- (BOOL) saveAndCompact: (NSError**)outError; + +/** Saves the current version to a new file, leaving the new file open, + so subsequent saves will be written to it. */ +- (BOOL) saveAs: (NSURL*)newFileURL + overwriteAllowed: (BOOL)overwriteAllowed + error: (NSError**)outError; + +/** Scans the file for damage. Returns YES if the file is OK, NO if problems are found. + If the 'repair' flag is set, will truncate the file to the last valid version. */ +- (BOOL) scavengeAndRepair: (BOOL)repair error: (NSError**)outError; + +@end diff -r 4c10b7956435 -r f2cd752db494 bindings/Cocoa/MYOttoman.mm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bindings/Cocoa/MYOttoman.mm Thu Sep 24 21:47:06 2009 -0700 @@ -0,0 +1,205 @@ +// +// MYOttoman.mm +// Ottoman +// +// Created by Jens Alfke on 9/21/09. +// Copyright 2009 Jens Alfke. All rights reserved. +// + +#import "MYOttoman.h" +#import "MYOttoman_internal.h" +extern "C" { +#import "Test.h" +} + +#include "Ottoman.h" +#include "VersionDictionary.h" +#include "File.h" + + +@interface MYOttoman () +- (void) _versionsChanged; +@end + + +namespace Mooseyard { + + class ObjCOwnedOttoman :public Ottoman { + public: + ObjCOwnedOttoman (MYOttoman *owner) + :_owner(owner) + { } + + ObjCOwnedOttoman (MYOttoman *owner, NSURL *fileURL, bool writeable) + :Ottoman(fileURL.path.fileSystemRepresentation, writeable), + _owner(owner) + { } + + protected: + virtual void versionsChanged() { + [_owner _versionsChanged]; + } + private: + MYOttoman *_owner; + }; + +} + + +using namespace Mooseyard; + + +static BOOL ErrorToNSError (const File::Error &x, NSError **outError) { + if (outError) { + *outError = [NSError errorWithDomain: NSPOSIXErrorDomain + code: x.code + userInfo: nil]; + } + return NO; +} + + +@interface MYOttoman () +@property (readonly) Ottoman* ottoman; +@end + + +@implementation MYOttoman + + +- (id) init { + return [self initWithURL: nil writeable: YES error: nil]; +} + +- (id) initWithURL: (NSURL*)fileURL + writeable: (BOOL)writeable + error: (NSError**)outError +{ + self = [super init]; + if (self) { + try { + if (fileURL) { + NSAssert([fileURL isFileURL], @"MYOttoman only supports file: URLs"); + _ottoman = new ObjCOwnedOttoman(self, fileURL, writeable); + } else { + _ottoman = new ObjCOwnedOttoman(self); + } + } catch (const File::Error &x) { + ErrorToNSError(x,outError); + [self release]; + return nil; + } + + if (writeable) + _currentVersion = [[MYCurrentVersionDictionary alloc] + _initWithOverlayDictionary: self.ottoman->currentVersion()]; + } + return self; +} + +- (void) dealloc +{ + [_lastVersion release]; + [_currentVersion release]; + delete (Ottoman*)_ottoman; + [super dealloc]; +} + +- (void) finalize { + delete (Ottoman*)_ottoman; + [super finalize]; +} + +- (void) close { + delete (Ottoman*)_ottoman; + _ottoman = nil; +} + + +- (Ottoman*) ottoman { + Assert(_ottoman, @"MYOttoman has already been closed"); + return (Ottoman*) _ottoman; +} + +- (NSURL*) URL { + const char *path = self.ottoman->filename(); + return path ?[NSURL fileURLWithPath: [NSString stringWithUTF8String: path]] :nil; +} + + +- (MYVersionDictionary*) lastVersion { + if (!_lastVersion) + _lastVersion = [[MYVersionDictionary alloc] _initWithVersionDictionary: self.ottoman->lastVersion()]; + return _lastVersion; +} + +- (MYCurrentVersionDictionary*) currentVersion { + return _currentVersion; +} + +- (void) _versionsChanged { + [_lastVersion autorelease]; + _lastVersion = nil; +} + + +- (BOOL) needsSync { + return self.ottoman->needsSync(); +} + + +- (BOOL) sync: (NSError**)outError { + if (outError) *outError = nil; + try { + return self.ottoman->sync(); + } catch (const File::Error &x) { + return ErrorToNSError(x,outError); + } +} + +- (BOOL) save: (NSError**)outError { + if (outError) *outError = nil; + try { + return self.ottoman->save(); + } catch (const File::Error &x) { + return ErrorToNSError(x,outError); + } +} + +- (BOOL) saveAndCompact: (NSError**)outError { + if (outError) *outError = nil; + try { + return self.ottoman->saveAndCompact(); + } catch (const File::Error &x) { + return ErrorToNSError(x,outError); + } +} + +- (BOOL) saveAs: (NSURL*)newFileURL + overwriteAllowed: (BOOL)overwriteAllowed + error: (NSError**)outError +{ + NSParameterAssert(newFileURL!=nil); + NSAssert([newFileURL isFileURL], @"MYOttoman only supports file: URLs"); + if (outError) *outError = nil; + try { + self.ottoman->saveAs(newFileURL.path.fileSystemRepresentation, overwriteAllowed); + return YES; + } catch (const File::Error &x) { + return ErrorToNSError(x,outError); + } +} + + +- (BOOL) scavengeAndRepair: (BOOL)repair error: (NSError**)outError { + if (outError) *outError = nil; + try { + return self.ottoman->scavenge(repair); + } catch (const File::Error &x) { + return ErrorToNSError(x,outError); + } +} + + +@end + diff -r 4c10b7956435 -r f2cd752db494 bindings/Cocoa/MYOttoman_internal.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bindings/Cocoa/MYOttoman_internal.h Thu Sep 24 21:47:06 2009 -0700 @@ -0,0 +1,23 @@ +// +// MYOttoman_internal +// Ottoman +// +// Created by Jens Alfke on 9/22/09. +// Copyright 2009 Jens Alfke. All rights reserved. +// + + +#import "MYVersionDictionary.h" + +namespace Mooseyard { + class OverlayDictionary; + class VersionDictionary; +} + +@interface MYVersionDictionary () +- (id) _initWithVersionDictionary: (const Mooseyard::VersionDictionary*)dict; +@end + +@interface MYCurrentVersionDictionary () +- (id) _initWithOverlayDictionary: (Mooseyard::OverlayDictionary*)dict; +@end diff -r 4c10b7956435 -r f2cd752db494 bindings/Cocoa/MYOttoman_test.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bindings/Cocoa/MYOttoman_test.m Thu Sep 24 21:47:06 2009 -0700 @@ -0,0 +1,121 @@ +// +// MYOttoman_test.m +// Ottoman +// +// Created by Jens Alfke on 9/24/09. +// Copyright 2009 Jens Alfke. All rights reserved. +// + +#import "MYOttoman.h" +#import "MYVersionDictionary.h" + +#import "Test.h" + +static NSData* dataOf (NSString *str) { + return [str dataUsingEncoding: NSUTF8StringEncoding]; +} + +/* +static NSString* stringOf (id data) { + if (!data) + return nil; + CAssert([data isKindOfClass: [NSData class]], @"stringOf expected NSData, got %@", [data class]); + NSString *str = [[[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding] autorelease]; + CAssert(str!=nil, @"Bad data (couldn't decode UTF-8): %@", data); + return str; +} +*/ + +static void checkFile (NSString *path) { + NSError *error; + NSDictionary *fileInfo = [[NSFileManager defaultManager] attributesOfItemAtPath: path error: &error]; + CAssert(fileInfo, @"Couldn't get info for %@: %@", path,error); + CAssert([fileInfo fileSize] >= 512, @"Unexpected file size %lld", [fileInfo fileSize]); + NSTimeInterval age = - [[fileInfo fileModificationDate] timeIntervalSinceNow]; + CAssert(age < 60.0, @"File %@ is too old: %.1lf sec", path,age); +} + + +TestCase(CreateMYOttoman) { + MYOttoman *o = [[MYOttoman alloc] init]; + MYCurrentVersionDictionary *cur = o.currentVersion; + CAssertEq(o.lastVersion,nil); + + [cur setObject: dataOf(@"first value") forKey: dataOf(@"first key")]; + [cur setObject: dataOf(@"second value") forKey: dataOf(@"second key")]; + [cur setObject: dataOf(@"third value") forKey: dataOf(@"third key")]; + [cur removeObjectForKey: dataOf(@"third key")]; + [cur removeObjectForKey: dataOf(@"bogus")]; + + CAssertEq(cur.count, 2); + CAssertEqual([cur objectForKey: dataOf(@"first key")], dataOf(@"first value")); + CAssertEqual([cur objectForKey: dataOf(@"second key")], dataOf(@"second value")); + CAssertEqual([cur objectForKey: dataOf(@"third key")], nil); + + NSError *error; + if (![o saveAs: [NSURL fileURLWithPath: @"/tmp/myottomantest.ottoman"] + overwriteAllowed: YES error: &error]) + CAssert(NO, @"saveAs: failed: %@", error); + checkFile(@"/tmp/myottomantest.ottoman"); + + MYVersionDictionary *last = o.lastVersion; + CAssert(last, @"lastVersion is nil after save"); + CAssertEq(last.generation,0); + CAssertEqual([last objectForKey: dataOf(@"first key")], dataOf(@"first value")); + CAssertEqual([last objectForKey: dataOf(@"second key")], dataOf(@"second value")); + CAssertEqual([last objectForKey: dataOf(@"third key")], nil); + + [cur setObject: dataOf(@"fourth value") forKey: dataOf(@"fourth key")]; + [cur setObject: dataOf(@"new first value") forKey: dataOf(@"first key")]; + [cur removeObjectForKey: dataOf(@"second key")]; + CAssertEq(cur.count, 2); + CAssertEqual([cur objectForKey: dataOf(@"first key")], dataOf(@"new first value")); + CAssertEqual([cur objectForKey: dataOf(@"second key")], nil); + CAssertEqual([cur objectForKey: dataOf(@"fourth key")], dataOf(@"fourth value")); + + if (![o save: &error]) + CAssert(NO, @"save: failed: %@", error); + + CAssertEqual([last objectForKey: dataOf(@"first key")], dataOf(@"first value")); + CAssertEqual([last objectForKey: dataOf(@"second key")], dataOf(@"second value")); + CAssertEqual([last objectForKey: dataOf(@"third key")], nil); + + last = o.lastVersion; + CAssertEq(last.generation,1); + CAssertEqual([last objectForKey: dataOf(@"first key")], dataOf(@"new first value")); + CAssertEqual([last objectForKey: dataOf(@"second key")], nil); + CAssertEqual([last objectForKey: dataOf(@"third key")], nil); + CAssertEqual([last objectForKey: dataOf(@"fourth key")], dataOf(@"fourth value")); + + [o close]; + [o release]; + + o = [[MYOttoman alloc] initWithURL: [NSURL fileURLWithPath: @"/tmp/myottomantest.ottoman"] + writeable: NO error: &error]; + CAssert(o, @"Failed to re-open Ottoman: %@", error); + CAssertEq(o.currentVersion, nil); + last = o.lastVersion; + CAssertEq(last.generation,1); + CAssertEqual([last objectForKey: dataOf(@"first key")], dataOf(@"new first value")); + CAssertEqual([last objectForKey: dataOf(@"second key")], nil); + CAssertEqual([last objectForKey: dataOf(@"third key")], nil); + CAssertEqual([last objectForKey: dataOf(@"fourth key")], dataOf(@"fourth value")); + + MYVersionDictionary *prev = last.previousVersion; + CAssert(prev!=nil); + CAssertEq(prev.generation,0); + CAssertEq(prev.previousVersion,nil); + CAssertEqual([prev objectForKey: dataOf(@"first key")], dataOf(@"first value")); + CAssertEqual([prev objectForKey: dataOf(@"second key")], dataOf(@"second value")); + CAssertEqual([prev objectForKey: dataOf(@"third key")], nil); + + [o close]; + [o release]; +} + + +int main(int argc, const char**argv) { + const char* testArgs[2] = {"", "Test_All"}; + RunTestCases(2,testArgs); + return 0; +} diff -r 4c10b7956435 -r f2cd752db494 bindings/Cocoa/MYVersionDictionary.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bindings/Cocoa/MYVersionDictionary.h Thu Sep 24 21:47:06 2009 -0700 @@ -0,0 +1,55 @@ +// +// MYVersionDictionary.h +// Ottoman +// +// Created by Jens Alfke on 9/21/09. +// Copyright 2009 Jens Alfke. All rights reserved. +// + +#import + +#ifdef __cplusplus +namespace Mooseyard { + class OverlayDictionary; + class VersionDictionary; +} +#endif + + +/** A persistent NSDictionary embedded in a memory-mapped file, + representing one complete version of a MYOttoman dictionary. */ +@interface MYVersionDictionary : NSDictionary +{ +#ifdef __cplusplus + const Mooseyard::VersionDictionary *_dict; +#else + void *_dict; +#endif +} + +/** The generation number. The first version in a new file is zero. */ +@property (readonly) int generation; + +/** The time at which this version was written to the file. */ +@property (readonly) NSDate *dateCreated; + +/** The previous version, before this one was saved. */ +@property (readonly) MYVersionDictionary *previousVersion; + +/** Is this the latest version in the file? */ +@property (readonly) BOOL isLatestVersion; + +@end + + + +@interface MYCurrentVersionDictionary : NSMutableDictionary +{ +#ifdef __cplusplus + Mooseyard::OverlayDictionary *_dict; +#else + void *_dict; +#endif +} + +@end diff -r 4c10b7956435 -r f2cd752db494 bindings/Cocoa/MYVersionDictionary.mm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bindings/Cocoa/MYVersionDictionary.mm Thu Sep 24 21:47:06 2009 -0700 @@ -0,0 +1,187 @@ +// +// MYVersionDictionary.m +// Ottoman +// +// Created by Jens Alfke on 9/21/09. +// Copyright 2009 Jens Alfke. All rights reserved. +// + +#import "MYVersionDictionary.h" +#import "MYOttoman_internal.h" +#import + +#include "VersionDictionary.h" + +using namespace Mooseyard; + + +@interface MYDictionaryEnumerator : NSEnumerator +{ + Dictionary::Iterator *_iter; + BOOL _started; +} +- (id) initWithDict: (const Dictionary*)dict; +@end + + + +static id BlobToNSData (const Blob &blob, bool copy =true) { + if (!blob) + return nil; + else if (copy) + return [NSData dataWithBytes: blob.bytes length: blob.length]; + else + return [NSData dataWithBytesNoCopy: (void*)blob.bytes + length: blob.length + freeWhenDone: NO]; +} + +static Blob BlobFromId (id object) { + if ([object isKindOfClass: [NSData class]]) + return Blob([object bytes], [object length]); + else + return Key(); +} + + + +@implementation MYVersionDictionary + + +- (id) _initWithVersionDictionary: (VersionDictionary*)dict +{ + self = [super init]; + if (self != nil) { + if (!dict) { + [self release]; + return nil; + } + _dict = dict; + } + return self; +} + + +- (NSUInteger)count { + return _dict->count(); +} + +- (id)objectForKey:(id)keyObject { + return BlobToNSData( _dict->get( Key(BlobFromId(keyObject)) ) ); +} + +- (NSEnumerator *)keyEnumerator { + return [[[MYDictionaryEnumerator alloc] initWithDict: _dict] autorelease]; +} + +- (int) generation { + return _dict->generation(); +} + +- (NSDate*) dateCreated { + return [NSDate dateWithTimeIntervalSince1970: _dict->timestamp()]; +} + +- (BOOL) isLatestVersion { + return _dict->isLatestVersion(); +} + +- (MYVersionDictionary*) previousVersion { + const VersionDictionary *prev = _dict->previousVersion(); + if (prev) + return [[[MYVersionDictionary alloc] _initWithVersionDictionary: prev] autorelease]; + else + return nil; +} + +@end + + + + +@implementation MYCurrentVersionDictionary + + +- (id) _initWithOverlayDictionary: (OverlayDictionary*)dict +{ + self = [super init]; + if (self != nil) { + if (!dict) { + [self release]; + return nil; + } + _dict = dict; + } + return self; +} + + +- (NSUInteger)count { + return _dict->count(); +} + +- (id)objectForKey:(id)keyObject { + Blob keyBlob = BlobFromId(keyObject); + return keyBlob ? BlobToNSData( _dict->get( Key(keyBlob) ) ) :nil; +} + +- (NSEnumerator *)keyEnumerator { + return [[[MYDictionaryEnumerator alloc] initWithDict: _dict] autorelease]; +} + +- (void)setObject:(id)anObject forKey:(id)aKey { + Key key = Key(BlobFromId(aKey)); + NSAssert1(key, @"Invalid %@ key; only NSData supported", [aKey class]); + Blob value = BlobFromId(anObject); + NSAssert1(key, @"Invalid %@ value; only NSData supported", [aKey class]); + _dict->put(key, value); +} + +- (void)removeObjectForKey:(id)aKey { + Key key = Key(BlobFromId(aKey)); + NSAssert1(key, @"Invalid key class %@; only NSData supported", [aKey class]); + _dict->remove(key); +} + +@end + + + + +@implementation MYDictionaryEnumerator + +- (id) initWithDict: (Dictionary*)dict +{ + self = [super init]; + if (self != nil) { + _iter = dict->iterate(); + } + return self; +} + +- (void) dealloc +{ + delete _iter; + [super dealloc]; +} + +- (void) finalize { + delete _iter; + [super finalize]; +} + +- (id)nextObject { + if (_started) { + if (!_iter->next()) + return nil; + } else { + if (!_iter->hasValue()) + return nil; + _started = YES; + } + Key key = _iter->key(); + return [NSData dataWithBytes: key.bytes length: key.length]; +} + + +@end