├── .gitignore ├── .gitmodules ├── Demo-Mac ├── DemoAppController.h ├── DemoAppController.m ├── DemoQuery.h ├── DemoQuery.m ├── EmptyGNUstepApp.m ├── Frameworks │ └── README.txt ├── ShoppingDemo-Info.plist ├── ShoppingDemo.xib ├── ShoppingItem.h ├── ShoppingItem.m ├── deleteDB.sh └── editDB.sh ├── Demo-iOS ├── ConfigViewController.h ├── ConfigViewController.m ├── ConfigViewController.xib ├── DemoAppDelegate.h ├── DemoAppDelegate.m ├── EmptyAppDelegate.h ├── EmptyAppDelegate.m ├── Entitlements.plist ├── Frameworks │ └── README ├── MainWindow.xib ├── RootViewController.h ├── RootViewController.m ├── RootViewController.xib ├── en.lproj │ └── InfoPlist.strings ├── iOS Demo-Info.plist ├── iOS Demo-Prefix.pch ├── iOS Empty App-Info.plist ├── list_area___checkbox___checked.png ├── list_area___checkbox___unchecked.png └── main.m ├── GNUmakefile ├── GNUstep ├── BUILDING.txt ├── SETUP.txt └── fix_NSFileManager.patch ├── Listener ├── TDHTTPConnection.h ├── TDHTTPConnection.m ├── TDHTTPResponse.h ├── TDHTTPResponse.m ├── TDHTTPServer.h ├── TDListener.h ├── TDListener.m ├── TouchDBListener-Info.plist ├── TouchDBListener.exp ├── TouchServ.m └── en.lproj │ └── InfoPlist.strings ├── README.md ├── Source ├── BuildFatLibrary.sh ├── ChangeTracker │ ├── TDChangeTracker.h │ ├── TDChangeTracker.m │ ├── TDConnectionChangeTracker.h │ ├── TDConnectionChangeTracker.m │ ├── TDSocketChangeTracker.h │ └── TDSocketChangeTracker.m ├── TDAuthorizer.h ├── TDAuthorizer.m ├── TDBase64.h ├── TDBase64.m ├── TDBatcher.h ├── TDBatcher.m ├── TDBlobStore.h ├── TDBlobStore.m ├── TDBlobStore_Tests.m ├── TDC.h ├── TDC.m ├── TDCanonicalJSON.h ├── TDCanonicalJSON.m ├── TDChangeTracker_Tests.m ├── TDCollateJSON.h ├── TDCollateJSON.m ├── TDGNUstep.h ├── TDGNUstep.m ├── TDInternal.h ├── TDJSON.h ├── TDJSON.m ├── TDJSViewCompiler.h ├── TDJSViewCompiler.m ├── TDJSViewCompiler_Test.m ├── TDMisc.h ├── TDMisc.m ├── TDMultiStreamWriter.h ├── TDMultiStreamWriter.m ├── TDMultipartDocumentReader.h ├── TDMultipartDocumentReader.m ├── TDMultipartDownloader.h ├── TDMultipartDownloader.m ├── TDMultipartReader.h ├── TDMultipartReader.m ├── TDMultipartUploader.h ├── TDMultipartUploader.m ├── TDMultipartWriter.h ├── TDMultipartWriter.m ├── TDOAuth1Authorizer.h ├── TDOAuth1Authorizer.m ├── TDPersonaAuthorizer.h ├── TDPersonaAuthorizer.m ├── TDPuller.h ├── TDPuller.m ├── TDPusher.h ├── TDPusher.m ├── TDReachability.h ├── TDReachability.m ├── TDRemoteRequest.h ├── TDRemoteRequest.m ├── TDReplicator+Backgrounding.m ├── TDReplicator.h ├── TDReplicator.m ├── TDReplicatorManager.h ├── TDReplicatorManager.m ├── TDReplicator_Tests.m ├── TDRouter+Handlers.m ├── TDRouter.h ├── TDRouter.m ├── TDRouter_Tests.m ├── TDSequenceMap.h ├── TDSequenceMap.m ├── TDStatus.h ├── TDStatus.m ├── TDURLProtocol.h ├── TDURLProtocol.m ├── TD_Attachment.h ├── TD_Attachment.m ├── TD_Body.h ├── TD_Body.m ├── TD_Database+Attachments.h ├── TD_Database+Attachments.m ├── TD_Database+Insertion.h ├── TD_Database+Insertion.m ├── TD_Database+LocalDocs.h ├── TD_Database+LocalDocs.m ├── TD_Database+Replication.h ├── TD_Database+Replication.m ├── TD_Database.h ├── TD_Database.m ├── TD_DatabaseManager.h ├── TD_DatabaseManager.m ├── TD_Database_Tests.m ├── TD_Revision.h ├── TD_Revision.m ├── TD_Server.h ├── TD_Server.m ├── TD_View.h ├── TD_View.m ├── TD_View_Tests.m ├── TouchDB-Info.plist ├── TouchDB.exp ├── TouchDB.h └── TouchDBPrefix.h ├── TouchDB.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── TouchDB.xccheckout └── vendor └── google-toolbox-for-mac ├── GTMDefines.h ├── GTMNSData+zlib.h ├── GTMNSData+zlib.m └── README /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pbxuser 3 | *.perspectivev3 4 | *.mode1v3 5 | *.framework 6 | *~ 7 | xcuserdata/ 8 | build/ 9 | DerivedData/ 10 | obj/ 11 | derived_src/ 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/fmdb"] 2 | path = vendor/fmdb 3 | url = git://github.com/couchbaselabs/fmdb.git 4 | [submodule "vendor/CocoaHTTPServer"] 5 | path = vendor/CocoaHTTPServer 6 | url = git://github.com/couchbaselabs/CocoaHTTPServer.git 7 | [submodule "vendor/MYUtilities"] 8 | path = vendor/MYUtilities 9 | url = git://github.com/snej/MYUtilities.git 10 | [submodule "vendor/oauthconsumer"] 11 | path = vendor/oauthconsumer 12 | url = git://github.com/couchbaselabs/ios-oauthconsumer.git 13 | -------------------------------------------------------------------------------- /Demo-Mac/DemoAppController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoAppController.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/1/11. 6 | // Copyright (c) 2011 Couchbase, Inc, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import 17 | @class CouchDatabase, CouchPersistentReplication, DemoQuery; 18 | 19 | 20 | /** Generic application delegate for simple Mac OS CouchDB demo apps. 21 | The name of the (local) database to use should be added to the app's Info.plist 22 | under the 'DemoDatabase' key. */ 23 | @interface DemoAppController : NSObject 24 | { 25 | IBOutlet NSWindow* _window; 26 | IBOutlet NSTableView* _table; 27 | IBOutlet NSArrayController* _tableController; 28 | IBOutlet NSProgressIndicator* _syncProgress; 29 | IBOutlet NSTextField* _syncHostField; 30 | IBOutlet NSLevelIndicator* _syncStatusView; 31 | 32 | IBOutlet NSPanel* _syncConfigSheet; 33 | IBOutlet NSTextField* _syncURLField; 34 | IBOutlet NSButtonCell* _syncPushCheckbox, *_syncPullCheckbox; 35 | 36 | CouchDatabase* _database; 37 | DemoQuery* _query; 38 | BOOL _syncConfiguringDefault; 39 | CouchPersistentReplication *_pull, *_push; 40 | BOOL _glowing; 41 | } 42 | 43 | @property (retain) DemoQuery* query; 44 | 45 | - (IBAction) compact: (id)sender; 46 | - (IBAction) configureSync: (id)sender; 47 | - (IBAction) dismissSyncConfigSheet:(id)sender; 48 | - (IBAction) resetSync: (id)sender; 49 | 50 | @property (retain) NSURL* syncURL; 51 | 52 | - (void) startContinuousSyncWith: (NSURL*)otherDbURL; 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Demo-Mac/DemoQuery.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoQuery.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/1/11. 6 | // Copyright (c) 2011 Couchbase, Inc, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import 17 | @class CouchQuery, CouchLiveQuery, RESTOperation; 18 | 19 | 20 | /** Simple controller for CouchDB demo apps. 21 | This class acts as glue between a CouchQuery (a CouchDB view) and an NSArrayController. 22 | The app can then bind its UI controls to the NSArrayController and get basic CRUD operations 23 | without needing any code. */ 24 | @interface DemoQuery : NSObject 25 | { 26 | CouchLiveQuery* _query; 27 | RESTOperation* _op; 28 | NSMutableArray* _entries; 29 | Class _modelClass; 30 | } 31 | 32 | - (id) initWithQuery: (CouchQuery*)query; 33 | 34 | /** Class to instantiate for entries. Defaults to DemoItem. */ 35 | @property (assign) Class modelClass; 36 | 37 | /** The documents returned by the query, wrapped in DemoItem objects. 38 | An NSArrayController can be bound to this property. */ 39 | //@property (readonly) NSMutableArray* entries; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Demo-Mac/DemoQuery.m: -------------------------------------------------------------------------------- 1 | // 2 | // DemoQuery.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/1/11. 6 | // Copyright (c) 2011 Couchbase, Inc, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "DemoQuery.h" 17 | #import 18 | 19 | 20 | @interface DemoQuery () 21 | - (void) loadEntriesFrom: (CouchQueryEnumerator*)rows; 22 | @end 23 | 24 | 25 | @implementation DemoQuery 26 | 27 | 28 | - (id) initWithQuery: (CouchQuery*)query 29 | { 30 | NSParameterAssert(query); 31 | self = [super init]; 32 | if (self != nil) { 33 | _modelClass = [CouchModel class]; 34 | _query = [query asLiveQuery]; 35 | 36 | _query.prefetch = YES; // for efficiency, include docs on first load 37 | [_query start]; 38 | 39 | // Observe changes to _query.rows: 40 | [_query addObserver: self forKeyPath: @"rows" options: 0 context: NULL]; 41 | } 42 | return self; 43 | } 44 | 45 | 46 | - (void) dealloc 47 | { 48 | [_query removeObserver: self forKeyPath: @"rows"]; 49 | } 50 | 51 | 52 | @synthesize modelClass=_modelClass; 53 | 54 | 55 | - (void) loadEntriesFrom: (CouchQueryEnumerator*)rows { 56 | NSLog(@"Reloading %lu rows from sequence #%lu...", 57 | (unsigned long)rows.count, (unsigned long)rows.sequenceNumber); 58 | NSMutableArray* entries = [NSMutableArray array]; 59 | 60 | for (CouchQueryRow* row in rows) { 61 | CouchModel* item = [_modelClass modelForDocument: row.document]; 62 | item.autosaves = YES; 63 | [entries addObject: item]; 64 | // If this item isn't in the prior _entries, it's an external insertion: 65 | if (_entries && [_entries indexOfObjectIdenticalTo: item] == NSNotFound) 66 | [item markExternallyChanged]; 67 | } 68 | 69 | for (CouchModel* item in _entries) { 70 | if ([item isNew]) 71 | [entries addObject: item]; 72 | } 73 | 74 | if (![entries isEqual:_entries]) { 75 | NSLog(@" ...entries changed! (was %u, now %u)", 76 | (unsigned)_entries.count, (unsigned)entries.count); 77 | [self willChangeValueForKey: @"entries"]; 78 | _entries = [entries mutableCopy]; 79 | [self didChangeValueForKey: @"entries"]; 80 | } 81 | } 82 | 83 | 84 | - (void)observeValueForKeyPath: (NSString*)keyPath ofObject: (id)object 85 | change: (NSDictionary*)change context: (void*)context 86 | { 87 | if (object == _query) { 88 | [self loadEntriesFrom: _query.rows]; 89 | } 90 | } 91 | 92 | 93 | #pragma mark - 94 | #pragma mark ENTRIES PROPERTY: 95 | 96 | 97 | - (NSUInteger) countOfEntries { 98 | return _entries.count; 99 | } 100 | 101 | 102 | - (CouchModel*)objectInEntriesAtIndex: (NSUInteger)index { 103 | return _entries[index]; 104 | } 105 | 106 | 107 | - (void) insertObject: (CouchModel*)object inEntriesAtIndex: (NSUInteger)index { 108 | [_entries insertObject: object atIndex: index]; 109 | object.autosaves = YES; 110 | object.database = _query.database; 111 | } 112 | 113 | 114 | - (void) removeObjectFromEntriesAtIndex: (NSUInteger)index { 115 | CouchModel* item = _entries[index]; 116 | item.database = nil; 117 | [_entries removeObjectAtIndex: index]; 118 | } 119 | 120 | 121 | @end 122 | -------------------------------------------------------------------------------- /Demo-Mac/EmptyGNUstepApp.m: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyGNUstepApp.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 2/27/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | #if DEBUG 14 | #import "Logging.h" 15 | #import "Test.h" 16 | #else 17 | #define Warn NSLog 18 | #define Log NSLog 19 | #endif 20 | 21 | 22 | int main (int argc, const char * argv[]) 23 | { 24 | @autoreleasepool { 25 | #if DEBUG 26 | EnableLog(YES); 27 | //EnableLogTo(TDDatabase, YES); 28 | //EnableLogTo(TDDatabaseVerbose, YES); 29 | RunTestCases(argc, argv); 30 | #endif 31 | /* 32 | NSError* error; 33 | TDServer* server = [[TDServer alloc] initWithDirectory: @"/tmp/touchdbserver" error: &error]; 34 | if (!server) { 35 | Warn(@"FATAL: Error initializing TDServer: %@", error); 36 | exit(1); 37 | } 38 | NSLog(@"Started server %@", server); 39 | 40 | [[NSRunLoop currentRunLoop] run]; 41 | */ 42 | 43 | } 44 | return 0; 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Demo-Mac/Frameworks/README.txt: -------------------------------------------------------------------------------- 1 | Put CouchCocoa.framework in here 2 | -------------------------------------------------------------------------------- /Demo-Mac/ShoppingDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.couchbase.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | APPL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | DemoDatabase 22 | demo-shopping 23 | LSMinimumSystemVersion 24 | ${MACOSX_DEPLOYMENT_TARGET} 25 | NSMainNibFile 26 | ShoppingDemo 27 | NSPrincipalClass 28 | NSApplication 29 | SyncDatabaseURL 30 | http://127.0.0.1:5984/grocery-sync 31 | 32 | 33 | -------------------------------------------------------------------------------- /Demo-Mac/ShoppingItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // ShoppingItem.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 8/26/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class NSImage; 11 | 12 | 13 | @interface ShoppingItem : CouchModel 14 | { 15 | NSImage* _picture; 16 | } 17 | 18 | @property bool check; // bool is better than BOOL: it maps to true/false in JSON, not 0/1. 19 | @property (copy) NSString* text; 20 | @property (retain) NSDate* created_at; 21 | 22 | @property (retain) NSImage* picture; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Demo-Mac/ShoppingItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // ShoppingItem.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 8/26/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "ShoppingItem.h" 17 | #import 18 | 19 | 20 | static NSData* ImageJPEGData(NSImage* image); 21 | 22 | 23 | 24 | @implementation ShoppingItem 25 | 26 | 27 | @dynamic check, text, created_at; 28 | 29 | 30 | - (NSDictionary*) propertiesToSave { 31 | // Initialize created_at the first time the document is saved: 32 | if (self.created_at == nil) 33 | self.created_at = [NSDate date]; 34 | return [super propertiesToSave]; 35 | } 36 | 37 | 38 | - (NSImage*) picture { 39 | if (!_picture) { 40 | NSData* pictureData = [[self attachmentNamed: @"picture"] body]; 41 | if (pictureData) 42 | _picture = [[NSImage alloc] initWithData: pictureData]; 43 | } 44 | return _picture; 45 | } 46 | 47 | 48 | - (void) setPicture:(NSImage *)picture { 49 | if (_picture && picture == _picture) 50 | return; 51 | 52 | [self createAttachmentWithName: @"picture" 53 | type: @"image/jpeg" 54 | body: ImageJPEGData(picture)]; 55 | _picture = picture; 56 | } 57 | 58 | 59 | @end 60 | 61 | 62 | static NSData* ImageJPEGData(NSImage* image) { 63 | if (!image) 64 | return nil; 65 | NSBitmapImageRep* bitmapRep = nil; 66 | for (NSImageRep* rep in image.representations) { 67 | if ([rep isKindOfClass: [NSBitmapImageRep class]]) { 68 | bitmapRep = (NSBitmapImageRep*) rep; 69 | break; 70 | } 71 | } 72 | NSCAssert(bitmapRep != nil, @"No bitmap rep"); 73 | return [bitmapRep representationUsingType: NSJPEGFileType properties: nil]; 74 | } 75 | -------------------------------------------------------------------------------- /Demo-Mac/deleteDB.sh: -------------------------------------------------------------------------------- 1 | rm -rf ~/Library/Application\ Support/com.couchbase.TouchDB-Demo/ 2 | -------------------------------------------------------------------------------- /Demo-Mac/editDB.sh: -------------------------------------------------------------------------------- 1 | sqlite3 ~/Library/Application\ Support/com.couchbase.TouchDB-Demo/TouchDB/demo-shopping.touchdb -------------------------------------------------------------------------------- /Demo-iOS/ConfigViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigViewController.h 3 | // CouchDemo 4 | // 5 | // Created by Jens Alfke on 8/8/11. 6 | // Copyright 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class CouchServer; 11 | 12 | @interface ConfigViewController : UIViewController 13 | 14 | @property (weak, nonatomic, readonly) IBOutlet UITextField* urlField; 15 | @property (weak, nonatomic, readonly) IBOutlet UILabel* versionField; 16 | @property (weak, nonatomic, readonly) IBOutlet UISwitch* autoSyncSwitch; 17 | 18 | - (IBAction) learnMore:(id)sender; 19 | - (IBAction)done:(id)sender; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Demo-iOS/ConfigViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigViewController.m 3 | // CouchDemo 4 | // 5 | // Created by Jens Alfke on 8/8/11. 6 | // Copyright 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "ConfigViewController.h" 17 | 18 | // This symbol comes from GrocerySync_vers.c, generated by the versioning system. 19 | extern double GrocerySyncVersionNumber; 20 | 21 | 22 | @implementation ConfigViewController 23 | 24 | 25 | @synthesize urlField, versionField, autoSyncSwitch; 26 | 27 | 28 | - (id)init { 29 | self = [super initWithNibName: @"ConfigViewController" bundle: nil]; 30 | if (self) { 31 | // Custom initialization 32 | self.navigationItem.title = @"Configure Sync"; 33 | 34 | UIBarButtonItem* purgeButton = [[UIBarButtonItem alloc] initWithTitle: @"Done" 35 | style:UIBarButtonItemStyleDone 36 | target: self 37 | action: @selector(done:)]; 38 | self.navigationItem.leftBarButtonItem = purgeButton; 39 | } 40 | return self; 41 | } 42 | 43 | 44 | #pragma mark - View lifecycle 45 | 46 | 47 | - (void)viewWillAppear:(BOOL)animated { 48 | [super viewWillAppear:animated]; 49 | 50 | NSString *syncpoint = [[NSUserDefaults standardUserDefaults] objectForKey:@"syncpoint"]; 51 | self.urlField.text = syncpoint; 52 | 53 | // self.versionField.text = [NSString stringWithFormat: @"this is build #%.0lf", 54 | // GrocerySyncVersionNumber]; 55 | } 56 | 57 | 58 | - (IBAction)learnMore:(id)sender { 59 | static NSString* const kLearnMoreURLs[] = { 60 | @"http://www.couchbase.com/products-and-services/couchbase-single-server", 61 | @"http://couchdb.apache.org/", 62 | @"http://www.iriscouch.com/" 63 | }; 64 | NSURL* url = [NSURL URLWithString: kLearnMoreURLs[[sender tag]]]; 65 | [[UIApplication sharedApplication] openURL: url]; 66 | } 67 | 68 | 69 | - (void)pop { 70 | 71 | UINavigationController* navController = (UINavigationController*)self.parentViewController; 72 | [navController popViewControllerAnimated: YES]; 73 | } 74 | 75 | 76 | - (IBAction)done:(id)sender { 77 | NSString* syncpoint = self.urlField.text; 78 | if (syncpoint.length > 0) { 79 | NSURL *remoteURL = [NSURL URLWithString:syncpoint]; 80 | if (!remoteURL || ![remoteURL.scheme hasPrefix: @"http"]) { 81 | // Oops, not a valid URL: 82 | NSString* message = @"You entered an invalid URL. Do you want to fix it or revert back to what it was before?"; 83 | UIAlertView* alert = [[UIAlertView alloc] initWithTitle: @"Invalid URL" 84 | message: message 85 | delegate: self 86 | cancelButtonTitle: @"Fix It" 87 | otherButtonTitles: @"Revert", nil]; 88 | [alert show]; 89 | return; 90 | } 91 | 92 | // If user just enters the server URL, fill in a default database name: 93 | if ([remoteURL.path isEqual: @""] || [remoteURL.path isEqual: @"/"]) { 94 | remoteURL = [remoteURL URLByAppendingPathComponent: @"grocery-sync"]; 95 | syncpoint = remoteURL.absoluteString; 96 | } 97 | } 98 | [[NSUserDefaults standardUserDefaults] setObject: syncpoint forKey:@"syncpoint"]; 99 | [self pop]; 100 | } 101 | 102 | 103 | - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { 104 | if (buttonIndex > 0) { 105 | [self pop]; // Go back to the main screen without saving the URL 106 | } 107 | } 108 | 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /Demo-iOS/DemoAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoAppDelegate.h 3 | // iOS Demo 4 | // 5 | // Created by Jens Alfke on 12/9/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class CouchDatabase; 11 | 12 | 13 | @interface DemoAppDelegate : UIResponder 14 | 15 | @property (nonatomic, strong) CouchDatabase *database; 16 | 17 | @property (strong, nonatomic) IBOutlet UIWindow *window; 18 | @property (nonatomic, strong) IBOutlet UINavigationController *navigationController; 19 | 20 | - (void)showAlert: (NSString*)message error: (NSError*)error fatal: (BOOL)fatal; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Demo-iOS/DemoAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // DemoAppDelegate.m 3 | // iOS Demo 4 | // 5 | // Created by Jens Alfke on 12/9/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "DemoAppDelegate.h" 17 | #import "RootViewController.h" 18 | #import 19 | #import 20 | 21 | 22 | @implementation DemoAppDelegate 23 | 24 | 25 | @synthesize window, navigationController, database; 26 | 27 | 28 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 29 | { 30 | // Add the navigation controller's view to the window and display. 31 | [window addSubview:navigationController.view]; 32 | [window makeKeyAndVisible]; 33 | 34 | //gRESTLogLevel = kRESTLogRequestHeaders; 35 | gCouchLogLevel = 1; 36 | 37 | NSLog(@"Creating database..."); 38 | CouchTouchDBServer* server = [CouchTouchDBServer sharedInstance]; 39 | NSAssert(!server.error, @"Error initializing TouchDB: %@", server.error); 40 | 41 | // Create the database on the first run of the app. 42 | self.database = [server databaseNamed: @"grocery-sync"]; 43 | NSError* error; 44 | if (![self.database ensureCreated: &error]) { 45 | [self showAlert: @"Couldn't create local database." error: error fatal: YES]; 46 | return YES; 47 | } 48 | database.tracksChanges = YES; 49 | NSLog(@"...Created CouchDatabase at <%@>", self.database.URL); 50 | 51 | // Tell the RootViewController: 52 | RootViewController* root = (RootViewController*)navigationController.topViewController; 53 | [root useDatabase: database]; 54 | 55 | return YES; 56 | } 57 | 58 | 59 | 60 | 61 | // Display an error alert, without blocking. 62 | // If 'fatal' is true, the app will quit when it's pressed. 63 | - (void)showAlert: (NSString*)message error: (NSError*)error fatal: (BOOL)fatal { 64 | if (error) { 65 | message = [NSString stringWithFormat: @"%@\n\n%@", message, error.localizedDescription]; 66 | } 67 | UIAlertView* alert = [[UIAlertView alloc] initWithTitle: (fatal ? @"Fatal Error" : @"Error") 68 | message: message 69 | delegate: (fatal ? self : nil) 70 | cancelButtonTitle: (fatal ? @"Quit" : @"Sorry") 71 | otherButtonTitles: nil]; 72 | [alert show]; 73 | } 74 | 75 | - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { 76 | exit(0); 77 | } 78 | 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /Demo-iOS/EmptyAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyAppDelegate.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/18/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface EmptyAppDelegate : NSObject 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Demo-iOS/EmptyAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyAppDelegate.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/18/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "EmptyAppDelegate.h" 10 | #import "TouchDB.h" 11 | #import "Test.h" 12 | 13 | @implementation EmptyAppDelegate 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 16 | { 17 | NSError* error; 18 | TD_Server* tdServer = [[TD_Server alloc] initWithDirectory: @"/tmp/touchdb_empty_app" 19 | error: &error]; 20 | NSAssert(tdServer, @"Couldn't create server: %@", error); 21 | [TDURLProtocol setServer: tdServer]; 22 | return YES; 23 | } 24 | 25 | @end 26 | 27 | 28 | int main(int argc, char *argv[]) 29 | { 30 | @autoreleasepool { 31 | RunTestCases(argc,(const char**)argv); 32 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([EmptyAppDelegate class])); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Demo-iOS/Entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | get-task-allow 6 | 7 | application-identifier 8 | 5UCEE2F6FU.com.couchone.TouchDBDemo 9 | 10 | 11 | -------------------------------------------------------------------------------- /Demo-iOS/Frameworks/README: -------------------------------------------------------------------------------- 1 | Copy or symlink CouchCocoa.framework into this directory. 2 | -------------------------------------------------------------------------------- /Demo-iOS/RootViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RootViewController.h 3 | // Couchbase Mobile 4 | // 5 | // Created by Jan Lehnardt on 27/11/2010. 6 | // Copyright 2011 Couchbase, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 9 | // use this file except in compliance with the License. You may obtain a copy of 10 | // the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 16 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 17 | // License for the specific language governing permissions and limitations under 18 | // the License. 19 | // 20 | 21 | #import 22 | #import 23 | @class CouchDatabase, CouchPersistentReplication; 24 | 25 | 26 | @interface RootViewController : UIViewController 27 | { 28 | CouchDatabase *database; 29 | NSURL* remoteSyncURL; 30 | CouchPersistentReplication* _pull; 31 | CouchPersistentReplication* _push; 32 | 33 | UITableView *tableView; 34 | IBOutlet UIProgressView *progress; 35 | BOOL showingSyncButton; 36 | IBOutlet UITextField *addItemTextField; 37 | IBOutlet UIImageView *addItemBackground; 38 | } 39 | 40 | @property(nonatomic, strong) IBOutlet UITableView *tableView; 41 | @property(nonatomic, strong) IBOutlet CouchUITableSource* dataSource; 42 | 43 | -(void)useDatabase:(CouchDatabase*)theDatabase; 44 | 45 | - (IBAction)configureSync:(id)sender; 46 | - (IBAction) deleteCheckedItems:(id)sender; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Demo-iOS/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Demo-iOS/iOS Demo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFiles 12 | 13 | CFBundleIdentifier 14 | com.couchone.TouchDBDemo 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | ${PRODUCT_NAME} 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 1.0 27 | LSRequiresIPhoneOS 28 | 29 | NSMainNibFile 30 | MainWindow 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Demo-iOS/iOS Demo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'iOS Demo' target in the 'iOS Demo' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_5_0 8 | #warning "This project uses features only available in iOS SDK 5.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /Demo-iOS/iOS Empty App-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFiles 12 | 13 | CFBundleIdentifier 14 | com.couchone.${PRODUCT_NAME:rfc1034identifier} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | ${PRODUCT_NAME} 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 1.0 27 | LSRequiresIPhoneOS 28 | 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Demo-iOS/list_area___checkbox___checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbaselabs/TouchDB-iOS/eb303a9e7c51e338294c36e0b0af6ccc4c55fe47/Demo-iOS/list_area___checkbox___checked.png -------------------------------------------------------------------------------- /Demo-iOS/list_area___checkbox___unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbaselabs/TouchDB-iOS/eb303a9e7c51e338294c36e0b0af6ccc4c55fe47/Demo-iOS/list_area___checkbox___unchecked.png -------------------------------------------------------------------------------- /Demo-iOS/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // iOS Demo 4 | // 5 | // Created by Jens Alfke on 12/9/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Test.h" 11 | 12 | #import "DemoAppDelegate.h" 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | @autoreleasepool { 17 | RunTestCases(argc,(const char**)argv); 18 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([DemoAppDelegate class])); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | # TouchDB Makefile for GNUstep 2 | 3 | # Include the common variables defined by the Makefile Package 4 | include $(GNUSTEP_MAKEFILES)/common.make 5 | 6 | # Build a simple Objective-C program 7 | FRAMEWORK_NAME = TouchDB 8 | 9 | # The Objective-C files to compile 10 | TouchDB_OBJC_FILES = \ 11 | Source/TDDatabase.m \ 12 | Source/TDDatabase+Replication.m \ 13 | Source/TDDatabase+Attachments.m \ 14 | Source/TDDatabase+Insertion.m \ 15 | Source/TDDatabase+LocalDocs.m \ 16 | Source/TDAttachment.m \ 17 | Source/TDBody.m \ 18 | Source/TDRevision.m \ 19 | Source/TDView.m \ 20 | Source/TDDatabaseManager.m \ 21 | Source/TDServer.m \ 22 | Source/TDBlobStore.m \ 23 | \ 24 | Source/TDRouter.m \ 25 | Source/TDRouter+Handlers.m \ 26 | Source/TDURLProtocol.m \ 27 | Source/TDC.m \ 28 | \ 29 | Source/TDReplicator.m \ 30 | Source/TDPuller.m \ 31 | Source/TDPusher.m \ 32 | Source/TDReplicatorManager.m \ 33 | Source/TDRemoteRequest.m \ 34 | Source/TDMultipartDocumentReader.m \ 35 | Source/TDMultipartDownloader.m \ 36 | Source/TDMultipartReader.m \ 37 | Source/TDMultipartUploader.m \ 38 | Source/TDMultiStreamWriter.m \ 39 | Source/TDMultipartWriter.m \ 40 | Source/TDReachability_Stubs.m \ 41 | \ 42 | Source/TDBatcher.m \ 43 | Source/TDCanonicalJSON.m \ 44 | Source/TDCollateJSON.m \ 45 | Source/TDGNUstep.m \ 46 | Source/TDBase64.m \ 47 | Source/TDJSON.m \ 48 | Source/TDMisc.m \ 49 | Source/TDSequenceMap.m \ 50 | Source/TDStatus.m \ 51 | \ 52 | Source/TDBlobStore_Tests.m \ 53 | Source/TDDatabase_Tests.m \ 54 | Source/TDReplicator_Tests.m \ 55 | Source/TDRouter_Tests.m \ 56 | Source/TDView_Tests.m \ 57 | Source/ChangeTracker/TDChangeTracker.m \ 58 | Source/ChangeTracker/TDSocketChangeTracker.m \ 59 | Source/ChangeTracker/TDConnectionChangeTracker.m \ 60 | \ 61 | vendor/fmdb/src/FMDatabaseAdditions.m \ 62 | vendor/fmdb/src/FMDatabase.m \ 63 | vendor/fmdb/src/FMResultSet.m \ 64 | \ 65 | vendor/MYUtilities/CollectionUtils.m \ 66 | vendor/MYUtilities/ExceptionUtils.m \ 67 | vendor/MYUtilities/Logging.m \ 68 | vendor/MYUtilities/MYBlockUtils.m \ 69 | vendor/MYUtilities/Test.m \ 70 | \ 71 | vendor/google-toolbox-for-mac/GTMNSData+zlib.m 72 | 73 | # TEMPORARILY DISABLED: 74 | #Source/TDMultiStreamWriter.m 75 | 76 | TouchDB_HEADER_FILES_DIR = Source 77 | TouchDB_HEADER_FILES = \ 78 | TouchDB.h \ 79 | TDBatcher.h \ 80 | TDBlobStore.h \ 81 | TDBody.h \ 82 | TDDatabase+Attachments.h \ 83 | TDDatabase.h \ 84 | TDDatabase+Insertion.h \ 85 | TDDatabase+LocalDocs.h \ 86 | TDDatabase+Replication.h \ 87 | TDJSON.h \ 88 | TDPuller.h \ 89 | TDPusher.h \ 90 | TDReplicator.h \ 91 | TDRevision.h \ 92 | TDRouter.h \ 93 | TDServer.h \ 94 | TDStatus.h \ 95 | TDURLProtocol.h \ 96 | TDView.h \ 97 | TDC.h 98 | 99 | TouchDB_INCLUDE_DIRS = \ 100 | -ISource \ 101 | -ISource/ChangeTracker \ 102 | -Ivendor/MYUtilities \ 103 | -Ivendor/google-toolbox-for-mac \ 104 | -Ivendor/fmdb/src 105 | 106 | TouchDB_OBJCFLAGS = \ 107 | -include Source/TouchDBPrefix.h 108 | 109 | 110 | 111 | TOOL_NAME = TouchTool 112 | 113 | TouchTool_OBJC_FILES = Demo-Mac/EmptyGNUstepApp.m 114 | 115 | TouchTool_OBJCFLAGS = \ 116 | -include Source/TDGNUstep.h 117 | 118 | TouchTool_OBJC_LIBS = \ 119 | -lTouchDB -LTouchDB.framework \ 120 | -lsqlite3 \ 121 | -lcrypto \ 122 | -luuid 123 | 124 | TouchTool_INCLUDE_DIRS = \ 125 | -Ivendor/MYUtilities 126 | 127 | 128 | OBJCFLAGS = \ 129 | -fblocks \ 130 | -Werror \ 131 | -Wall \ 132 | -DDEBUG=1 133 | 134 | #LDFLAGS = -v 135 | 136 | -include GNUmakefile.preamble 137 | 138 | # Include in the rules for making GNUstep frameworks 139 | include $(GNUSTEP_MAKEFILES)/framework.make 140 | include $(GNUSTEP_MAKEFILES)/tool.make 141 | 142 | -include GNUmakefile.postamble 143 | 144 | -------------------------------------------------------------------------------- /GNUstep/BUILDING.txt: -------------------------------------------------------------------------------- 1 | If you haven't installed GNUstep yet, first follow the steps in SETUP.txt (which is basically a 2 | shellscript but you might want to run the lines manually.) 3 | 4 | Disclaimer: This has only been tested on Ubuntu Linux; YMMV on other platforms. I [Jens] am not a 5 | Linux or GNUstep expert. 6 | 7 | There is one small GNUstep patch needed that hasn't made its way upstream yet; you'll find that in 8 | `fix_NSFileManager.patch`. You should apply that to `NSFileManager.m` in the `gnustep-base` source 9 | directory and rebuild. 10 | 11 | After that you should be able to cd to the TouchDB directory and enter 12 | make OPTFLAG= debug=yes 13 | to build a debug version of TouchDB including unit tests. The output is "TouchDB.framework". 14 | 15 | To run the unit tests, enter 16 | env LD_LIBRARY_PATH=$LD_LIBRARY_PATH:TouchDB.framework/Versions/Current obj/TouchTool Test_All 17 | which just tells the loader where to find the TouchDB library, then starts the TouchTool binary and 18 | tells it to run all the unit tests. 19 | 20 | To build a release version (optimized, no unit tests), enter 21 | make 22 | 23 | If you need to debug the GNUstep Base framework, it helps to build and install a non-optimized 24 | version of it. To do that: 25 | cd ~/gnustep-src/gnustep-base 26 | make OPTFLAG= debug=yes 27 | sudo -E make install 28 | -------------------------------------------------------------------------------- /GNUstep/SETUP.txt: -------------------------------------------------------------------------------- 1 | ## Commands for setting up GNUstep on an Ubuntu system 2 | ## Originally from Thomas Davie 3 | ## Adapted by Jens Alfke 4 | ## 5 | ## NOTE: I think this will run automatically as a shell-script, 6 | ## but see the comment on line 44 about adding a line to your .bashrc file. 7 | ## Without that, your makefiles won't work after you open a new shell... 8 | 9 | set -e 10 | mkdir ~/gnustep-src 11 | cd ~/gnustep-src 12 | 13 | # Install packages needed for building: 14 | sudo apt-get install subversion 15 | sudo apt-get install g++ 16 | sudo apt-get install gobjc 17 | sudo apt-get install libffi-dev 18 | sudo apt-get install libxml2-dev 19 | sudo apt-get install libicu-dev 20 | sudo apt-get install libgnutls-dev 21 | 22 | # Check out and build LLVM: 23 | svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm 24 | cd llvm/tools 25 | svn co http://llvm.org/svn/llvm-project/cfe/trunk clang 26 | cd ../projects 27 | svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt 28 | cd ../../ 29 | mkdir build 30 | cd build 31 | ../llvm/configure --enable-optimized 32 | make 33 | sudo make install 34 | cd ../ 35 | 36 | # Check out GNUstep and the Objective-C runtime: 37 | svn co http://svn.gna.org/svn/gnustep/tools/make/trunk/ gnustep-make 38 | svn co http://svn.gna.org/svn/gnustep/libs/base/trunk/ gnustep-base 39 | svn co http://svn.gna.org/svn/gnustep/libs/libobjc2/1.6/ libobjc2 40 | 41 | # Build the GNUstep make system: 42 | cd gnustep-make 43 | ./configure --prefix=/usr/GNUstep --enable-native-objc-exceptions --with-layout=gnustep --with-config-file=/usr/GNUstep/Local/Configuration/GNUstep.conf 44 | make 45 | sudo make install 46 | 47 | /usr/GNUstep/System/Library/Makefiles/GNUstep.sh 48 | ## Now edit ~/.bashrc and add a line to run /usr/GNUstep/System/Library/Makefiles/GNUstep.sh 49 | 50 | ./configure --enable-libffi --with-default-config=/usr/GNUstep/Local/Configuration/GNUstep.conf 51 | make 52 | sudo -E make install 53 | 54 | # Build libobjc2: 55 | cd ../libobjc2 56 | export CC=clang 57 | make 58 | sudo -E make install 59 | 60 | # Reconfigure GNUstep make system to use libobjc2: 61 | cd ../gnustep-make 62 | ./configure --prefix=/usr/GNUstep --enable-native-objc-exceptions --with-layout=gnustep --with-config-file=/usr/GNUstep/Local/Configuration/GNUstep.conf 63 | sudo make install 64 | 65 | # Build Base framework: 66 | cd ../gnustep-base 67 | make clean 68 | ./configure --with-openssl-include=/usr/local/ssl/include --with-openssl-library=/usr/local/ssl/lib/ --enable-libffi --with-default-config=/usr/GNUstep/Local/Configuration/GNUstep.conf 69 | make 70 | sudo -E make install 71 | -------------------------------------------------------------------------------- /GNUstep/fix_NSFileManager.patch: -------------------------------------------------------------------------------- 1 | Index: Source/NSFileManager.m 2 | =================================================================== 3 | --- Source/NSFileManager.m (revision 34870) 4 | +++ Source/NSFileManager.m (working copy) 5 | @@ -854,6 +854,7 @@ 6 | { 7 | ASSIGN(_lastError, 8 | @"Could not create directory - already exists"); 9 | + errno = EEXIST; 10 | return NO; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Listener/TDHTTPConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDHTTPConnection.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/29/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "HTTPConnection.h" 10 | @class TDListener; 11 | 12 | 13 | @interface TDHTTPConnection : HTTPConnection 14 | 15 | @property (readonly) TDListener* listener; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Listener/TDHTTPConnection.m: -------------------------------------------------------------------------------- 1 | // 2 | // TDHTTPConnection.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/29/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | // Based on CocoaHTTPServer/Samples/PostHTTPServer/MyHTTPConnection.m 17 | 18 | #import "TDHTTPConnection.h" 19 | #import "TDHTTPServer.h" 20 | #import "TDHTTPResponse.h" 21 | #import "TDListener.h" 22 | #import "TD_Server.h" 23 | #import "TDRouter.h" 24 | 25 | #import "HTTPMessage.h" 26 | #import "HTTPDataResponse.h" 27 | 28 | #import "Test.h" 29 | 30 | 31 | @implementation TDHTTPConnection 32 | 33 | 34 | - (TDListener*) listener { 35 | return ((TDHTTPServer*)config.server).listener; 36 | } 37 | 38 | 39 | - (BOOL)isPasswordProtected:(NSString *)path { 40 | return self.listener.requiresAuth; 41 | } 42 | 43 | - (NSString*) realm { 44 | return self.listener.realm; 45 | } 46 | 47 | - (NSString*) passwordForUser: (NSString*)username { 48 | LogTo(TDListener, @"Login attempted for user '%@'", username); 49 | return [self.listener passwordForUser: username]; 50 | } 51 | 52 | 53 | - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path { 54 | return $equal(method, @"POST") || $equal(method, @"PUT") || $equal(method, @"DELETE") 55 | || [super supportsMethod: method atPath: path]; 56 | } 57 | 58 | 59 | - (NSObject*)httpResponseForMethod:(NSString *)method URI:(NSString *)path { 60 | if (requestContentLength > 0) 61 | LogTo(TDListener, @"%@ %@ {+%u}", method, path, (unsigned)requestContentLength); 62 | else 63 | LogTo(TDListener, @"%@ %@", method, path); 64 | 65 | // Construct an NSURLRequest from the HTTPRequest: 66 | NSMutableURLRequest* urlRequest = [NSMutableURLRequest requestWithURL: request.url]; 67 | urlRequest.HTTPMethod = method; 68 | urlRequest.HTTPBody = request.body; 69 | NSDictionary* headers = request.allHeaderFields; 70 | for (NSString* header in headers) 71 | [urlRequest setValue: headers[header] forHTTPHeaderField: header]; 72 | 73 | // Create a TDRouter: 74 | TDRouter* router = [[TDRouter alloc] initWithServer: ((TDHTTPServer*)config.server).tdServer 75 | request: urlRequest 76 | isLocal: NO]; 77 | router.processRanges = NO; // The HTTP server framework does this already 78 | TDHTTPResponse* response = [[TDHTTPResponse alloc] initWithRouter: router 79 | forConnection: self]; 80 | 81 | return response; 82 | } 83 | 84 | 85 | - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path { 86 | if ($equal(method, @"PUT")) { 87 | // Allow PUT to /newdbname without a request body. 88 | return ! $equal([path stringByDeletingLastPathComponent], @"/"); 89 | } 90 | return $equal(method, @"POST") || [super expectsRequestBodyFromMethod:method atPath:path]; 91 | } 92 | 93 | - (void)prepareForBodyWithSize:(UInt64)contentLength { 94 | // Could use this method to open a temp file for large uploads 95 | } 96 | 97 | - (void)processBodyData:(NSData *)postDataChunk { 98 | // Remember: In order to support LARGE POST uploads, the data is read in chunks. 99 | // This prevents a 50 MB upload from being stored in RAM. 100 | // The size of the chunks are limited by the POST_CHUNKSIZE definition. 101 | // Therefore, this method may be called multiple times for the same POST request. 102 | 103 | if (![request appendData:postDataChunk]) 104 | Warn(@"TDHTTPConnection: couldn't append data chunk"); 105 | } 106 | 107 | 108 | @end 109 | -------------------------------------------------------------------------------- /Listener/TDHTTPResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDHTTPResponse.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/29/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "HTTPResponse.h" 10 | @class TDHTTPConnection, TDRouter, TDResponse; 11 | 12 | 13 | @interface TDHTTPResponse : NSObject 14 | { 15 | TDRouter* _router; 16 | TDHTTPConnection* _connection; 17 | TDResponse* _response; 18 | BOOL _finished; 19 | BOOL _askedIfChunked; 20 | BOOL _chunked; 21 | BOOL _delayedHeaders; 22 | NSData* _data; // Data received, waiting to be read by the connection 23 | BOOL _dataMutable; // Is _data an NSMutableData? 24 | UInt64 _dataOffset; // Offset in response of 1st byte of _data 25 | UInt64 _offset; // Offset in response for next readData 26 | } 27 | 28 | - (id) initWithRouter: (TDRouter*)router forConnection:(TDHTTPConnection*)connection; 29 | 30 | @property UInt64 offset; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Listener/TDHTTPServer.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDHTTPServer.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/29/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "HTTPServer.h" 10 | @class TDListener, TD_Server; 11 | 12 | 13 | @interface TDHTTPServer : HTTPServer { 14 | @private 15 | TDListener* _listener; 16 | TD_Server* _tdServer; 17 | } 18 | 19 | @property (retain) TDListener* listener; 20 | @property (retain) TD_Server* tdServer; 21 | 22 | @end 23 | 24 | 25 | -------------------------------------------------------------------------------- /Listener/TDListener.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDListener.h 3 | // TouchDBListener 4 | // 5 | // Created by Jens Alfke on 12/29/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class TDHTTPServer, TD_Server; 11 | 12 | 13 | /** A simple HTTP server that provides remote access to the TouchDB REST API. */ 14 | @interface TDListener : NSObject 15 | { 16 | TDHTTPServer* _httpServer; 17 | TD_Server* _tdServer; 18 | NSString* _realm; 19 | BOOL _readOnly; 20 | BOOL _requiresAuth; 21 | NSDictionary* _passwords; 22 | } 23 | 24 | /** Initializes a TDListener. 25 | @param server The TD_Server whose databases to serve. 26 | @param port The TCP port number to listen on. Use 0 to automatically pick an available port (you can get the port number after the server starts by getting the .port property.) */ 27 | - (id) initWithTDServer: (TD_Server*)server port: (UInt16)port; 28 | 29 | /** The TCP port number that the listener is listening on. 30 | If the listener has not yet started, this will return 0. */ 31 | @property (readonly) UInt16 port; 32 | 33 | 34 | /** The Bonjour service name and type to advertise as. 35 | @param name The service name; this can be arbitrary but is generally the device user's name. An empty string will be mapped to the device's name. 36 | @param type The service type; the type of a generic HTTP server is "_http._tcp." but you should use something more specific. */ 37 | - (void) setBonjourName: (NSString*)name type: (NSString*)type; 38 | 39 | /** Bonjour metadata associated with the service. Changes will be visible almost immediately. 40 | The keys are NSStrings and values are NSData. Total size should be kept small (under 1kbyte if possible) as this data is multicast over UDP. */ 41 | @property (copy) NSDictionary* TXTRecordDictionary; 42 | 43 | 44 | /** If set to YES, remote requests will not be allowed to make any changes to the server or its databases. */ 45 | @property BOOL readOnly; 46 | 47 | /** If set to YES, all requests will be required to authenticate. 48 | Setting a .passwords dictionary automatically enables this.*/ 49 | @property BOOL requiresAuth; 50 | 51 | /** Security realm string to return in authentication challenges. */ 52 | @property (copy) NSString* realm; 53 | 54 | /** Sets user names and passwords for authentication. 55 | @param passwords A dictionary mapping user names to passwords. */ 56 | - (void) setPasswords: (NSDictionary*)passwords; 57 | 58 | /** Returns the password assigned to a user name, or nil if the name is not recognized. */ 59 | - (NSString*) passwordForUser: (NSString*)username; 60 | 61 | 62 | /** Starts the listener. */ 63 | - (BOOL) start; 64 | 65 | /** Stops the listener. */ 66 | - (void) stop; 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /Listener/TDListener.m: -------------------------------------------------------------------------------- 1 | // 2 | // TDListener.m 3 | // TouchDBListener 4 | // 5 | // Created by Jens Alfke on 12/29/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "TDListener.h" 17 | #import "TDHTTPServer.h" 18 | #import "TDHTTPConnection.h" 19 | #import "TD_Server.h" 20 | 21 | #import "HTTPServer.h" 22 | 23 | 24 | @implementation TDListener 25 | 26 | 27 | @synthesize readOnly=_readOnly, requiresAuth=_requiresAuth, realm=_realm; 28 | 29 | 30 | - (id) initWithTDServer: (TD_Server*)server port: (UInt16)port { 31 | self = [super init]; 32 | if (self) { 33 | _tdServer = server; 34 | _httpServer = [[TDHTTPServer alloc] init]; 35 | _httpServer.listener = self; 36 | _httpServer.tdServer = _tdServer; 37 | _httpServer.port = port; 38 | _httpServer.connectionClass = [TDHTTPConnection class]; 39 | self.realm = @"TouchDB"; 40 | } 41 | return self; 42 | } 43 | 44 | 45 | - (void)dealloc 46 | { 47 | [self stop]; 48 | } 49 | 50 | 51 | - (void) setBonjourName: (NSString*)name type: (NSString*)type { 52 | _httpServer.name = name; 53 | _httpServer.type = type; 54 | } 55 | 56 | - (NSDictionary *)TXTRecordDictionary {return _httpServer.TXTRecordDictionary;} 57 | - (void)setTXTRecordDictionary:(NSDictionary *)dict {_httpServer.TXTRecordDictionary = dict;} 58 | 59 | 60 | 61 | - (BOOL) start { 62 | NSError* error; 63 | return [_httpServer start: &error]; 64 | } 65 | 66 | - (void) stop { 67 | [_httpServer stop]; 68 | } 69 | 70 | 71 | - (UInt16) port { 72 | return _httpServer.listeningPort; 73 | } 74 | 75 | 76 | - (void) setPasswords: (NSDictionary*)passwords { 77 | _passwords = [passwords copy]; 78 | _requiresAuth = (_passwords != nil); 79 | } 80 | 81 | - (NSString*) passwordForUser:(NSString *)username { 82 | return _passwords[username]; 83 | } 84 | 85 | 86 | @end 87 | 88 | 89 | 90 | @implementation TDHTTPServer 91 | 92 | @synthesize listener=_listener, tdServer=_tdServer; 93 | 94 | @end -------------------------------------------------------------------------------- /Listener/TouchDBListener-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.couchbase.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | NSHumanReadableCopyright 26 | Copyright © 2012 Couchbase, Inc. All rights reserved. 27 | NSPrincipalClass 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Listener/TouchDBListener.exp: -------------------------------------------------------------------------------- 1 | # TouchDBListener.exp 2 | # TouchDB 3 | # 4 | # Created by Jens Alfke on 12/29/11. 5 | # Copyright (c) 2011 Couchbase, Inc. All rights reserved. 6 | 7 | .objc_class_name_TDListener 8 | -------------------------------------------------------------------------------- /Listener/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Source/ChangeTracker/TDChangeTracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDChangeTracker.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 6/20/11. 6 | // Copyright 2011 Couchbase, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import 17 | @class TDChangeTracker; 18 | @protocol TDAuthorizer; 19 | 20 | 21 | @protocol TDChangeTrackerClient 22 | @optional 23 | - (void) changeTrackerReceivedChange: (NSDictionary*)change; 24 | - (void) changeTrackerReceivedChanges: (NSArray*)changes; 25 | - (void) changeTrackerStopped: (TDChangeTracker*)tracker; 26 | @end 27 | 28 | 29 | typedef enum TDChangeTrackerMode { 30 | kOneShot, 31 | kLongPoll, 32 | kContinuous 33 | } TDChangeTrackerMode; 34 | 35 | 36 | /** Reads the continuous-mode _changes feed of a database, and sends the individual change entries to its client. */ 37 | @interface TDChangeTracker : NSObject 38 | { 39 | @protected 40 | NSURL* _databaseURL; 41 | id __weak _client; 42 | TDChangeTrackerMode _mode; 43 | id _lastSequenceID; 44 | unsigned _limit; 45 | NSError* _error; 46 | BOOL _includeConflicts; 47 | NSString* _filterName; 48 | NSDictionary* _filterParameters; 49 | NSTimeInterval _heartbeat; 50 | NSDictionary* _requestHeaders; 51 | id _authorizer; 52 | unsigned _retryCount; 53 | } 54 | 55 | - (id)initWithDatabaseURL: (NSURL*)databaseURL 56 | mode: (TDChangeTrackerMode)mode 57 | conflicts: (BOOL)includeConflicts 58 | lastSequence: (id)lastSequenceID 59 | client: (id)client; 60 | 61 | @property (readonly, nonatomic) NSURL* databaseURL; 62 | @property (readonly, nonatomic) NSString* databaseName; 63 | @property (readonly) NSURL* changesFeedURL; 64 | @property (readonly, copy, nonatomic) id lastSequenceID; 65 | @property (strong, nonatomic) NSError* error; 66 | @property (weak, nonatomic) id client; 67 | @property (strong, nonatomic) NSDictionary *requestHeaders; 68 | @property (strong, nonatomic) id authorizer; 69 | 70 | @property (nonatomic) TDChangeTrackerMode mode; 71 | @property (copy) NSString* filterName; 72 | @property (copy) NSDictionary* filterParameters; 73 | @property (nonatomic) unsigned limit; 74 | @property (nonatomic) NSTimeInterval heartbeat; 75 | @property (nonatomic) NSArray *docIDs; 76 | 77 | - (BOOL) start; 78 | - (void) stop; 79 | 80 | /** Asks the tracker to retry connecting, _if_ it's currently disconnected but waiting to retry. 81 | This should be called when the reachability of the remote host changes, or when the 82 | app is reactivated. */ 83 | - (void) retry; 84 | 85 | // Protected 86 | @property (readonly) NSString* changesFeedPath; 87 | - (void) setUpstreamError: (NSString*)message; 88 | - (void) failedWithError: (NSError*)error; 89 | - (NSInteger) receivedPollResponse: (NSData*)body errorMessage: (NSString**)errorMessage; 90 | - (BOOL) receivedChanges: (NSArray*)changes errorMessage: (NSString**)errorMessage; 91 | - (BOOL) receivedChange: (NSDictionary*)change; 92 | - (void) stopped; // override this 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /Source/ChangeTracker/TDConnectionChangeTracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDConnectionChangeTracker.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/1/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "TDChangeTracker.h" 10 | 11 | 12 | /** TDChangeTracker that uses a regular NSURLConnection. 13 | This unfortunately doesn't work with regular CouchDB in continuous mode, apparently due to some bug in CFNetwork. */ 14 | @interface TDConnectionChangeTracker : TDChangeTracker 15 | { 16 | @private 17 | NSURLConnection* _connection; 18 | NSMutableData* _inputBuffer; 19 | CFAbsoluteTime _startTime; 20 | bool _challenged; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Source/ChangeTracker/TDSocketChangeTracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDSocketChangeTracker.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/2/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "TDChangeTracker.h" 10 | 11 | 12 | /** TDChangeTracker implementation that uses a raw TCP socket to read the chunk-mode HTTP response. */ 13 | @interface TDSocketChangeTracker : TDChangeTracker 14 | { 15 | @private 16 | NSInputStream* _trackingInput; 17 | 18 | NSMutableData* _inputBuffer; 19 | NSMutableData* _changeBuffer; 20 | CFHTTPMessageRef _unauthResponse; 21 | NSURLCredential* _credential; 22 | CFAbsoluteTime _startTime; 23 | bool _gotResponseHeaders; 24 | bool _parsing; 25 | bool _inputAvailable; 26 | bool _atEOF; 27 | } 28 | @end 29 | -------------------------------------------------------------------------------- /Source/TDAuthorizer.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDAuthorizer.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 5/21/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | /** Protocol for adding authorization to HTTP requests. */ 13 | @protocol TDAuthorizer 14 | 15 | /** Should generate and return an authorization string for the given request. 16 | The string, if non-nil, will be set as the value of the "Authorization:" HTTP header. */ 17 | - (NSString*) authorizeURLRequest: (NSMutableURLRequest*)request 18 | forRealm: (NSString*)realm; 19 | 20 | - (NSString*) authorizeHTTPMessage: (CFHTTPMessageRef)message 21 | forRealm: (NSString*)realm; 22 | 23 | @optional 24 | 25 | - (NSString*) loginPathForSite: (NSURL*)site; 26 | - (NSDictionary*) loginParametersForSite: (NSURL*)site; 27 | 28 | @end 29 | 30 | 31 | 32 | /** Simple implementation of TDAuthorizer that does HTTP Basic Auth. */ 33 | @interface TDBasicAuthorizer : NSObject 34 | { 35 | @private 36 | NSURLCredential* _credential; 37 | } 38 | 39 | /** Initialize given a credential object that contains a username and password. */ 40 | - (id) initWithCredential: (NSURLCredential*)credential; 41 | 42 | /** Initialize given a URL alone -- will use a baked-in username/password in the URL, 43 | or look up a credential from the keychain. */ 44 | - (id)initWithURL: (NSURL*)url; 45 | 46 | @end 47 | 48 | 49 | 50 | /** Implementation of TDAuthorizer that supports MAC authorization as used in OAuth 2. */ 51 | @interface TDMACAuthorizer : NSObject 52 | { 53 | @private 54 | NSString *_key, *_identifier; 55 | NSDate* _issueTime; 56 | NSData* (*_hmacFunction)(NSData*, NSData*); 57 | 58 | } 59 | 60 | /** Initialize given MAC credentials */ 61 | - (id) initWithKey: (NSString*)key 62 | identifier: (NSString*)identifier 63 | algorithm: (NSString*)algorithm 64 | issueTime: (NSDate*)issueTime; 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /Source/TDBase64.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDBase64.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 9/14/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TDBase64 : NSObject 12 | + (NSString*) encode:(const void*) input length:(size_t) length; 13 | + (NSString*) encode:(NSData*) rawBytes; 14 | + (NSData*) decode:(const char*) string length:(size_t) inputLength; 15 | + (NSData*) decode:(NSString*) string; 16 | 17 | /** Decodes the URL-safe Base64 variant that uses '-' and '_' instead of '+' and '/', and omits trailing '=' characters. */ 18 | + (NSData*) decodeURLSafe: (NSString*)string; 19 | + (NSData*) decodeURLSafe: (const char*)string length: (size_t)inputLength; 20 | @end -------------------------------------------------------------------------------- /Source/TDBase64.m: -------------------------------------------------------------------------------- 1 | // 2 | // TDBase64.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 9/14/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "TDBase64.h" 17 | 18 | // Based on public-domain source code by cyrus.najmabadi@gmail.com 19 | // taken from http://www.cocoadev.com/index.pl?BaseSixtyFour 20 | 21 | 22 | @implementation TDBase64 23 | 24 | 25 | static const uint8_t kEncodingTable[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 26 | static int8_t kDecodingTable[256]; 27 | 28 | + (void) initialize { 29 | if (self == [TDBase64 class]) { 30 | memset(kDecodingTable, 0xFF, sizeof(kDecodingTable)); 31 | for (NSUInteger i = 0; i < sizeof(kEncodingTable); i++) { 32 | kDecodingTable[kEncodingTable[i]] = (int8_t)i; 33 | } 34 | // Alternate characters used in the URL-safe Base64 encoding (RFC 4648, sec. 5) 35 | kDecodingTable['-'] = 62; 36 | kDecodingTable['='] = 63; 37 | } 38 | } 39 | 40 | 41 | + (NSString*) encode: (const void*)input length: (size_t)length { 42 | if (input == NULL) 43 | return nil; 44 | NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4]; 45 | uint8_t* output = (uint8_t*)data.mutableBytes; 46 | 47 | for (NSUInteger i = 0; i < length; i += 3) { 48 | NSInteger value = 0; 49 | for (NSUInteger j = i; j < (i + 3); j++) { 50 | value <<= 8; 51 | 52 | if (j < length) { 53 | value |= ((const uint8_t*)input)[j]; 54 | } 55 | } 56 | 57 | NSInteger index = (i / 3) * 4; 58 | output[index + 0] = kEncodingTable[(value >> 18) & 0x3F]; 59 | output[index + 1] = kEncodingTable[(value >> 12) & 0x3F]; 60 | output[index + 2] = (i + 1) < length ? kEncodingTable[(value >> 6) & 0x3F] : '='; 61 | output[index + 3] = (i + 2) < length ? kEncodingTable[(value >> 0) & 0x3F] : '='; 62 | } 63 | 64 | return [[NSString alloc] initWithData:data 65 | encoding:NSASCIIStringEncoding]; 66 | } 67 | 68 | 69 | + (NSString*) encode: (NSData*)rawBytes { 70 | return [self encode: rawBytes.bytes length: rawBytes.length]; 71 | } 72 | 73 | 74 | + (NSData*) decode: (const char*)string length: (size_t)inputLength { 75 | if (inputLength % 4 != 0) 76 | return nil; 77 | return [self decodeURLSafe: string length: inputLength]; 78 | } 79 | 80 | + (NSData*) decodeURLSafe: (const char*)string length: (size_t)inputLength { 81 | if (string == NULL) 82 | return nil; 83 | while (inputLength > 0 && string[inputLength - 1] == '=') { 84 | inputLength--; 85 | } 86 | 87 | size_t outputLength = inputLength * 3 / 4; 88 | NSMutableData* data = [NSMutableData dataWithLength:outputLength]; 89 | uint8_t* output = data.mutableBytes; 90 | 91 | NSUInteger inputPoint = 0; 92 | NSUInteger outputPoint = 0; 93 | while (inputPoint < inputLength) { 94 | uint8_t i0 = string[inputPoint++]; 95 | uint8_t i1 = string[inputPoint++]; 96 | uint8_t i2 = inputPoint < inputLength ? string[inputPoint++] : 'A'; /* 'A' will decode to \0 */ 97 | uint8_t i3 = inputPoint < inputLength ? string[inputPoint++] : 'A'; 98 | 99 | if (kDecodingTable[i0] < 0 || kDecodingTable[i1] < 0 100 | || kDecodingTable[i2] < 0 || kDecodingTable[i3] < 0) 101 | return nil; 102 | 103 | output[outputPoint++] = (uint8_t)((kDecodingTable[i0] << 2) | (kDecodingTable[i1] >> 4)); 104 | if (outputPoint < outputLength) { 105 | output[outputPoint++] = (uint8_t)(((kDecodingTable[i1] & 0xf) << 4) | (kDecodingTable[i2] >> 2)); 106 | } 107 | if (outputPoint < outputLength) { 108 | output[outputPoint++] = (uint8_t)(((kDecodingTable[i2] & 0x3) << 6) | kDecodingTable[i3]); 109 | } 110 | } 111 | 112 | return data; 113 | } 114 | 115 | 116 | + (NSData*) decode:(NSString*) string { 117 | NSData* ascii = [string dataUsingEncoding: NSASCIIStringEncoding]; 118 | return [self decode: ascii.bytes length: ascii.length]; 119 | } 120 | 121 | 122 | + (NSData*) decodeURLSafe: (NSString*)string { 123 | NSData* ascii = [string dataUsingEncoding: NSASCIIStringEncoding]; 124 | return [self decodeURLSafe: ascii.bytes length: ascii.length]; 125 | } 126 | 127 | 128 | @end 129 | -------------------------------------------------------------------------------- /Source/TDBatcher.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDBatcher.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/15/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | /** Utility that queues up objects until the queue fills up or a time interval elapses, 13 | then passes objects, in groups of its capacity, to a client-supplied processor block. */ 14 | @interface TDBatcher : NSObject 15 | { 16 | NSUInteger _capacity; 17 | NSTimeInterval _delay; 18 | NSMutableArray* _inbox; 19 | bool _scheduled; 20 | NSTimeInterval _scheduledDelay; 21 | void (^_processor)(NSArray*); 22 | } 23 | 24 | - (id) initWithCapacity: (NSUInteger)capacity 25 | delay: (NSTimeInterval)delay 26 | processor: (void (^)(NSArray*))block; 27 | 28 | @property (readonly) NSUInteger count; 29 | 30 | - (void) queueObject: (id)object; 31 | - (void) queueObjects: (NSArray*)objects; 32 | 33 | /** Sends queued objects to the processor block (up to the capacity). */ 34 | - (void) flush; 35 | 36 | /** Sends _all_ the queued objects at once to the processor block. 37 | After this method returns, all the queued objects will have been processed.*/ 38 | - (void) flushAll; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Source/TDBatcher.m: -------------------------------------------------------------------------------- 1 | // 2 | // TDBatcher.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/15/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "TDBatcher.h" 17 | 18 | @implementation TDBatcher 19 | 20 | 21 | - (id) initWithCapacity: (NSUInteger)capacity 22 | delay: (NSTimeInterval)delay 23 | processor: (void (^)(NSArray*))block { 24 | self = [super init]; 25 | if (self) { 26 | _capacity = capacity; 27 | _delay = delay; 28 | _processor = [block copy]; 29 | } 30 | return self; 31 | } 32 | 33 | 34 | 35 | 36 | - (void) unschedule { 37 | _scheduled = false; 38 | [NSObject cancelPreviousPerformRequestsWithTarget: self 39 | selector: @selector(processNow) object:nil]; 40 | } 41 | 42 | 43 | - (void) scheduleWithDelay: (NSTimeInterval)delay { 44 | if (_scheduled && delay < _scheduledDelay) 45 | [self unschedule]; 46 | if (!_scheduled) { 47 | _scheduled = true; 48 | _scheduledDelay = delay; 49 | [self performSelector: @selector(processNow) withObject: nil afterDelay: delay]; 50 | } 51 | } 52 | 53 | 54 | - (void) processNow { 55 | _scheduled = false; 56 | NSArray* toProcess; 57 | NSUInteger count = _inbox.count; 58 | if (count == 0) { 59 | return; 60 | } else if (count <= _capacity) { 61 | toProcess = _inbox; 62 | _inbox = nil; 63 | } else { 64 | toProcess = [_inbox subarrayWithRange: NSMakeRange(0, _capacity)]; 65 | [_inbox removeObjectsInRange: NSMakeRange(0, _capacity)]; 66 | // There are more objects left, so schedule them Real Soon: 67 | [self scheduleWithDelay: 0.0]; 68 | } 69 | _processor(toProcess); 70 | } 71 | 72 | 73 | - (void) queueObjects: (NSArray*)objects { 74 | if (objects.count == 0) 75 | return; 76 | if (!_inbox) 77 | _inbox = [[NSMutableArray alloc] init]; 78 | [_inbox addObjectsFromArray: objects]; 79 | 80 | if (_inbox.count < _capacity) 81 | [self scheduleWithDelay: _delay]; 82 | else { 83 | [self unschedule]; 84 | [self processNow]; 85 | } 86 | } 87 | 88 | 89 | - (void) queueObject: (id)object { 90 | [self queueObjects: @[object]]; 91 | } 92 | 93 | 94 | - (void) flush { 95 | [self unschedule]; 96 | [self processNow]; 97 | } 98 | 99 | 100 | - (void) flushAll { 101 | if (_inbox.count > 0) { 102 | [self unschedule]; 103 | NSArray* toProcess = _inbox; 104 | _inbox = nil; 105 | _processor(toProcess); 106 | } 107 | } 108 | 109 | 110 | - (NSUInteger) count { 111 | return _inbox.count; 112 | } 113 | 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /Source/TDBlobStore.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDBlobStore.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/10/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #ifdef GNUSTEP 12 | #import 13 | #import 14 | #else 15 | #define COMMON_DIGEST_FOR_OPENSSL 16 | #import 17 | #endif 18 | 19 | 20 | /** Key identifying a data blob. This happens to be a SHA-1 digest. */ 21 | typedef struct TDBlobKey { 22 | uint8_t bytes[SHA_DIGEST_LENGTH]; 23 | } TDBlobKey; 24 | 25 | 26 | /** A persistent content-addressable store for arbitrary-size data blobs. 27 | Each blob is stored as a file named by its SHA-1 digest. */ 28 | @interface TDBlobStore : NSObject 29 | { 30 | NSString* _path; 31 | NSString* _tempDir; 32 | } 33 | 34 | - (id) initWithPath: (NSString*)dir error: (NSError**)outError; 35 | 36 | - (NSData*) blobForKey: (TDBlobKey)key; 37 | - (NSInputStream*) blobInputStreamForKey: (TDBlobKey)key 38 | length: (UInt64*)outLength; 39 | 40 | - (BOOL) storeBlob: (NSData*)blob 41 | creatingKey: (TDBlobKey*)outKey; 42 | 43 | @property (readonly) NSString* path; 44 | @property (readonly) NSUInteger count; 45 | @property (readonly) NSArray* allKeys; 46 | @property (readonly) UInt64 totalDataSize; 47 | 48 | - (NSInteger) deleteBlobsExceptWithKeys: (NSSet*)keysToKeep; 49 | 50 | + (TDBlobKey) keyForBlob: (NSData*)blob; 51 | + (NSData*) keyDataForBlob: (NSData*)blob; 52 | 53 | /** Returns the path of the file storing the attachment with the given key, or nil. 54 | DO NOT MODIFY THIS FILE! */ 55 | - (NSString*) pathForKey: (TDBlobKey)key; 56 | 57 | @end 58 | 59 | 60 | 61 | typedef struct { 62 | uint8_t bytes[MD5_DIGEST_LENGTH]; 63 | } TDMD5Key; 64 | 65 | 66 | /** Lets you stream a large attachment to a TDBlobStore asynchronously, e.g. from a network download. */ 67 | @interface TDBlobStoreWriter : NSObject { 68 | @private 69 | TDBlobStore* _store; 70 | NSString* _tempPath; 71 | NSFileHandle* _out; 72 | UInt64 _length; 73 | SHA_CTX _shaCtx; 74 | MD5_CTX _md5Ctx; 75 | TDBlobKey _blobKey; 76 | TDMD5Key _MD5Digest; 77 | } 78 | 79 | - (id) initWithStore: (TDBlobStore*)store; 80 | 81 | /** Appends data to the blob. Call this when new data is available. */ 82 | - (void) appendData: (NSData*)data; 83 | 84 | /** Call this after all the data has been added. */ 85 | - (void) finish; 86 | 87 | /** Call this to cancel before finishing the data. */ 88 | - (void) cancel; 89 | 90 | /** Installs a finished blob into the store. */ 91 | - (BOOL) install; 92 | 93 | /** The number of bytes in the blob. */ 94 | @property (readonly) UInt64 length; 95 | 96 | /** After finishing, this is the key for looking up the blob through the TDBlobStore. */ 97 | @property (readonly) TDBlobKey blobKey; 98 | 99 | /** After finishing, this is the MD5 digest of the blob, in base64 with an "md5-" prefix. 100 | (This is useful for compatibility with CouchDB, which stores MD5 digests of attachments.) */ 101 | @property (readonly) NSString* MD5DigestString; 102 | @property (readonly) NSString* SHA1DigestString; 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /Source/TDBlobStore_Tests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TDBlobStore_Tests.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 1/31/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "TDBlobStore.h" 10 | #import "Test.h" 11 | 12 | 13 | static TDBlobStore* createStore(void) { 14 | NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent: @"TDBlobStoreTest"]; 15 | [[NSFileManager defaultManager] removeItemAtPath: path error: NULL]; 16 | NSError* error; 17 | TDBlobStore* store = [[TDBlobStore alloc] initWithPath: path error: &error]; 18 | CAssert(store, @"Couldn't create TDBlobStore: %@", error); 19 | return store; 20 | } 21 | 22 | static void deleteStore(TDBlobStore* store) { 23 | [[NSFileManager defaultManager] removeItemAtPath: store.path error: NULL]; 24 | } 25 | 26 | 27 | TestCase(TDBlobStoreBasic) { 28 | TDBlobStore* store = createStore(); 29 | NSData* item = [@"this is an item" dataUsingEncoding: NSUTF8StringEncoding]; 30 | TDBlobKey key, key2; 31 | CAssert([store storeBlob: item creatingKey: &key]); 32 | CAssert([store storeBlob: item creatingKey: &key2]); 33 | CAssert(memcmp(&key, &key2, sizeof(key)) == 0); 34 | 35 | NSData* readItem = [store blobForKey: key]; 36 | CAssertEqual(readItem, item); 37 | deleteStore(store); 38 | } 39 | 40 | 41 | TestCase(TDBlobStoreWriter) { 42 | TDBlobStore* store = createStore(); 43 | TDBlobStoreWriter* writer = [[TDBlobStoreWriter alloc] initWithStore: store]; 44 | CAssert(writer); 45 | 46 | [writer appendData: [@"part 1, " dataUsingEncoding: NSUTF8StringEncoding]]; 47 | [writer appendData: [@"part 2, " dataUsingEncoding: NSUTF8StringEncoding]]; 48 | [writer appendData: [@"part 3" dataUsingEncoding: NSUTF8StringEncoding]]; 49 | [writer finish]; 50 | CAssert([writer install]); 51 | 52 | NSData* readItem = [store blobForKey: writer.blobKey]; 53 | CAssertEqual(readItem, [@"part 1, part 2, part 3" dataUsingEncoding: NSUTF8StringEncoding]); 54 | 55 | deleteStore(store); 56 | } 57 | 58 | 59 | TestCase(TDBlobStore) { 60 | RequireTestCase(TDBlobStoreBasic); 61 | RequireTestCase(TDBlobStoreWriter); 62 | } -------------------------------------------------------------------------------- /Source/TDC.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDC.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 3/5/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #ifndef _TDC_ 10 | #define _TDC_ 11 | #include 12 | #include 13 | 14 | 15 | /* Simple ANSI C API to TouchDB. */ 16 | 17 | 18 | /** C structure describing a MIME entity, used for requests and responses. 19 | This structure and the data it points to should be considered read-only. 20 | It should be created only by calling TDCMIMECreate, and freed only by calling TDCMIMEFree. */ 21 | typedef struct { 22 | unsigned headerCount; 23 | const char** headerNames; 24 | const char** headerValues; 25 | size_t contentLength; 26 | const void* content; 27 | void* _private; 28 | } TDCMIME; 29 | 30 | 31 | /** Allocates a TDCMIME structure on the heap, copying the headers and (optionally) the content. 32 | @param headerCount The number of MIME headers. 33 | @param headerNames An array of headerCount pointers to C strings, each a header name. (May be NULL if the headerCount is 0.) 34 | @param headerValues An array of headerCount pointers to C strings, each a header value corresponding to the header name with the same index. (May be NULL if the headerCount is 0.) 35 | @param contentLength The length of the content in bytes. 36 | @param content The content itself (may be NULL if the conten length is 0.) 37 | @param copyContent If true, the content will be copied into a new heap block owned by the TDCMIME structure. If false, the content pointer will be adopted by the TDCMIME and will be freed when the TDCMIME itself is freed (this implies the caller must have used malloc to allocate it.) If allocation fails, the content block is still valid and the caller is responsible for freeing it. 38 | @return The allocated TDCMIME structure, or NULL if memory allocation failed. 39 | */ 40 | TDCMIME* TDCMIMECreate(unsigned headerCount, 41 | const char** headerNames, 42 | const char** headerValues, 43 | size_t contentLength, 44 | const void* content, 45 | bool copyContent); 46 | 47 | 48 | /** Frees a TDCMIME structure and all the data it points to. 49 | The structure *must* have been allocated by calling TDCMIMECreate. 50 | It is a safe no-op to pass NULL to this function. 51 | @param The TDCMIME pointer to free, or NULL. */ 52 | void TDCMIMEFree(TDCMIME* mime); 53 | 54 | 55 | /** Initializes the TDC API. 56 | This must be called once (and only once), before the first call to TDCSendRequest. 57 | @param dataDirectoryPath The base directory in which TouchDB should store data files. This directory's parent directory must exist and be writeable. */ 58 | void TDCInitialize(const char* dataDirectoryPath); 59 | 60 | 61 | /** Synchronously calls a TouchDB REST API method. 62 | This method is thread-safe: it may be called from any thread and from multiple threads simultaneously. This allows the application to implement asynchronous operations. 63 | @param method The HTTP method -- "GET", "PUT", etc. 64 | @param url The target URL. The scheme and host are ignored. We suggest using "touchdb:///". 65 | @param headersAndBody The request headers and body. TouchDB takes ownership of this; the caller should NOT use, modify or free it after the call. 66 | @param outResponse On return, may be filled in with a pointer to an allocated TDCMIME structure. Caller is responsible for freeing this via TDCMIMEFree. The value may be filled in as NULL if there is no meaningful data to return, as in some error conditions. 67 | @return The HTTP status code of the response. */ 68 | int TDCSendRequest(const char* method, 69 | const char* url, 70 | TDCMIME* headersAndBody, 71 | TDCMIME** outResponse); 72 | 73 | #endif // _TDC_ 74 | -------------------------------------------------------------------------------- /Source/TDCanonicalJSON.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDCanonicalJSON.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 8/15/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** Generates a canonical JSON form of an object tree, suitable for signing. 12 | See algorithm at . */ 13 | @interface TDCanonicalJSON : NSObject 14 | { 15 | @private 16 | id _input; 17 | NSString* _ignoreKeyPrefix; 18 | NSArray* _whitelistedKeys; 19 | NSMutableString* _output; 20 | } 21 | 22 | - (id) initWithObject: (id)object; 23 | 24 | /** If non-nil, dictionary keys beginning with this prefix will be ignored. */ 25 | @property (nonatomic, copy) NSString* ignoreKeyPrefix; 26 | 27 | /** Keys to include even if they begin with the ignorePrefix. */ 28 | @property (nonatomic, copy) NSArray* whitelistedKeys; 29 | 30 | /** Canonical JSON string from the input object tree. 31 | This isn't directly useful for tasks like signing or generating digests; you probably want to use .canonicalData instead for that. */ 32 | @property (readonly) NSString* canonicalString; 33 | 34 | /** Canonical form of UTF-8 encoded JSON data from the input object tree. */ 35 | @property (readonly) NSData* canonicalData; 36 | 37 | 38 | /** Convenience method that instantiates a TDCanonicalJSON object and uses it to encode the object. */ 39 | + (NSData*) canonicalData: (id)rootObject; 40 | 41 | /** Convenience method that instantiates a TDCanonicalJSON object and uses it to encode the object, returning a string. */ 42 | + (NSString*) canonicalString: (id)rootObject; 43 | 44 | 45 | /** Returns a dictionary's keys in the same order in which they would be written out in canonical JSON. */ 46 | + (NSArray*) orderedKeys: (NSDictionary*)dict; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Source/TDCollateJSON.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDCollator.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/8/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** SQLite collation function for JSON-formatted strings. 12 | The "context" parameter should be one of the three collation mode constants below. 13 | WARNING: This function *only* works on valid JSON with no whitespace. 14 | If called on non-JSON strings it is quite likely to crash! */ 15 | int TDCollateJSON(void *context, 16 | int len1, const void * chars1, 17 | int len2, const void * chars2); 18 | 19 | /** Collation that compares only a limited number of top-level collection items. 20 | If the first 'arrayLimit' items of the top-level array/object have been parsed and are equal, it will stop and return 0. (This is useful for view result grouping.) */ 21 | int TDCollateJSONLimited(void *context, 22 | int len1, const void * chars1, 23 | int len2, const void * chars2, 24 | unsigned arrayLimit); 25 | 26 | // CouchDB's default collation rules, including Unicode collation for strings 27 | #define kTDCollateJSON_Unicode ((void*)0) 28 | 29 | // CouchDB's "raw" collation rules (which order scalar types differently, beware) 30 | #define kTDCollateJSON_Raw ((void*)1) 31 | 32 | // ASCII mode, which is like CouchDB default except that strings are compared as binary UTF-8 33 | #define kTDCollateJSON_ASCII ((void*)2) 34 | -------------------------------------------------------------------------------- /Source/TDGNUstep.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDGNUstep.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 2/27/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #ifdef GNUSTEP 10 | 11 | /* Stuff that's in iOS / OS X but not GNUstep or Linux */ 12 | 13 | #define _GNU_SOURCE 14 | 15 | #import 16 | 17 | 18 | #ifndef NS_BLOCKS_AVAILABLE 19 | #define NS_BLOCKS_AVAILABLE 1 20 | #endif 21 | 22 | 23 | typedef int32_t SInt32; 24 | typedef uint32_t UInt32; 25 | typedef int64_t SInt64; 26 | typedef uint64_t UInt64; 27 | typedef int8_t SInt8; 28 | typedef uint8_t UInt8; 29 | 30 | 31 | // in BSD but not Linux: 32 | int digittoint(int c); 33 | 34 | 35 | typedef double CFAbsoluteTime; 36 | CFAbsoluteTime CFAbsoluteTimeGetCurrent(void); 37 | 38 | 39 | #define NSRunLoopCommonModes NSDefaultRunLoopMode 40 | 41 | 42 | typedef NSComparisonResult (^NSComparator)(id obj1, id obj2); 43 | 44 | @interface NSArray (GNUstep) 45 | - (NSArray *)sortedArrayUsingComparator:(NSComparator)cmptr; 46 | @end 47 | 48 | 49 | @interface NSMutableArray (GNUstep) 50 | - (void)sortUsingComparator:(NSComparator)cmptr; 51 | @end 52 | 53 | 54 | enum { 55 | NSDataReadingMappedIfSafe = 1UL << 0, 56 | NSDataReadingUncached = 1UL << 1, 57 | }; 58 | typedef NSUInteger NSDataReadingOptions; 59 | 60 | enum { 61 | NSDataSearchBackwards = 1UL << 0, 62 | NSDataSearchAnchored = 1UL << 1 63 | }; 64 | typedef NSUInteger NSDataSearchOptions; 65 | 66 | @interface NSData (GNUstep) 67 | + (id)dataWithContentsOfFile:(NSString *)path options:(NSDataReadingOptions)readOptionsMask error:(NSError **)errorPtr; 68 | - (NSRange)rangeOfData:(NSData *)dataToFind options:(NSDataSearchOptions)mask range:(NSRange)searchRange; 69 | @end 70 | 71 | 72 | @interface NSOperationQueue (GNUstep) 73 | - (void)addOperationWithBlock:(void (^)(void))block; 74 | @end 75 | 76 | 77 | @protocol NSURLConnectionDelegate 78 | @end 79 | 80 | 81 | @protocol NSStreamDelegate 82 | @end 83 | 84 | 85 | enum { 86 | NSURLRequestReloadIgnoringLocalCacheData = NSURLRequestReloadIgnoringCacheData 87 | }; 88 | 89 | 90 | #endif // GNUSTEP 91 | -------------------------------------------------------------------------------- /Source/TDGNUstep.m: -------------------------------------------------------------------------------- 1 | // 2 | // TDGNUstep.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 2/27/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "TDGNUstep.h" 17 | #import 18 | 19 | 20 | @interface NSError (GNUstep) 21 | + (NSError*) _last; 22 | @end 23 | 24 | 25 | int digittoint(int c) { 26 | if (!isxdigit(c)) 27 | return 0; 28 | else if (c <= '9') 29 | return c - '0'; 30 | else if (c <= 'F') 31 | return 10 + c - 'A'; 32 | else 33 | return 10 + c - 'a'; 34 | } 35 | 36 | 37 | CFAbsoluteTime CFAbsoluteTimeGetCurrent(void) { 38 | // NOTE: The time base for this isn't the same as CF's (1970 vs 2001), but this is only being 39 | // used in TouchDB to calculate relative times, so that doesn't matter. 40 | return time(NULL); 41 | } 42 | 43 | 44 | static NSComparisonResult callComparator(id a, id b, void* context) { 45 | return ((NSComparator)context)(a, b); 46 | } 47 | 48 | @implementation NSArray (GNUstep) 49 | 50 | - (NSArray *)sortedArrayUsingComparator:(NSComparator)cmptr { 51 | return [self sortedArrayUsingFunction: &callComparator context: cmptr]; 52 | } 53 | 54 | @end 55 | 56 | @implementation NSMutableArray (GNUstep) 57 | 58 | - (void)sortUsingComparator:(NSComparator)cmptr { 59 | [self sortUsingFunction: &callComparator context: cmptr]; 60 | } 61 | 62 | @end 63 | 64 | 65 | 66 | @implementation NSData (GNUstep) 67 | 68 | + (id)dataWithContentsOfFile:(NSString *)path 69 | options:(NSDataReadingOptions)options 70 | error:(NSError **)errorPtr 71 | { 72 | NSData* data; 73 | if (options & NSDataReadingMappedIfSafe) 74 | data = [self dataWithContentsOfMappedFile: path]; 75 | else 76 | data = [self dataWithContentsOfFile: path]; 77 | if (!data && errorPtr) 78 | *errorPtr = [NSError _last]; 79 | return data; 80 | } 81 | 82 | - (NSRange)rangeOfData:(NSData *)dataToFind 83 | options:(NSDataSearchOptions)options 84 | range:(NSRange)searchRange 85 | { 86 | NSParameterAssert(dataToFind); 87 | // TODO: Implement NSDataSearchBackwards 88 | NSAssert(!(options & NSDataSearchBackwards), @"NSDataSearchBackwards not implemented yet"); 89 | NSUInteger patternLen = dataToFind.length; 90 | if (patternLen == 0) 91 | return NSMakeRange(NSNotFound, 0); 92 | const void* patternBytes = dataToFind.bytes; 93 | NSUInteger myLen = self.length; 94 | const void* myBytes = self.bytes; 95 | const void* start = NULL; 96 | if (options & NSDataSearchAnchored) { 97 | if (patternLen <= myLen && memcmp(myBytes, patternBytes, patternLen) == 0) 98 | start = myBytes; 99 | } else { 100 | start = memmem(myBytes, myLen, patternBytes, patternLen); 101 | } 102 | if (!start) 103 | return NSMakeRange(NSNotFound, 0); 104 | return NSMakeRange(start - myBytes, patternLen); 105 | } 106 | 107 | @end 108 | 109 | 110 | @interface BlockOperation : NSOperation 111 | { 112 | void (^_blockToRun)(void); 113 | } 114 | @end 115 | 116 | @implementation BlockOperation 117 | 118 | - (id) initWithBlock: (void (^)(void))block { 119 | self = [super init]; 120 | if (self) 121 | _blockToRun = [block copy]; 122 | return self; 123 | } 124 | 125 | - (void)main { 126 | _blockToRun(); 127 | } 128 | 129 | - (void) dealloc { 130 | [_blockToRun release]; 131 | [super dealloc]; 132 | } 133 | 134 | @end 135 | 136 | 137 | @implementation NSOperationQueue (GNUstep) 138 | 139 | - (void)addOperationWithBlock:(void (^)(void))block { 140 | NSOperation* op = [[BlockOperation alloc] initWithBlock: block]; 141 | [self addOperation: op]; 142 | [op release]; 143 | } 144 | 145 | @end 146 | -------------------------------------------------------------------------------- /Source/TDInternal.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDInternal.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/8/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TD_Database+Attachments.h" 11 | #import "TD_DatabaseManager.h" 12 | #import "TD_View.h" 13 | #import "TD_Server.h" 14 | #import "TDRouter.h" 15 | #import "TDReplicator.h" 16 | #import "TDRemoteRequest.h" 17 | #import "TDBlobStore.h" 18 | @class TD_Attachment; 19 | 20 | 21 | @interface TD_Database () 22 | @property (readwrite, copy) NSString* name; // make it settable 23 | @property (readonly) FMDatabase* fmdb; 24 | @property (readonly) TDBlobStore* attachmentStore; 25 | - (BOOL) openFMDB; 26 | - (SInt64) getDocNumericID: (NSString*)docID; 27 | - (SequenceNumber) getSequenceOfDocument: (SInt64)docNumericID 28 | revision: (NSString*)revID 29 | onlyCurrent: (BOOL)onlyCurrent; 30 | - (TD_RevisionList*) getAllRevisionsOfDocumentID: (NSString*)docID 31 | numericID: (SInt64)docNumericID 32 | onlyCurrent: (BOOL)onlyCurrent; 33 | - (TDStatus) deleteViewNamed: (NSString*)name; 34 | - (NSMutableDictionary*) documentPropertiesFromJSON: (NSData*)json 35 | docID: (NSString*)docID 36 | revID: (NSString*)revID 37 | deleted: (BOOL)deleted 38 | sequence: (SequenceNumber)sequence 39 | options: (TDContentOptions)options; 40 | - (NSString*) winningRevIDOfDocNumericID: (SInt64)docNumericID 41 | isDeleted: (BOOL*)outIsDeleted; 42 | @end 43 | 44 | @interface TD_Database (Insertion_Internal) 45 | - (NSData*) encodeDocumentJSON: (TD_Revision*)rev; 46 | - (TDStatus) validateRevision: (TD_Revision*)newRev previousRevision: (TD_Revision*)oldRev; 47 | @end 48 | 49 | @interface TD_Database (Attachments_Internal) 50 | - (void) rememberAttachmentWritersForDigests: (NSDictionary*)writersByDigests; 51 | #if DEBUG 52 | - (id) attachmentWriterForAttachment: (NSDictionary*)attachment; 53 | #endif 54 | - (BOOL) storeBlob: (NSData*)blob creatingKey: (TDBlobKey*)outKey; 55 | - (TDStatus) insertAttachment: (TD_Attachment*)attachment 56 | forSequence: (SequenceNumber)sequence; 57 | - (TDStatus) copyAttachmentNamed: (NSString*)name 58 | fromSequence: (SequenceNumber)fromSequence 59 | toSequence: (SequenceNumber)toSequence; 60 | - (BOOL) inlineFollowingAttachmentsIn: (TD_Revision*)rev error: (NSError**)outError; 61 | @end 62 | 63 | @interface TD_Database (Replication_Internal) 64 | - (void) stopAndForgetReplicator: (TDReplicator*)repl; 65 | - (NSString*) lastSequenceWithCheckpointID: (NSString*)checkpointID; 66 | - (BOOL) setLastSequence: (NSString*)lastSequence withCheckpointID: (NSString*)checkpointID; 67 | + (NSString*) joinQuotedStrings: (NSArray*)strings; 68 | @end 69 | 70 | 71 | @interface TD_View () 72 | - (id) initWithDatabase: (TD_Database*)db name: (NSString*)name; 73 | @property (readonly) int viewID; 74 | - (NSArray*) dump; 75 | - (void) databaseClosing; 76 | @end 77 | 78 | 79 | @interface TD_Server () 80 | #if DEBUG 81 | + (TD_Server*) createEmptyAtPath: (NSString*)path; // for testing 82 | + (TD_Server*) createEmptyAtTemporaryPath: (NSString*)name; // for testing 83 | #endif 84 | @end 85 | 86 | 87 | @interface TD_DatabaseManager () 88 | @property (readonly, nonatomic) TDReplicatorManager* replicatorManager; 89 | #if DEBUG 90 | + (TD_DatabaseManager*) createEmptyAtPath: (NSString*)path; // for testing 91 | + (TD_DatabaseManager*) createEmptyAtTemporaryPath: (NSString*)name; // for testing 92 | #endif 93 | @end 94 | 95 | 96 | @interface TDRouter () 97 | - (id) initWithDatabaseManager: (TD_DatabaseManager*)dbManager request: (NSURLRequest*)request; 98 | @end 99 | 100 | 101 | @interface TDReplicator () 102 | // protected: 103 | @property (copy) NSString* lastSequence; 104 | @property (readwrite, nonatomic) NSUInteger changesProcessed, changesTotal; 105 | - (void) maybeCreateRemoteDB; 106 | - (void) beginReplicating; 107 | - (void) addToInbox: (TD_Revision*)rev; 108 | - (void) addRevsToInbox: (TD_RevisionList*)revs; 109 | - (void) processInbox: (TD_RevisionList*)inbox; // override this 110 | - (TDRemoteJSONRequest*) sendAsyncRequest: (NSString*)method 111 | path: (NSString*)relativePath 112 | body: (id)body 113 | onCompletion: (TDRemoteRequestCompletionBlock)onCompletion; 114 | - (void) addRemoteRequest: (TDRemoteRequest*)request; 115 | - (void) removeRemoteRequest: (TDRemoteRequest*)request; 116 | - (void) asyncTaskStarted; 117 | - (void) asyncTasksFinished: (NSUInteger)numTasks; 118 | - (void) stopped; 119 | - (void) databaseClosing; 120 | - (void) revisionFailed; // subclasses call this if a transfer fails 121 | - (void) retry; 122 | 123 | - (void) reachabilityChanged: (TDReachability*)host; 124 | - (BOOL) goOffline; 125 | - (BOOL) goOnline; 126 | #if DEBUG 127 | @property (readonly) BOOL savingCheckpoint; 128 | #endif 129 | @end 130 | -------------------------------------------------------------------------------- /Source/TDJSON.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDJSON.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 2/27/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | // Conditional compilation for JSONKit and/or NSJSONSerialization. 13 | // If the app supports OS versions prior to NSJSONSerialization, we'll use JSONKit. 14 | #ifdef __IPHONE_OS_VERSION_MIN_REQUIRED 15 | #define USE_NSJSON (__IPHONE_OS_VERSION_MIN_REQUIRED >= 50000) 16 | #elif defined(TARGET_OS_MAC) 17 | #define USE_NSJSON (MAC_OS_X_VERSION_MIN_REQUIRED >= 1070) 18 | #elif defined(GNUSTEP) 19 | #define USE_NSJSON 1 20 | #else 21 | #define USE_NSJSON 0 22 | #endif 23 | 24 | 25 | /** Identical to the corresponding NSJSON option flags. */ 26 | enum { 27 | TDJSONReadingMutableContainers = (1UL << 0), 28 | TDJSONReadingMutableLeaves = (1UL << 1), 29 | TDJSONReadingAllowFragments = (1UL << 2) 30 | }; 31 | typedef NSUInteger TDJSONReadingOptions; 32 | 33 | /** Identical to the corresponding NSJSON option flags. */ 34 | enum { 35 | TDJSONWritingPrettyPrinted = (1UL << 0), 36 | 37 | TDJSONWritingAllowFragments = (1UL << 23) // This one I made up 38 | }; 39 | typedef NSUInteger TDJSONWritingOptions; 40 | 41 | 42 | #if USE_NSJSON 43 | 44 | @interface TDJSON : NSJSONSerialization 45 | @end 46 | 47 | #else 48 | 49 | @interface TDJSON : NSObject 50 | + (NSData *)dataWithJSONObject:(id)obj 51 | options:(TDJSONWritingOptions)opt 52 | error:(NSError **)error; 53 | + (id)JSONObjectWithData:(NSData *)data 54 | options:(TDJSONReadingOptions)opt 55 | error:(NSError **)error; 56 | @end 57 | 58 | #endif // USE_NSJSON 59 | 60 | 61 | @interface TDJSON (Extensions) 62 | /** Same as -dataWithJSONObject... but returns an NSString. */ 63 | + (NSString*) stringWithJSONObject:(id)obj 64 | options:(TDJSONWritingOptions)opt 65 | error:(NSError **)error; 66 | 67 | /** Given valid JSON data representing a dictionary, inserts the contents of the given NSDictionary into it and returns the resulting JSON data. 68 | This does not parse or regenerate the JSON, so it's quite fast. 69 | But it will generate invalid JSON if the input JSON begins or ends with whitespace, or if the dictionary contains any keys that are already in the original JSON. */ 70 | + (NSData*) appendDictionary: (NSDictionary*)dict 71 | toJSONDictionaryData: (NSData*)json; 72 | @end 73 | 74 | 75 | /** Wrapper for an NSArray of JSON data, that avoids having to parse the data if it's not used. 76 | NSData objects in the array will be parsed into native objects before being returned to the caller from -objectAtIndex. */ 77 | @interface TDLazyArrayOfJSON : NSArray 78 | { 79 | NSMutableArray* _array; 80 | } 81 | - (id) initWithArray: (NSMutableArray*)array; 82 | @end 83 | -------------------------------------------------------------------------------- /Source/TDJSON.m: -------------------------------------------------------------------------------- 1 | // 2 | // TDJSON.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 2/27/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "TDJSON.h" 17 | 18 | #if !USE_NSJSON 19 | #import "JSONKit.h" 20 | #endif 21 | 22 | 23 | @implementation TDJSON 24 | 25 | 26 | #if USE_NSJSON 27 | 28 | 29 | + (NSData *)dataWithJSONObject:(id)object 30 | options:(NSJSONWritingOptions)options 31 | error:(NSError **)error 32 | { 33 | if ((options & TDJSONWritingAllowFragments) 34 | && ![object isKindOfClass: [NSDictionary class]] 35 | && ![object isKindOfClass: [NSArray class]]) { 36 | // NSJSONSerialization won't write fragments, so if I get one wrap it in an array first: 37 | object = [[NSArray alloc] initWithObjects: &object count: 1]; 38 | NSData* json = [super dataWithJSONObject: object 39 | options: (options & ~TDJSONWritingAllowFragments) 40 | error: NULL]; 41 | return [json subdataWithRange: NSMakeRange(1, json.length - 2)]; 42 | } else { 43 | return [super dataWithJSONObject: object options: options error: error]; 44 | } 45 | } 46 | 47 | 48 | #else // not USE_NSJSON 49 | 50 | + (NSData *)dataWithJSONObject:(id)obj 51 | options:(TDJSONWritingOptions)opt 52 | error:(NSError **)error 53 | { 54 | Assert(obj); 55 | return [obj JSONDataWithOptions: 0 error: error]; 56 | } 57 | 58 | 59 | + (id)JSONObjectWithData:(NSData *)data 60 | options:(TDJSONReadingOptions)opt 61 | error:(NSError **)error 62 | { 63 | Assert(data); 64 | if (opt & (TDJSONReadingMutableContainers | TDJSONReadingMutableLeaves)) 65 | return [data mutableObjectFromJSONDataWithParseOptions: 0 error: error]; 66 | else 67 | return [data objectFromJSONDataWithParseOptions: 0 error: error]; 68 | } 69 | 70 | 71 | #endif // USE_NSJSON 72 | 73 | 74 | + (NSString*) stringWithJSONObject:(id)obj 75 | options:(TDJSONWritingOptions)opt 76 | error:(NSError **)error 77 | { 78 | return [[self dataWithJSONObject: obj options: opt error: error] my_UTF8ToString]; 79 | } 80 | 81 | 82 | + (NSData*) appendDictionary: (NSDictionary*)dict 83 | toJSONDictionaryData: (NSData*)json 84 | { 85 | if (!dict.count) 86 | return json; 87 | NSData* extraJson = [self dataWithJSONObject: dict options: 0 error: NULL]; 88 | if (!extraJson) 89 | return nil; 90 | size_t jsonLength = json.length; 91 | size_t extraLength = extraJson.length; 92 | CAssert(jsonLength >= 2); 93 | CAssertEq(*(const char*)json.bytes, '{'); 94 | if (jsonLength == 2) // Original JSON was empty 95 | return extraJson; 96 | NSMutableData* newJson = [NSMutableData dataWithLength: jsonLength + extraLength - 1]; 97 | if (!newJson) 98 | return nil; 99 | uint8_t* dst = newJson.mutableBytes; 100 | memcpy(dst, json.bytes, jsonLength - 1); // Copy json w/o trailing '}' 101 | dst += jsonLength - 1; 102 | *dst++ = ','; // Add a ',' 103 | memcpy(dst, (const uint8_t*)extraJson.bytes + 1, extraLength - 1); // Add "extra" after '{' 104 | return newJson; 105 | } 106 | 107 | 108 | @end 109 | 110 | 111 | @implementation TDLazyArrayOfJSON 112 | 113 | - (id) initWithArray: (NSMutableArray*)array { 114 | self = [super init]; 115 | if (self) { 116 | _array = array; 117 | } 118 | return self; 119 | } 120 | 121 | - (NSUInteger)count { 122 | return _array.count; 123 | } 124 | 125 | - (id)objectAtIndex:(NSUInteger)index { 126 | id obj = [_array objectAtIndex: index]; 127 | if ([obj isKindOfClass: [NSData class]]) { 128 | obj = [TDJSON JSONObjectWithData: obj options: TDJSONReadingAllowFragments 129 | error: nil]; 130 | [_array replaceObjectAtIndex: index withObject: obj]; 131 | } 132 | return obj; 133 | } 134 | 135 | @end 136 | -------------------------------------------------------------------------------- /Source/TDJSViewCompiler.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDJSViewCompiler.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 1/4/13. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 8 | // except in compliance with the License. You may obtain a copy of the License at 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // Unless required by applicable law or agreed to in writing, software distributed under the 11 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 12 | // either express or implied. See the License for the specific language governing permissions 13 | // and limitations under the License. 14 | // 15 | 16 | #import 17 | #import 18 | 19 | 20 | /** A view compiler for TouchDB that compiles and runs traditional JavaScript map/reduce functions. 21 | Requires the JavaScriptCore framework; this is a public system framework on Mac OS but private 22 | on iOS; so on the latter platform you'll need to link your app with your own copy of 23 | JavaScriptCore. See . */ 24 | @interface TDJSViewCompiler : NSObject 25 | @end 26 | -------------------------------------------------------------------------------- /Source/TDJSViewCompiler_Test.m: -------------------------------------------------------------------------------- 1 | // 2 | // TDJSViewCompiler_Test.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 1/4/13. 6 | // 7 | // 8 | 9 | #import "TDJSViewCompiler.h" 10 | #import "Test.h" 11 | 12 | 13 | TestCase(JSMapFunction) { 14 | TDJSViewCompiler* c = [[TDJSViewCompiler alloc] init]; 15 | TDMapBlock mapBlock = [c compileMapFunction: @"function(doc){emit(doc.key, doc);}" 16 | language: @"javascript"]; 17 | CAssert(mapBlock); 18 | 19 | NSDictionary* doc = @{@"_id": @"doc1", @"_rev": @"1-xyzzy", @"key": @"value"}; 20 | 21 | NSMutableArray* emitted = [NSMutableArray array]; 22 | TDMapEmitBlock emit = ^(id key, id value) { 23 | Log(@"Emitted: %@ -> %@", key, value); 24 | [emitted addObject: key]; 25 | [emitted addObject: value]; 26 | }; 27 | mapBlock(doc, emit); 28 | 29 | CAssertEqual(emitted, (@[@"value", doc])); 30 | } 31 | 32 | 33 | TestCase(JSReduceFunction) { 34 | TDJSViewCompiler* c = [[TDJSViewCompiler alloc] init]; 35 | TDReduceBlock reduceBlock = [c compileReduceFunction: @"function(k,v,r){return [k,v,r];}" 36 | language: @"javascript"]; 37 | CAssert(reduceBlock); 38 | 39 | NSArray* keys = @[@"master", @"schlage", @"medeco"]; 40 | NSArray* values = @[@1, @2, @3]; 41 | id result = reduceBlock(keys, values, false); 42 | 43 | CAssertEqual(result, (@[keys, values, @NO])); 44 | } 45 | 46 | 47 | TestCase(TDJSViewCompiler) { 48 | RequireTestCase(JSMapFunction); 49 | RequireTestCase(JSReduceFunction); 50 | } 51 | -------------------------------------------------------------------------------- /Source/TDMisc.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDMisc.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 1/13/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | extern NSString* const TDHTTPErrorDomain; 13 | 14 | NSString* TDCreateUUID( void ); 15 | 16 | NSData* TDSHA1Digest( NSData* input ); 17 | NSData* TDSHA256Digest( NSData* input ); 18 | 19 | NSString* TDHexSHA1Digest( NSData* input ); 20 | 21 | NSData* TDHMACSHA1(NSData* key, NSData* data); 22 | NSData* TDHMACSHA256(NSData* key, NSData* data); 23 | 24 | /** Generates a hex dump of a sequence of bytes. 25 | The result is lowercase. This is important for CouchDB compatibility. */ 26 | NSString* TDHexFromBytes( const void* bytes, size_t length); 27 | 28 | NSComparisonResult TDSequenceCompare( SequenceNumber a, SequenceNumber b); 29 | 30 | /** Escapes a document or revision ID for use in a URL. 31 | This does the usual %-escaping, but makes sure that '/' is escaped in case the ID appears in the path portion of the URL, and that '&' is escaped in case the ID appears in a query value. */ 32 | NSString* TDEscapeID( NSString* param ); 33 | 34 | /** Escapes a string to be used as the value of a query parameter in a URL. 35 | This does the usual %-escaping, but makes sure that '&' is also escaped. */ 36 | NSString* TDEscapeURLParam( NSString* param ); 37 | 38 | /** Wraps a string in double-quotes and prepends backslashes to any existing double-quote or backslash characters in it. */ 39 | NSString* TDQuoteString( NSString* param ); 40 | 41 | /** Undoes effect of TDQuoteString, i.e. removes backslash escapes and any surrounding double-quotes. 42 | If the string has no surrounding double-quotes it will be returned as-is. */ 43 | NSString* TDUnquoteString( NSString* param ); 44 | 45 | /** Returns YES if this error appears to be due to the computer being offline or the remote host being unreachable. */ 46 | BOOL TDIsOfflineError( NSError* error ); 47 | 48 | /** Returns YES if this is a network/HTTP error that is likely to be transient. 49 | Examples are timeout, connection lost, 502 Bad Gateway... */ 50 | BOOL TDMayBeTransientError( NSError* error ); 51 | 52 | /** Returns YES if this error appears to be due to a creating a file/dir that already exists. */ 53 | BOOL TDIsFileExistsError( NSError* error ); 54 | 55 | /** Returns the input URL without the query string or fragment identifier, just ending with the path. */ 56 | NSURL* TDURLWithoutQuery( NSURL* url ); 57 | 58 | /** Appends path components to a URL. These will NOT be URL-escaped, so you can include queries. */ 59 | NSURL* TDAppendToURL(NSURL* baseURL, NSString* toAppend); 60 | -------------------------------------------------------------------------------- /Source/TDMultiStreamWriter.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDMultiStreamWriter.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 2/3/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** A stream aggregator that reads from a concatenated sequence of other inputs. 12 | Use this to combine multiple input streams (and data blobs) together into one. 13 | This is useful when uploading multipart MIME bodies. */ 14 | @interface TDMultiStreamWriter : NSObject 15 | { 16 | @private 17 | NSMutableArray* _inputs; 18 | NSUInteger _nextInputIndex; 19 | NSInputStream* _currentInput; 20 | uint8_t* _buffer; 21 | NSUInteger _bufferSize, _bufferLength; 22 | NSOutputStream* _output; 23 | NSInputStream* _input; 24 | NSError* _error; 25 | @protected 26 | SInt64 _length; 27 | SInt64 _totalBytesWritten; 28 | } 29 | 30 | - (void) addStream: (NSInputStream*)stream length: (UInt64)length; 31 | - (void) addStream: (NSInputStream*)stream; 32 | - (void) addData: (NSData*)data; 33 | - (BOOL) addFileURL: (NSURL*)fileURL; 34 | - (BOOL) addFile: (NSString*)path; 35 | 36 | /** Total length of the stream. 37 | This is just computed by adding the values passed to -addStream:length:, and the lengths of the NSData objects and files added. 38 | If -addStream: has been called (the version without length:) the length is unknown and will be returned as -1. 39 | (Many clients won't care about the length, but TDMultipartUploader does.) */ 40 | @property (readonly) SInt64 length; 41 | 42 | /** Returns an input stream; reading from this will return the contents of all added streams in sequence. 43 | This stream can be set as the HTTPBodyStream of an NSURLRequest. 44 | It is the caller's responsibility to close the returned stream. */ 45 | - (NSInputStream*) openForInputStream; 46 | 47 | /** Associates an output stream; the data from all of the added streams will be written to the output, asynchronously. */ 48 | - (void) openForOutputTo: (NSOutputStream*)output; 49 | 50 | - (void) close; 51 | 52 | @property (readonly) BOOL isOpen; 53 | 54 | @property (readonly, strong) NSError* error; 55 | 56 | /** Convenience method that opens an output stream, collects all the data, and returns it. */ 57 | - (NSData*) allOutput; 58 | 59 | // protected: 60 | - (void) addInput: (id)input length: (UInt64)length; 61 | - (void) opened; 62 | @end 63 | -------------------------------------------------------------------------------- /Source/TDMultipartDocumentReader.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDMultipartDocumentReader.h 3 | // 4 | // 5 | // Created by Jens Alfke on 3/29/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "TDMultipartReader.h" 10 | #import 11 | @class TD_Database, TD_Revision, TDBlobStoreWriter, TDMultipartDocumentReader; 12 | 13 | 14 | typedef void(^TDMultipartDocumentReaderCompletionBlock)(TDMultipartDocumentReader*); 15 | 16 | 17 | @interface TDMultipartDocumentReader : NSObject 18 | { 19 | @private 20 | TD_Database* _database; 21 | TDStatus _status; 22 | TDMultipartReader* _multipartReader; 23 | NSMutableData* _jsonBuffer; 24 | TDBlobStoreWriter* _curAttachment; 25 | NSMutableDictionary* _attachmentsByName; // maps attachment name --> TDBlobStoreWriter 26 | NSMutableDictionary* _attachmentsByDigest; // maps attachment MD5 --> TDBlobStoreWriter 27 | NSMutableDictionary* _document; 28 | TDMultipartDocumentReaderCompletionBlock _completionBlock; 29 | } 30 | 31 | // synchronous: 32 | + (NSDictionary*) readData: (NSData*)data 33 | ofType: (NSString*)contentType 34 | toDatabase: (TD_Database*)database 35 | status: (TDStatus*)outStatus; 36 | 37 | // asynchronous: 38 | + (TDStatus) readStream: (NSInputStream*)stream 39 | ofType: (NSString*)contentType 40 | toDatabase: (TD_Database*)database 41 | then: (TDMultipartDocumentReaderCompletionBlock)completionBlock; 42 | 43 | - (id) initWithDatabase: (TD_Database*)database; 44 | 45 | @property (readonly, nonatomic) TDStatus status; 46 | @property (readonly, nonatomic) NSDictionary* document; 47 | @property (readonly, nonatomic) NSUInteger attachmentCount; 48 | 49 | - (BOOL) setContentType: (NSString*)contentType; 50 | 51 | - (BOOL) appendData: (NSData*)data; 52 | 53 | - (TDStatus) readStream: (NSInputStream*)stream 54 | ofType: (NSString*)contentType 55 | then: (TDMultipartDocumentReaderCompletionBlock)completionBlock; 56 | 57 | - (BOOL) finish; 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /Source/TDMultipartDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDMultipartDownloader.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 1/31/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "TDRemoteRequest.h" 10 | @class TDMultipartDocumentReader, TD_Database; 11 | 12 | 13 | /** Downloads a remote CouchDB document in multipart format. 14 | Attachments are added to the database, but the document body isn't. */ 15 | @interface TDMultipartDownloader : TDRemoteRequest 16 | { 17 | @private 18 | TD_Database* _db; 19 | TDMultipartDocumentReader* _reader; 20 | } 21 | 22 | - (id) initWithURL: (NSURL*)url 23 | database: (TD_Database*)database 24 | requestHeaders: (NSDictionary *) requestHeaders 25 | onCompletion: (TDRemoteRequestCompletionBlock)onCompletion; 26 | 27 | @property (readonly) NSDictionary* document; 28 | 29 | @end -------------------------------------------------------------------------------- /Source/TDMultipartReader.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDMultipartReader.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 1/30/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @protocol TDMultipartReaderDelegate; 11 | 12 | 13 | /** Streaming MIME multipart reader. */ 14 | @interface TDMultipartReader : NSObject 15 | { 16 | @private 17 | __weak id _delegate; 18 | NSData* _boundary; 19 | NSMutableData* _buffer; 20 | NSMutableDictionary* _headers; 21 | int _state; 22 | NSString* _error; 23 | } 24 | 25 | /** Initializes the reader. 26 | Returns nil if the content type isn't a valid multipart type, or doesn't contain a "boundary" parameter. 27 | @param contentType the entire MIME Content-Type string, with parameters. 28 | @param delegate The delegate object that will be called as parts are parsed. */ 29 | - (id) initWithContentType: (NSString*)contentType 30 | delegate: (id)delegate; 31 | 32 | /** Call this when more data is available. */ 33 | - (void) appendData: (NSData*)data; 34 | 35 | /** Has the reader successfully finished reading the entire multipart body? */ 36 | @property (readonly) BOOL finished; 37 | 38 | /** Was there a fatal parse error? */ 39 | @property (readonly) NSString* error; 40 | 41 | /** The MIME headers of the part currently being parsed. 42 | You can call this from your -appendToPart and/or -finishedPart overrides. */ 43 | @property (readonly) NSDictionary* headers; 44 | 45 | @end 46 | 47 | 48 | 49 | @protocol TDMultipartReaderDelegate 50 | 51 | /** This method is called when a part's headers have been parsed, before its data is parsed. */ 52 | - (void) startedPart: (NSDictionary*)headers; 53 | 54 | /** This method is called to append data to a part's body. */ 55 | - (void) appendToPart: (NSData*)data; 56 | 57 | /** This method is called when a part is complete. */ 58 | - (void) finishedPart; 59 | 60 | @end -------------------------------------------------------------------------------- /Source/TDMultipartUploader.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDMultipartUploader.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 2/5/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "TDRemoteRequest.h" 10 | #import "TDMultipartWriter.h" 11 | 12 | 13 | @interface TDMultipartUploader : TDRemoteRequest 14 | { 15 | @private 16 | TDMultipartWriter* _multipartWriter; 17 | } 18 | 19 | - (id) initWithURL: (NSURL *)url 20 | streamer: (TDMultipartWriter*)streamer 21 | requestHeaders: (NSDictionary *) requestHeaders 22 | onCompletion: (TDRemoteRequestCompletionBlock)onCompletion; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Source/TDMultipartUploader.m: -------------------------------------------------------------------------------- 1 | // 2 | // TDMultipartUploader.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 2/5/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "TDMultipartUploader.h" 17 | 18 | 19 | @implementation TDMultipartUploader 20 | 21 | - (id) initWithURL: (NSURL *)url 22 | streamer: (TDMultipartWriter*)writer 23 | requestHeaders: (NSDictionary *) requestHeaders 24 | onCompletion: (TDRemoteRequestCompletionBlock)onCompletion 25 | { 26 | Assert(writer); 27 | self = [super initWithMethod: @"PUT" 28 | URL: url 29 | body: writer 30 | requestHeaders: requestHeaders 31 | onCompletion: onCompletion]; 32 | if (self) { 33 | _multipartWriter = writer; 34 | // It's important to set a Content-Length header -- without this, CFNetwork won't know the 35 | // length of the body stream, so it has to send the body chunked. But unfortunately CouchDB 36 | // doesn't correctly parse chunked multipart bodies: 37 | // https://issues.apache.org/jira/browse/COUCHDB-1403 38 | SInt64 length = _multipartWriter.length; 39 | Assert(length >= 0, @"HTTP multipart upload body has indeterminate length"); 40 | [_request setValue: $sprintf(@"%lld", length) forHTTPHeaderField: @"Content-Length"]; 41 | } 42 | return self; 43 | } 44 | 45 | 46 | 47 | 48 | - (void) start { 49 | [_multipartWriter openForURLRequest: _request]; 50 | [super start]; 51 | } 52 | 53 | 54 | - (NSInputStream *)connection:(NSURLConnection *)connection 55 | needNewBodyStream:(NSURLRequest *)request 56 | { 57 | LogTo(TDRemoteRequest, @"%@: Needs new body stream, resetting writer...", self); 58 | [_multipartWriter close]; 59 | return [_multipartWriter openForInputStream]; 60 | } 61 | 62 | 63 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { 64 | if ($equal(error.domain, NSURLErrorDomain) && error.code == NSURLErrorRequestBodyStreamExhausted) { 65 | // The connection is complaining that the body input stream closed prematurely. 66 | // Check whether this is because the multipart writer got an error on _its_ input stream: 67 | NSError* writerError = _multipartWriter.error; 68 | if (writerError) 69 | error = writerError; 70 | } 71 | [super connection: connection didFailWithError: error]; 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /Source/TDMultipartWriter.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDMultipartWriter.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 2/2/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "TDMultiStreamWriter.h" 10 | 11 | 12 | /** A streaming MIME multipart body generator, suitable for use with an NSURLRequest. 13 | Reads from a sequence of input streams (or data blobs) and inserts boundary strings between them. Can keep track of the total MIME body length so you can set it as the request's Content-Length, for servers that have trouble with chunked encodings. */ 14 | @interface TDMultipartWriter : TDMultiStreamWriter 15 | { 16 | @private 17 | NSString* _boundary; 18 | NSString* _contentType; 19 | NSData* _finalBoundary; 20 | NSDictionary* _nextPartsHeaders; 21 | } 22 | 23 | /** Initializes an instance. 24 | @param type The base content type, e.g. "application/json". 25 | @param boundary The MIME part boundary to use, or nil to automatically generate one (a long random string). If you specify a boundary, you have to ensure that it appears nowhere in any of the input data! */ 26 | - (id) initWithContentType: (NSString*)type boundary: (NSString*)boundary; 27 | 28 | /** The full MIME Content-Type header value, including the boundary parameter. */ 29 | @property (readonly) NSString* contentType; 30 | 31 | /** The boundary string. */ 32 | @property (readonly) NSString* boundary; 33 | 34 | /** Call this before adding a new stream/data/file to specify the MIME headers that should go with it. */ 35 | - (void) setNextPartsHeaders: (NSDictionary*)headers; 36 | 37 | /** Attaches the writer to the URL request. 38 | This calls -openForInputStream and sets the resulting input stream as the HTTPBodyStream of the request. It also sets the Content-Type header of the request. */ 39 | - (void) openForURLRequest: (NSMutableURLRequest*)request; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Source/TDMultipartWriter.m: -------------------------------------------------------------------------------- 1 | // 2 | // TDMultipartWriter.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 2/2/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "TDMultipartWriter.h" 17 | #import "TDMisc.h" 18 | #import "CollectionUtils.h" 19 | #import "Test.h" 20 | 21 | 22 | @implementation TDMultipartWriter 23 | 24 | 25 | - (id) initWithContentType: (NSString*)type boundary: (NSString*)boundary { 26 | self = [super init]; 27 | if (self) { 28 | _contentType = [type copy]; 29 | _boundary = [(boundary ?: TDCreateUUID()) copy]; 30 | // Account for the final boundary to be written by -opened. Add its length now, because the 31 | // client is probably going to ask for my .length *before* it calls -open. 32 | NSString* finalBoundaryStr = $sprintf(@"\r\n--%@--", _boundary); 33 | _finalBoundary = [finalBoundaryStr dataUsingEncoding: NSUTF8StringEncoding]; 34 | _length += _finalBoundary.length; 35 | } 36 | return self; 37 | } 38 | 39 | 40 | 41 | 42 | @synthesize boundary=_boundary; 43 | 44 | 45 | - (NSString*) contentType { 46 | return $sprintf(@"%@; boundary=\"%@\"", _contentType, _boundary); 47 | } 48 | 49 | 50 | - (void) setNextPartsHeaders: (NSDictionary*)headers { 51 | _nextPartsHeaders = headers; 52 | } 53 | 54 | 55 | - (void) addInput: (id)part length:(UInt64)length { 56 | NSMutableString* headers = [NSMutableString stringWithFormat: @"\r\n--%@\r\n", _boundary]; 57 | [headers appendFormat: @"Content-Length: %llu\r\n", length]; 58 | for (NSString* name in _nextPartsHeaders) { 59 | // Strip any CR or LF in the header value. This isn't real quoting, just enough to ensure 60 | // a spoofer can't add bogus headers by putting CRLF into a header value! 61 | NSMutableString* value = [_nextPartsHeaders[name] mutableCopy]; 62 | [value replaceOccurrencesOfString: @"\r" withString: @"" 63 | options: 0 range: NSMakeRange(0, value.length)]; 64 | [value replaceOccurrencesOfString: @"\n" withString: @"" 65 | options: 0 range: NSMakeRange(0, value.length)]; 66 | [headers appendFormat: @"%@: %@\r\n", name, value]; 67 | } 68 | [headers appendString: @"\r\n"]; 69 | NSData* separator = [headers dataUsingEncoding: NSUTF8StringEncoding]; 70 | [self setNextPartsHeaders: nil]; 71 | 72 | [super addInput: separator length: separator.length]; 73 | [super addInput: part length: length]; 74 | } 75 | 76 | 77 | - (void) opened { 78 | if (_finalBoundary) { 79 | // Append the final boundary: 80 | [super addInput: _finalBoundary length: 0]; 81 | // _length was already adjusted for this in -init 82 | _finalBoundary = nil; 83 | } 84 | [super opened]; 85 | } 86 | 87 | 88 | - (void) openForURLRequest: (NSMutableURLRequest*)request; 89 | { 90 | request.HTTPBodyStream = [self openForInputStream]; 91 | [request setValue: self.contentType forHTTPHeaderField: @"Content-Type"]; 92 | } 93 | 94 | 95 | @end 96 | 97 | 98 | 99 | 100 | 101 | TestCase(TDMultipartWriter) { 102 | NSString* expectedOutput = @"\r\n--BOUNDARY\r\nContent-Length: 16\r\n\r\n\r\n--BOUNDARY\r\nContent-Length: 10\r\nContent-Type: something\r\n\r\n<2nd part>\r\n--BOUNDARY--"; 103 | RequireTestCase(TDMultiStreamWriter); 104 | for (unsigned bufSize = 1; bufSize < expectedOutput.length+1; ++bufSize) { 105 | TDMultipartWriter* mp = [[TDMultipartWriter alloc] initWithContentType: @"foo/bar" 106 | boundary: @"BOUNDARY"]; 107 | CAssertEqual(mp.contentType, @"foo/bar; boundary=\"BOUNDARY\""); 108 | CAssertEqual(mp.boundary, @"BOUNDARY"); 109 | [mp addData: [@"" dataUsingEncoding: NSUTF8StringEncoding]]; 110 | [mp setNextPartsHeaders: $dict({@"Content-Type", @"something"})]; 111 | [mp addData: [@"<2nd part>" dataUsingEncoding: NSUTF8StringEncoding]]; 112 | CAssertEq((NSUInteger)mp.length, expectedOutput.length); 113 | 114 | NSData* output = [mp allOutput]; 115 | CAssertEqual(output.my_UTF8ToString, expectedOutput); 116 | [mp close]; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Source/TDOAuth1Authorizer.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDOAuth1Authorizer.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 5/21/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "TDAuthorizer.h" 10 | @class OAConsumer, OAToken; 11 | @protocol OASignatureProviding; 12 | 13 | 14 | /** Implementation of TAuthorizer for OAuth 1 requests. */ 15 | @interface TDOAuth1Authorizer : NSObject 16 | { 17 | OAConsumer* _consumer; 18 | OAToken* _token; 19 | id _signatureProvider; 20 | } 21 | 22 | - (id) initWithConsumerKey: (NSString*)consumerKey 23 | consumerSecret: (NSString*)consumerSecret 24 | token: (NSString*)token 25 | tokenSecret: (NSString*)tokenSecret 26 | signatureMethod: (NSString*)signatureMethod; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Source/TDOAuth1Authorizer.m: -------------------------------------------------------------------------------- 1 | // 2 | // TDOAuth1Authorizer.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 5/21/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "TDOAuth1Authorizer.h" 17 | #import "TDMisc.h" 18 | #import "TDBase64.h" 19 | #import "OAMutableURLRequest.h" 20 | #import "OAConsumer.h" 21 | #import "OAToken.h" 22 | #import "OAPlaintextSignatureProvider.h" 23 | 24 | 25 | @implementation TDOAuth1Authorizer 26 | 27 | 28 | - (id) initWithConsumerKey: (NSString*)consumerKey 29 | consumerSecret: (NSString*)consumerSecret 30 | token: (NSString*)token 31 | tokenSecret: (NSString*)tokenSecret 32 | signatureMethod: (NSString*)signatureMethod 33 | { 34 | self = [super init]; 35 | if (self) { 36 | if (!consumerKey || !consumerSecret || !token || !tokenSecret) { 37 | return nil; 38 | } 39 | _consumer = [[OAConsumer alloc] initWithKey: consumerKey secret: consumerSecret]; 40 | _token = [[OAToken alloc] initWithKey: token secret: tokenSecret]; 41 | if ([signatureMethod isEqualToString: @"HMAC-SHA1"] || signatureMethod == nil) 42 | _signatureProvider = [[OAHMAC_SHA1SignatureProvider alloc] init]; 43 | else if ([signatureMethod isEqualToString: @"PLAINTEXT"]) 44 | _signatureProvider = [[OAPlaintextSignatureProvider alloc] init]; 45 | else { 46 | Warn(@"Unsupported signature method '%@'", signatureMethod); 47 | return nil; 48 | } 49 | } 50 | return self; 51 | } 52 | 53 | 54 | 55 | 56 | // TDAuthorizer API: 57 | - (NSString*) authorizeURLRequest: (NSMutableURLRequest*)request 58 | forRealm: (NSString*)realm 59 | { 60 | OAMutableURLRequest* oarq = [[OAMutableURLRequest alloc] initWithURL: request.URL 61 | consumer: _consumer 62 | token: _token 63 | realm: realm 64 | signatureProvider: _signatureProvider]; 65 | oarq.HTTPMethod = request.HTTPMethod; 66 | oarq.HTTPBody = request.HTTPBody; 67 | [oarq prepare]; 68 | NSString* authorization = [oarq valueForHTTPHeaderField: @"Authorization"]; 69 | return authorization; 70 | } 71 | 72 | - (NSString*) authorizeHTTPMessage: (CFHTTPMessageRef)message 73 | forRealm: (NSString*)realm 74 | { 75 | NSURL* url = CFBridgingRelease(CFHTTPMessageCopyRequestURL(message)); 76 | OAMutableURLRequest* oarq = [[OAMutableURLRequest alloc] initWithURL: url 77 | consumer: _consumer 78 | token: _token 79 | realm: realm 80 | signatureProvider: _signatureProvider]; 81 | oarq.HTTPMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(message)); 82 | oarq.HTTPBody = CFBridgingRelease(CFHTTPMessageCopyBody(message)); 83 | [oarq prepare]; 84 | return [oarq valueForHTTPHeaderField: @"Authorization"]; 85 | } 86 | 87 | @end 88 | 89 | 90 | // Include our own implementation of this instead of the OAuth library's, because it drags in 91 | // a bunch of other stuff including its own SHA1 and Base64 implementations. 92 | @implementation OAHMAC_SHA1SignatureProvider 93 | 94 | - (NSString *)name { 95 | return @"HMAC-SHA1"; 96 | } 97 | 98 | - (NSString *)signClearText:(NSString *)text withSecret:(NSString *)secret { 99 | return [TDBase64 encode: TDHMACSHA1([secret dataUsingEncoding: NSUTF8StringEncoding], 100 | [text dataUsingEncoding: NSUTF8StringEncoding])]; 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /Source/TDPersonaAuthorizer.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDPersonaAuthorizer.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 1/9/13. 6 | // 7 | // 8 | 9 | #import "TDAuthorizer.h" 10 | 11 | /** Authorizer for the Persona decentralized-identity system. See http://persona.org */ 12 | @interface TDPersonaAuthorizer: NSObject 13 | 14 | + (NSURL*) originForSite: (NSURL*)url; 15 | 16 | + (NSString*) registerAssertion: (NSString*)assertion; 17 | 18 | - (id) initWithEmailAddress: (NSString*)emailAddress; 19 | 20 | @property (readonly) NSString* emailAddress; 21 | 22 | - (NSString*) assertionForSite: (NSURL*)site; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Source/TDPuller.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDPuller.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/2/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "TDReplicator.h" 10 | #import 11 | @class TDChangeTracker, TDSequenceMap; 12 | 13 | 14 | /** Replicator that pulls from a remote CouchDB. */ 15 | @interface TDPuller : TDReplicator 16 | { 17 | @private 18 | TDChangeTracker* _changeTracker; 19 | BOOL _caughtUp; // Have I received all current _changes entries? 20 | TDSequenceMap* _pendingSequences; // Received but not yet copied into local DB 21 | NSMutableArray* _revsToPull; // Queue of TDPulledRevisions to download 22 | NSMutableArray* _deletedRevsToPull; // Separate lower-priority of deleted TDPulledRevisions 23 | NSMutableArray* _bulkRevsToPull; // TDPulledRevisions that can be fetched in bulk 24 | NSUInteger _httpConnectionCount; // Number of active NSURLConnections 25 | TDBatcher* _downloadsToInsert; // Queue of TDPulledRevisions, with bodies, to insert in DB 26 | } 27 | 28 | @end 29 | 30 | 31 | 32 | /** A revision received from a remote server during a pull. Tracks the opaque remote sequence ID. */ 33 | @interface TDPulledRevision : TD_Revision 34 | { 35 | @private 36 | id _remoteSequenceID; 37 | bool _conflicted; 38 | } 39 | 40 | @property (copy) id remoteSequenceID; 41 | @property bool conflicted; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Source/TDPusher.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDPusher.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/5/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "TDPuller.h" 10 | #import 11 | 12 | 13 | /** Replicator that pushes to a remote CouchDB. */ 14 | @interface TDPusher : TDReplicator 15 | { 16 | BOOL _createTarget; 17 | BOOL _creatingTarget; 18 | BOOL _observing; 19 | BOOL _uploading; 20 | NSMutableArray* _uploaderQueue; 21 | BOOL _dontSendMultipart; 22 | NSMutableIndexSet* _pendingSequences; 23 | SequenceNumber _maxPendingSequence; 24 | } 25 | 26 | @property BOOL createTarget; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Source/TDReachability.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDReachability.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 2/13/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #ifdef GNUSTEP 12 | typedef uint32_t SCNetworkReachabilityFlags; 13 | typedef void* SCNetworkReachabilityRef; 14 | typedef void* CFRunLoopRef; 15 | #else 16 | #import 17 | #endif 18 | 19 | 20 | typedef void (^TDReachabilityOnChangeBlock)(void); 21 | 22 | 23 | /** Asynchronously tracks the reachability of an Internet host, using the SystemConfiguration framework's reachability API. 24 | You can get called when reachability changes by either KV-observing the properties, or setting an "onChange" block. 25 | "Reachable" means simply that the local IP stack has resolved the host's DNS name and knows how to route packets toward its IP address. It does NOT guarantee that you can successfully connect. Generally it just means that you have an Internet connection. */ 26 | @interface TDReachability : NSObject 27 | { 28 | NSString* _hostName; 29 | SCNetworkReachabilityRef _ref; 30 | CFRunLoopRef _runLoop; 31 | SCNetworkReachabilityFlags _reachabilityFlags; 32 | BOOL _reachabilityKnown; 33 | TDReachabilityOnChangeBlock _onChange; 34 | } 35 | 36 | - (id) initWithHostName: (NSString*)hostName; 37 | 38 | @property (readonly, nonatomic) NSString* hostName; 39 | 40 | /** Starts tracking reachability. 41 | You have to call this after creating the object, or none of its properties will change. The current thread must have a runloop. 42 | @return YES if tracking started, or NO if there was an error. */ 43 | - (BOOL) start; 44 | 45 | /** Stops tracking reachability. 46 | This is called automatically by -dealloc, but to be safe you can call it when you release your TDReachability instance, to make sure that in case of a leak it isn't left running forever. */ 47 | - (void) stop; 48 | 49 | /** YES if the host's reachability has been determined, NO if it hasn't yet or if there was an error. */ 50 | @property (readonly, nonatomic) BOOL reachabilityKnown; 51 | 52 | /** The exact reachability flags; see Apple docs for the meanings of the bits. */ 53 | @property (readonly, nonatomic) SCNetworkReachabilityFlags reachabilityFlags; 54 | 55 | /** Is this host reachable via a currently active network interface? */ 56 | @property (readonly) BOOL reachable; 57 | 58 | /** Is this host reachable by WiFi (or wired Ethernet)? */ 59 | @property (readonly) BOOL reachableByWiFi; 60 | 61 | /** If you set this, the block will be called whenever the reachability related properties change. */ 62 | @property (copy) TDReachabilityOnChangeBlock onChange; 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /Source/TDRemoteRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDRemoteRequest.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/15/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @protocol TDAuthorizer; 11 | 12 | 13 | /** The signature of the completion block called by a TDRemoteRequest. 14 | @param result On success, a 'result' object; by default this is the TDRemoteRequest iself, but subclasses may return something else. On failure, this will likely be nil. 15 | @param error The error, if any, else nil. */ 16 | typedef void (^TDRemoteRequestCompletionBlock)(id result, NSError* error); 17 | 18 | 19 | /** Asynchronous HTTP request; a fairly simple wrapper around NSURLConnection that calls a completion block when ready. */ 20 | @interface TDRemoteRequest : NSObject 25 | { 26 | @protected 27 | NSMutableURLRequest* _request; 28 | id _authorizer; 29 | TDRemoteRequestCompletionBlock _onCompletion; 30 | NSURLConnection* _connection; 31 | int _status; 32 | UInt8 _retryCount; 33 | bool _dontLog404; 34 | bool _challenged; 35 | } 36 | 37 | /** Creates a request; call -start to send it on its way. */ 38 | - (id) initWithMethod: (NSString*)method 39 | URL: (NSURL*)url 40 | body: (id)body 41 | requestHeaders: (NSDictionary *)requestHeaders 42 | onCompletion: (TDRemoteRequestCompletionBlock)onCompletion; 43 | 44 | @property NSTimeInterval timeoutInterval; 45 | @property (strong, nonatomic) idauthorizer; 46 | 47 | /** In some cases a kTDStatusNotFound Not Found is an expected condition and shouldn't be logged; call this to suppress that log message. */ 48 | - (void) dontLog404; 49 | 50 | /** Starts a request; when finished, the onCompletion block will be called. */ 51 | - (void) start; 52 | 53 | /** Stops the request, calling the onCompletion block. */ 54 | - (void) stop; 55 | 56 | /** JSON-compatible dictionary with status information, to be returned from _activity API */ 57 | @property (readonly) NSMutableDictionary* statusInfo; 58 | 59 | // protected: 60 | - (void) setupRequest: (NSMutableURLRequest*)request withBody: (id)body; 61 | - (void) clearConnection; 62 | - (void) cancelWithStatus: (int)status; 63 | - (void) respondWithResult: (id)result error: (NSError*)error; 64 | 65 | // The value to use for the User-Agent HTTP header. 66 | + (NSString*) userAgentHeader; 67 | 68 | // Shared subroutines to handle NSURLAuthenticationMethodServerTrust challenges 69 | + (BOOL) checkTrust: (SecTrustRef)trust forHost: (NSString*)host; 70 | 71 | @end 72 | 73 | 74 | /** A request that parses its response body as JSON. 75 | The parsed object will be returned as the first parameter of the completion block. */ 76 | @interface TDRemoteJSONRequest : TDRemoteRequest 77 | { 78 | @private 79 | NSMutableData* _jsonBuffer; 80 | } 81 | @end 82 | -------------------------------------------------------------------------------- /Source/TDReplicator+Backgrounding.m: -------------------------------------------------------------------------------- 1 | // 2 | // TDReplicator+Backgrounding.m 3 | // CouchbaseLite 4 | // 5 | // Created by Jens Alfke on 8/15/13. 6 | // 7 | // 8 | 9 | #if TARGET_OS_IPHONE 10 | 11 | #import "TDReplicator.h" 12 | #import "TDInternal.h" 13 | #import "MYBlockUtils.h" 14 | 15 | #import 16 | 17 | 18 | @implementation TDReplicator (Backgrounding) 19 | 20 | 21 | // Called when the replicator starts 22 | - (void) setupBackgrounding { 23 | _bgTask = UIBackgroundTaskInvalid; 24 | [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(appBackgrounding:) 25 | name: UIApplicationDidEnterBackgroundNotification 26 | object: nil]; 27 | [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(appForegrounding:) 28 | name: UIApplicationWillEnterForegroundNotification 29 | object: nil]; 30 | } 31 | 32 | 33 | - (void) endBGTask { 34 | if (_bgTask != UIBackgroundTaskInvalid) { 35 | [[UIApplication sharedApplication] endBackgroundTask: _bgTask]; 36 | _bgTask = UIBackgroundTaskInvalid; 37 | } 38 | } 39 | 40 | 41 | // Called when the replicator stops 42 | - (void) endBackgrounding { 43 | [self endBGTask]; 44 | [[NSNotificationCenter defaultCenter] removeObserver: self 45 | name: UIApplicationDidEnterBackgroundNotification 46 | object: nil]; 47 | [[NSNotificationCenter defaultCenter] removeObserver: self 48 | name: UIApplicationWillEnterForegroundNotification 49 | object: nil]; 50 | } 51 | 52 | 53 | // Called when the replicator goes idle 54 | - (void) okToEndBackgrounding { 55 | if (_bgTask != UIBackgroundTaskInvalid) { 56 | LogTo(Sync, @"%@: Now idle; stopping background task (%d)", self, _bgTask); 57 | [self stop]; 58 | } 59 | } 60 | 61 | 62 | - (void) appBackgrounding: (NSNotification*)n { 63 | // Danger: This is called on the main thread! It switches to the replicator's thread to do its 64 | // work, but it has to block until that work is done, because UIApplication requires 65 | // background tasks to be registered before the notification handler returns; otherwise the app 66 | // simply suspends itself. 67 | NSLog(@"APP BACKGROUNDING"); 68 | MYOnThreadSynchronously(_thread, ^{ 69 | if (self.active) { 70 | _bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^{ 71 | // Called if process runs out of background time before replication finishes: 72 | MYOnThreadSynchronously(_thread, ^{ 73 | LogTo(Sync, @"%@: Background task (%d) ran out of time!", self, _bgTask); 74 | [self stop]; 75 | }); 76 | }]; 77 | LogTo(Sync, @"%@: App going into background (bgTask=%d)", self, _bgTask); 78 | if (_bgTask == UIBackgroundTaskInvalid) { 79 | // Backgrounding isn't possible for whatever reason, so just stop now: 80 | [self stop]; 81 | } 82 | } else { 83 | LogTo(Sync, @"%@: App going into background", self);//TEMP 84 | [self stop]; 85 | } 86 | }); 87 | } 88 | 89 | 90 | - (void) appForegrounding: (NSNotification*)n { 91 | // Danger: This is called on the main thread! 92 | NSLog(@"APP FOREGROUNDING"); 93 | MYOnThread(_thread, ^{ 94 | if (_bgTask != UIBackgroundTaskInvalid) { 95 | LogTo(Sync, @"%@: App returning to foreground (bgTask=%d)", self, _bgTask); 96 | [self endBGTask]; 97 | } 98 | }); 99 | } 100 | 101 | 102 | @end 103 | 104 | #endif // TARGET_OS_IPHONE -------------------------------------------------------------------------------- /Source/TDReplicator.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDReplicator.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/6/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TD_Database, TD_RevisionList, TDBatcher, TDReachability; 12 | @protocol TDAuthorizer; 13 | 14 | 15 | /** Posted when changesProcessed or changesTotal changes. */ 16 | extern NSString* TDReplicatorProgressChangedNotification; 17 | 18 | /** Posted when replicator stops running. */ 19 | extern NSString* TDReplicatorStoppedNotification; 20 | 21 | 22 | /** Abstract base class for push or pull replications. */ 23 | @interface TDReplicator : NSObject 24 | { 25 | @protected 26 | NSThread* _thread; 27 | TD_Database* __weak _db; 28 | NSURL* _remote; 29 | BOOL _continuous; 30 | NSString* _filterName; 31 | NSDictionary* _filterParameters; 32 | NSArray* _docIDs; 33 | NSString* _lastSequence; 34 | BOOL _lastSequenceChanged; 35 | NSDictionary* _remoteCheckpoint; 36 | BOOL _savingCheckpoint, _overdueForSave; 37 | BOOL _running, _online, _active; 38 | unsigned _revisionsFailed; 39 | NSError* _error; 40 | NSString* _sessionID; 41 | TDBatcher* _batcher; 42 | NSMutableArray* _remoteRequests; 43 | int _asyncTaskCount; 44 | NSUInteger _changesProcessed, _changesTotal; 45 | CFAbsoluteTime _startTime; 46 | id _authorizer; 47 | NSDictionary* _options; 48 | NSDictionary* _requestHeaders; 49 | @private 50 | TDReachability* _host; 51 | #if TARGET_OS_IPHONE 52 | NSUInteger /*UIBackgroundTaskIdentifier*/ _bgTask; 53 | #endif 54 | } 55 | 56 | + (NSString *)progressChangedNotification; 57 | + (NSString *)stoppedNotification; 58 | 59 | - (id) initWithDB: (TD_Database*)db 60 | remote: (NSURL*)remote 61 | push: (BOOL)push 62 | continuous: (BOOL)continuous; 63 | 64 | @property (weak, readonly) TD_Database* db; 65 | @property (readonly) NSURL* remote; 66 | @property (readonly) BOOL isPush; 67 | @property (readonly) BOOL continuous; 68 | @property (copy) NSString* filterName; 69 | @property (copy) NSDictionary* filterParameters; 70 | @property (copy) NSArray *docIDs; 71 | @property (copy) NSDictionary* options; 72 | 73 | /** Optional dictionary of headers to be added to all requests to remote servers. */ 74 | @property (copy) NSDictionary* requestHeaders; 75 | 76 | @property (strong) id authorizer; 77 | 78 | /** Do these two replicators have identical settings? */ 79 | - (bool) hasSameSettingsAs: (TDReplicator*)other; 80 | 81 | /** Starts the replicator. 82 | Replicators run asynchronously so nothing will happen until later. 83 | A replicator can only be started once; don't reuse it after it stops. */ 84 | - (void) start; 85 | 86 | /** Request to stop the replicator. 87 | Any pending asynchronous operations will be canceled. 88 | TDReplicatorStoppedNotification will be posted when it finally stops. */ 89 | - (void) stop; 90 | 91 | /** Is the replicator running? (Observable) */ 92 | @property (readonly, nonatomic) BOOL running; 93 | 94 | /** Is the replicator able to connect to the remote host? */ 95 | @property (readonly, nonatomic) BOOL online; 96 | 97 | /** Is the replicator actively sending/receiving revisions? (Observable) */ 98 | @property (readonly, nonatomic) BOOL active; 99 | 100 | /** Latest error encountered while replicating. 101 | This is set to nil when starting. It may also be set to nil by the client if desired. 102 | Not all errors are fatal; if .running is still true, the replicator will retry. */ 103 | @property (strong, nonatomic) NSError* error; 104 | 105 | /** A unique-per-process string identifying this replicator instance. */ 106 | @property (copy, nonatomic) NSString* sessionID; 107 | 108 | /** Number of changes (docs or other metadata) transferred so far. */ 109 | @property (readonly, nonatomic) NSUInteger changesProcessed; 110 | 111 | /** Approximate total number of changes to transfer. 112 | This is only an estimate and its value will change during replication. It starts at zero and returns to zero when replication stops. */ 113 | @property (readonly, nonatomic) NSUInteger changesTotal; 114 | 115 | /** JSON-compatible array of status info about active remote HTTP requests. */ 116 | @property (readonly) NSArray* activeRequestsStatus; 117 | 118 | /** Timeout interval for HTTP requests sent by this replicator. 119 | (Derived from options key "connection_timeout", in milliseconds.) */ 120 | @property (readonly) NSTimeInterval requestTimeout; 121 | 122 | @end 123 | -------------------------------------------------------------------------------- /Source/TDReplicatorManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDReplicatorManager.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 2/15/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class TD_DatabaseManager; 11 | @protocol TDAuthorizer; 12 | 13 | 14 | extern NSString* const kTDReplicatorDatabaseName; 15 | 16 | 17 | /** Manages the _replicator database for persistent replications. 18 | It doesn't really have an API; it works on its own by monitoring the '_replicator' database, and docs in it, for changes. Applications use the regular document APIs to manage replications. 19 | A TD_Server owns an instance of this class. */ 20 | @interface TDReplicatorManager : NSObject 21 | { 22 | TD_DatabaseManager* _dbManager; 23 | TD_Database* _replicatorDB; 24 | NSThread* _thread; 25 | NSMutableDictionary* _replicatorsByDocID; 26 | BOOL _updateInProgress; 27 | } 28 | 29 | - (id) initWithDatabaseManager: (TD_DatabaseManager*)dbManager; 30 | 31 | - (void) start; 32 | - (void) stop; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Source/TDRouter.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDRouter.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 11/30/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class TD_Server, TD_DatabaseManager, TDResponse, TD_Body, TDMultipartWriter; 11 | 12 | 13 | typedef TDStatus (^OnAccessCheckBlock)(TD_Database*, NSString *docID, SEL action); 14 | typedef void (^OnResponseReadyBlock)(TDResponse*); 15 | typedef void (^OnDataAvailableBlock)(NSData* data, BOOL finished); 16 | typedef void (^OnFinishedBlock)(); 17 | 18 | 19 | @interface TDRouter : NSObject 20 | { 21 | @private 22 | TD_Server* _server; 23 | TD_DatabaseManager* _dbManager; 24 | NSURLRequest* _request; 25 | NSMutableArray* _path; 26 | NSDictionary* _queries; 27 | NSMutableArray* _queryRetainer; 28 | TDResponse* _response; 29 | TD_Database* _db; 30 | BOOL _local; 31 | BOOL _waiting; 32 | BOOL _responseSent; 33 | BOOL _processRanges; 34 | OnAccessCheckBlock _onAccessCheck; 35 | OnResponseReadyBlock _onResponseReady; 36 | OnDataAvailableBlock _onDataAvailable; 37 | OnFinishedBlock _onFinished; 38 | BOOL _running; 39 | BOOL _longpoll; 40 | TD_FilterBlock _changesFilter; 41 | NSDictionary* _changesFilterParams; 42 | BOOL _changesIncludeDocs; 43 | BOOL _changesIncludeConflicts; 44 | } 45 | 46 | - (id) initWithServer: (TD_Server*)server request: (NSURLRequest*)request isLocal: (BOOL)isLocal; 47 | 48 | @property BOOL processRanges; 49 | 50 | @property (copy) OnAccessCheckBlock onAccessCheck; 51 | @property (copy) OnResponseReadyBlock onResponseReady; 52 | @property (copy) OnDataAvailableBlock onDataAvailable; 53 | @property (copy) OnFinishedBlock onFinished; 54 | 55 | @property (readonly) NSURLRequest* request; 56 | @property (readonly) TDResponse* response; 57 | 58 | - (void) start; 59 | - (void) stop; 60 | 61 | + (NSString*) versionString; 62 | 63 | @end 64 | 65 | 66 | @interface TDRouter (Internal) 67 | - (NSString*) query: (NSString*)param; 68 | - (BOOL) boolQuery: (NSString*)param; 69 | - (int) intQuery: (NSString*)param defaultValue: (int)defaultValue; 70 | - (id) jsonQuery: (NSString*)param error: (NSError**)outError; 71 | - (NSMutableDictionary*) jsonQueries; 72 | - (BOOL) cacheWithEtag: (NSString*)etag; 73 | - (TDContentOptions) contentOptions; 74 | - (BOOL) getQueryOptions: (struct TDQueryOptions*)options; 75 | @property (readonly) NSString* multipartRequestType; 76 | @property (readonly) NSDictionary* bodyAsDictionary; 77 | @property (readonly) NSString* ifMatch; 78 | - (TDStatus) openDB; 79 | - (void) sendResponseHeaders; 80 | - (void) sendResponseBodyAndFinish: (BOOL)finished; 81 | - (void) finished; 82 | @end 83 | 84 | 85 | 86 | @interface TDResponse : NSObject 87 | { 88 | @private 89 | TDStatus _internalStatus; 90 | int _status; 91 | NSString* _statusMsg; 92 | NSString* _statusReason; 93 | NSMutableDictionary* _headers; 94 | TD_Body* _body; 95 | } 96 | 97 | @property (nonatomic) TDStatus internalStatus; 98 | @property (nonatomic) int status; 99 | @property (nonatomic, readonly) NSString* statusMsg; 100 | @property (nonatomic, copy) NSString* statusReason; 101 | @property (nonatomic, strong) NSMutableDictionary* headers; 102 | @property (nonatomic, strong) TD_Body* body; 103 | @property (nonatomic, copy) id bodyObject; 104 | @property (nonatomic, readonly) NSString* baseContentType; 105 | 106 | - (void) reset; 107 | - (NSString*) objectForKeyedSubscript: (NSString*)header; 108 | - (void) setObject: (NSString*)value forKeyedSubscript:(NSString*)header; 109 | 110 | - (void) setMultipartBody: (TDMultipartWriter*)mp; 111 | - (void) setMultipartBody: (NSArray*)parts type: (NSString*)type; 112 | 113 | @end -------------------------------------------------------------------------------- /Source/TDSequenceMap.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDSequenceMap.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 2/21/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | /** A data structure representing a type of array that allows object values to be added to the end, and removed in arbitrary order; it's used by the replicator to keep track of which revisions have been transferred and what sequences to checkpoint. */ 13 | @interface TDSequenceMap : NSObject 14 | { 15 | NSMutableIndexSet* _sequences; // Sequence numbers currently in the map 16 | NSUInteger _lastSequence; // last generated sequence 17 | NSMutableArray* _values; // values of remaining sequences 18 | NSUInteger _firstValueSequence; // sequence # of first item in _values 19 | } 20 | 21 | - (id) init; 22 | 23 | /** Adds a value to the map, assigning it a sequence number and returning it. 24 | Sequence numbers start at 1 and increment from there. */ 25 | - (SequenceNumber) addValue: (id)value; 26 | 27 | /** Removes a sequence and its associated value. */ 28 | - (void) removeSequence: (SequenceNumber)sequence; 29 | 30 | @property (readonly) BOOL isEmpty; 31 | 32 | /** Returns the maximum consecutively-removed sequence number. 33 | This is one less than the minimum remaining sequence number. */ 34 | - (SequenceNumber) checkpointedSequence; 35 | 36 | /** Returns the value associated with the checkpointedSequence. */ 37 | - (id) checkpointedValue; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Source/TDSequenceMap.m: -------------------------------------------------------------------------------- 1 | // 2 | // TDSequenceMap.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 2/21/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "TDSequenceMap.h" 17 | 18 | 19 | @implementation TDSequenceMap 20 | 21 | 22 | - (id)init 23 | { 24 | self = [super init]; 25 | if (self) { 26 | _sequences = [[NSMutableIndexSet alloc] init]; 27 | _values = [[NSMutableArray alloc] initWithCapacity: 100]; 28 | _firstValueSequence = 1; 29 | } 30 | return self; 31 | } 32 | 33 | 34 | 35 | 36 | - (SequenceNumber) addValue: (id)value { 37 | [_sequences addIndex: ++_lastSequence]; 38 | [_values addObject: value]; 39 | return _lastSequence; 40 | } 41 | 42 | 43 | - (void) removeSequence: (SequenceNumber)sequence { 44 | Assert(sequence > 0 && sequence <= (SequenceNumber)_lastSequence, 45 | @"Invalid sequence %lld (latest is %u)", sequence, _lastSequence); 46 | [_sequences removeIndex: (NSUInteger) sequence]; 47 | } 48 | 49 | 50 | - (BOOL) isEmpty { 51 | return _sequences.firstIndex == NSNotFound; 52 | } 53 | 54 | 55 | - (SequenceNumber) checkpointedSequence { 56 | NSUInteger sequence = _sequences.firstIndex; 57 | sequence = (sequence == NSNotFound) ? _lastSequence : sequence-1; 58 | 59 | if (sequence > _firstValueSequence) { 60 | // Garbage-collect inaccessible values: 61 | NSUInteger numToRemove = sequence - _firstValueSequence; 62 | [_values removeObjectsInRange: NSMakeRange(0, numToRemove)]; 63 | _firstValueSequence += numToRemove; 64 | } 65 | return sequence; 66 | } 67 | 68 | 69 | - (id) checkpointedValue { 70 | NSInteger index = (NSInteger)([self checkpointedSequence] - _firstValueSequence); 71 | return (index >= 0) ? _values[index] : nil; 72 | } 73 | 74 | 75 | @end 76 | 77 | 78 | 79 | TestCase(TDSequenceMap) { 80 | TDSequenceMap* map = [[TDSequenceMap alloc] init]; 81 | CAssertEq(map.checkpointedSequence, 0); 82 | CAssertEqual(map.checkpointedValue, nil); 83 | CAssert(map.isEmpty); 84 | 85 | CAssertEq([map addValue: @"one"], 1); 86 | CAssertEq(map.checkpointedSequence, 0); 87 | CAssertEqual(map.checkpointedValue, nil); 88 | CAssert(!map.isEmpty); 89 | 90 | CAssertEq([map addValue: @"two"], 2); 91 | CAssertEq(map.checkpointedSequence, 0); 92 | CAssertEqual(map.checkpointedValue, nil); 93 | 94 | CAssertEq([map addValue: @"three"], 3); 95 | CAssertEq(map.checkpointedSequence, 0); 96 | CAssertEqual(map.checkpointedValue, nil); 97 | 98 | [map removeSequence: 2]; 99 | CAssertEq(map.checkpointedSequence, 0); 100 | CAssertEqual(map.checkpointedValue, nil); 101 | 102 | [map removeSequence: 1]; 103 | CAssertEq(map.checkpointedSequence, 2); 104 | CAssertEqual(map.checkpointedValue, @"two"); 105 | 106 | CAssertEq([map addValue: @"four"], 4); 107 | CAssertEq(map.checkpointedSequence, 2); 108 | CAssertEqual(map.checkpointedValue, @"two"); 109 | 110 | [map removeSequence: 3]; 111 | CAssertEq(map.checkpointedSequence, 3); 112 | CAssertEqual(map.checkpointedValue, @"three"); 113 | 114 | [map removeSequence: 4]; 115 | CAssertEq(map.checkpointedSequence, 4); 116 | CAssertEqual(map.checkpointedValue, @"four"); 117 | CAssert(map.isEmpty); 118 | } 119 | -------------------------------------------------------------------------------- /Source/TDStatus.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDStatus.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 4/5/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | 10 | /** TouchDB internal status/error codes. Superset of HTTP status codes. */ 11 | typedef enum { 12 | kTDStatusOK = 200, 13 | kTDStatusCreated = 201, 14 | kTDStatusAccepted = 206, 15 | 16 | kTDStatusNotModified = 304, 17 | 18 | kTDStatusBadRequest = 400, 19 | kTDStatusUnauthorized = 401, 20 | kTDStatusForbidden = 403, 21 | kTDStatusNotFound = 404, 22 | kTDStatusNotAcceptable = 406, 23 | kTDStatusConflict = 409, 24 | kTDStatusDuplicate = 412, // Formally known as "Precondition Failed" 25 | kTDStatusUnsupportedType= 415, 26 | 27 | kTDStatusServerError = 500, 28 | 29 | // Non-HTTP errors: 30 | kTDStatusBadEncoding = 490, 31 | kTDStatusBadAttachment = 491, 32 | kTDStatusAttachmentNotFound = 492, 33 | kTDStatusBadJSON = 493, 34 | kTDStatusBadID = 494, 35 | kTDStatusBadParam = 495, 36 | kTDStatusDeleted = 496, // Document deleted 37 | 38 | kTDStatusUpstreamError = 589, // Error from remote replication server 39 | kTDStatusDBError = 590, // SQLite error 40 | kTDStatusCorruptError = 591, // bad data in database 41 | kTDStatusAttachmentError= 592, // problem with attachment store 42 | kTDStatusCallbackError = 593, // app callback (emit fn, etc.) failed 43 | kTDStatusException = 594, // Exception raised/caught 44 | } TDStatus; 45 | 46 | 47 | static inline bool TDStatusIsError(TDStatus status) {return status >= 300;} 48 | 49 | int TDStatusToHTTPStatus( TDStatus status, NSString** outMessage ); 50 | 51 | NSError* TDStatusToNSError( TDStatus status, NSURL* url ); 52 | NSError* TDStatusToNSErrorWithInfo( TDStatus status, NSURL* url, NSDictionary* extraInfo ); 53 | -------------------------------------------------------------------------------- /Source/TDStatus.m: -------------------------------------------------------------------------------- 1 | // 2 | // TDStatus.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 4/6/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "TDStatus.h" 17 | 18 | 19 | NSString* const TDHTTPErrorDomain = @"TDHTTP"; 20 | 21 | 22 | struct StatusMapEntry { 23 | TDStatus status; 24 | int httpStatus; 25 | const char* message; 26 | }; 27 | 28 | static const struct StatusMapEntry kStatusMap[] = { 29 | // For compatibility with CouchDB, return the same strings it does (see couch_httpd.erl) 30 | {kTDStatusBadRequest, 400, "bad_request"}, 31 | {kTDStatusUnauthorized, 401, "unauthorized"}, 32 | {kTDStatusNotFound, 404, "not_found"}, 33 | {kTDStatusForbidden, 403, "forbidden"}, 34 | {kTDStatusNotAcceptable, 406, "not_acceptable"}, 35 | {kTDStatusConflict, 409, "conflict"}, 36 | {kTDStatusDuplicate, 412, "file_exists"}, // really 'Precondition Failed' 37 | {kTDStatusUnsupportedType, 415, "bad_content_type"}, 38 | 39 | // These are nonstandard status codes; map them to closest HTTP equivalents: 40 | {kTDStatusBadEncoding, 400, "Bad data encoding"}, 41 | {kTDStatusBadAttachment, 400, "Invalid attachment"}, 42 | {kTDStatusAttachmentNotFound, 404, "Attachment not found"}, 43 | {kTDStatusBadJSON, 400, "Invalid JSON"}, 44 | {kTDStatusBadID, 400, "Invalid database/document/revision ID"}, 45 | {kTDStatusBadParam, 400, "Invalid parameter in JSON body"}, 46 | {kTDStatusDeleted, 404, "deleted"}, 47 | 48 | {kTDStatusUpstreamError, 502, "Invalid response from remote replication server"}, 49 | {kTDStatusDBError, 500, "Database error!"}, 50 | {kTDStatusCorruptError, 500, "Invalid data in database"}, 51 | {kTDStatusAttachmentError, 500, "Attachment store error"}, 52 | {kTDStatusCallbackError, 500, "Application callback block failed"}, 53 | {kTDStatusException, 500, "Internal error"}, 54 | }; 55 | 56 | 57 | int TDStatusToHTTPStatus( TDStatus status, NSString** outMessage ) { 58 | for (unsigned i=0; i < sizeof(kStatusMap)/sizeof(kStatusMap[0]); ++i) { 59 | if (kStatusMap[i].status == status) { 60 | if (outMessage) 61 | *outMessage = [NSString stringWithUTF8String: kStatusMap[i].message]; 62 | return kStatusMap[i].httpStatus; 63 | } 64 | } 65 | if (outMessage) 66 | *outMessage = [NSHTTPURLResponse localizedStringForStatusCode: status]; 67 | return status; 68 | } 69 | 70 | 71 | NSError* TDStatusToNSErrorWithInfo( TDStatus status, NSURL* url, NSDictionary* extraInfo ) { 72 | NSString* reason; 73 | status = TDStatusToHTTPStatus(status, &reason); 74 | NSMutableDictionary* info = $mdict({NSURLErrorKey, url}, 75 | {NSLocalizedFailureReasonErrorKey, reason}, 76 | {NSLocalizedDescriptionKey, $sprintf(@"%i %@", status, reason)}); 77 | if (extraInfo) 78 | [info addEntriesFromDictionary: extraInfo]; 79 | return [NSError errorWithDomain: TDHTTPErrorDomain code: status userInfo: [info copy]]; 80 | } 81 | 82 | 83 | NSError* TDStatusToNSError( TDStatus status, NSURL* url ) { 84 | return TDStatusToNSErrorWithInfo(status, url, nil); 85 | } 86 | -------------------------------------------------------------------------------- /Source/TDURLProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // TDURLProtocol.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 11/30/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class TD_Server, TDRouter; 11 | 12 | @interface TDURLProtocol : NSURLProtocol 13 | { 14 | @private 15 | TDRouter* _router; 16 | } 17 | 18 | /** The root URL served by this protocol, "touchdb:///". */ 19 | + (NSURL*) rootURL; 20 | 21 | /** An alternate root URL with HTTP scheme; use this for CouchApps in UIWebViews. 22 | (This URL will have the hostname of the touchdb: URL with ".touchdb." appended.) */ 23 | + (NSURL*) HTTPURLForServerURL: (NSURL*)serverURL; 24 | 25 | /** Registers a TD_Server instance with a URL hostname. 26 | 'touchdb:' URLs with that hostname will be routed to that server. 27 | If the server is nil, that hostname is unregistered, and URLs with that hostname will cause a host-not-found error. 28 | If the hostname is nil or an empty string, "localhost" is substituted. */ 29 | + (NSURL*) registerServer: (TD_Server*)server forHostname: (NSString*)hostname; 30 | 31 | /** Returns the TD_Server instance that's been registered with a specific hostname. */ 32 | + (TD_Server*) serverForHostname: (NSString*)hostname; 33 | 34 | /** Registers a TD_Server instance with a new unique hostname, and returns the root URL at which the server can now be reached. */ 35 | + (NSURL*) registerServer: (TD_Server*)server; 36 | 37 | /** Unregisters a TD_Server. After this, the server can be safely closed. */ 38 | + (void) unregisterServer: (TD_Server*)server; 39 | 40 | /** A convenience to register a server with the default hostname "localhost". */ 41 | + (void) setServer: (TD_Server*)server; 42 | 43 | /** Returns the server registered with the hostname "localhost". */ 44 | + (TD_Server*) server; 45 | 46 | /** Returns YES if TDURLProtocol will handle this URL. */ 47 | + (BOOL) handlesURL: (NSURL*)url; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /Source/TD_Attachment.h: -------------------------------------------------------------------------------- 1 | // 2 | // TD_Attachment.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 4/3/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "TD_Database+Attachments.h" 10 | #import "TDBlobStore.h" 11 | 12 | 13 | /** A simple container for attachment metadata. */ 14 | @interface TD_Attachment : NSObject 15 | { 16 | @private 17 | NSString* _name; 18 | NSString* _contentType; 19 | @public 20 | // Yes, these are public. They're simple scalar values so it's not really worth 21 | // creating accessor methods for them all. 22 | TDBlobKey blobKey; 23 | UInt64 length; 24 | UInt64 encodedLength; 25 | TDAttachmentEncoding encoding; 26 | unsigned revpos; 27 | } 28 | 29 | - (id) initWithName: (NSString*)name contentType: (NSString*)contentType; 30 | 31 | @property (readonly, nonatomic) NSString* name; 32 | @property (readonly, nonatomic) NSString* contentType; 33 | 34 | @property (readonly) bool isValid; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /Source/TD_Attachment.m: -------------------------------------------------------------------------------- 1 | // 2 | // TD_Attachment.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 4/3/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "TD_Attachment.h" 17 | 18 | 19 | @implementation TD_Attachment 20 | 21 | 22 | @synthesize name=_name, contentType=_contentType; 23 | 24 | 25 | - (id) initWithName: (NSString*)name contentType: (NSString*)contentType { 26 | Assert(name); 27 | self = [super init]; 28 | if (self) { 29 | _name = [name copy]; 30 | _contentType = [contentType copy]; 31 | } 32 | return self; 33 | } 34 | 35 | 36 | 37 | 38 | - (bool) isValid { 39 | if (encoding) { 40 | if (encodedLength == 0 && length > 0) 41 | return false; 42 | } else if (encodedLength > 0) { 43 | return false; 44 | } 45 | if (revpos == 0) 46 | return false; 47 | #if DEBUG 48 | size_t i; 49 | for (i=0; i 10 | 11 | 12 | /** A request/response/document body, stored as either JSON or an NSDictionary. */ 13 | @interface TD_Body : NSObject 14 | { 15 | @private 16 | NSData* _json; 17 | NSDictionary* _object; 18 | BOOL _error; 19 | } 20 | 21 | - (id) initWithProperties: (NSDictionary*)properties; 22 | - (id) initWithArray: (NSArray*)array; 23 | - (id) initWithJSON: (NSData*)json; 24 | 25 | + (TD_Body*) bodyWithProperties: (id)properties; 26 | + (TD_Body*) bodyWithJSON: (NSData*)json; 27 | 28 | @property (readonly) BOOL isValidJSON; 29 | @property (readonly) NSData* asJSON; 30 | @property (readonly) NSData* asPrettyJSON; 31 | @property (readonly) NSString* asJSONString; 32 | @property (readonly) id asObject; 33 | @property (readonly) BOOL error; 34 | 35 | @property (readonly) NSDictionary* properties; 36 | - (id) objectForKeyedSubscript: (NSString*)key; // enables subscript access in Xcode 4.4+ 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /Source/TD_Body.m: -------------------------------------------------------------------------------- 1 | // 2 | // TD_Body.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 6/19/10. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "TD_Body.h" 17 | 18 | 19 | @implementation TD_Body 20 | 21 | - (id) initWithProperties: (NSDictionary*)properties { 22 | NSParameterAssert(properties); 23 | self = [super init]; 24 | if (self) { 25 | _object = [properties copy]; 26 | } 27 | return self; 28 | } 29 | 30 | - (id) initWithArray: (NSArray*)array { 31 | return [self initWithProperties: (id)array]; 32 | } 33 | 34 | - (id) initWithJSON: (NSData*)json { 35 | self = [super init]; 36 | if (self) { 37 | _json = json ? [json copy] : [[NSData alloc] init]; 38 | } 39 | return self; 40 | } 41 | 42 | + (TD_Body*) bodyWithProperties: (NSDictionary*)properties { 43 | return [[self alloc] initWithProperties: properties]; 44 | } 45 | + (TD_Body*) bodyWithJSON: (NSData*)json { 46 | return [[self alloc] initWithJSON: json]; 47 | } 48 | 49 | @synthesize error=_error; 50 | 51 | - (BOOL) isValidJSON { 52 | // Yes, this is just like asObject except it doesn't warn. 53 | if (!_object && !_error) { 54 | _object = [[TDJSON JSONObjectWithData: _json options: 0 error: NULL] copy]; 55 | if (!_object) { 56 | _error = YES; 57 | } 58 | } 59 | return _object != nil; 60 | } 61 | 62 | - (NSData*) asJSON { 63 | if (!_json && !_error) { 64 | _json = [[TDJSON dataWithJSONObject: _object options: 0 error: NULL] copy]; 65 | if (!_json) { 66 | Warn(@"TD_Body: couldn't convert to JSON"); 67 | _error = YES; 68 | } 69 | } 70 | return _json; 71 | } 72 | 73 | - (NSData*) asPrettyJSON { 74 | id props = self.asObject; 75 | if (props) { 76 | NSData* json = [TDJSON dataWithJSONObject: props 77 | options: TDJSONWritingPrettyPrinted 78 | error: NULL]; 79 | if (json) { 80 | NSMutableData* mjson = [json mutableCopy]; 81 | [mjson appendBytes: "\n" length: 1]; 82 | return mjson; 83 | } 84 | } 85 | return self.asJSON; 86 | } 87 | 88 | - (NSString*) asJSONString { 89 | return self.asJSON.my_UTF8ToString; 90 | } 91 | 92 | - (id) asObject { 93 | if (!_object && !_error) { 94 | NSError* error = nil; 95 | _object = [[TDJSON JSONObjectWithData: _json options: 0 error: &error] copy]; 96 | if (!_object) { 97 | Warn(@"TD_Body: couldn't parse JSON: %@ (error=%@)", [_json my_UTF8ToString], error); 98 | _error = YES; 99 | } 100 | } 101 | return _object; 102 | } 103 | 104 | - (NSDictionary*) properties { 105 | id object = self.asObject; 106 | if ([object isKindOfClass: [NSDictionary class]]) 107 | return object; 108 | else 109 | return nil; 110 | } 111 | 112 | - (id) objectForKeyedSubscript: (NSString*)key { 113 | return (self.properties)[key]; 114 | } 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /Source/TD_Database+Attachments.h: -------------------------------------------------------------------------------- 1 | // 2 | // TD_Database+Attachments.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 1/18/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class TDBlobStoreWriter, TDMultipartWriter; 11 | 12 | 13 | /** Types of encoding/compression of stored attachments. */ 14 | typedef enum { 15 | kTDAttachmentEncodingNone, 16 | kTDAttachmentEncodingGZIP 17 | } TDAttachmentEncoding; 18 | 19 | 20 | @interface TD_Database (Attachments) 21 | 22 | /** Creates a TDBlobStoreWriter object that can be used to stream an attachment to the store. */ 23 | - (TDBlobStoreWriter*) attachmentWriter; 24 | 25 | /** Creates TD_Attachment objects from the revision's '_attachments' property. */ 26 | - (NSDictionary*) attachmentsFromRevision: (TD_Revision*)rev 27 | status: (TDStatus*)outStatus; 28 | 29 | /** Given a newly-added revision, adds the necessary attachment rows to the database and stores inline attachments into the blob store. */ 30 | - (TDStatus) processAttachments: (NSDictionary*)attachments 31 | forRevision: (TD_Revision*)rev 32 | withParentSequence: (SequenceNumber)parentSequence; 33 | 34 | /** Constructs an "_attachments" dictionary for a revision, to be inserted in its JSON body. */ 35 | - (NSDictionary*) getAttachmentDictForSequence: (SequenceNumber)sequence 36 | options: (TDContentOptions)options; 37 | 38 | /** Modifies a TD_Revision's _attachments dictionary by changing all attachments with revpos < minRevPos into stubs; and if 'attachmentsFollow' is true, the remaining attachments will be modified to _not_ be stubs but include a "follows" key instead of a body. */ 39 | + (void) stubOutAttachmentsIn: (TD_Revision*)rev 40 | beforeRevPos: (int)minRevPos 41 | attachmentsFollow: (BOOL)attachmentsFollow; 42 | 43 | /** Generates a MIME multipart writer for a revision, with separate body parts for each attachment whose "follows" property is set. */ 44 | - (TDMultipartWriter*) multipartWriterForRevision: (TD_Revision*)rev 45 | contentType: (NSString*)contentType; 46 | 47 | /** Returns the content and metadata of an attachment. 48 | If you pass NULL for the 'outEncoding' parameter, it signifies that you don't care about encodings and just want the 'real' data, so it'll be decoded for you. */ 49 | - (NSData*) getAttachmentForSequence: (SequenceNumber)sequence 50 | named: (NSString*)filename 51 | type: (NSString**)outType 52 | encoding: (TDAttachmentEncoding*)outEncoding 53 | status: (TDStatus*)outStatus; 54 | 55 | /** Returns the location of an attachment's file in the blob store. */ 56 | - (NSString*) getAttachmentPathForSequence: (SequenceNumber)sequence 57 | named: (NSString*)filename 58 | type: (NSString**)outType 59 | encoding: (TDAttachmentEncoding*)outEncoding 60 | status: (TDStatus*)outStatus; 61 | 62 | /** Uses the "digest" field of the attachment dict to look up the attachment in the store and return a file URL to it. DO NOT MODIFY THIS FILE! */ 63 | - (NSURL*) fileForAttachmentDict: (NSDictionary*)attachmentDict; 64 | 65 | /** Deletes obsolete attachments from the database and blob store. */ 66 | - (TDStatus) garbageCollectAttachments; 67 | 68 | /** Updates or deletes an attachment, creating a new document revision in the process. 69 | Used by the PUT / DELETE methods called on attachment URLs. */ 70 | - (TD_Revision*) updateAttachment: (NSString*)filename 71 | body: (TDBlobStoreWriter*)body 72 | type: (NSString*)contentType 73 | encoding: (TDAttachmentEncoding)encoding 74 | ofDocID: (NSString*)docID 75 | revID: (NSString*)oldRevID 76 | status: (TDStatus*)outStatus; 77 | @end 78 | -------------------------------------------------------------------------------- /Source/TD_Database+Insertion.h: -------------------------------------------------------------------------------- 1 | // 2 | // TD_Database+Insertion.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 1/18/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @protocol TD_ValidationContext; 11 | 12 | 13 | /** Validation block, used to approve revisions being added to the database. */ 14 | typedef BOOL (^TD_ValidationBlock) (TD_Revision* newRevision, 15 | id context); 16 | 17 | 18 | @interface TD_Database (Insertion) 19 | 20 | + (BOOL) isValidDocumentID: (NSString*)str; 21 | 22 | + (NSString*) generateDocumentID; 23 | 24 | /** Stores a new (or initial) revision of a document. This is what's invoked by a PUT or POST. As with those, the previous revision ID must be supplied when necessary and the call will fail if it doesn't match. 25 | @param revision The revision to add. If the docID is nil, a new UUID will be assigned. Its revID must be nil. It must have a JSON body. 26 | @param prevRevID The ID of the revision to replace (same as the "?rev=" parameter to a PUT), or nil if this is a new document. 27 | @param allowConflict If NO, an error status kTDStatusConflict will be returned if the insertion would create a conflict, i.e. if the previous revision already has a child. 28 | @param status On return, an HTTP status code indicating success or failure. 29 | @return A new TD_Revision with the docID, revID and sequence filled in (but no body). */ 30 | - (TD_Revision*) putRevision: (TD_Revision*)revision 31 | prevRevisionID: (NSString*)prevRevID 32 | allowConflict: (BOOL)allowConflict 33 | status: (TDStatus*)outStatus; 34 | 35 | /** Inserts an already-existing revision replicated from a remote database. It must already have a revision ID. This may create a conflict! The revision's history must be given; ancestor revision IDs that don't already exist locally will create phantom revisions with no content. */ 36 | - (TDStatus) forceInsert: (TD_Revision*)rev 37 | revisionHistory: (NSArray*)history 38 | source: (NSURL*)source; 39 | 40 | /** Parses the _revisions dict from a document into an array of revision ID strings */ 41 | + (NSArray*) parseCouchDBRevisionHistory: (NSDictionary*)docProperties; 42 | 43 | /** Define or clear a named document validation function. */ 44 | - (void) defineValidation: (NSString*)validationName asBlock: (TD_ValidationBlock)validationBlock; 45 | - (TD_ValidationBlock) validationNamed: (NSString*)validationName; 46 | 47 | /** Compacts the database storage by removing the bodies and attachments of obsolete revisions. */ 48 | - (TDStatus) compact; 49 | 50 | /** Purges specific revisions, which deletes them completely from the local database _without_ adding a "tombstone" revision. It's as though they were never there. 51 | @param docsToRevs A dictionary mapping document IDs to arrays of revision IDs. 52 | @param outResult On success will point to an NSDictionary with the same form as docsToRev, containing the doc/revision IDs that were actually removed. */ 53 | - (TDStatus) purgeRevisions: (NSDictionary*)docsToRevs 54 | result: (NSDictionary**)outResult; 55 | 56 | @end 57 | 58 | 59 | 60 | typedef BOOL (^TDChangeEnumeratorBlock) (NSString* key, id oldValue, id newValue); 61 | 62 | 63 | /** Context passed into a TDValidationBlock. */ 64 | @protocol TD_ValidationContext 65 | /** The contents of the current revision of the document, or nil if this is a new document. */ 66 | @property (readonly) TD_Revision* currentRevision; 67 | 68 | /** The type of HTTP status to report, if the validate block returns NO. 69 | The default value is 403 ("Forbidden"). */ 70 | @property TDStatus errorType; 71 | 72 | /** The error message to return in the HTTP response, if the validate block returns NO. 73 | The default value is "invalid document". */ 74 | @property (copy) NSString* errorMessage; 75 | 76 | /** Returns an array of all the keys whose values are different between the current and new revisions. */ 77 | @property (readonly) NSArray* changedKeys; 78 | 79 | /** Returns YES if only the keys given in the 'allowedKeys' array have changed; else returns NO and sets a default error message naming the offending key. */ 80 | - (BOOL) allowChangesOnlyTo: (NSArray*)allowedKeys; 81 | 82 | /** Returns YES if none of the keys given in the 'disallowedKeys' array have changed; else returns NO and sets a default error message naming the offending key. */ 83 | - (BOOL) disallowChangesTo: (NSArray*)disallowedKeys; 84 | 85 | /** Calls the 'enumerator' block for each key that's changed, passing both the old and new values. 86 | If the block returns NO, the enumeration stops and sets a default error message, and the method returns NO; else the method returns YES. */ 87 | - (BOOL) enumerateChanges: (TDChangeEnumeratorBlock)enumerator; 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /Source/TD_Database+LocalDocs.h: -------------------------------------------------------------------------------- 1 | // 2 | // TD_Database+LocalDocs.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 1/10/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface TD_Database (LocalDocs) 13 | 14 | - (TD_Revision*) getLocalDocumentWithID: (NSString*)docID 15 | revisionID: (NSString*)revID; 16 | 17 | - (TD_Revision*) putLocalRevision: (TD_Revision*)revision 18 | prevRevisionID: (NSString*)prevRevID 19 | status: (TDStatus*)outStatus; 20 | 21 | - (TDStatus) deleteLocalDocumentWithID: (NSString*)docID 22 | revisionID: (NSString*)revID; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Source/TD_Database+LocalDocs.m: -------------------------------------------------------------------------------- 1 | // 2 | // TD_Database+LocalDocs.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 1/10/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "TD_Database+LocalDocs.h" 17 | #import 18 | #import "TD_Body.h" 19 | #import "TDInternal.h" 20 | 21 | #import "FMDatabase.h" 22 | 23 | 24 | @implementation TD_Database (LocalDocs) 25 | 26 | 27 | - (TD_Revision*) getLocalDocumentWithID: (NSString*)docID 28 | revisionID: (NSString*)revID 29 | { 30 | TD_Revision* result = nil; 31 | FMResultSet *r = [_fmdb executeQuery: @"SELECT revid, json FROM localdocs WHERE docid=?",docID]; 32 | if ([r next]) { 33 | NSString* gotRevID = [r stringForColumnIndex: 0]; 34 | if (revID && !$equal(revID, gotRevID)) 35 | return nil; 36 | NSData* json = [r dataNoCopyForColumnIndex: 1]; 37 | NSMutableDictionary* properties; 38 | if (json.length==0 || (json.length==2 && memcmp(json.bytes, "{}", 2)==0)) 39 | properties = $mdict(); // workaround for issue #44 40 | else { 41 | properties = [TDJSON JSONObjectWithData: json 42 | options:TDJSONReadingMutableContainers 43 | error: NULL]; 44 | if (!properties) 45 | return nil; 46 | } 47 | properties[@"_id"] = docID; 48 | properties[@"_rev"] = gotRevID; 49 | result = [[TD_Revision alloc] initWithDocID: docID revID: gotRevID deleted:NO]; 50 | result.properties = properties; 51 | } 52 | [r close]; 53 | return result; 54 | } 55 | 56 | 57 | - (TD_Revision*) putLocalRevision: (TD_Revision*)revision 58 | prevRevisionID: (NSString*)prevRevID 59 | status: (TDStatus*)outStatus 60 | { 61 | NSString* docID = revision.docID; 62 | if (![docID hasPrefix: @"_local/"]) { 63 | *outStatus = kTDStatusBadID; 64 | return nil; 65 | } 66 | if (!revision.deleted) { 67 | // PUT: 68 | NSData* json = [self encodeDocumentJSON: revision]; 69 | NSString* newRevID; 70 | if (prevRevID) { 71 | unsigned generation = [TD_Revision generationFromRevID: prevRevID]; 72 | if (generation == 0) { 73 | *outStatus = kTDStatusBadID; 74 | return nil; 75 | } 76 | newRevID = $sprintf(@"%d-local", ++generation); 77 | if (![_fmdb executeUpdate: @"UPDATE localdocs SET revid=?, json=? " 78 | "WHERE docid=? AND revid=?", 79 | newRevID, json, docID, prevRevID]) { 80 | *outStatus = kTDStatusDBError; 81 | return nil; 82 | } 83 | } else { 84 | newRevID = @"1-local"; 85 | // The docid column is unique so the insert will be a no-op if there is already 86 | // a doc with this ID. 87 | if (![_fmdb executeUpdate: @"INSERT OR IGNORE INTO localdocs (docid, revid, json) " 88 | "VALUES (?, ?, ?)", 89 | docID, newRevID, json]) { 90 | *outStatus = kTDStatusDBError; 91 | return nil; 92 | } 93 | } 94 | if (_fmdb.changes == 0) { 95 | *outStatus = kTDStatusConflict; 96 | return nil; 97 | } 98 | *outStatus = kTDStatusCreated; 99 | return [revision copyWithDocID: docID revID: newRevID]; 100 | 101 | } else { 102 | // DELETE: 103 | *outStatus = [self deleteLocalDocumentWithID: docID revisionID: prevRevID]; 104 | return *outStatus < 300 ? revision : nil; 105 | } 106 | } 107 | 108 | 109 | - (TDStatus) deleteLocalDocumentWithID: (NSString*)docID revisionID: (NSString*)revID { 110 | if (!docID) 111 | return kTDStatusBadID; 112 | if (!revID) { 113 | // Didn't specify a revision to delete: kTDStatusNotFound or a kTDStatusConflict, depending 114 | return [self getLocalDocumentWithID: docID revisionID: nil] ? kTDStatusConflict : kTDStatusNotFound; 115 | } 116 | if (![_fmdb executeUpdate: @"DELETE FROM localdocs WHERE docid=? AND revid=?", docID, revID]) 117 | return kTDStatusDBError; 118 | if (_fmdb.changes == 0) 119 | return [self getLocalDocumentWithID: docID revisionID: nil] ? kTDStatusConflict : kTDStatusNotFound; 120 | return kTDStatusOK; 121 | } 122 | 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /Source/TD_Database+Replication.h: -------------------------------------------------------------------------------- 1 | // 2 | // TD_Database+Replication.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 1/18/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class TDReplicator; 11 | 12 | 13 | @interface TD_Database (Replication) 14 | 15 | @property (readonly) NSArray* activeReplicators; 16 | 17 | - (TDReplicator*) activeReplicatorLike: (TDReplicator*)repl; 18 | 19 | - (void) addActiveReplicator: (TDReplicator*)repl; 20 | 21 | - (BOOL) findMissingRevisions: (TD_RevisionList*)revs; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Source/TD_Database+Replication.m: -------------------------------------------------------------------------------- 1 | // 2 | // TD_Database+Replication.m 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/27/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import "TD_Database+Replication.h" 17 | #import "TDInternal.h" 18 | #import "TDPuller.h" 19 | #import "MYBlockUtils.h" 20 | 21 | #import "FMDatabase.h" 22 | #import "FMDatabaseAdditions.h" 23 | 24 | 25 | #define kActiveReplicatorCleanupDelay 10.0 26 | 27 | 28 | @implementation TD_Database (Replication) 29 | 30 | 31 | - (NSArray*) activeReplicators { 32 | return _activeReplicators; 33 | } 34 | 35 | - (void) addActiveReplicator: (TDReplicator*)repl { 36 | if (!_activeReplicators) { 37 | _activeReplicators = [[NSMutableArray alloc] init]; 38 | [[NSNotificationCenter defaultCenter] addObserver: self 39 | selector: @selector(replicatorDidStop:) 40 | name: TDReplicatorStoppedNotification 41 | object: nil]; 42 | } 43 | if (![_activeReplicators containsObject: repl]) 44 | [_activeReplicators addObject: repl]; 45 | } 46 | 47 | 48 | - (TDReplicator*) activeReplicatorLike: (TDReplicator*)repl { 49 | for (TDReplicator* activeRepl in _activeReplicators) { 50 | if ([activeRepl hasSameSettingsAs: repl]) 51 | return activeRepl; 52 | } 53 | return nil; 54 | } 55 | 56 | 57 | - (void) stopAndForgetReplicator: (TDReplicator*)repl { 58 | [repl databaseClosing]; 59 | [_activeReplicators removeObjectIdenticalTo: repl]; 60 | } 61 | 62 | 63 | - (void) replicatorDidStop: (NSNotification*)n { 64 | TDReplicator* repl = n.object; 65 | if (repl.error) // Leave it around a while so clients can see the error 66 | MYAfterDelay(kActiveReplicatorCleanupDelay, 67 | ^{[_activeReplicators removeObjectIdenticalTo: repl];}); 68 | else 69 | [_activeReplicators removeObjectIdenticalTo: repl]; 70 | } 71 | 72 | 73 | - (NSString*) lastSequenceWithCheckpointID: (NSString*)checkpointID { 74 | // This table schema is out of date but I'm keeping it the way it is for compatibility. 75 | // The 'remote' column now stores the opaque checkpoint IDs, and 'push' is ignored. 76 | return [_fmdb stringForQuery:@"SELECT last_sequence FROM replicators WHERE remote=?", 77 | checkpointID]; 78 | } 79 | 80 | - (BOOL) setLastSequence: (NSString*)lastSequence withCheckpointID: (NSString*)checkpointID { 81 | return [_fmdb executeUpdate: 82 | @"INSERT OR REPLACE INTO replicators (remote, push, last_sequence) VALUES (?, -1, ?)", 83 | checkpointID, lastSequence]; 84 | } 85 | 86 | 87 | + (NSString*) joinQuotedStrings: (NSArray*)strings { 88 | if (strings.count == 0) 89 | return @""; 90 | NSMutableString* result = [NSMutableString stringWithString: @"'"]; 91 | BOOL first = YES; 92 | for (NSString* str in strings) { 93 | if (first) 94 | first = NO; 95 | else 96 | [result appendString: @"','"]; 97 | NSRange range = NSMakeRange(result.length, str.length); 98 | [result appendString: str]; 99 | [result replaceOccurrencesOfString: @"'" withString: @"''" 100 | options: NSLiteralSearch range: range]; 101 | } 102 | [result appendString: @"'"]; 103 | return result; 104 | } 105 | 106 | 107 | - (BOOL) findMissingRevisions: (TD_RevisionList*)revs { 108 | if (revs.count == 0) 109 | return YES; 110 | NSString* sql = $sprintf(@"SELECT docid, revid FROM revs, docs " 111 | "WHERE revid in (%@) AND docid IN (%@) " 112 | "AND revs.doc_id == docs.doc_id", 113 | [TD_Database joinQuotedStrings: revs.allRevIDs], 114 | [TD_Database joinQuotedStrings: revs.allDocIDs]); 115 | // ?? Not sure sqlite will optimize this fully. May need a first query that looks up all 116 | // the numeric doc_ids from the docids. 117 | FMResultSet* r = [_fmdb executeQuery: sql]; 118 | if (!r) 119 | return NO; 120 | while ([r next]) { 121 | @autoreleasepool { 122 | TD_Revision* rev = [revs revWithDocID: [r stringForColumnIndex: 0] 123 | revID: [r stringForColumnIndex: 1]]; 124 | if (rev) 125 | [revs removeRev: rev]; 126 | } 127 | } 128 | [r close]; 129 | return YES; 130 | } 131 | 132 | 133 | @end 134 | -------------------------------------------------------------------------------- /Source/TD_DatabaseManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // TD_DatabaseManager.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 3/22/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TDStatus.h" 11 | @class TD_Database, TDReplicator, TDReplicatorManager; 12 | 13 | 14 | typedef struct TD_DatabaseManagerOptions { 15 | bool readOnly; 16 | bool noReplicator; 17 | } TD_DatabaseManagerOptions; 18 | 19 | extern const TD_DatabaseManagerOptions kTD_DatabaseManagerDefaultOptions; 20 | 21 | 22 | /** Manages a directory containing TD_Databases. */ 23 | @interface TD_DatabaseManager : NSObject 24 | { 25 | @private 26 | NSString* _dir; 27 | TD_DatabaseManagerOptions _options; 28 | NSMutableDictionary* _databases; 29 | TDReplicatorManager* _replicatorManager; 30 | } 31 | 32 | + (BOOL) isValidDatabaseName: (NSString*)name; 33 | 34 | - (id) initWithDirectory: (NSString*)dirPath 35 | options: (const TD_DatabaseManagerOptions*)options 36 | error: (NSError**)outError; 37 | 38 | @property (readonly) NSString* directory; 39 | 40 | - (TD_Database*) databaseNamed: (NSString*)name; 41 | - (TD_Database*) existingDatabaseNamed: (NSString*)name; 42 | 43 | - (BOOL) deleteDatabaseNamed: (NSString*)name; 44 | 45 | @property (readonly) NSArray* allDatabaseNames; 46 | @property (readonly) NSArray* allOpenDatabases; 47 | 48 | - (void) close; 49 | 50 | - (TDStatus) validateReplicatorProperties: (NSDictionary*)properties; 51 | - (TDReplicator*) replicatorWithProperties: (NSDictionary*)body 52 | status: (TDStatus*)outStatus; 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Source/TD_Revision.h: -------------------------------------------------------------------------------- 1 | // 2 | // TD_Revision.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/2/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class TD_Body; 11 | 12 | 13 | /** Database sequence ID */ 14 | typedef SInt64 SequenceNumber; 15 | 16 | 17 | /** Stores information about a revision -- its docID, revID, and whether it's deleted. It can also store the sequence number and document contents (they can be added after creation). */ 18 | @interface TD_Revision : NSObject 19 | { 20 | @private 21 | NSString* _docID, *_revID; 22 | TD_Body* _body; 23 | SequenceNumber _sequence; 24 | bool _deleted; 25 | bool _missing; 26 | } 27 | 28 | - (id) initWithDocID: (NSString*)docID 29 | revID: (NSString*)revID 30 | deleted: (BOOL)deleted; 31 | - (id) initWithBody: (TD_Body*)body; 32 | - (id) initWithProperties: (NSDictionary*)properties; 33 | 34 | + (TD_Revision*) revisionWithProperties: (NSDictionary*)properties; 35 | 36 | @property (readonly) NSString* docID; 37 | @property (readonly) NSString* revID; 38 | @property (readonly) bool deleted; 39 | @property bool missing; 40 | 41 | @property (strong) TD_Body* body; 42 | @property (copy) NSDictionary* properties; 43 | @property (copy) NSData* asJSON; 44 | 45 | - (id) objectForKeyedSubscript: (NSString*)key; // enables subscript access in Xcode 4.4+ 46 | 47 | @property SequenceNumber sequence; 48 | 49 | - (NSComparisonResult) compareSequences: (TD_Revision*)rev; 50 | 51 | /** Generation number: 1 for a new document, 2 for the 2nd revision, ... 52 | Extracted from the numeric prefix of the revID. */ 53 | @property (readonly) unsigned generation; 54 | 55 | + (unsigned) generationFromRevID: (NSString*)revID; 56 | 57 | + (BOOL) parseRevID: (NSString*)revID 58 | intoGeneration: (int*)outNum 59 | andSuffix: (NSString**)outSuffix; 60 | 61 | - (TD_Revision*) copyWithDocID: (NSString*)docID revID: (NSString*)revID; 62 | 63 | @end 64 | 65 | 66 | 67 | /** An ordered list of TDRevs. */ 68 | @interface TD_RevisionList : NSObject 69 | { 70 | @private 71 | NSMutableArray* _revs; 72 | } 73 | 74 | - (id) init; 75 | - (id) initWithArray: (NSArray*)revs; 76 | 77 | @property (readonly) NSUInteger count; 78 | 79 | - (TD_Revision*) revWithDocID: (NSString*)docID revID: (NSString*)revID; 80 | 81 | - (NSEnumerator*) objectEnumerator; 82 | 83 | @property (readonly) NSArray* allRevisions; 84 | @property (readonly) NSArray* allDocIDs; 85 | @property (readonly) NSArray* allRevIDs; 86 | 87 | - (TD_Revision*) objectAtIndexedSubscript: (NSUInteger)index; // enables subscript access in XC4.4+ 88 | 89 | - (void) addRev: (TD_Revision*)rev; 90 | - (void) removeRev: (TD_Revision*)rev; 91 | 92 | - (void) limit: (NSUInteger)limit; 93 | - (void) sortBySequence; 94 | 95 | @end 96 | 97 | 98 | /** Compares revision IDs by CouchDB rules: generation number first, then the suffix. */ 99 | NSComparisonResult TDCompareRevIDs(NSString* revID1, NSString* revID2); 100 | 101 | /** SQLite-compatible collation (comparison) function for revision IDs. */ 102 | int TDCollateRevIDs(void *context, 103 | int len1, const void * chars1, 104 | int len2, const void * chars2); 105 | -------------------------------------------------------------------------------- /Source/TD_Server.h: -------------------------------------------------------------------------------- 1 | // 2 | // TD_Server.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 11/30/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "TD_DatabaseManager.h" 10 | 11 | 12 | /** Thread-safe top-level interface to TouchDB API. 13 | A TD_Server owns a background thread on which it runs a TD_DatabaseManager and all related tasks. 14 | The database objects can only be called by queueing blocks that will run on the background thread. */ 15 | @interface TD_Server : NSObject 16 | { 17 | @private 18 | TD_DatabaseManager* _manager; 19 | NSThread* _serverThread; 20 | BOOL _stopRunLoop; 21 | } 22 | 23 | - (id) initWithDirectory: (NSString*)dirPath error: (NSError**)outError; 24 | 25 | - (id) initWithDirectory: (NSString*)dirPath 26 | options: (const TD_DatabaseManagerOptions*)options 27 | error: (NSError**)outError; 28 | 29 | @property (readonly) NSString* directory; 30 | 31 | - (void) queue: (void(^)())block; 32 | - (void) tellDatabaseManager: (void (^)(TD_DatabaseManager*))block; 33 | - (void) tellDatabaseNamed: (NSString*)dbName to: (void (^)(TD_Database*))block; 34 | - (id) waitForDatabaseManager: (id (^)(TD_DatabaseManager*))block; 35 | 36 | - (void) close; 37 | 38 | @end 39 | 40 | 41 | /** Starts a TD_Server and registers it with TDURLProtocol so you can call it using the CouchDB-compatible REST API. 42 | @param serverDirectory The top-level directory where you want the server to store databases. Will be created if it does not already exist. 43 | @param outError An error will be stored here if the function returns nil. 44 | @return The root URL of the REST API, or nil if the server failed to start. */ 45 | NSURL* TDStartServer(NSString* serverDirectory, NSError** outError); 46 | -------------------------------------------------------------------------------- /Source/TD_View.h: -------------------------------------------------------------------------------- 1 | // 2 | // TD_View.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/8/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | typedef void (^TDMapEmitBlock)(id key, id value); 14 | 15 | /** A "map" function called when a document is to be added to a view. 16 | @param doc The contents of the document being analyzed. 17 | @param emit A block to be called to add a key/value pair to the view. Your block can call it zero, one or multiple times. */ 18 | typedef void (^TDMapBlock)(NSDictionary* doc, TDMapEmitBlock emit); 19 | 20 | /** A "reduce" function called to summarize the results of a view. 21 | @param keys An array of keys to be reduced (or nil if this is a rereduce). 22 | @param values A parallel array of values to be reduced, corresponding 1::1 with the keys. 23 | @param rereduce YES if the input values are the results of previous reductions. 24 | @return The reduced value; almost always a scalar or small fixed-size object. */ 25 | typedef id (^TDReduceBlock)(NSArray* keys, NSArray* values, BOOL rereduce); 26 | 27 | 28 | /** Standard query options for views. */ 29 | typedef struct TDQueryOptions { 30 | __unsafe_unretained id startKey; 31 | __unsafe_unretained id endKey; 32 | __unsafe_unretained NSArray* keys; 33 | unsigned skip; 34 | unsigned limit; 35 | unsigned groupLevel; 36 | TDContentOptions content; 37 | BOOL descending; 38 | BOOL includeDocs; 39 | BOOL updateSeq; 40 | BOOL inclusiveEnd; 41 | BOOL reduce; 42 | BOOL group; 43 | BOOL includeDeletedDocs; // only works with _all_docs, not regular views 44 | } TDQueryOptions; 45 | 46 | extern const TDQueryOptions kDefaultTDQueryOptions; 47 | 48 | 49 | typedef enum { 50 | kTDViewCollationUnicode, 51 | kTDViewCollationRaw, 52 | kTDViewCollationASCII 53 | } TDViewCollation; 54 | 55 | 56 | /** An external object that knows how to map source code of some sort into executable functions. */ 57 | @protocol TDViewCompiler 58 | - (TDMapBlock) compileMapFunction: (NSString*)mapSource language: (NSString*)language; 59 | - (TDReduceBlock) compileReduceFunction: (NSString*)reduceSource language: (NSString*)language; 60 | @end 61 | 62 | 63 | /** Represents a view available in a database. */ 64 | @interface TD_View : NSObject 65 | { 66 | @private 67 | TD_Database* __weak _db; 68 | NSString* _name; 69 | int _viewID; 70 | TDMapBlock _mapBlock; 71 | TDReduceBlock _reduceBlock; 72 | TDViewCollation _collation; 73 | TDContentOptions _mapContentOptions; 74 | } 75 | 76 | - (void) deleteView; 77 | 78 | @property (readonly) TD_Database* database; 79 | @property (readonly) NSString* name; 80 | 81 | @property (readonly) TDMapBlock mapBlock; 82 | @property (readonly) TDReduceBlock reduceBlock; 83 | 84 | @property TDViewCollation collation; 85 | @property TDContentOptions mapContentOptions; 86 | 87 | - (BOOL) setMapBlock: (TDMapBlock)mapBlock 88 | reduceBlock: (TDReduceBlock)reduceBlock 89 | version: (NSString*)version; 90 | 91 | /** Compiles a view (using the registered TDViewCompiler) from the properties found in a CouchDB-style design document. */ 92 | - (BOOL) compileFromProperties: (NSDictionary*)viewProps; 93 | 94 | - (void) removeIndex; 95 | 96 | /** Is the view's index currently out of date? */ 97 | @property (readonly) BOOL stale; 98 | 99 | /** Updates the view's index (incrementally) if necessary. 100 | @return 200 if updated, 304 if already up-to-date, else an error code */ 101 | - (TDStatus) updateIndex; 102 | 103 | @property (readonly) SequenceNumber lastSequenceIndexed; 104 | 105 | /** Queries the view. Does NOT first update the index. 106 | @param options The options to use. 107 | @return An array of result rows -- each is a dictionary with "key" and "value" keys, and possibly "id" and "doc". */ 108 | - (NSArray*) queryWithOptions: (const TDQueryOptions*)options 109 | status: (TDStatus*)outStatus; 110 | 111 | /** Utility function to use in reduce blocks. Totals an array of NSNumbers. */ 112 | + (NSNumber*) totalValues: (NSArray*)values; 113 | 114 | + (void) setCompiler: (id)compiler; 115 | + (id) compiler; 116 | 117 | @end 118 | -------------------------------------------------------------------------------- /Source/TouchDB-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.couchbase.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | ${CURRENT_PROJECT_VERSION} 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | ${CURRENT_PROJECT_VERSION} 25 | NSHumanReadableCopyright 26 | Copyright © 2012 Couchbase, Inc. All rights reserved. 27 | NSPrincipalClass 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Source/TouchDB.exp: -------------------------------------------------------------------------------- 1 | # Symbols exported by the framework 2 | .objc_class_name_TD_Database 3 | .objc_class_name_TD_View 4 | .objc_class_name_TDBlobStore 5 | .objc_class_name_TDBlobStoreWriter 6 | .objc_class_name_TD_Body 7 | .objc_class_name_TD_Revision 8 | .objc_class_name_TD_RevisionList 9 | 10 | .objc_class_name_TDReplicator 11 | .objc_class_name_TDPusher 12 | .objc_class_name_TDPuller 13 | .objc_class_name_TDPersonaAuthorizer 14 | 15 | .objc_class_name_TDURLProtocol 16 | .objc_class_name_TDRouter 17 | .objc_class_name_TD_Server 18 | .objc_class_name_TD_DatabaseManager 19 | 20 | _TDStatusToHTTPStatus 21 | _TDStatusToNSError 22 | 23 | _TD_DatabaseChangeNotification 24 | _TD_DatabaseWillCloseNotification 25 | _TDReplicatorProgressChangedNotification 26 | _TDReplicatorStoppedNotification 27 | 28 | _kTD_DatabaseManagerDefaultOptions 29 | -------------------------------------------------------------------------------- /Source/TouchDB.h: -------------------------------------------------------------------------------- 1 | // 2 | // TouchDB.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/2/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | 16 | #import 17 | #import 18 | #import 19 | #import 20 | 21 | #import 22 | 23 | #import 24 | -------------------------------------------------------------------------------- /Source/TouchDBPrefix.h: -------------------------------------------------------------------------------- 1 | // 2 | // TouchDBPrefix.h 3 | // TouchDB 4 | // 5 | // Created by Jens Alfke on 12/7/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #ifdef __OBJC__ 10 | 11 | #ifdef GNUSTEP 12 | #import "TDGNUstep.h" 13 | #endif 14 | 15 | #import 16 | 17 | #import "TDJSON.h" 18 | 19 | #import "CollectionUtils.h" 20 | #import "Logging.h" 21 | #import "Test.h" 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /TouchDB.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TouchDB.xcodeproj/project.xcworkspace/xcshareddata/TouchDB.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectIdentifier 6 | 803EBEF9-48D8-4D9E-8D55-A003EC250E30 7 | IDESourceControlProjectName 8 | TouchDB 9 | IDESourceControlProjectOriginsDictionary 10 | 11 | 0DC589DD-AC60-4BE4-BFF8-438608B4486E 12 | git://github.com/couchbaselabs/ios-oauthconsumer.git 13 | 276FA442-E663-4F8A-9996-7CCD0835EFD1 14 | git://github.com/snej/MYUtilities.git 15 | 30B0BC09-8C70-40C1-A3CB-322BC91CA6EA 16 | git://github.com/couchbaselabs/CocoaHTTPServer.git 17 | 3766A697-D255-4F30-B6C7-305427917365 18 | ssh://github.com/couchbaselabs/CouchCocoa.git 19 | 555843E8-6F06-4308-98C6-DC96F1F4CB71 20 | ssh://github.com/couchbaselabs/TouchDB-iOS.git 21 | 722BA749-E14C-43CE-80C0-D2667D95CAE9 22 | git://github.com/couchbaselabs/fmdb.git 23 | 24 | IDESourceControlProjectPath 25 | TouchDB.xcodeproj/project.xcworkspace 26 | IDESourceControlProjectRelativeInstallPathDictionary 27 | 28 | 0DC589DD-AC60-4BE4-BFF8-438608B4486E 29 | ../../vendor/oauthconsumer 30 | 276FA442-E663-4F8A-9996-7CCD0835EFD1 31 | ../../vendor/MYUtilities 32 | 30B0BC09-8C70-40C1-A3CB-322BC91CA6EA 33 | ../../vendor/CocoaHTTPServer 34 | 3766A697-D255-4F30-B6C7-305427917365 35 | ../../../CouchCocoa 36 | 555843E8-6F06-4308-98C6-DC96F1F4CB71 37 | ../.. 38 | 722BA749-E14C-43CE-80C0-D2667D95CAE9 39 | ../../vendor/fmdb 40 | 41 | IDESourceControlProjectURL 42 | ssh://github.com/couchbaselabs/TouchDB-iOS.git 43 | IDESourceControlProjectVersion 44 | 110 45 | IDESourceControlProjectWCCIdentifier 46 | 555843E8-6F06-4308-98C6-DC96F1F4CB71 47 | IDESourceControlProjectWCConfigurations 48 | 49 | 50 | IDESourceControlRepositoryExtensionIdentifierKey 51 | public.vcs.git 52 | IDESourceControlWCCIdentifierKey 53 | 555843E8-6F06-4308-98C6-DC96F1F4CB71 54 | IDESourceControlWCCName 55 | TouchDB 56 | 57 | 58 | IDESourceControlRepositoryExtensionIdentifierKey 59 | public.vcs.git 60 | IDESourceControlWCCIdentifierKey 61 | 30B0BC09-8C70-40C1-A3CB-322BC91CA6EA 62 | IDESourceControlWCCName 63 | CocoaHTTPServer 64 | 65 | 66 | IDESourceControlRepositoryExtensionIdentifierKey 67 | public.vcs.git 68 | IDESourceControlWCCIdentifierKey 69 | 0DC589DD-AC60-4BE4-BFF8-438608B4486E 70 | IDESourceControlWCCName 71 | oauthconsumer 72 | 73 | 74 | IDESourceControlRepositoryExtensionIdentifierKey 75 | public.vcs.git 76 | IDESourceControlWCCIdentifierKey 77 | 276FA442-E663-4F8A-9996-7CCD0835EFD1 78 | IDESourceControlWCCName 79 | MYUtilities 80 | 81 | 82 | IDESourceControlRepositoryExtensionIdentifierKey 83 | public.vcs.git 84 | IDESourceControlWCCIdentifierKey 85 | 3766A697-D255-4F30-B6C7-305427917365 86 | IDESourceControlWCCName 87 | CouchCocoa 88 | 89 | 90 | IDESourceControlRepositoryExtensionIdentifierKey 91 | public.vcs.git 92 | IDESourceControlWCCIdentifierKey 93 | 722BA749-E14C-43CE-80C0-D2667D95CAE9 94 | IDESourceControlWCCName 95 | fmdb 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /vendor/google-toolbox-for-mac/GTMNSData+zlib.h: -------------------------------------------------------------------------------- 1 | // 2 | // GTMNSData+zlib.h 3 | // 4 | // Copyright 2007-2008 Google Inc. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy 8 | // of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | // License for the specific language governing permissions and limitations under 16 | // the License. 17 | // 18 | 19 | #import 20 | #import "GTMDefines.h" 21 | 22 | /// Helpers for dealing w/ zlib inflate/deflate calls. 23 | @interface NSData (GTMZLibAdditions) 24 | 25 | /// Return an autoreleased NSData w/ the result of gzipping the bytes. 26 | // 27 | // Uses the default compression level. 28 | + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes 29 | length:(NSUInteger)length; 30 | 31 | /// Return an autoreleased NSData w/ the result of gzipping the payload of |data|. 32 | // 33 | // Uses the default compression level. 34 | + (NSData *)gtm_dataByGzippingData:(NSData *)data; 35 | 36 | /// Return an autoreleased NSData w/ the result of gzipping the bytes using |level| compression level. 37 | // 38 | // |level| can be 1-9, any other values will be clipped to that range. 39 | + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes 40 | length:(NSUInteger)length 41 | compressionLevel:(int)level; 42 | 43 | /// Return an autoreleased NSData w/ the result of gzipping the payload of |data| using |level| compression level. 44 | + (NSData *)gtm_dataByGzippingData:(NSData *)data 45 | compressionLevel:(int)level; 46 | 47 | // NOTE: deflate is *NOT* gzip. deflate is a "zlib" stream. pick which one 48 | // you really want to create. (the inflate api will handle either) 49 | 50 | /// Return an autoreleased NSData w/ the result of deflating the bytes. 51 | // 52 | // Uses the default compression level. 53 | + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes 54 | length:(NSUInteger)length; 55 | 56 | /// Return an autoreleased NSData w/ the result of deflating the payload of |data|. 57 | // 58 | // Uses the default compression level. 59 | + (NSData *)gtm_dataByDeflatingData:(NSData *)data; 60 | 61 | /// Return an autoreleased NSData w/ the result of deflating the bytes using |level| compression level. 62 | // 63 | // |level| can be 1-9, any other values will be clipped to that range. 64 | + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes 65 | length:(NSUInteger)length 66 | compressionLevel:(int)level; 67 | 68 | /// Return an autoreleased NSData w/ the result of deflating the payload of |data| using |level| compression level. 69 | + (NSData *)gtm_dataByDeflatingData:(NSData *)data 70 | compressionLevel:(int)level; 71 | 72 | 73 | /// Return an autoreleased NSData w/ the result of decompressing the bytes. 74 | // 75 | // The bytes to decompress can be zlib or gzip payloads. 76 | + (NSData *)gtm_dataByInflatingBytes:(const void *)bytes 77 | length:(NSUInteger)length; 78 | 79 | /// Return an autoreleased NSData w/ the result of decompressing the payload of |data|. 80 | // 81 | // The data to decompress can be zlib or gzip payloads. 82 | + (NSData *)gtm_dataByInflatingData:(NSData *)data; 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /vendor/google-toolbox-for-mac/README: -------------------------------------------------------------------------------- 1 | This directory contains a couple of source files from the Google Toolkit For Mac (GTM) repository. 2 | It's a Subversion repo so I'm not including it here as a real Git submodule. 3 | The source repo is at 4 | --Jens --------------------------------------------------------------------------------