├── .gitignore ├── .gitmodules ├── Couch ├── CouchAttachment.h ├── CouchAttachment.m ├── CouchChangeTracker.h ├── CouchChangeTracker.m ├── CouchCocoa.h ├── CouchConnectionChangeTracker.h ├── CouchConnectionChangeTracker.m ├── CouchDatabase.h ├── CouchDatabase.m ├── CouchDesignDocument.h ├── CouchDesignDocument.m ├── CouchDesignDocument_Embedded.h ├── CouchDesignDocument_Embedded.m ├── CouchDocument.h ├── CouchDocument.m ├── CouchEmbeddedServer.h ├── CouchEmbeddedServer.m ├── CouchInternal.h ├── CouchPersistentReplication.h ├── CouchPersistentReplication.m ├── CouchPrefix.pch ├── CouchQuery.h ├── CouchQuery.m ├── CouchReplication.h ├── CouchReplication.m ├── CouchResource.h ├── CouchResource.m ├── CouchRevision.h ├── CouchRevision.m ├── CouchServer.h ├── CouchServer.m ├── CouchSocketChangeTracker.h ├── CouchSocketChangeTracker.m ├── CouchTouchDBDatabase.h ├── CouchTouchDBDatabase.m ├── CouchTouchDBServer.h ├── CouchTouchDBServer.m ├── CouchbaseCallbacks.h └── CouchbaseMobile.h ├── CouchCocoa.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── Demo-Addresses.xcscheme │ ├── Demo-Shopping.xcscheme │ ├── Documentation.xcscheme │ ├── Mac Framework.xcscheme │ └── iOS Framework.xcscheme ├── Demo ├── AddressCard.h ├── AddressCard.m ├── AddressesDemo-Info.plist ├── AddressesDemo.xib ├── DemoAppController.h ├── DemoAppController.m ├── DemoQuery.h ├── DemoQuery.m ├── ShoppingDemo-Info.plist ├── ShoppingDemo.xib ├── ShoppingItem.h └── ShoppingItem.m ├── Doxyfile ├── Model ├── CouchDynamicObject.h ├── CouchDynamicObject.m ├── CouchModel.h ├── CouchModel.m ├── CouchModelFactory.h └── CouchModelFactory.m ├── README.md ├── REST ├── REST.h ├── RESTBase64.h ├── RESTBase64.m ├── RESTBody.h ├── RESTBody.m ├── RESTCache.h ├── RESTCache.m ├── RESTInternal.h ├── RESTInternal.m ├── RESTOperation.h ├── RESTOperation.m ├── RESTResource.h └── RESTResource.m ├── Resources ├── CouchCocoa-Info.plist └── logo.png ├── Test ├── CouchTestCase.h ├── CouchTestCase.m ├── Resources │ ├── CouchCocoa-Info.plist │ └── logo.png ├── Test_Couch.m ├── Test_DynamicObject.m ├── Test_Model.m ├── Test_REST.m ├── libcrypto-iphonesimulator.a └── logo.png └── UI └── iOS ├── CouchUITableSource.h └── CouchUITableSource.m /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | *.pbxuser 4 | *.perspectivev3 5 | *.mode1v3 6 | xcuserdata/ 7 | build/ 8 | Documentation/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Vendor/JSONKit"] 2 | path = Vendor/JSONKit 3 | url = git://github.com/johnezang/JSONKit.git 4 | -------------------------------------------------------------------------------- /Couch/CouchAttachment.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchAttachment.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 5/26/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 "CouchResource.h" 17 | @class CouchDocument, CouchRevision; 18 | 19 | 20 | /** A binary attachment to a document. 21 | Actually a CouchAttachment may be a child of either a CouchDocument or a CouchRevision. The latter represents an attachment immutably as it appeared in one revision of its document. So if you PUT a change to an attachment, the updated attachment will have a new CouchAttachment object. */ 22 | @interface CouchAttachment : CouchResource 23 | { 24 | @private 25 | NSDictionary* _metadata; 26 | } 27 | 28 | /** The owning document revision. */ 29 | @property (readonly) CouchRevision* revision; 30 | 31 | /** The owning document. */ 32 | @property (readonly) CouchDocument* document; 33 | 34 | /** The filename (last URL path component). */ 35 | @property (readonly) NSString* name; 36 | 37 | /** The MIME type of the contents. */ 38 | @property (readonly) NSString* contentType; 39 | 40 | /** The length in bytes of the contents. */ 41 | @property (readonly) UInt64 length; 42 | 43 | /** The CouchDB metadata about the attachment, that lives in the document. */ 44 | @property (readonly) NSDictionary* metadata; 45 | 46 | /** Synchronous accessors for the body data. 47 | These are convenient, but have no means of error handling. */ 48 | @property (copy) NSData* body; 49 | 50 | /** The attachment's URL without the revision ID. 51 | This URL will always resolve to the current revision of the attachment. */ 52 | @property (readonly) NSURL* unversionedURL; 53 | 54 | /** Asynchronous setter for the body. (Use inherited -GET to get it.) */ 55 | - (RESTOperation*) PUT: (NSData*)body contentType: (NSString*)contentType; 56 | 57 | /** Asynchronous setter for the body. (Use inherited -GET to get it.) */ 58 | - (RESTOperation*) PUT: (NSData*)body; 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /Couch/CouchAttachment.m: -------------------------------------------------------------------------------- 1 | // 2 | // CouchAttachment.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 5/26/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 "CouchAttachment.h" 17 | #import "CouchInternal.h" 18 | 19 | 20 | @implementation CouchAttachment 21 | 22 | 23 | - (id) initWithParent: (CouchResource*)parent 24 | name: (NSString*)name 25 | metadata: (NSDictionary*)metadata 26 | { 27 | NSParameterAssert(metadata); 28 | self = [super initWithParent: parent relativePath: name]; 29 | if (self) { 30 | _metadata = [metadata copy]; 31 | } 32 | return self; 33 | } 34 | 35 | 36 | - (void)dealloc { 37 | [_metadata release]; 38 | [super dealloc]; 39 | } 40 | 41 | 42 | @synthesize metadata=_metadata; 43 | 44 | 45 | - (NSString*) name { 46 | return self.relativePath; 47 | } 48 | 49 | 50 | - (CouchRevision*) revision { 51 | id parent = self.parent; 52 | if ([parent isKindOfClass: [CouchRevision class]]) 53 | return parent; 54 | else 55 | return nil; 56 | } 57 | 58 | 59 | - (CouchDocument*) document { 60 | RESTResource* parent = self.parent; 61 | if ([parent isKindOfClass: [CouchRevision class]]) 62 | parent = [parent parent]; 63 | return (CouchDocument*)parent; 64 | } 65 | 66 | 67 | - (NSURL*) unversionedURL { 68 | return [self.document.URL URLByAppendingPathComponent: self.name]; 69 | } 70 | 71 | 72 | - (NSString*) contentType { 73 | return $castIf(NSString, [_metadata objectForKey: @"content_type"]); 74 | } 75 | 76 | 77 | - (UInt64) length { 78 | NSNumber* lengthObj = $castIf(NSNumber, [_metadata objectForKey: @"length"]); 79 | return lengthObj ? [lengthObj longLongValue] : 0; 80 | } 81 | 82 | 83 | #pragma mark - 84 | #pragma mark BODY 85 | 86 | 87 | - (RESTOperation*) PUT: (NSData*)body contentType: (NSString*)contentType { 88 | NSDictionary* params = [NSDictionary dictionaryWithObject: contentType 89 | forKey: @"Content-Type"]; 90 | return [self PUT: body parameters: params]; 91 | } 92 | 93 | 94 | - (RESTOperation*) PUT: (NSData*)body { 95 | return [self PUT: body contentType: self.contentType]; 96 | } 97 | 98 | 99 | - (NSData*) body { 100 | NSData* body = [RESTBody dataWithBase64: $castIf(NSString, [_metadata objectForKey: @"data"])]; 101 | if (body) 102 | return body; 103 | 104 | RESTOperation* op = [self GET]; 105 | if ([op wait]) 106 | return op.responseBody.content; 107 | else { 108 | Warn(@"Synchronous CouchAttachment.body getter failed: %@", op.error); 109 | return nil; 110 | } 111 | } 112 | 113 | 114 | - (void) setBody: (NSData*)body { 115 | RESTOperation* op = [self PUT: body]; 116 | if (![op wait]) 117 | Warn(@"Synchronous CouchAttachment.body setter failed: %@", op.error); 118 | } 119 | 120 | /* 121 | - (NSMutableURLRequest*) requestWithMethod: (NSString*)method 122 | parameters: (NSDictionary*)parameters { 123 | if ([method isEqualToString: @"PUT"] || [method isEqualToString: @"DELETE"]) { 124 | // Add a ?rev= query param with the current document revision: 125 | NSString* revisionID = self.revision.revisionID; 126 | if (revisionID) { 127 | NSMutableDictionary* nuParams = [[parameters mutableCopy] autorelease]; 128 | if (!nuParams) 129 | nuParams = [NSMutableDictionary dictionary]; 130 | [nuParams setObject: revisionID forKey: @"?rev"]; 131 | parameters = nuParams; 132 | } 133 | } 134 | return [super requestWithMethod: method parameters: parameters]; 135 | } 136 | */ 137 | 138 | - (NSError*) operation: (RESTOperation*)op willCompleteWithError: (NSError*)error { 139 | error = [super operation: op willCompleteWithError: error]; 140 | 141 | if (!error && op.isSuccessful) { 142 | if (op.isPUT) { 143 | NSString* revisionID = $castIf(NSString, [op.responseBody.fromJSON objectForKey: @"rev"]); 144 | if (revisionID) 145 | self.document.currentRevisionID = revisionID; 146 | } 147 | } 148 | return error; 149 | } 150 | 151 | 152 | @end 153 | -------------------------------------------------------------------------------- /Couch/CouchChangeTracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchChangeTracker.h 3 | // CouchCocoa 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 CouchChangeTracker; 18 | 19 | 20 | @protocol CouchChangeTrackerClient 21 | - (void) changeTrackerReceivedChange: (NSDictionary*)change; 22 | @optional 23 | - (NSURLCredential*) authCredential; 24 | - (void) changeTrackerStopped: (CouchChangeTracker*)tracker; 25 | @end 26 | 27 | 28 | typedef enum CouchChangeTrackerMode { 29 | kOneShot, 30 | kLongPoll, 31 | kContinuous 32 | } CouchChangeTrackerMode; 33 | 34 | 35 | /** Reads the continuous-mode _changes feed of a database, and sends the individual change entries to its client's -changeTrackerReceivedChange:. 36 | This class is used internally by CouchDatabase and you shouldn't need to use it yourself. */ 37 | @interface CouchChangeTracker : NSObject 38 | { 39 | @protected 40 | NSURL* _databaseURL; 41 | id _client; 42 | CouchChangeTrackerMode _mode; 43 | NSUInteger _lastSequenceNumber; 44 | } 45 | 46 | - (id)initWithDatabaseURL: (NSURL*)databaseURL 47 | mode: (CouchChangeTrackerMode)mode 48 | lastSequence: (NSUInteger)lastSequence 49 | client: (id)client; 50 | 51 | @property (readonly, nonatomic) NSURL* databaseURL; 52 | @property (readonly, nonatomic) NSString* databaseName; 53 | @property (readonly, nonatomic) CouchChangeTrackerMode mode; 54 | @property (readonly, nonatomic) NSUInteger lastSequenceNumber; 55 | 56 | - (BOOL) start; 57 | - (void) stop; 58 | 59 | // Protected 60 | @property (readonly) NSURLCredential* authCredential; 61 | @property (readonly) NSURL* changesFeedURL; 62 | @property (readonly) NSString* changesFeedPath; 63 | - (void) receivedChunk: (NSData*)chunk; 64 | - (BOOL) receivedPollResponse: (NSData*)body; 65 | - (void) stopped; // override this 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /Couch/CouchChangeTracker.m: -------------------------------------------------------------------------------- 1 | // 2 | // CouchChangeTracker.m 3 | // CouchCocoa 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 | // 17 | 18 | #import "CouchChangeTracker.h" 19 | #import "CouchConnectionChangeTracker.h" 20 | #import "CouchSocketChangeTracker.h" 21 | #import "CouchInternal.h" 22 | 23 | 24 | @implementation CouchChangeTracker 25 | 26 | @synthesize lastSequenceNumber=_lastSequenceNumber, databaseURL=_databaseURL, mode=_mode; 27 | 28 | - (id)initWithDatabaseURL: (NSURL*)databaseURL 29 | mode: (CouchChangeTrackerMode)mode 30 | lastSequence: (NSUInteger)lastSequence 31 | client: (id)client { 32 | NSParameterAssert(databaseURL); 33 | NSParameterAssert(client); 34 | self = [super init]; 35 | if (self) { 36 | if ([self class] == [CouchChangeTracker class]) { 37 | [self release]; 38 | if (mode == kContinuous && [databaseURL.scheme.lowercaseString hasPrefix: @"http"]) { 39 | return (id) [[CouchSocketChangeTracker alloc] initWithDatabaseURL: databaseURL 40 | mode: mode 41 | lastSequence: lastSequence 42 | client: client]; 43 | } else { 44 | return (id) [[CouchConnectionChangeTracker alloc] initWithDatabaseURL: databaseURL 45 | mode: mode 46 | lastSequence: lastSequence 47 | client: client]; 48 | } 49 | } 50 | 51 | _databaseURL = [databaseURL retain]; 52 | _client = client; 53 | _mode = mode; 54 | _lastSequenceNumber = lastSequence; 55 | } 56 | return self; 57 | } 58 | 59 | - (NSString*) databaseName { 60 | return _databaseURL.lastPathComponent; 61 | } 62 | 63 | - (NSString*) changesFeedPath { 64 | static NSString* const kModeNames[3] = {@"normal", @"longpoll", @"continuous"}; 65 | return [NSString stringWithFormat: @"_changes?feed=%@&heartbeat=300000&since=%lu", 66 | kModeNames[_mode], 67 | (unsigned long)_lastSequenceNumber]; 68 | } 69 | 70 | - (NSURL*) changesFeedURL { 71 | return [NSURL URLWithString: [NSString stringWithFormat: @"%@/%@", 72 | _databaseURL.absoluteString, self.changesFeedPath]]; 73 | } 74 | 75 | - (NSString*) description { 76 | return [NSString stringWithFormat: @"%@[%@]", [self class], self.databaseName]; 77 | } 78 | 79 | - (void)dealloc { 80 | [self stop]; 81 | [_databaseURL release]; 82 | [super dealloc]; 83 | } 84 | 85 | - (NSURLCredential*) authCredential { 86 | if ([_client respondsToSelector: @selector(authCredential)]) 87 | return _client.authCredential; 88 | else 89 | return nil; 90 | } 91 | 92 | - (BOOL) start { 93 | return NO; 94 | } 95 | 96 | - (void) stop { 97 | [self stopped]; 98 | } 99 | 100 | - (void) stopped { 101 | if ([_client respondsToSelector: @selector(changeTrackerStopped:)]) 102 | [_client changeTrackerStopped: self]; 103 | } 104 | 105 | - (BOOL) receivedChange: (NSDictionary*)change { 106 | if (![change isKindOfClass: [NSDictionary class]]) 107 | return NO; 108 | id seq = [change objectForKey: @"seq"]; 109 | if (!seq) 110 | return NO; 111 | [_client changeTrackerReceivedChange: change]; 112 | _lastSequenceNumber = [seq intValue]; 113 | return YES; 114 | } 115 | 116 | - (void) receivedChunk: (NSData*)chunk { 117 | NSString* line = [[[NSString alloc] initWithData: chunk encoding:NSUTF8StringEncoding] 118 | autorelease]; 119 | if (!line) { 120 | Warn(@"Couldn't parse UTF-8 from _changes"); 121 | return; 122 | } 123 | if (line.length == 0 || [line isEqualToString: @"\n"]) 124 | return; 125 | id change = [RESTBody JSONObjectWithString: line]; 126 | if (!change) 127 | Warn(@"Received unparseable change line from server: %@", line); 128 | else if (![self receivedChange: change]) { 129 | COUCHLOG(@"%@: Couldn't interpret change line %@", self, line); 130 | } 131 | } 132 | 133 | - (BOOL) receivedPollResponse: (NSData*)body { 134 | if (!body) 135 | return NO; 136 | NSDictionary* changeDict = $castIf(NSDictionary, 137 | [RESTBody JSONObjectWithData: body]); 138 | NSArray* changes = $castIf(NSArray, [changeDict objectForKey: @"results"]); 139 | if (!changes) 140 | return NO; 141 | for (NSDictionary* change in changes) { 142 | if (![self receivedChange: change]) 143 | return NO; 144 | } 145 | return YES; 146 | } 147 | 148 | @end 149 | -------------------------------------------------------------------------------- /Couch/CouchCocoa.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchCocoa.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/12/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 "REST.h" 17 | #import "CouchAttachment.h" 18 | #import "CouchDatabase.h" 19 | #import "CouchDesignDocument.h" 20 | #import "CouchDocument.h" 21 | #import "CouchModel.h" 22 | #import "CouchPersistentReplication.h" 23 | #import "CouchQuery.h" 24 | #import "CouchRevision.h" 25 | #import "CouchServer.h" 26 | 27 | #import "CouchTouchDBServer.h" 28 | #import "CouchTouchDBDatabase.h" 29 | #import "CouchDesignDocument_Embedded.h" 30 | 31 | 32 | /** @mainpage About CouchCocoa 33 | 34 | @section intro_sec Introduction 35 | 36 | CouchCocoa is a medium-level Objective-C API for working with CouchDB on iOS and Mac OS. By "medium-level" we mean: 37 | 38 | @li It doesn't require knowledge of the HTTP API, only of CouchDB's architecture. You won't have to remember special paths or URL query parameters. 39 | @li But it doesn't provide a full-fledged model layer like CoreData or ActiveRecord. You're still working with CouchDB documents as, basically, NSDictionaries, and you'll need your own mapping between those and your app's object model. 40 | 41 | This API is not the only way to access CouchDB on iOS and Mac OS. There are other Objective-C APIs available, such as Trundle, or you can go down to the metal and talk to CouchDB's HTTP interface yourself using NSURLConnection. 42 | 43 | The source code, Git repository and wiki are available on Github. 44 | 45 | @section concepts_sec Basic Concepts 46 | 47 | CouchCocoa has two layers. The lower layer, called "REST", implements a service-agnostic interface to web applications that follow the REST architectural style. It wraps NSURLConnection to make it easy to represent a REST API's endpoint URLs as a hierarchy of RESTResource objects and perform the basic get/put/create/delete operations on them. Operations are represented by the RESTOperation class, which can be used either synchronously or asynchronously. This layer knows nothing about CouchDB in particular. 48 | 49 | The upper layer, whose classes are prefixed with "Couch", extends the REST layer with a class hierarchy representing CouchDB abstractions like servers, databases, documents and views. Each of these has its own subclass of RESTResource, and so each instance represents a particular URL on the CouchDB server. However, this API relieves you of having to know about the details of the CouchDB URL structure. 50 | 51 | Most of the time you'll be working with methods declared in the Couch classes, but don't forget that they inherit from the REST classes. In particular, there are some commonly used methods declared in RESTResource, like -URL, -GET, and -DELETE, that are easy to overlook if you forget about inheritance. 52 | 53 | There is also an optional even-higher level API, the CouchModel class, that models database documents as Objective-C objects and document properties as Objective-C 2 properties. It's a little bit like a "CoreData Lite". You don't have to use it, but it can make your code a lot simpler and clearer. 54 | 55 | Some example code snippets are available. 56 | 57 | @section restrictions_sec Restrictions 58 | 59 | @subsection compatibility_sec Compatibility 60 | 61 | This library supports both iOS 4.0+ and Mac OS X 10.6+ (32- and 64-bit). 62 | 63 | It uses the traditional reference-counted memory model. It has not yet been adapted to work with either garbage collection or Automatic Reference Counting. 64 | 65 | @subsection threads_sec Thread Safety 66 | 67 | This library is @b not fully thread-safe. It @em can be used on multiple threads, with certain precautions. 68 | 69 | Any individual CouchServer instance, and all the objects created directly or indirectly by it (databases, documents, queries, operations, etc.) must be accessed only on the thread that created it. In particular, you should never pass any CouchCocoa object pointer to code running on another thread. 70 | 71 | However, you can create multiple CouchServer instances (and object trees based on them) on different threads. They'll be completely independent of each other, so they won't step on each other's toes. It's even OK if they have the same server URL -- they'll be different clients as far as the server is concerned. (This means that changes made by one will show up as external change notifications to the others.) 72 | 73 | */ 74 | -------------------------------------------------------------------------------- /Couch/CouchConnectionChangeTracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchConnectionChangeTracker.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 12/1/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "CouchChangeTracker.h" 10 | 11 | 12 | /** CouchChangeTracker 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 CouchConnectionChangeTracker : CouchChangeTracker 15 | { 16 | @private 17 | NSURLConnection* _connection; 18 | int _status; 19 | NSMutableData* _inputBuffer; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Couch/CouchConnectionChangeTracker.m: -------------------------------------------------------------------------------- 1 | // 2 | // CouchConnectionChangeTracker.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 12/1/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | // 9 | 10 | #import "CouchConnectionChangeTracker.h" 11 | #import "CouchInternal.h" 12 | 13 | 14 | @implementation CouchConnectionChangeTracker 15 | 16 | - (BOOL) start { 17 | // For some reason continuous mode doesn't work with CFNetwork. 18 | if (_mode == kContinuous) 19 | _mode = kLongPoll; 20 | 21 | _inputBuffer = [[NSMutableData alloc] init]; 22 | 23 | NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL: self.changesFeedURL]; 24 | request.cachePolicy = NSURLRequestReloadIgnoringCacheData; 25 | request.timeoutInterval = 6.02e23; 26 | 27 | _connection = [[NSURLConnection connectionWithRequest: request delegate: self] retain]; 28 | [_connection start]; 29 | COUCHLOG(@"%@: Started... <%@>", self, request.URL); 30 | return YES; 31 | } 32 | 33 | 34 | - (void) clearConnection { 35 | [_connection autorelease]; 36 | _connection = nil; 37 | [_inputBuffer release]; 38 | _inputBuffer = nil; 39 | _status = 0; 40 | } 41 | 42 | 43 | - (void) stopped { 44 | COUCHLOG2(@"%@: Stopped", self); 45 | [self clearConnection]; 46 | [super stopped]; 47 | } 48 | 49 | 50 | - (void) stop { 51 | [_connection cancel]; 52 | [super stop]; 53 | } 54 | 55 | 56 | - (void)connection:(NSURLConnection *)connection 57 | didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 58 | { 59 | COUCHLOG2(@"%@: didReceiveAuthenticationChallenge", self); 60 | if (challenge.previousFailureCount == 0) { 61 | NSURLCredential* credential = self.authCredential; 62 | if (credential) { 63 | [challenge.sender useCredential: credential forAuthenticationChallenge: challenge]; 64 | return; 65 | } 66 | } 67 | // give up 68 | [challenge.sender cancelAuthenticationChallenge: challenge]; 69 | } 70 | 71 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { 72 | _status = (int) ((NSHTTPURLResponse*)response).statusCode; 73 | COUCHLOG3(@"%@: Got response, status %d", self, _status); 74 | if (_status >= 300) { 75 | Warn(@"%@: Got status %i", self, _status); 76 | [self stop]; 77 | } 78 | } 79 | 80 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { 81 | COUCHLOG3(@"%@: Got %lu bytes", self, (unsigned long)data.length); 82 | [_inputBuffer appendData: data]; 83 | 84 | if (_mode == kContinuous) { 85 | // In continuous mode, break input into lines and parse each as JSON: 86 | for (;;) { 87 | const char* start = _inputBuffer.bytes; 88 | const char* eol = strnstr(start, "\n", _inputBuffer.length); 89 | if (!eol) 90 | break; // Wait till we have a complete line 91 | ptrdiff_t lineLength = eol - start; 92 | NSData* chunk = [[[NSData alloc] initWithBytes: start 93 | length: lineLength] autorelease]; 94 | [_inputBuffer replaceBytesInRange: NSMakeRange(0, lineLength + 1) 95 | withBytes: NULL length: 0]; 96 | // Finally! Send the line to the database to parse: 97 | [self receivedChunk: chunk]; 98 | } 99 | } 100 | } 101 | 102 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { 103 | Warn(@"%@: Got error %@", self, error); 104 | [self stopped]; 105 | } 106 | 107 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection { 108 | if (_mode != kContinuous) { 109 | int status = _status; 110 | NSData* input = [_inputBuffer retain]; 111 | COUCHLOG3(@"%@: Got entire body, %u bytes", self, (unsigned)input.length); 112 | BOOL responseOK = [self receivedPollResponse: input]; 113 | [input release]; 114 | 115 | [self clearConnection]; 116 | if (_mode == kLongPoll && status == 200 && responseOK) 117 | [self start]; // Next poll... 118 | else 119 | [self stopped]; 120 | } else { 121 | [self stopped]; 122 | } 123 | } 124 | 125 | @end 126 | -------------------------------------------------------------------------------- /Couch/CouchDesignDocument.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchDesignDocument.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/8/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 "CouchDocument.h" 17 | @class CouchQuery; 18 | 19 | 20 | /** Language parameter for JavaScript map and reduce functions. */ 21 | extern NSString* const kCouchLanguageJavaScript; 22 | 23 | /** Language parameter for Erlang map and reduce functions. */ 24 | extern NSString* const kCouchLanguageErlang; 25 | 26 | 27 | /** A Design Document is a special document type that contains things like map/reduce functions, and the code and static resources for CouchApps. */ 28 | @interface CouchDesignDocument : CouchDocument 29 | { 30 | @private 31 | NSString* _language; 32 | NSMutableDictionary* _views; 33 | NSMutableDictionary* _filters; 34 | NSString* _validation; 35 | NSMutableDictionary* _viewOptions; 36 | NSString* _viewsRevisionID; 37 | BOOL _changed; 38 | BOOL _changedValidation; 39 | BOOL _changedViewOptions; 40 | RESTOperation* _savingOp; 41 | } 42 | 43 | /** Creates a query for the given named view. 44 | If view definitions have been modified but not saved yet, they will be saved first. */ 45 | - (CouchQuery*) queryViewNamed: (NSString*)viewName; 46 | 47 | /** Indicates whether a given language is available for writing functions in, in this database. */ 48 | - (BOOL) isLanguageAvailable: (NSString*)language; 49 | 50 | /** The language that the functions in this design document are written in. 51 | Defaults to kCouchLanguageJavaScript. */ 52 | @property (copy) NSString* language; 53 | 54 | /** Fetches and returns the names of all the views defined in this design document. 55 | The first call fetches the entire design document synchronously; subsequent calls are cached. */ 56 | @property (readonly) NSArray* viewNames; 57 | 58 | /** Returns the map function of the view with the given name. */ 59 | - (NSString*) mapFunctionOfViewNamed: (NSString*)viewName; 60 | 61 | /** Returns the reduce function of the view with the given name. */ 62 | - (NSString*) reduceFunctionOfViewNamed: (NSString*)viewName; 63 | 64 | /** Sets the definition of a view, or deletes it. 65 | After making changes to one or more views, you should call -saveChanges to PUT them back to the database. 66 | If the new definition is identical to the existing one, the design document will not be marked as changed or saved back to the database. 67 | @param viewName The name of the view, in the scope of this design doc. 68 | @param mapFunction The source code of the map function. If nil, the view will be deleted. 69 | @param reduceFunction The source code of the reduce function. Optional; pass nil for none. */ 70 | - (void) defineViewNamed: (NSString*)viewName 71 | map: (NSString*)mapFunction 72 | reduce: (NSString*)reduceFunction; 73 | 74 | /** A shortcut that defines a simple view with no reduce function. 75 | After making changes to one or more views, you should call -saveChanges to PUT them back to the database. 76 | If the new definition is identical to the existing one, the design document will not be marked as changed or saved back to the database. 77 | @param viewName The name of the view, in the scope of this design doc. 78 | @param mapFunction The source code of the map function. If nil, the view will be deleted. */ 79 | - (void) defineViewNamed: (NSString*)viewName 80 | map: (NSString*)mapFunction; 81 | 82 | /** The filter functions defined in this design document. 83 | @return An NSDictionary whose keys are filter names and values are function source code. */ 84 | @property (readonly) NSDictionary* filters; 85 | 86 | /** Sets the definition of a filter, or deletes it. 87 | After making changes to one or more filters, you should call -saveChanges to PUT them back to the database. 88 | If the new definition is identical to the existing one, the design document will not be marked as changed or saved back to the database. 89 | @param filterName The name of the filter, in the scope of this design doc. 90 | @param filterFunction The source code of the filter function. If nil, the filter will be deleted. */ 91 | - (void) defineFilterNamed: (NSString*)filterName 92 | asFunction: (NSString*)filterFunction; 93 | 94 | /** The validation function, a JavaScript function that validates document contents. */ 95 | @property (copy) NSString* validation; 96 | 97 | /** Should view query results include the document local sequence number in the index? 98 | Setting this to YES sets the 'local_seq' property of the design document's 'options' property to 'true'. 99 | This affects every view in this design document. */ 100 | @property (nonatomic, assign) BOOL includeLocalSequence; 101 | 102 | /** Have the contents of the design document been changed in-memory but not yet saved? */ 103 | @property (readonly) BOOL changed; 104 | 105 | /** Saves changes, asynchronously. If there are no current changes, returns nil. */ 106 | - (RESTOperation*) saveChanges; 107 | 108 | @end 109 | -------------------------------------------------------------------------------- /Couch/CouchDesignDocument.m: -------------------------------------------------------------------------------- 1 | // 2 | // CouchDesignDocument.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/8/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 "CouchDesignDocument.h" 17 | #import "CouchInternal.h" 18 | 19 | 20 | NSString* const kCouchLanguageJavaScript = @"javascript"; 21 | NSString* const kCouchLanguageErlang = @"erlang"; 22 | 23 | 24 | @interface CouchDesignDocument () 25 | @property (readwrite) BOOL changed; 26 | @end 27 | 28 | 29 | @implementation CouchDesignDocument 30 | 31 | 32 | - (void)dealloc { 33 | [_validation release]; 34 | [_language release]; 35 | [_views release]; 36 | [_filters release]; 37 | [_viewsRevisionID release]; 38 | [_viewOptions release]; 39 | [super dealloc]; 40 | } 41 | 42 | 43 | - (CouchQuery*) queryViewNamed: (NSString*)viewName { 44 | [[self saveChanges] wait]; 45 | NSString* path = [@"_view/" stringByAppendingString: viewName]; 46 | return [[[CouchQuery alloc] initWithParent: self relativePath: path] autorelease]; 47 | } 48 | 49 | 50 | - (BOOL) isLanguageAvailable: (NSString*)language { 51 | // FIX: This should be determined by querying the server (somehow) 52 | if ([language isEqualToString: kCouchLanguageJavaScript]) 53 | return YES; 54 | else if ([language isEqualToString: kCouchLanguageErlang]) 55 | return YES; 56 | else 57 | return NO; 58 | } 59 | 60 | 61 | - (NSString*) language { 62 | if (_language) 63 | return _language; 64 | NSString* language = [self.properties objectForKey: @"language"]; 65 | return language ? language : kCouchLanguageJavaScript; 66 | } 67 | 68 | - (void) setLanguage:(NSString *)language { 69 | NSParameterAssert(language != nil); 70 | if (![language isEqualToString: self.language]) { 71 | [_language autorelease]; 72 | _language = [language copy]; 73 | _changed = YES; 74 | } 75 | } 76 | 77 | 78 | static NSMutableDictionary* mutableCopyProperty(NSDictionary* properties, 79 | NSString* key) 80 | { 81 | NSDictionary* dict = $castIf(NSDictionary, [properties objectForKey: key]); 82 | return dict ? [dict mutableCopy] : [[NSMutableDictionary alloc] init]; 83 | } 84 | 85 | 86 | /** Returns a dictionary mapping view names to the dictionaries defining them (as in the design document's JSON source.) 87 | The first call fetches the entire design document; subsequent calls are cached. */ 88 | - (NSDictionary*) views { 89 | if (_views && !$equal(_viewsRevisionID, self.currentRevisionID)) { 90 | // cache is invalid now: 91 | [_views release]; 92 | _views = nil; 93 | [_filters release]; 94 | _filters = nil; 95 | [_viewsRevisionID release]; 96 | _viewsRevisionID = nil; 97 | } 98 | if (!_views) { 99 | NSDictionary* properties = self.properties; 100 | _views = mutableCopyProperty(properties, @"views"); 101 | _filters = mutableCopyProperty(properties, @"filters"); 102 | _viewsRevisionID = [self.currentRevisionID copy]; 103 | } 104 | return _views; 105 | } 106 | 107 | 108 | - (NSArray*) viewNames { 109 | return [self.views allKeys]; 110 | } 111 | 112 | 113 | - (NSDictionary*) definitionOfViewNamed: (NSString*)viewName { 114 | return $castIf(NSDictionary, [self.views objectForKey: viewName]); 115 | } 116 | 117 | - (void) setDefinition: (NSDictionary*)definition ofViewNamed: (NSString*)viewName { 118 | NSDictionary* existingDefinition = [self definitionOfViewNamed: viewName]; 119 | if (definition != existingDefinition && ![definition isEqualToDictionary: existingDefinition]) { 120 | [_views setValue: definition forKey: viewName]; 121 | self.changed = YES; 122 | } 123 | } 124 | 125 | 126 | - (NSString*) mapFunctionOfViewNamed: (NSString*)viewName { 127 | return [[self definitionOfViewNamed: viewName] objectForKey: @"map"]; 128 | } 129 | 130 | 131 | - (NSString*) reduceFunctionOfViewNamed: (NSString*)viewName { 132 | return [[self definitionOfViewNamed: viewName] objectForKey: @"reduce"]; 133 | } 134 | 135 | 136 | - (void) defineViewNamed: (NSString*)viewName 137 | map: (NSString*)mapFunction 138 | reduce: (NSString*)reduceFunction 139 | { 140 | NSMutableDictionary* view = nil; 141 | if (mapFunction) { 142 | view = [[[self definitionOfViewNamed: viewName] mutableCopy] autorelease]; 143 | if (!view) 144 | view = [NSMutableDictionary dictionaryWithCapacity: 3]; 145 | [view setValue: mapFunction forKey: @"map"]; 146 | [view setValue: reduceFunction forKey: @"reduce"]; 147 | } 148 | [self setDefinition: view ofViewNamed: viewName]; 149 | } 150 | 151 | - (void) defineViewNamed: (NSString*)viewName 152 | map: (NSString*)mapFunction 153 | { 154 | [self defineViewNamed: viewName map: mapFunction reduce: nil]; 155 | } 156 | 157 | 158 | - (NSDictionary*) filters { 159 | [self views]; // This also loads/instantiates _filters 160 | return _filters; 161 | } 162 | 163 | 164 | - (void) defineFilterNamed: (NSString*)filterName 165 | asFunction: (NSString*)filterFunction 166 | { 167 | [self views]; // This also loads/instantiates _filters 168 | if (!$equal(filterFunction, [_filters objectForKey: filterName])) { 169 | [_filters setValue: filterFunction forKey: filterName]; 170 | self.changed = YES; 171 | } 172 | } 173 | 174 | 175 | - (NSString*) validation { 176 | if (_changedValidation) 177 | return _validation; 178 | return $castIf(NSString, [self.properties objectForKey: @"validate_doc_update"]); 179 | } 180 | 181 | - (void) setValidation:(NSString *)validation { 182 | if (!$equal(validation, self.validation)) { 183 | [_validation autorelease]; 184 | _validation = [validation copy]; 185 | _changedValidation = YES; 186 | _changed = YES; 187 | } 188 | } 189 | 190 | 191 | - (NSMutableDictionary*) viewOptions { 192 | if (!_viewOptions) { 193 | NSDictionary* viewOptions = $castIf(NSDictionary, [self.properties objectForKey: @"options"]); 194 | if (viewOptions) 195 | _viewOptions = [viewOptions mutableCopy]; 196 | else 197 | _viewOptions = [[NSMutableDictionary alloc] init]; 198 | } 199 | return _viewOptions; 200 | } 201 | 202 | - (BOOL) includeLocalSequence { 203 | return [$castIf(NSNumber, [[self viewOptions] objectForKey: @"local_seq"]) boolValue]; 204 | } 205 | 206 | - (void) setIncludeLocalSequence:(BOOL)localSeq { 207 | if (localSeq != self.includeLocalSequence) { 208 | [_viewOptions setObject: [NSNumber numberWithBool: localSeq] forKey: @"local_seq"]; 209 | _changedViewOptions = YES; 210 | _changed = YES; 211 | } 212 | } 213 | 214 | 215 | @synthesize changed=_changed; 216 | 217 | 218 | - (RESTOperation*) saveChanges { 219 | if (_savingOp) 220 | return _savingOp; 221 | if (!_changed) 222 | return nil; 223 | 224 | NSMutableDictionary* newProps = [[self.properties mutableCopy] autorelease]; 225 | if (!newProps) 226 | newProps = [NSMutableDictionary dictionary]; 227 | if (_views) 228 | [newProps setObject: _views forKey: @"views"]; 229 | if (_filters) 230 | [newProps setObject: _filters forKey: @"filters"]; 231 | if (_language) 232 | [newProps setValue: _language forKey: @"language"]; 233 | if (_changedValidation) 234 | [newProps setValue: _validation forKey: @"validate_doc_update"]; 235 | if (_changedViewOptions) 236 | [newProps setObject: _viewOptions forKey: @"options"]; 237 | 238 | _savingOp = [self putProperties: newProps]; 239 | [_savingOp onCompletion: ^{ 240 | if (_savingOp.error) 241 | Warn(@"Failed to save %@: %@", self, _savingOp.error); 242 | // TODO: What about conflicts? 243 | _savingOp = nil; 244 | _changedValidation = NO; 245 | _changedViewOptions = NO; 246 | [_language release]; 247 | _language = nil; 248 | self.changed = NO; 249 | }]; 250 | return _savingOp; 251 | } 252 | 253 | 254 | @end 255 | -------------------------------------------------------------------------------- /Couch/CouchDesignDocument_Embedded.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchDesignDocument_Embedded.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 10/3/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "CouchDesignDocument.h" 10 | #ifdef COUCHCOCOA_IMPL 11 | typedef id TDMapBlock; 12 | typedef id TDReduceBlock; 13 | typedef id TD_FilterBlock; 14 | typedef id TD_ValidationBlock; 15 | #else 16 | #import 17 | #import 18 | #endif 19 | 20 | 21 | #define MAPBLOCK(BLOCK) ^(NSDictionary* doc, void (^emit)(id key, id value)){BLOCK} 22 | #define REDUCEBLOCK(BLOCK) ^id(NSArray* keys, NSArray* values, BOOL rereduce){BLOCK} 23 | #define VALIDATIONBLOCK(BLOCK) ^BOOL(TD_Revision* newRevision, id context)\ 24 | {BLOCK} 25 | #define FILTERBLOCK(BLOCK) ^BOOL(TD_Revision* revision, NSDictionary* params) {BLOCK} 26 | 27 | 28 | /** Optional support for native Objective-C map/reduce functions. 29 | This is only available when talking to an embedded Couchbase database running in the same process as the app, e.g. Couchbase Mobile. */ 30 | @interface CouchDesignDocument (Embedded) 31 | 32 | /** Defines or deletes a native view. 33 | The view's definition is given as an Objective-C block (or NULL to delete the view). The body of the block should call the 'emit' block (passed in as a paramter) for every key/value pair it wants to write to the view. 34 | Since the function itself is obviously not stored in the database (only a unique string idenfitying it), you must re-define the view on every launch of the app! If the database needs to rebuild the view but the function hasn't been defined yet, it will fail and the view will be empty, causing weird problems later on. 35 | It is very important that this block be a law-abiding map function! As in other languages, it must be a "pure" function, with no side effects, that always emits the same values given the same input document. That means that it should not access or change any external state; be careful, since blocks make that so easy that you might do it inadvertently! 36 | The block may be called on any thread, or on multiple threads simultaneously. This won't be a problem if the code is "pure" as described above, since it will as a consequence also be thread-safe. */ 37 | - (void) defineViewNamed: (NSString*)viewName 38 | mapBlock: (TDMapBlock)mapBlock 39 | version: (NSString*)version; 40 | 41 | /** Defines or deletes a native view with both a map and a reduce function. 42 | For details, read the documentation of the -defineViewNamed:mapBlock: method.*/ 43 | - (void) defineViewNamed: (NSString*)viewName 44 | mapBlock: (TDMapBlock)mapBlock 45 | reduceBlock: (TDReduceBlock)reduceBlock 46 | version: (NSString*)version; 47 | 48 | - (void) defineFilterNamed: (NSString*)filterName 49 | block: (TD_FilterBlock)filterBlock; 50 | 51 | /** An Objective-C block that can validate any document being added/updated/deleted in this database. */ 52 | - (void) setValidationBlock: (TD_ValidationBlock)validationBlock; 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Couch/CouchDesignDocument_Embedded.m: -------------------------------------------------------------------------------- 1 | // 2 | // CouchDesignDocument_Embedded.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 10/3/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "CouchDesignDocument_Embedded.h" 10 | #import "CouchTouchDBServer.h" 11 | #import "CouchDatabase.h" 12 | 13 | 14 | // Redeclare API from TouchDB to avoid having to #include external headers: 15 | typedef unsigned TDContentOptions; 16 | enum { 17 | kTDIncludeLocalSeq = 16 // adds '_local_seq' property 18 | }; 19 | 20 | @class TD_Database, TD_Revision, TD_View; 21 | 22 | @interface TD_Server : NSObject 23 | - (TD_Database*) databaseNamed: (NSString*)name; 24 | @end 25 | 26 | @interface TD_Database : NSObject 27 | - (TD_View*) viewNamed: (NSString*)name; 28 | - (TD_View*) existingViewNamed: (NSString*)name; 29 | - (void) defineFilter: (NSString*)filterName asBlock: (TD_FilterBlock)filterBlock; 30 | - (void) defineValidation: (NSString*)validationName asBlock: (TD_ValidationBlock)validationBlock; 31 | - (TD_ValidationBlock) validationNamed: (NSString*)validationName; 32 | @end 33 | 34 | @interface TD_View : NSObject 35 | - (BOOL) setMapBlock: (TDMapBlock)mapBlock 36 | reduceBlock: (TDReduceBlock)reduceBlock 37 | version: (NSString*)version; 38 | - (void) deleteView; 39 | @property TDContentOptions mapContentOptions; 40 | @end 41 | 42 | 43 | 44 | @implementation CouchDesignDocument (Embedded) 45 | 46 | 47 | - (void) tellTDDatabase: (void(^)(TD_Database*))block { 48 | [(CouchTouchDBServer*)self.database.server tellTDDatabaseNamed: self.database.relativePath 49 | to: block]; 50 | } 51 | 52 | 53 | - (NSString*) qualifiedName: (NSString*)name { 54 | return [NSString stringWithFormat: @"%@/%@", self.relativePath.lastPathComponent, name]; 55 | } 56 | 57 | 58 | - (void) defineViewNamed: (NSString*)viewName 59 | mapBlock: (TDMapBlock)mapBlock 60 | version: (NSString*)version 61 | { 62 | [self defineViewNamed: viewName mapBlock: mapBlock reduceBlock: NULL version: version]; 63 | } 64 | 65 | 66 | - (void) defineViewNamed: (NSString*)viewName 67 | mapBlock: (TDMapBlock)mapBlock 68 | reduceBlock: (TDReduceBlock)reduceBlock 69 | version: (NSString*)version 70 | { 71 | NSAssert(mapBlock || !reduceBlock, @"Can't set a reduce block without a map block"); 72 | viewName = [self qualifiedName: viewName]; 73 | mapBlock = [mapBlock copy]; 74 | reduceBlock = [reduceBlock copy]; 75 | TDContentOptions mapContentOptions = self.includeLocalSequence ? kTDIncludeLocalSeq : 0; 76 | [self tellTDDatabase: ^(TD_Database* tddb) { 77 | if (mapBlock) { 78 | TD_View* view = [tddb viewNamed: viewName]; 79 | [view setMapBlock: mapBlock reduceBlock: reduceBlock version: version]; 80 | view.mapContentOptions = mapContentOptions; 81 | } else { 82 | [[tddb existingViewNamed: viewName] deleteView]; 83 | } 84 | }]; 85 | [mapBlock release]; 86 | [reduceBlock release]; 87 | } 88 | 89 | 90 | - (void) defineFilterNamed: (NSString*)filterName 91 | block: (TD_FilterBlock)filterBlock 92 | { 93 | filterName = [self qualifiedName: filterName]; 94 | filterBlock = [filterBlock copy]; 95 | [self tellTDDatabase: ^(TD_Database* tddb) { 96 | [tddb defineFilter: filterName asBlock: filterBlock]; 97 | }]; 98 | [filterBlock release]; 99 | } 100 | 101 | 102 | - (void) setValidationBlock: (TD_ValidationBlock)validateBlock { 103 | validateBlock = [validateBlock copy]; 104 | [self tellTDDatabase: ^(TD_Database* tddb) { 105 | [tddb defineValidation: self.relativePath asBlock: validateBlock]; 106 | }]; 107 | [validateBlock release]; 108 | } 109 | 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /Couch/CouchDocument.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchDocument.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 5/26/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 "CouchResource.h" 17 | @class CouchAttachment, CouchDatabase, CouchRevision; 18 | 19 | 20 | /** A CouchDB document, aka "record" aka "row". 21 | Note: Never alloc/init a CouchDocument directly. Instead get it from the database by calling -documentWithID: or -untitledDocument. */ 22 | @interface CouchDocument : CouchResource 23 | { 24 | @private 25 | id _modelObject; 26 | BOOL _isDeleted; 27 | NSString* _currentRevisionID; 28 | CouchRevision* _currentRevision; 29 | NSString *_documentID; 30 | } 31 | 32 | /** The unique ID of this document; its key in the database. */ 33 | @property (readonly) NSString* documentID; 34 | 35 | /** The document ID abbreviated to a maximum of 10 characters including ".." in the middle. 36 | Useful for logging or debugging. */ 37 | @property (readonly) NSString* abbreviatedID; 38 | 39 | /** YES if the document has been deleted from the database. */ 40 | @property (readonly) BOOL isDeleted; 41 | 42 | /** Optional reference to an application-defined model object representing this document. 43 | This property is unused and uninterpreted by CouchCocoa; use it for whatever you want. 44 | Note that this is not a strong/retained reference. */ 45 | @property (assign) id modelObject; 46 | 47 | #pragma mark REVISIONS: 48 | 49 | /** The ID of the current revision (if known; else nil). */ 50 | @property (readonly, copy) NSString* currentRevisionID; 51 | 52 | /** The current/latest revision. This object is cached. 53 | This method may need to make a synchronous call to the server to fetch the revision, if its revision ID is not yet known. */ 54 | - (CouchRevision*) currentRevision; 55 | 56 | /** The revision with the specified ID. 57 | This is merely a factory method that doesn't fetch anything from the server, 58 | or even verify that the ID is valid. */ 59 | - (CouchRevision*) revisionWithID: (NSString*)revisionID; 60 | 61 | /** Returns an array of available revisions. 62 | The ordering is essentially arbitrary, but usually chronological (unless there has been merging with changes from another server.) 63 | The number of historical revisions available may vary; it depends on how recently the database has been compacted. You should not rely on earlier revisions being available, except for those representing unresolved conflicts. */ 64 | - (NSArray*) getRevisionHistory; 65 | 66 | /** Returns all the leaf revisions in the document's revision tree. 67 | With TouchDB, this includes deleted revisions (i.e. previously-resolved conflicts.) 68 | With CouchDB, deleted revisions are NOT included, so this method acts identically to -getConflictingRevisions. */ 69 | - (NSArray*) getLeafRevisions; 70 | 71 | /** Compares the cached current revision against the server by sending a conditional GET. This is useful if you're connected to a remote database (not TouchDB) and don't have .tracksChanges turned on. 72 | There are a couple of possibilities: 73 | 1. The CouchDocument doesn't currently have a cached currentRevision. It will do nothing (not even contact the server). 74 | 2. The currentRevision is found to be still current. 75 | 3. The currentRevision is out of date. Updates the currentRevision from the server response. */ 76 | - (void) refresh; 77 | 78 | #pragma mark PROPERTIES: 79 | 80 | /** The contents of the current revision of the document. 81 | This is shorthand for self.currentRevision.properties. 82 | Any keys in the dictionary that begin with "_", such as "_id" and "_rev", contain CouchDB metadata. */ 83 | @property (readonly, copy) NSDictionary* properties; 84 | 85 | /** The user-defined properties, without the ones reserved by CouchDB. 86 | This is based on -properties, with every key whose name starts with "_" removed. */ 87 | @property (readonly, copy) NSDictionary* userProperties; 88 | 89 | /** Shorthand for [self.properties objectForKey: key]. */ 90 | - (id) propertyForKey: (NSString*)key; 91 | 92 | /** Same as -propertyForKey:. Enables "[]" access in Xcode 4.4+ */ 93 | - (id)objectForKeyedSubscript:(NSString*)key; 94 | 95 | /** Updates the document with new properties, creating a new revision (Asynchronous.) 96 | The properties dictionary needs to contain a "_rev" key whose value is the current revision's ID; the dictionary returned by -properties will already have this, so if you modify that dictionary you're OK. The exception is if this is a new document, as there is no current revision, so no "_rev" key is needed. 97 | If the PUT succeeds, the operation's resultObject will be set to the new CouchRevision. 98 | You should be prepared for the operation to fail with a 412 status, indicating that a newer revision has already been added by another client. 99 | In this case you need to call -currentRevision again, to get that newer revision, incorporate any changes into your properties dictionary, and try again. (This is not the same as a conflict resulting from synchronization. Those conflicts result in multiple versions of a document appearing in the database; but in this case, you were prevented from creating a conflict.) */ 100 | - (RESTOperation*) putProperties: (NSDictionary*)properties; 101 | 102 | #pragma mark CONFLICTS: 103 | 104 | /** Returns an array of revisions that are currently in conflict, in no particular order. 105 | If there is no conflict, returns an array of length 1 containing only the current revision. 106 | Returns nil if an error occurs. */ 107 | - (NSArray*) getConflictingRevisions; 108 | 109 | /** Resolves a conflict by choosing one existing revision as the winner. 110 | (This is the same as calling -resolveConflictingRevisions:withProperties:, passing in 111 | winningRevision.properties.) 112 | @param conflicts The array of conflicting revisions as returned by -getConflictingRevisions. 113 | @param winningRevision The revision from 'conflicts' whose properties should be used. */ 114 | - (RESTOperation*) resolveConflictingRevisions: (NSArray*)conflicts 115 | withRevision: (CouchRevision*)winningRevision; 116 | 117 | /** Resolves a conflict by creating a new winning revision from the given properties. 118 | @param conflicts The array of conflicting revisions as returned by -getConflictingRevisions. 119 | @param properties The properties to store into the document to resolve the conflict. */ 120 | - (RESTOperation*) resolveConflictingRevisions: (NSArray*)conflicts 121 | withProperties: (NSDictionary*)properties; 122 | 123 | @end 124 | 125 | 126 | /** This notification is posted by a CouchDocument in response to an external change (as reported by the _changes feed.) 127 | It is not sent in response to 'local' changes made by this CouchDatabase's object tree. 128 | It will not be sent unless change-tracking is enabled in its parent CouchDatabase. */ 129 | extern NSString* const kCouchDocumentChangeNotification; 130 | 131 | 132 | @protocol CouchDocumentModel 133 | /** If a CouchDocument's modelObject implements this method, it will be called whenever the document posts a kCouchDocumentChangeNotification. */ 134 | - (void) couchDocumentChanged: (CouchDocument*)doc; 135 | @end 136 | -------------------------------------------------------------------------------- /Couch/CouchEmbeddedServer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchEmbeddedServer.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 10/13/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "CouchServer.h" 10 | @class CouchbaseMobile; 11 | 12 | /** A convenience class that glues Couchbase Mobile into CouchCocoa -- this is ONLY for use with the older Couchbase Mobile, NOT with TouchDB! 13 | On creation, starts up an instance of Couchbase Mobile. 14 | This object will have a placeholder URL until the embedded server has started up, so you can't access it (i.e. creating any databases) until then. */ 15 | @interface CouchEmbeddedServer : CouchServer 16 | { 17 | @private 18 | CouchbaseMobile* _couchbase; 19 | NSError* _error; 20 | void(^_onStartBlock)(); 21 | } 22 | 23 | /** A shared per-process instance. Remember that CouchCocoa is not thread-safe so you can't 24 | use this shared instance among multiple threads. */ 25 | + (CouchEmbeddedServer*) sharedInstance; 26 | 27 | /** Preferred initializer. Starts up an in-process server. */ 28 | - (id) init; 29 | 30 | /** Inherited initializer, if you want to connect to a remote server for debugging purposes. 31 | (If you call -start:, the block will still be called.) */ 32 | - (id) initWithURL:(NSURL *)url; 33 | 34 | /** The underlying CouchbaseMobile object that manages the embedded server. */ 35 | @property (readonly) CouchbaseMobile* couchbase; 36 | 37 | /** Starts the server, asynchronously. 38 | @param onStartBlock A block to be called when the server finishes starting up (or fails to). At that point you can start to access databases, etc. 39 | @return YES if startup began, NO if a fatal error occurred. */ 40 | - (BOOL) start: (void(^)())onStartBlock; 41 | 42 | /** Is the embedded Couchbase server running? */ 43 | @property (readonly) BOOL running; 44 | 45 | /** If the server fails to start up, this will give the reason why. */ 46 | @property (readonly, retain) NSError* error; 47 | 48 | @end 49 | 50 | 51 | extern NSString* const CouchEmbeddedServerDidStartNotification; 52 | extern NSString* const CouchEmbeddedServerDidRestartNotification; 53 | -------------------------------------------------------------------------------- /Couch/CouchEmbeddedServer.m: -------------------------------------------------------------------------------- 1 | // 2 | // CouchEmbeddedServer.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 10/13/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "CouchEmbeddedServer.h" 10 | #import "CouchInternal.h" 11 | #import "CouchbaseMobile.h" // Local copy of public header from Couchbase framework 12 | 13 | #import 14 | 15 | 16 | NSString* const CouchEmbeddedServerDidStartNotification = @"CouchEmbeddedServerDidRestart"; 17 | NSString* const CouchEmbeddedServerDidRestartNotification = @"CouchEmbeddedServerDidRestart"; 18 | 19 | 20 | @interface CouchEmbeddedServer () 21 | @property (readwrite, retain) NSError* error; 22 | @end 23 | 24 | 25 | @implementation CouchEmbeddedServer 26 | 27 | 28 | + (CouchEmbeddedServer*) sharedInstance { 29 | static CouchEmbeddedServer* sInstance; 30 | static dispatch_once_t onceToken; 31 | dispatch_once(&onceToken, ^{ 32 | sInstance = [[self alloc] init]; 33 | }); 34 | return sInstance; 35 | } 36 | 37 | 38 | - (id)init { 39 | // We don't know the real port that the server will be assigned, so default to 0 for now. 40 | self = [super initWithURL: [NSURL URLWithString: @"http://127.0.0.1:0"]]; 41 | if (self) { 42 | // Look up class at runtime to avoid dependency on Couchbase.framework: 43 | Class couchbaseClass = NSClassFromString(@"CouchbaseMobile"); 44 | NSAssert(couchbaseClass!=nil, @"Not linked with Couchbase Mobile framework"); 45 | _couchbase = [[couchbaseClass alloc] init]; 46 | _couchbase.delegate = self; 47 | } 48 | return self; 49 | } 50 | 51 | 52 | - (id) initWithURL:(NSURL *)url { 53 | if (url) 54 | return [super initWithURL: url]; 55 | else 56 | return [self init]; 57 | } 58 | 59 | 60 | - (void)dealloc { 61 | [[NSNotificationCenter defaultCenter] removeObserver: self]; 62 | [_error release]; 63 | [_onStartBlock release]; 64 | [super dealloc]; 65 | } 66 | 67 | 68 | @synthesize couchbase = _couchbase; 69 | 70 | 71 | - (NSURL*)URL { 72 | NSURL* url = [super URL]; 73 | NSAssert(url.port != 0, @"Can't use CouchEmbeddedServer till it's started up"); 74 | return url; 75 | } 76 | 77 | 78 | - (BOOL) start: (void(^)())onStartBlock { 79 | BOOL ok; 80 | BOOL callItNow = NO; 81 | _onStartBlock = [onStartBlock copy]; 82 | if (_couchbase) { 83 | ok = [_couchbase start]; 84 | callItNow = !ok; 85 | self.error = _couchbase.error; 86 | } else { 87 | ok = YES; 88 | callItNow = YES; 89 | } 90 | if (callItNow) 91 | [self performSelector: @selector(callOnStart) withObject: nil afterDelay: 0.0]; 92 | return ok; 93 | } 94 | 95 | - (void) callOnStart { 96 | _onStartBlock(); 97 | } 98 | 99 | 100 | -(void)couchbaseMobile:(CouchbaseMobile*)couchbase didStart:(NSURL*)serverURL { 101 | COUCHLOG(@"CouchEmbeddedServer: Server started at <%@>", serverURL.absoluteString); 102 | BOOL firstStart = ([[[super URL] port] intValue] == 0); 103 | 104 | [self setURL: serverURL]; 105 | 106 | NSNotificationCenter* nctr = [NSNotificationCenter defaultCenter]; 107 | NSString* notificationToPost; 108 | if (firstStart) { 109 | if ([couchbase respondsToSelector: @selector(adminCredential)]) 110 | [self setCredential: couchbase.adminCredential]; 111 | self.tracksActiveOperations = YES; 112 | UIApplication* app = [UIApplication sharedApplication]; 113 | [nctr addObserver: self selector: @selector(finishActiveOperations) 114 | name: UIApplicationDidEnterBackgroundNotification object: app]; 115 | [nctr addObserver: self selector: @selector(finishActiveOperations) 116 | name: UIApplicationWillTerminateNotification object: app]; 117 | _onStartBlock(); 118 | notificationToPost = CouchEmbeddedServerDidStartNotification; 119 | } else { 120 | notificationToPost = CouchEmbeddedServerDidRestartNotification; 121 | } 122 | [nctr postNotificationName: notificationToPost object: self]; 123 | } 124 | 125 | 126 | -(void)couchbaseMobile:(CouchbaseMobile*)couchbase failedToStart:(NSError*)error { 127 | self.error = error; 128 | _onStartBlock(); 129 | } 130 | 131 | -(void)couchbase:(CouchbaseMobile*)couchbase didStart:(NSURL*)serverURL { 132 | [self couchbaseMobile: couchbase didStart: serverURL]; 133 | } 134 | -(void)couchbase:(CouchbaseMobile*)couchbase failedToStart:(NSError*)error { 135 | [self couchbaseMobile: couchbase failedToStart: error]; 136 | } 137 | 138 | 139 | @synthesize error = _error; 140 | 141 | 142 | - (BOOL) running { 143 | return _couchbase==nil || _couchbase.serverURL != nil; 144 | } 145 | 146 | 147 | - (BOOL) isEmbeddedServer { 148 | return _couchbase != nil; 149 | } 150 | 151 | 152 | - (void) finishActiveOperations { 153 | COUCHLOG(@"CouchEmbeddedServer: Finishing active operations"); 154 | [RESTOperation wait: self.activeOperations]; 155 | } 156 | 157 | 158 | @end 159 | -------------------------------------------------------------------------------- /Couch/CouchInternal.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchInternal.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/26/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 "CouchCocoa.h" 17 | #import "RESTInternal.h" 18 | 19 | 20 | #define COUCHLOG if(gCouchLogLevel < 1) ; else NSLog 21 | #define COUCHLOG2 if(gCouchLogLevel < 2) ; else NSLog 22 | #define COUCHLOG3 if(gCouchLogLevel < 3) ; else NSLog 23 | 24 | 25 | /** Type of block that's called when the database changes. */ 26 | typedef void (^OnDatabaseChangeBlock)(CouchDocument*, BOOL externalChange); 27 | 28 | 29 | @interface CouchAttachment () 30 | - (id) initWithParent: (CouchResource*)parent // must be CouchDocument or CouchRevision 31 | name: (NSString*)name 32 | metadata: (NSDictionary*)metadata; 33 | @end 34 | 35 | 36 | @interface CouchDatabase () 37 | - (void) documentAssignedID: (CouchDocument*)document; 38 | - (void) beginDocumentOperation: (CouchResource*)resource; 39 | - (void) endDocumentOperation: (CouchResource*)resource; 40 | - (void) onChange: (OnDatabaseChangeBlock)block; // convenience for unit tests 41 | - (void) unretainDocumentCache; 42 | - (void) changeTrackerReceivedChange: (NSDictionary*)change; 43 | @end 44 | 45 | 46 | @interface CouchDocument () 47 | - (id) initWithParent: (RESTResource*)parent 48 | relativePath: (NSString*)path 49 | documentID:(NSString *)documentID; 50 | @property (readwrite, copy) NSString* currentRevisionID; 51 | - (void) loadCurrentRevisionFrom: (CouchQueryRow*)row; 52 | - (void) bulkSaveCompleted: (NSDictionary*) result forProperties: (NSDictionary*)properties; 53 | - (BOOL) notifyChanged: (NSDictionary*)change; 54 | @end 55 | 56 | 57 | @interface CouchPersistentReplication () 58 | + (CouchPersistentReplication*) createWithReplicatorDatabase: (CouchDatabase*)replicatorDB 59 | source: (NSString*)source 60 | target: (NSString*)target; 61 | @end 62 | 63 | 64 | @interface CouchRevision () 65 | - (id) initWithDocument: (CouchDocument*)document revisionID: (NSString*)revisionID; 66 | - (id) initWithDocument: (CouchDocument*)document properties: (NSDictionary*)contents; 67 | - (id) initWithOperation: (RESTOperation*)operation; 68 | @property (readwrite) BOOL isDeleted; 69 | @property (readwrite, copy) NSDictionary* properties; 70 | @end 71 | 72 | 73 | @interface CouchReplication () 74 | @property (nonatomic, readwrite) bool pull; 75 | - (id) initWithDatabase: (CouchDatabase*)database 76 | remote: (NSURL*)remote; 77 | @end 78 | 79 | 80 | @interface CouchPersistentReplication () 81 | @property (readonly) NSString* sourceURLStr; 82 | @property (readonly) NSString* targetURLStr; 83 | @end 84 | 85 | 86 | 87 | @interface CouchServer () 88 | @property (readonly) CouchDatabase* replicatorDatabase; 89 | @property (readonly) BOOL isEmbeddedServer; 90 | - (CouchPersistentReplication*) replicationWithSource: (NSString*)source 91 | target: (NSString*)target; 92 | - (void) registerActiveTask: (NSDictionary*)activeTask; 93 | @end 94 | 95 | 96 | /** A query that allows custom map and reduce functions to be supplied at runtime. 97 | Usually created by calling -[CouchDatabase slowQueryWithMapFunction:]. */ 98 | @interface CouchFunctionQuery : CouchQuery 99 | { 100 | NSDictionary* _viewDefinition; 101 | } 102 | 103 | - (id) initWithDatabase: (CouchDatabase*)db 104 | map: (NSString*)map 105 | reduce: (NSString*)reduce 106 | language: (NSString*)language; 107 | @end 108 | -------------------------------------------------------------------------------- /Couch/CouchPersistentReplication.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchPersistentReplication.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 9/8/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "CouchModel.h" 10 | #import "CouchReplication.h" 11 | 12 | 13 | /** Possible current states of a replication. */ 14 | typedef enum { 15 | kReplicationIdle, /**< No current replication activity. */ 16 | kReplicationTriggered, /**< Replication in progress. */ 17 | kReplicationCompleted, /**< Replication finished successfully. */ 18 | kReplicationError /**< Replication failed with an error. */ 19 | } CouchReplicationState; 20 | 21 | 22 | /** A model object representing a persistent replication to or from another database. 23 | Each instance represents a document in the server's special _replication database. 24 | Instances are created by the -replicate... factory methods on CouchDatabase. */ 25 | @interface CouchPersistentReplication : CouchModel 26 | { 27 | @private 28 | CouchReplicationState _state; 29 | unsigned _completed, _total; 30 | NSString* _statusString; 31 | NSError* _error; 32 | CouchReplicationMode _mode; 33 | } 34 | 35 | /** The local database being replicated to/from. */ 36 | @property (readonly) CouchDatabase* localDatabase; 37 | 38 | /** The remote database being replicated to/from. */ 39 | @property (readonly) NSURL* remoteURL; 40 | 41 | /** Does the replication pull from (as opposed to push to) the target? */ 42 | @property (nonatomic, readonly) bool pull; 43 | 44 | /** Should the target database be created if it doesn't already exist? (Defaults to NO). */ 45 | @property bool create_target; 46 | 47 | /** Should the replication operate continuously, copying changes as soon as the source database is modified? (Defaults to NO). */ 48 | @property bool continuous; 49 | 50 | /** Path of an optional filter function to run on the source server. 51 | Only documents for which the function returns true are sent to the destination. 52 | The path looks like "designdocname/filtername". */ 53 | @property (copy) NSString* filter; 54 | 55 | /** Parameters to pass to the filter function. 56 | Should be a JSON-compatible dictionary. */ 57 | @property (copy) NSDictionary* query_params; 58 | 59 | /** Sets the documents to specify as part of the replication. */ 60 | @property (copy) NSArray *doc_ids; 61 | 62 | /** Extra HTTP headers to send in all requests to the remote server. 63 | Should map strings (header names) to strings. */ 64 | @property (copy) NSDictionary* headers; 65 | 66 | /** OAuth parameters that the replicator should use when authenticating to the remote database. 67 | Keys in the dictionary should be "consumer_key", "consumer_secret", "token", "token_secret", and optionally "signature_method". */ 68 | @property (nonatomic, copy) NSDictionary* OAuth; 69 | 70 | /** Sets the "user_ctx" property of the replication, which identifies what privileges it will run with when accessing the local server. To replicate design documents, this should be set to a value with "_admin" in the list of roles. 71 | The server will not let you specify privileges you don't have, so the request to create the replication must be made with credentials that match what you're setting here, unless the server is in no-authentication "admin party" mode. 72 | See , section 8, for details. 73 | If both 'user' and 'roles' are nil, the user_ctx will be cleared. 74 | @param username A server username, or nil 75 | @param roles An array of CouchDB role name strings, or nil */ 76 | - (void) actAsUser: (NSString*)username withRoles: (NSArray*)roles; 77 | 78 | /** A convenience that calls -actAsUser:withRoles: to specify the _admin role. */ 79 | - (void) actAsAdmin; 80 | 81 | /** Restarts a replication; this is most useful to make a non-continuous replication run again after it's stopped. */ 82 | - (void) restart; 83 | 84 | /** The current state of replication activity. */ 85 | @property (readonly) CouchReplicationState state; 86 | 87 | /** The number of completed changes processed, if the task is active, else 0 (observable). */ 88 | @property (nonatomic, readonly) unsigned completed; 89 | 90 | /** The total number of changes to be processed, if the task is active, else 0 (observable). */ 91 | @property (nonatomic, readonly) unsigned total; 92 | 93 | @property (nonatomic, readonly, retain) NSError* error; 94 | 95 | @property (nonatomic, readonly) CouchReplicationMode mode; 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /Couch/CouchPrefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files in the 'Couch' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | 9 | #define COUCHCOCOA_IMPL 10 | -------------------------------------------------------------------------------- /Couch/CouchReplication.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchReplication.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 8/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 | 17 | #import 18 | @class CouchDatabase, RESTOperation; 19 | 20 | 21 | typedef enum { 22 | kCouchReplicationStopped, 23 | kCouchReplicationOffline, 24 | kCouchReplicationIdle, 25 | kCouchReplicationActive 26 | } CouchReplicationMode; 27 | 28 | 29 | /** Tracks a CouchDB replication. Can be used to observe its progress. */ 30 | @interface CouchReplication : NSObject 31 | { 32 | CouchDatabase* _database; 33 | NSURL* _remote; 34 | bool _pull, _createTarget, _continuous; 35 | NSString* _filter; 36 | NSDictionary* _filterParams; 37 | NSDictionary* _options; 38 | NSDictionary* _headers; 39 | NSDictionary* _oauth; 40 | BOOL _running; 41 | NSString* _taskID; 42 | NSString* _status; 43 | unsigned _completed, _total; 44 | CouchReplicationMode _mode; 45 | NSError* _error; 46 | NSArray* _currentRequests; 47 | } 48 | 49 | /** The local database being replicated to/from. */ 50 | @property (nonatomic, readonly) CouchDatabase* localDatabase; 51 | 52 | /** The URL of the remote database. */ 53 | @property (nonatomic, readonly) NSURL* remoteURL; 54 | 55 | /** Does the replication pull from (as opposed to push to) the target? */ 56 | @property (nonatomic, readonly) bool pull; 57 | 58 | /** Should the target database be created if it doesn't already exist? (Defaults to NO). */ 59 | @property (nonatomic) bool createTarget; 60 | 61 | /** Should the replication operate continuously, copying changes as soon as the source database is modified? (Defaults to NO). */ 62 | @property (nonatomic) bool continuous; 63 | 64 | /** Path of an optional filter function to run on the source server. 65 | Only documents for which the function returns true are replicated. 66 | The path looks like "designdocname/filtername". */ 67 | @property (nonatomic, copy) NSString* filter; 68 | 69 | /** Parameters to pass to the filter function. 70 | Should be a JSON-compatible dictionary. */ 71 | @property (nonatomic, copy) NSDictionary* filterParams; 72 | 73 | /** Extra HTTP headers to send in all requests to the remote server. 74 | Should map strings (header names) to strings. */ 75 | @property (nonatomic, copy) NSDictionary* headers; 76 | 77 | /** OAuth parameters that the replicator should use when authenticating to the remote database. 78 | Keys in the dictionary should be "consumer_key", "consumer_secret", "token", "token_secret", and optionally "signature_method". */ 79 | @property (nonatomic, copy) NSDictionary* OAuth; 80 | 81 | /** Other options to be provided to the replicator. 82 | These will be added to the JSON body of the POST to /_replicate. */ 83 | @property (nonatomic, copy) NSDictionary* options; 84 | 85 | 86 | /** Starts the replication, asynchronously. 87 | @return The operation to start replication, or nil if replication is already started. */ 88 | - (RESTOperation*) start; 89 | 90 | /** Stops replication, asynchronously. */ 91 | - (void) stop; 92 | 93 | @property (nonatomic, readonly) BOOL running; 94 | 95 | /** The current status string from the server, if active, else nil (observable). 96 | Usually of the form "Processed 123 / 123 changes". */ 97 | @property (nonatomic, readonly, copy) NSString* status; 98 | 99 | /** The number of completed changes processed, if the task is active, else 0 (observable). */ 100 | @property (nonatomic, readonly) unsigned completed; 101 | 102 | /** The total number of changes to be processed, if the task is active, else 0 (observable). */ 103 | @property (nonatomic, readonly) unsigned total; 104 | 105 | @property (nonatomic, readonly, retain) NSError* error; 106 | 107 | @property (nonatomic, readonly) CouchReplicationMode mode; 108 | 109 | @property (nonatomic, readonly) NSArray* currentRequests; 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /Couch/CouchResource.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchResource.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 5/29/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 "RESTResource.h" 17 | @class CouchDatabase; 18 | 19 | 20 | /** NSError domain string used for errors returned from the CouchDB server. */ 21 | extern NSString* const kCouchDBErrorDomain; 22 | 23 | 24 | /** Superclass of CouchDB model classes. Adds Couch-specific error handling to RESTResource. */ 25 | @interface CouchResource : RESTResource 26 | 27 | /** The owning database. */ 28 | @property (readonly) CouchDatabase* database; 29 | 30 | /** Returns a CouchResource object with the given relative path from the receiver. 31 | (This will always be an instance of CouchResource, not something more specific like CouchDocument. This is a convenience to reach API endpoints like _bulk_docs or _purge that aren't part of the regular object model. */ 32 | - (CouchResource*) childWithPath: (NSString*)relativePath; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Couch/CouchResource.m: -------------------------------------------------------------------------------- 1 | // 2 | // CouchResource.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 5/29/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 "CouchResource.h" 17 | #import "RESTInternal.h" 18 | 19 | 20 | NSString* const kCouchDBErrorDomain = @"CouchDB"; 21 | 22 | 23 | @implementation CouchResource 24 | 25 | 26 | - (CouchDatabase*) database { 27 | return [(CouchResource*)self.parent database]; 28 | // No, this is not an infinite regress. CouchDatabase overrides this to return self. 29 | } 30 | 31 | 32 | - (CouchResource*) childWithPath: (NSString*)relativePath { 33 | return [[[CouchResource alloc] initWithParent: self relativePath: relativePath] autorelease]; 34 | } 35 | 36 | 37 | - (NSError*) operation: (RESTOperation*)op willCompleteWithError: (NSError*)error { 38 | error = [super operation: op willCompleteWithError: error]; 39 | 40 | int httpStatus = op.httpStatus; 41 | if (httpStatus == 0) 42 | return error; // Some kind non-HTTP error 43 | 44 | 45 | if (httpStatus >= 400) { 46 | NSDictionary* json = $castIf(NSDictionary, op.responseBody.fromJSON); 47 | if (json) { 48 | // Interpret extra error info in JSON body of op: 49 | NSString* errorName = [[json objectForKey: @"error"] description]; 50 | if (errorName) { 51 | NSString* reason = [[json objectForKey: @"reason"] description]; 52 | if (reason) 53 | reason = [NSString stringWithFormat: @"%@: %@", errorName, reason]; 54 | NSDictionary* info = [NSDictionary dictionaryWithObjectsAndKeys: 55 | error, NSUnderlyingErrorKey, 56 | errorName, NSLocalizedFailureReasonErrorKey, 57 | reason, NSLocalizedDescriptionKey, 58 | nil]; 59 | error = [NSError errorWithDomain: kCouchDBErrorDomain code: error.code 60 | userInfo: info]; 61 | } 62 | } 63 | } 64 | return error; 65 | } 66 | 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /Couch/CouchRevision.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchRevision.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/28/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 "CouchResource.h" 17 | @class CouchAttachment, CouchDocument, RESTOperation; 18 | 19 | /** A single revision of a CouchDocument. */ 20 | @interface CouchRevision : CouchResource 21 | { 22 | @private 23 | NSDictionary* _properties; 24 | BOOL _isDeleted; 25 | BOOL _gotProperties; 26 | } 27 | 28 | /** The document this is a revision of. */ 29 | @property (readonly) CouchDocument* document; 30 | 31 | /** The document's ID. */ 32 | @property (readonly) NSString* documentID; 33 | 34 | /** The ID of this revision. */ 35 | @property (readonly) NSString* revisionID; 36 | 37 | /** Is this the current/latest revision of its document? */ 38 | @property (readonly) BOOL isCurrent; 39 | 40 | /** Does this revision mark the deletion of its document? */ 41 | @property (readonly) BOOL isDeleted; 42 | 43 | 44 | #pragma mark PROPERTIES 45 | 46 | /** The document as returned from the server and parsed from JSON. (Synchronous) 47 | Keys beginning with "_" are defined and reserved by CouchDB; others are app-specific. 48 | The properties are cached for the lifespan of this object, so subsequent calls after the first are cheap. 49 | (This accessor is synchronous.) */ 50 | @property (readonly, copy) NSDictionary* properties; 51 | 52 | /** The user-defined properties, without the ones reserved by CouchDB. 53 | This is based on -properties, with every key whose name starts with "_" removed. */ 54 | @property (readonly, copy) NSDictionary* userProperties; 55 | 56 | /** Shorthand for [self.properties objectForKey: key]. (Synchronous) */ 57 | - (id) propertyForKey: (NSString*)key; 58 | 59 | /** Same as -propertyForKey:. Enables "[]" access in Xcode 4.4+ */ 60 | - (id)objectForKeyedSubscript:(NSString*)key; 61 | 62 | /** Has this object fetched its contents from the server yet? */ 63 | @property (readonly) BOOL propertiesAreLoaded; 64 | 65 | /** Creates a new revision with the given properties. 66 | This is asynchronous. Watch response for conflict errors! 67 | If successful, the new CouchRevision will be available as the operation's resultObject. */ 68 | - (RESTOperation*) putProperties: (NSDictionary*)properties; 69 | 70 | #pragma mark ATTACHMENTS 71 | 72 | /** The names of all attachments (array of strings). */ 73 | @property (readonly) NSArray* attachmentNames; 74 | 75 | /** Looks up the attachment with the given name (without fetching its contents). */ 76 | - (CouchAttachment*) attachmentNamed: (NSString*)name; 77 | 78 | /** Creates a new attachment object, but doesn't write it to the database yet. 79 | To actually create the attachment, you'll need to call -PUT on the CouchAttachment. 80 | It's OK to call this with an attachment name that already exists; saving will overwrite the old attachment contents. */ 81 | - (CouchAttachment*) createAttachmentWithName: (NSString*)name type: (NSString*)contentType; 82 | 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /Couch/CouchServer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchServer.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 5/26/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 "CouchResource.h" 17 | @class CouchDatabase, CouchLiveQuery, CouchPersistentReplication, RESTCache; 18 | 19 | 20 | /** The top level of a CouchDB server. Contains CouchDatabases. */ 21 | @interface CouchServer : CouchResource 22 | { 23 | @private 24 | RESTCache* _dbCache; 25 | NSMutableArray* _newDocumentIDs; 26 | RESTResource* _activityRsrc; 27 | int _activeTasksObserverCount; 28 | NSArray* _activeTasks; 29 | RESTOperation* _activeTasksOp; 30 | NSTimer* _activityPollTimer; 31 | CouchLiveQuery* _replicationsQuery; 32 | } 33 | 34 | /** Initialize given a server URL. */ 35 | - (id) initWithURL: (NSURL*)url; 36 | 37 | /** Without a URL, connects to localhost on default port 5984. */ 38 | - (id) init; 39 | 40 | /** Releases all resources used by the CouchServer instance. */ 41 | - (void) close; 42 | 43 | /** Fetches the server's current version string. (Synchronous) */ 44 | - (NSString*) getVersion: (NSError**)outError; 45 | 46 | /** Returns an array of unique-ID strings generated by the server. (Synchronous) */ 47 | - (NSArray*) generateUUIDs: (NSUInteger)count; 48 | 49 | /** Returns a single new document ID generated by the server. (Synchronous) */ 50 | - (NSString*) generateDocumentID; 51 | 52 | /** Returns array of CouchDatabase objects representing all the databases on the server. (Synchronous) */ 53 | - (NSArray*) getDatabases; 54 | 55 | /** Just creates a CouchDatabase object; makes no calls to the server. 56 | The database doesn't need to exist (you can call -create on it afterwards to create it.) 57 | Multiple calls with the same name will return the same CouchDatabase instance. */ 58 | - (CouchDatabase*) databaseNamed: (NSString*)name; 59 | 60 | /** Same as -databaseNamed:. Enables "[]" access in Xcode 4.4+ */ 61 | - (id)objectForKeyedSubscript:(NSString*)key; 62 | 63 | #pragma mark - ACTIVITY: 64 | 65 | /** The list of active server tasks, as parsed JSON (observable). 66 | This is updated asynchronously while the activityPollInterval is nonzero. */ 67 | @property (nonatomic, readonly, retain) NSArray* activeTasks; 68 | 69 | - (void) checkActiveTasks; 70 | 71 | /** How often to poll the server's list of active tasks and update .activeTasks. */ 72 | @property NSTimeInterval activityPollInterval; 73 | 74 | #pragma mark - REPLICATION: 75 | 76 | /** All currently defined CouchPersistentReplications (as stored in the replicator database.) 77 | To create a replication, use the methods on CouchDatabase. */ 78 | @property (readonly) NSArray* replications; 79 | 80 | @end 81 | 82 | 83 | /** The current level of logging used by CouchCocoa. 84 | Default value is 0, which disables logging. 85 | Set to 1 for some logging, or 2 or 3 for more. 86 | See also: gRESTLogLevel, which logs HTTP requests/responses. */ 87 | extern int gCouchLogLevel; 88 | -------------------------------------------------------------------------------- /Couch/CouchSocketChangeTracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchSocketChangeTracker.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 12/2/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "CouchChangeTracker.h" 10 | 11 | 12 | /** CouchChangeTracker implementation that uses a raw TCP socket to read the chunk-mode HTTP response. */ 13 | @interface CouchSocketChangeTracker : CouchChangeTracker 14 | { 15 | @private 16 | NSInputStream* _trackingInput; 17 | NSOutputStream* _trackingOutput; 18 | NSString* _trackingRequest; 19 | int _retryCount; 20 | 21 | NSMutableData* _inputBuffer; 22 | int _state; 23 | } 24 | @end 25 | -------------------------------------------------------------------------------- /Couch/CouchTouchDBDatabase.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchTouchDBDatabase.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 5/25/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "CouchDatabase.h" 10 | 11 | @interface CouchTouchDBDatabase : CouchDatabase 12 | { 13 | @private 14 | BOOL _tracking; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Couch/CouchTouchDBDatabase.m: -------------------------------------------------------------------------------- 1 | // 2 | // CouchTouchDBDatabase.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 5/25/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "CouchTouchDBDatabase.h" 10 | #import "CouchTouchDBServer.h" 11 | #import "CouchInternal.h" 12 | 13 | 14 | @interface TD_Database : NSObject 15 | @property (readonly) BOOL exists; 16 | - (BOOL) replaceWithDatabaseFile: (NSString*)databasePath 17 | withAttachments: (NSString*)attachmentsPath 18 | error: (NSError**)outError; 19 | @end 20 | 21 | 22 | // Declared in TD_Database.h and TD_Revision.h; redeclare here to avoid linking against TouchDB: 23 | static NSString* const TD_DatabaseChangeNotification = @"TD_DatabaseChange"; 24 | 25 | @interface TD_Revision : NSObject 26 | @property (readonly) NSString* docID; 27 | @property (readonly) NSString* revID; 28 | @property (readonly) BOOL deleted; 29 | @property SInt64 sequence; 30 | @end 31 | 32 | 33 | @implementation CouchTouchDBDatabase 34 | 35 | 36 | - (void) installCannedDatabase: (NSString*)cannedDbPath 37 | withAttachments: (NSString*)cannedAttPath 38 | { 39 | [(CouchTouchDBServer*)self.parent tellTDDatabaseNamed: self.relativePath 40 | to: ^(TD_Database* tddb) { 41 | // Careful: this block runs async on the background TouchDB thread 42 | NSError* error = nil; 43 | if (!tddb.exists) { 44 | BOOL ok = [tddb replaceWithDatabaseFile: cannedDbPath 45 | withAttachments: cannedAttPath 46 | error: &error]; 47 | NSAssert(ok, @"Failed to install database: %@", error); 48 | } 49 | }]; 50 | } 51 | 52 | 53 | - (BOOL) tracksChanges { 54 | return _tracking; 55 | } 56 | 57 | - (void) setTracksChanges: (BOOL)track { 58 | if (track == _tracking) 59 | return; 60 | _tracking = track; 61 | 62 | if (track) { 63 | [(CouchTouchDBServer*)self.parent tellTDDatabaseNamed: self.relativePath 64 | to: ^(TD_Database* tddb) { 65 | [[NSNotificationCenter defaultCenter] addObserver: self 66 | selector: @selector(tdDatabaseChanged:) 67 | name: TD_DatabaseChangeNotification 68 | object: tddb]; 69 | }]; 70 | } else { 71 | [[NSNotificationCenter defaultCenter] removeObserver: self 72 | name: TD_DatabaseChangeNotification 73 | object: nil]; 74 | } 75 | } 76 | 77 | 78 | - (void) tdDatabaseChanged: (NSNotification*)n { 79 | // Careful! This method is called on the TouchDB background thread! 80 | if (!_tracking) 81 | return; 82 | 83 | NSDictionary* userInfo = n.userInfo; 84 | TD_Revision* rev = [userInfo objectForKey: @"winner"]; // I want winning rev, not newest one 85 | if (!rev) 86 | return; 87 | SInt64 sequence = [[userInfo objectForKey: @"rev"] sequence]; 88 | 89 | // Adapted from -[TDRouter changeDictForRev:] 90 | NSArray* changes = [NSArray arrayWithObject: [NSDictionary dictionaryWithObject: rev.revID 91 | forKey: @"rev"]]; 92 | NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: 93 | [NSNumber numberWithLongLong: sequence], @"seq", 94 | rev.docID, @"id", 95 | changes, @"changes", 96 | [NSNumber numberWithBool: rev.deleted], @"deleted", 97 | nil]; 98 | [self performSelectorOnMainThread: @selector(changeTrackerReceivedChange:) 99 | withObject: dict 100 | waitUntilDone: NO]; 101 | } 102 | 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /Couch/CouchTouchDBServer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchTouchDBServer.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 12/20/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "CouchServer.h" 10 | @class TD_Server, TD_Database; 11 | struct TD_DatabaseManagerOptions; 12 | 13 | 14 | /** A convenience class that glues TouchDB into CouchCocoa. 15 | On creation, starts up an instance of TD_Server and sets up TDURLProtocol to serve it. 16 | The CouchServer URL is set to the root URL served by the protocol, so you can treat it just like a normal remote server instance. */ 17 | @interface CouchTouchDBServer : CouchServer 18 | { 19 | @private 20 | TD_Server* _touchServer; 21 | NSError* _error; 22 | BOOL _observing; 23 | } 24 | 25 | /** A shared per-process instance. Remember that CouchCocoa is not thread-safe so you can't 26 | use this shared instance among multiple threads. */ 27 | + (CouchTouchDBServer*) sharedInstance; 28 | 29 | /** Preferred initializer. Starts up an in-process server. */ 30 | - (id)init; 31 | 32 | /** Starts up a server that stores its data at the given path. 33 | @param serverPath The filesystem path to the server directory. If it doesn't already exist it will be created. */ 34 | - (id) initWithServerPath: (NSString*)serverPath; 35 | 36 | /** Starts up a server that stores its data at the given path. 37 | @param serverPath The filesystem path to the server directory. If it doesn't already exist it will be created. 38 | @param options Option settings; can be used to open in read-only mode or to disable the replicator. */ 39 | - (id) initWithServerPath: (NSString*)serverPath 40 | options: (const struct TD_DatabaseManagerOptions*)options; 41 | 42 | /** Inherited initializer, if you want to connect to a remote server for debugging purposes. */ 43 | - (id) initWithURL: (NSURL*)url; 44 | 45 | /** Shuts down the TouchDB server. */ 46 | - (void) close; 47 | 48 | /** If this is non-nil, the server failed to initialize. */ 49 | @property (readonly) NSError* error; 50 | 51 | /** Invokes the given block on the TouchDB server thread, passing it a pointer to the TD_Server. 52 | You can use this to (carefully!) access the TD_Server API. 53 | Be aware that the block may not run immediately; it's queued and will be called immediately before the server handles the next REST call. */ 54 | - (void) tellTDServer: (void (^)(TD_Server*))block; 55 | 56 | /** Invokes the given block on the TouchDB server thread, passing it a pointer to a TD_Database. 57 | You can use this to (carefully!) access the TD_Database API. 58 | Be aware that the block may not run immediately; it's queued and will be called immediately before the server handles the next REST call. */ 59 | - (void) tellTDDatabaseNamed: (NSString*)dbName to: (void (^)(TD_Database*))block; 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /Couch/CouchTouchDBServer.m: -------------------------------------------------------------------------------- 1 | // 2 | // CouchTouchDBServer.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 12/20/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "CouchTouchDBServer.h" 10 | #import "CouchTouchDBDatabase.h" 11 | #import "CouchInternal.h" 12 | 13 | 14 | // Declare essential bits of TD_Server and TDURLProtocol to avoid having to #import TouchDB: 15 | @interface TD_Server : NSObject 16 | - (id) initWithDirectory: (NSString*)dirPath 17 | error: (NSError**)outError; 18 | - (id) initWithDirectory: (NSString*)dirPath 19 | options: (const struct TD_DatabaseManagerOptions*)options 20 | error: (NSError**)outError; 21 | - (void) queue: (void(^)())block; 22 | - (void) tellDatabaseNamed: (NSString*)dbName to: (void (^)(TD_Database*))block; 23 | - (void) close; 24 | @end 25 | 26 | @interface TDURLProtocol : NSURLProtocol 27 | + (NSURL*) rootURL; 28 | + (void) setServer: (TD_Server*)server; 29 | + (NSURL*) registerServer: (TD_Server*)server; 30 | @end 31 | 32 | @interface TDReplicator 33 | + (NSString *)progressChangedNotification; 34 | + (NSString *)stoppedNotification; 35 | @end 36 | 37 | 38 | @implementation CouchTouchDBServer 39 | 40 | 41 | + (CouchTouchDBServer*) sharedInstance { 42 | static CouchTouchDBServer* sInstance; 43 | static NSThread* sThread; 44 | static dispatch_once_t onceToken; 45 | dispatch_once(&onceToken, ^{ 46 | sInstance = [[self alloc] init]; 47 | sThread = [NSThread currentThread]; 48 | }); 49 | NSAssert([NSThread currentThread] == sThread, 50 | @"Don't use CouchTouchDBServer sharedInstance on multiple threads"); 51 | return sInstance; 52 | } 53 | 54 | 55 | - (id)init { 56 | NSArray* paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, 57 | NSUserDomainMask, YES); 58 | NSString* path = [paths objectAtIndex:0]; 59 | #if !TARGET_OS_IPHONE 60 | path = [path stringByAppendingPathComponent: [[NSBundle mainBundle] bundleIdentifier]]; 61 | #endif 62 | path = [path stringByAppendingPathComponent: @"TouchDB"]; 63 | 64 | [[NSFileManager defaultManager] createDirectoryAtPath: path 65 | withIntermediateDirectories: YES 66 | attributes: nil error: NULL]; 67 | 68 | return [self initWithServerPath: path options: NULL]; 69 | } 70 | 71 | 72 | - (id) initWithServerPath: (NSString*)serverPath 73 | options: (const struct TD_DatabaseManagerOptions*)options 74 | { 75 | // On Mac OS TouchDB.framework is linked dynamically, so avoid explicit references to its 76 | // classes because they'd create link errors building CouchCocoa. 77 | Class classTDURLProtocol = NSClassFromString(@"TDURLProtocol"); 78 | Class classTDServer = NSClassFromString(@"TD_Server"); 79 | NSAssert(classTDURLProtocol && classTDServer, 80 | @"Not linked with TouchDB framework (or you didn't use the -ObjC linker flag)"); 81 | 82 | COUCHLOG(@"Creating CouchTouchDBServer at %@", serverPath); 83 | NSError* error; 84 | TD_Server* server; 85 | if ([classTDServer instancesRespondToSelector: @selector(initWithDirectory:options:error:)]) { 86 | server = [[classTDServer alloc] initWithDirectory: serverPath 87 | options: options 88 | error: &error]; 89 | } else { 90 | NSAssert(!options, @"TD_Server initializer with options is unavailable in TouchDB"); 91 | server = [[classTDServer alloc] initWithDirectory: serverPath 92 | error: &error]; 93 | } 94 | NSURL* rootURL = server ? [classTDURLProtocol registerServer: server] 95 | : [classTDURLProtocol rootURL]; 96 | 97 | self = [super initWithURL: rootURL]; 98 | if (self) { 99 | _touchServer = server; 100 | if (!server) 101 | _error = [error retain]; 102 | } else { 103 | [server release]; 104 | } 105 | return self; 106 | } 107 | 108 | - (id) initWithServerPath: (NSString*)serverPath { 109 | return [self initWithServerPath: serverPath options: NULL]; 110 | } 111 | 112 | 113 | - (id) initWithURL:(NSURL *)url { 114 | if (url) 115 | return [super initWithURL: url]; 116 | else 117 | return [self init]; 118 | } 119 | 120 | 121 | - (id) copyWithZone: (NSZone*)zone { 122 | CouchTouchDBServer* copied = [[[self class] alloc] initWithURL: self.URL]; 123 | if (copied) 124 | copied->_touchServer = _touchServer; 125 | return copied; 126 | } 127 | 128 | 129 | - (void) dealloc { 130 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 131 | [self close]; 132 | [_error release]; 133 | [super dealloc]; 134 | } 135 | 136 | 137 | @synthesize error=_error; 138 | 139 | 140 | - (Class) databaseClass { 141 | return [CouchTouchDBDatabase class]; 142 | } 143 | 144 | 145 | - (void) tellTDServer: (void (^)(TD_Server*))block { 146 | TD_Server* server = _touchServer; 147 | [_touchServer queue: ^{ block(server); }]; 148 | } 149 | 150 | 151 | - (void) tellTDDatabaseNamed: (NSString*)dbName to: (void (^)(TD_Database*))block { 152 | [_touchServer tellDatabaseNamed: dbName to: block]; 153 | } 154 | 155 | 156 | - (void) close { 157 | [super close]; 158 | [_touchServer close]; 159 | [_touchServer release]; 160 | _touchServer = nil; 161 | } 162 | 163 | 164 | #pragma mark - ACTIVITY: 165 | 166 | // I don't have to resort to polling the /_activity URL; I can listen for direct notifications 167 | // from TDReplication. 168 | 169 | - (void) setActivityPollInterval: (NSTimeInterval)interval { 170 | BOOL observe = (interval > 0.0); 171 | if (observe == _observing) 172 | return; 173 | 174 | // Look up the notification names (since I am not linked against TouchDB): 175 | Class classTDReplicator = NSClassFromString(@"TDReplicator"); 176 | NSAssert(classTDReplicator, @"Couldn't find class TDReplicator"); 177 | NSString* replProgressChangedNotification = [classTDReplicator progressChangedNotification]; 178 | NSString* replStoppedNotification = [classTDReplicator stoppedNotification]; 179 | 180 | if (observe) { 181 | [[NSNotificationCenter defaultCenter] addObserver: self 182 | selector: @selector(replicationProgressChanged:) 183 | name: replProgressChangedNotification 184 | object: nil]; 185 | [[NSNotificationCenter defaultCenter] addObserver: self 186 | selector: @selector(replicationProgressChanged:) 187 | name: replStoppedNotification 188 | object: nil]; 189 | [self performSelector: @selector(checkActiveTasks) withObject: nil afterDelay: 0.0]; 190 | } else { 191 | [[NSNotificationCenter defaultCenter] removeObserver:self 192 | name:replProgressChangedNotification 193 | object:nil]; 194 | [[NSNotificationCenter defaultCenter] removeObserver:self 195 | name:replStoppedNotification 196 | object:nil]; 197 | } 198 | _observing = observe; 199 | } 200 | 201 | - (NSTimeInterval) activityPollInterval { 202 | return _observing ? 1.0 : 0.0; 203 | } 204 | 205 | 206 | - (void) replicationProgressChanged: (NSNotification*)n { 207 | COUCHLOG(@"%@: Replication progress changed", self);//TEMP 208 | // This is called on the background TouchDB thread, so dispatch to main thread 209 | [self performSelectorOnMainThread: @selector(checkActiveTasks) withObject: nil 210 | waitUntilDone: NO]; 211 | } 212 | 213 | 214 | 215 | 216 | @end 217 | -------------------------------------------------------------------------------- /Couch/CouchbaseCallbacks.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchbaseCallbacks.h 3 | // iErl14 4 | // 5 | // Created by Jens Alfke on 10/3/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @protocol CouchbaseValidationContext; 11 | 12 | 13 | typedef void (^CouchEmitBlock)(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 zero, one or multiple times. */ 18 | typedef void (^CouchMapBlock)(NSDictionary* doc, CouchEmitBlock emit); 19 | 20 | /** A "reduce" function called to summarize the results of a view. 21 | @param keys An array of keys to be reduced. 22 | @param values A parallel array of values to be reduced, corresponding one-to-one with the keys. 23 | @param rereduce YES if the input keys and values are the results of previous reductions. 24 | @return The reduced value; almost always a scalar or small fixed-size object. */ 25 | typedef id (^CouchReduceBlock)(NSArray* keys, NSArray* values, BOOL rereduce); 26 | 27 | /** Called to validate a document before it's added to the database. 28 | @param doc The submitted document contents. 29 | @param context Lets the block access relevant information and specify an error message. 30 | @return YES to accept the document, NO to reject it. */ 31 | typedef BOOL (^CouchValidateUpdateBlock)(NSDictionary* doc, 32 | id context); 33 | 34 | /** Central per-process registry for native design-document functions. 35 | Associates a key (a unique ID stored in the design doc as the "source" of the function) 36 | with a C block. 37 | This class is thread-safe. */ 38 | @interface CouchbaseCallbacks : NSObject 39 | { 40 | NSMutableArray* _registries; 41 | } 42 | 43 | + (CouchbaseCallbacks*) sharedInstance; 44 | 45 | - (NSString*) generateKey; 46 | 47 | - (void) registerMapBlock: (CouchMapBlock)block forKey: (NSString*)key; 48 | - (void) registerReduceBlock: (CouchReduceBlock)block forKey: (NSString*)key; 49 | - (void) registerValidateUpdateBlock: (CouchValidateUpdateBlock)block forKey: (NSString*)key; 50 | 51 | - (CouchMapBlock) mapBlockForKey: (NSString*)key; 52 | - (CouchReduceBlock) reduceBlockForKey: (NSString*)key; 53 | - (CouchValidateUpdateBlock) validateUpdateBlockForKey: (NSString*)key; 54 | 55 | @end 56 | 57 | 58 | /** Context passed into a CouchValidateUpdateBlock. */ 59 | @protocol CouchbaseValidationContext 60 | 61 | /** The contents of the current revision of the document, or nil if this is a new document. */ 62 | @property (readonly) NSDictionary* currentRevision; 63 | 64 | /** The name of the database being updated. */ 65 | @property (readonly) NSString* databaseName; 66 | 67 | /** The name of the logged-in user, or nil if this is an anonymous request. */ 68 | @property (readonly) NSString* userName; 69 | 70 | /** Does the user have admin privileges? 71 | (If the database is in the default "admin party" mode, this will be YES even when the userName is nil.) */ 72 | @property (readonly) BOOL isAdmin; 73 | 74 | /** The database's security object, which assigns roles and privileges. */ 75 | @property (readonly) NSDictionary* security; 76 | 77 | /** The type of error to report, if the validate block returns NO. 78 | The default value is "forbidden", which will result in an HTTP 403 status. */ 79 | @property (copy) NSString* errorType; 80 | 81 | /** The error message to return in the HTTP response, if the validate block returns NO. 82 | The default value is "invalid document". */ 83 | @property (copy) NSString* errorMessage; 84 | @end 85 | -------------------------------------------------------------------------------- /Couch/CouchbaseMobile.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchbaseMobile.h 3 | // Couchbase Mobile 4 | // 5 | // Created by J Chris Anderson on 3/2/11. 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 | @class CouchbaseMobile; 23 | 24 | 25 | @protocol CouchbaseDelegate 26 | @required 27 | /** Called after a CouchbaseMobile instance finishes starting up. 28 | @param couchbase The instance of CouchbaseMobile. 29 | @param serverURL The URL at which the Couchbase server is listening. */ 30 | -(void)couchbaseMobile:(CouchbaseMobile*)couchbase didStart:(NSURL*)serverURL; 31 | 32 | /** Called after a CouchbaseMobile instance fails to start up. 33 | @param couchbase The instance of CouchbaseMobile. 34 | @param error The error that occurred. */ 35 | -(void)couchbaseMobile:(CouchbaseMobile*)couchbase failedToStart:(NSError*)error; 36 | @end 37 | 38 | 39 | /** Manages an embedded instance of CouchDB that runs in a background thread. */ 40 | @interface CouchbaseMobile : NSObject 41 | { 42 | @private 43 | id _delegate; 44 | CFAbsoluteTime _timeStarted; 45 | NSString* _rootDirectory; 46 | NSString* _bundlePath; 47 | NSString* _iniFilePath; 48 | NSURL* _serverURL; 49 | NSError* _error; 50 | uint8_t _logLevel; 51 | BOOL _autoRestart; 52 | BOOL _started; 53 | } 54 | 55 | /** Convenience to instantiate and start a new instance. */ 56 | + (CouchbaseMobile*) startCouchbase: (id)delegate; 57 | 58 | /** Initializes the instance. */ 59 | - (id) init; 60 | 61 | /** The delegate object, which will be notified when the server starts. */ 62 | @property (assign) id delegate; 63 | 64 | /** Starts the server, asynchronously. The delegate will be called when it's ready. 65 | @return YES if the server is starting, NO if it failed to start. */ 66 | - (BOOL) start; 67 | 68 | /** Restart the server, necessary if app being suspended closes its listening socket */ 69 | - (void) restart; 70 | 71 | /** The HTTP URL the server is listening on. 72 | Will be nil until the server has finished starting up, some time after -start is called. 73 | This property is KV-observable, so an alternative to setting a delegate is to observe this 74 | property and the -error property and wait for one of them to become non-nil. */ 75 | @property (readonly, retain) NSURL* serverURL; 76 | 77 | /** If the server fails to start up, this will be set to a description of the error. 78 | This is KV-observable. */ 79 | @property (readonly, retain) NSError* error; 80 | 81 | /** Defaults to YES, set to NO to prevent auto-restart behavior when app returns from background */ 82 | @property (assign) BOOL autoRestart; 83 | 84 | /** A credential containing the admin username and password of the server. 85 | These are required in any requests sent to the server. The password is generated randomly on first launch. */ 86 | @property (readonly) NSURLCredential* adminCredential; 87 | 88 | #pragma mark CONFIGURATION: 89 | 90 | /** Initializes the instance with a nonstandard location for the runtime resources. 91 | (The default location is Resources/CouchbaseResources, but some application frameworks 92 | require resources to go elsewhere, so in that case you might need to use a custom path.) */ 93 | - (id) initWithBundlePath: (NSString*)bundlePath; 94 | 95 | /** The root directory where Couchbase Mobile will store data files. 96 | This defaults to ~/CouchbaseMobile. 97 | You may NOT change this after starting the server. */ 98 | @property (copy) NSString* rootDirectory; 99 | 100 | /** The directory where CouchDB writes its log files. */ 101 | @property (readonly) NSString* logDirectory; 102 | 103 | /** The directory where CouchDB stores its database files. */ 104 | @property (readonly) NSString* databaseDirectory; 105 | 106 | /** The path to an app-specific CouchDB configuration (".ini") file. 107 | Optional; defaults to nil. 108 | The settings in this file will override the default CouchDB settings in default.ini, but 109 | will in turn be overridden by any locally-made settings (see -localIniFilePath). */ 110 | @property (copy) NSString* iniFilePath; 111 | 112 | /** The path to the mutable local configuration file. 113 | This starts out empty, but will be modified if the app sends PUT requests to the server's 114 | _config URI. The app can restore the default configuration at launch by deleting or 115 | emptying the file at this path before calling -start.*/ 116 | @property (readonly) NSString* localIniFilePath; 117 | 118 | /** Controls the amount of logging by Erlang and CouchDB. 119 | Defaults to 0, meaning none. 120 | 1 logs errors only, 2 also logs CouchDB info (like HTTP requests), 3 logs Erlang 'progress'. */ 121 | @property uint8_t logLevel; 122 | 123 | /** Copies a database file into the databaseDirectory if no such file exists there already. 124 | Call this before -start, to set up initial contents of one or more databases on first run. */ 125 | - (BOOL) installDefaultDatabase: (NSString*)databasePath; 126 | 127 | @end 128 | -------------------------------------------------------------------------------- /CouchCocoa.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CouchCocoa.xcodeproj/xcshareddata/xcschemes/Demo-Addresses.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /CouchCocoa.xcodeproj/xcshareddata/xcschemes/Demo-Shopping.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 65 | 66 | 70 | 71 | 75 | 76 | 80 | 81 | 82 | 83 | 89 | 90 | 96 | 97 | 98 | 99 | 101 | 102 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /CouchCocoa.xcodeproj/xcshareddata/xcschemes/Documentation.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 42 | 43 | 44 | 45 | 51 | 52 | 54 | 55 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /CouchCocoa.xcodeproj/xcshareddata/xcschemes/Mac Framework.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 61 | 62 | 66 | 67 | 71 | 72 | 73 | 74 | 83 | 84 | 85 | 86 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /CouchCocoa.xcodeproj/xcshareddata/xcschemes/iOS Framework.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 56 | 57 | 61 | 62 | 66 | 67 | 71 | 72 | 73 | 74 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /Demo/AddressCard.h: -------------------------------------------------------------------------------- 1 | // 2 | // AddressCard.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 "CouchModel.h" 10 | 11 | @interface AddressCard : CouchModel 12 | 13 | @property (copy) NSString* first; 14 | @property (copy) NSString* last; 15 | @property (copy) NSString* email; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Demo/AddressCard.m: -------------------------------------------------------------------------------- 1 | // 2 | // AddressCard.m 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 "AddressCard.h" 10 | 11 | @implementation AddressCard 12 | 13 | @dynamic first, last, email; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Demo/AddressesDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleDocumentTypes 8 | 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.yourcompany.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleURLTypes 22 | 23 | CFBundleVersion 24 | 1 25 | DemoDatabase 26 | demo-addresses 27 | LSMinimumSystemVersion 28 | ${MACOSX_DEPLOYMENT_TARGET} 29 | NSMainNibFile 30 | AddressesDemo 31 | NSPrincipalClass 32 | NSApplication 33 | NSServices 34 | 35 | UTExportedTypeDeclarations 36 | 37 | UTImportedTypeDeclarations 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Demo/DemoAppController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoAppController.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/1/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 CouchDatabase, CouchReplication, 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 | 29 | CouchDatabase* _database; 30 | DemoQuery* _query; 31 | CouchReplication *_pull, *_push; 32 | BOOL _glowing; 33 | } 34 | 35 | @property (retain) DemoQuery* query; 36 | 37 | - (void) startContinuousSyncWith: (NSURL*)otherDbURL; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Demo/DemoAppController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DemoAppController.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/1/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 "DemoAppController.h" 17 | #import "DemoQuery.h" 18 | #import "CouchModel.h" 19 | #import 20 | 21 | 22 | #define kChangeGlowDuration 3.0 23 | 24 | 25 | int main (int argc, const char * argv[]) { 26 | return NSApplicationMain(argc, argv); 27 | } 28 | 29 | 30 | @implementation DemoAppController 31 | 32 | 33 | @synthesize query = _query; 34 | 35 | 36 | - (void) applicationDidFinishLaunching: (NSNotification*)n { 37 | gRESTLogLevel = kRESTLogRequestURLs; 38 | gCouchLogLevel = 1; 39 | 40 | NSDictionary* bundleInfo = [[NSBundle mainBundle] infoDictionary]; 41 | NSString* dbName = [bundleInfo objectForKey: @"DemoDatabase"]; 42 | if (!dbName) { 43 | NSLog(@"FATAL: Please specify a CouchDB database name in the app's Info.plist under the 'DemoDatabase' key"); 44 | exit(1); 45 | } 46 | 47 | CouchServer *server = [[CouchServer alloc] init]; 48 | _database = [[server databaseNamed: dbName] retain]; 49 | [server release]; 50 | 51 | RESTOperation* op = [_database create]; 52 | if (![op wait]) { 53 | NSAssert(op.error.code == 412, @"Error creating db: %@", op.error); 54 | } 55 | 56 | CouchQuery* q = [_database getAllDocuments]; 57 | q.descending = YES; 58 | self.query = [[[DemoQuery alloc] initWithQuery: q] autorelease]; 59 | self.query.modelClass =_tableController.objectClass; 60 | 61 | // Enable continuous sync: 62 | NSString* otherDbURL = [bundleInfo objectForKey: @"SyncDatabaseURL"]; 63 | if (otherDbURL.length > 0) 64 | [self startContinuousSyncWith: [NSURL URLWithString: otherDbURL]]; 65 | } 66 | 67 | 68 | - (void) startContinuousSyncWith: (NSURL*)otherDbURL { 69 | _pull = [[_database pullFromDatabaseAtURL: otherDbURL] retain]; 70 | _push = [[_database pushToDatabaseAtURL: otherDbURL] retain]; 71 | _pull.continuous = _push.continuous = YES; 72 | } 73 | 74 | 75 | #pragma mark HIGHLIGHTING NEW ITEMS: 76 | 77 | 78 | - (void) updateTableGlows { 79 | _glowing = NO; 80 | [_table setNeedsDisplay: YES]; 81 | } 82 | 83 | 84 | - (void)tableView:(NSTableView *)tableView 85 | willDisplayCell:(id)cell 86 | forTableColumn:(NSTableColumn *)tableColumn 87 | row:(NSInteger)row 88 | { 89 | NSColor* bg = nil; 90 | 91 | NSArray* items = _tableController.arrangedObjects; 92 | if (row >= items.count) 93 | return; // Don't know why I get called on illegal rows, but it happens... 94 | CouchModel* item = [items objectAtIndex: row]; 95 | NSTimeInterval changedFor = item.timeSinceExternallyChanged; 96 | if (changedFor > 0 && changedFor < kChangeGlowDuration) { 97 | float fraction = 1.0 - changedFor / kChangeGlowDuration; 98 | if (YES || [cell isKindOfClass: [NSButtonCell class]]) 99 | bg = [[NSColor controlBackgroundColor] blendedColorWithFraction: fraction 100 | ofColor: [NSColor yellowColor]]; 101 | else 102 | bg = [[NSColor yellowColor] colorWithAlphaComponent: fraction]; 103 | 104 | if (!_glowing) { 105 | _glowing = YES; 106 | [self performSelector: @selector(updateTableGlows) withObject: nil afterDelay: 0.1]; 107 | } 108 | } 109 | 110 | [cell setBackgroundColor: bg]; 111 | [cell setDrawsBackground: (bg != nil)]; 112 | } 113 | 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /Demo/DemoQuery.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoQuery.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/1/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 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/DemoQuery.m: -------------------------------------------------------------------------------- 1 | // 2 | // DemoQuery.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/1/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 "DemoQuery.h" 17 | #import "CouchModel.h" 18 | 19 | #import 20 | 21 | 22 | @interface DemoQuery () 23 | - (void) loadEntriesFrom: (CouchQueryEnumerator*)rows; 24 | @end 25 | 26 | 27 | @implementation DemoQuery 28 | 29 | 30 | - (id) initWithQuery: (CouchQuery*)query 31 | { 32 | NSParameterAssert(query); 33 | self = [super init]; 34 | if (self != nil) { 35 | _modelClass = [CouchModel class]; 36 | _query = [[query asLiveQuery] retain]; 37 | 38 | _query.prefetch = YES; // for efficiency, include docs on first load 39 | [_query start]; 40 | 41 | // Observe changes to _query.rows: 42 | [_query addObserver: self forKeyPath: @"rows" options: 0 context: NULL]; 43 | } 44 | return self; 45 | } 46 | 47 | 48 | - (void) dealloc 49 | { 50 | [_entries release]; 51 | [_query removeObserver: self forKeyPath: @"rows"]; 52 | [_query release]; 53 | [super dealloc]; 54 | } 55 | 56 | 57 | @synthesize modelClass=_modelClass; 58 | 59 | 60 | - (void) loadEntriesFrom: (CouchQueryEnumerator*)rows { 61 | NSLog(@"Reloading %lu rows from sequence #%lu...", 62 | (unsigned long)rows.count, (unsigned long)rows.sequenceNumber); 63 | NSMutableArray* entries = [NSMutableArray array]; 64 | 65 | for (CouchQueryRow* row in rows) { 66 | CouchModel* item = [_modelClass modelForDocument: row.document]; 67 | item.autosaves = YES; 68 | [entries addObject: item]; 69 | // If this item isn't in the prior _entries, it's an external insertion: 70 | if (_entries && [_entries indexOfObjectIdenticalTo: item] == NSNotFound) 71 | [item markExternallyChanged]; 72 | } 73 | 74 | for (CouchModel* item in _entries) { 75 | if ([item isNew]) 76 | [entries addObject: item]; 77 | } 78 | 79 | if (![entries isEqual:_entries]) { 80 | NSLog(@" ...entries changed! (was %u, now %u)", 81 | (unsigned)_entries.count, (unsigned)entries.count); 82 | [self willChangeValueForKey: @"entries"]; 83 | [_entries release]; 84 | _entries = [entries mutableCopy]; 85 | [self didChangeValueForKey: @"entries"]; 86 | } 87 | } 88 | 89 | 90 | - (void)observeValueForKeyPath: (NSString*)keyPath ofObject: (id)object 91 | change: (NSDictionary*)change context: (void*)context 92 | { 93 | if (object == _query) { 94 | [self loadEntriesFrom: _query.rows]; 95 | } 96 | } 97 | 98 | 99 | #pragma mark - 100 | #pragma mark ENTRIES PROPERTY: 101 | 102 | 103 | - (NSUInteger) countOfEntries { 104 | return _entries.count; 105 | } 106 | 107 | 108 | - (CouchModel*)objectInEntriesAtIndex: (NSUInteger)index { 109 | return [_entries objectAtIndex: index]; 110 | } 111 | 112 | 113 | - (void) insertObject: (CouchModel*)object inEntriesAtIndex: (NSUInteger)index { 114 | [_entries insertObject: object atIndex: index]; 115 | object.autosaves = YES; 116 | object.database = _query.database; 117 | } 118 | 119 | 120 | - (void) removeObjectFromEntriesAtIndex: (NSUInteger)index { 121 | CouchModel* item = [_entries objectAtIndex: index]; 122 | item.database = nil; 123 | [_entries removeObjectAtIndex: index]; 124 | } 125 | 126 | 127 | @end 128 | -------------------------------------------------------------------------------- /Demo/ShoppingDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.yourcompany.${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 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Demo/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 "CouchModel.h" 10 | 11 | @interface ShoppingItem : CouchModel 12 | 13 | @property bool check; // bool is better than BOOL: it maps to true/false in JSON, not 0/1. 14 | @property (copy) NSString* text; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Demo/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 | 9 | #import "ShoppingItem.h" 10 | 11 | @implementation ShoppingItem 12 | 13 | @dynamic check, text; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Model/CouchDynamicObject.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchDynamicObject.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 8/6/09. 6 | // Copyright 2009 Jens Alfke. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | /** A generic class with runtime support for dynamic properties. 13 | You can subclass this and declare properties in the subclass without needing to implement them or make instance variables; simply note them as '@@dynamic' in the @@implementation. 14 | The dynamic accessors will be bridged to calls to -getValueOfProperty: and setValue:ofProperty:, allowing you to easily store property values in an NSDictionary or other container. */ 15 | @interface CouchDynamicObject : NSObject 16 | 17 | /** Returns the names of all properties defined in this class and superclasses up to CouchDynamicObject. */ 18 | + (NSSet*) propertyNames; 19 | 20 | /** Returns the value of a named property. 21 | This method will only be called for properties that have been declared in the class's @@interface using @@property. 22 | You must override this method -- the base implementation just raises an exception. */ 23 | - (id) getValueOfProperty: (NSString*)property; 24 | 25 | /** Sets the value of a named property. 26 | This method will only be called for properties that have been declared in the class's @@interface using @@property, and are not declared readonly. 27 | You must override this method -- the base implementation just raises an exception. 28 | @return YES if the property was set, NO if it isn't settable; an exception will be raised. 29 | Default implementation returns NO. */ 30 | - (BOOL) setValue: (id)value ofProperty: (NSString*)property; 31 | 32 | 33 | // FOR SUBCLASSES TO CALL: 34 | 35 | /** Given the name of an object-valued property, returns the class of the property's value. 36 | Returns nil if the property doesn't exist, or if its type isn't an object pointer or is 'id'. */ 37 | + (Class) classOfProperty: (NSString*)propertyName; 38 | 39 | + (NSString*) getterKey: (SEL)sel; 40 | + (NSString*) setterKey: (SEL)sel; 41 | 42 | // ADVANCED STUFF FOR SUBCLASSES TO OVERRIDE: 43 | 44 | + (IMP) impForGetterOfProperty: (NSString*)property ofClass: (Class)propertyClass; 45 | + (IMP) impForSetterOfProperty: (NSString*)property ofClass: (Class)propertyClass; 46 | + (IMP) impForGetterOfProperty: (NSString*)property ofType: (const char*)propertyType; 47 | + (IMP) impForSetterOfProperty: (NSString*)property ofType: (const char*)propertyType; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /Model/CouchModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchModel.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 "CouchDynamicObject.h" 10 | @class CouchAttachment, CouchDatabase, CouchDocument, RESTOperation; 11 | 12 | 13 | /** Generic model class for Couch documents. 14 | There's a 1::1 mapping between these and CouchDocuments; call +modelForDocument: to get (or create) a model object for a document, and .document to get the document of a model. 15 | You should subclass this and declare properties in the subclass's @@interface. As with NSManagedObject, you don't need to implement their accessor methods or declare instance variables; simply note them as '@@dynamic' in the class @@implementation. The property value will automatically be fetched from or stored to the document, using the same name. 16 | Supported scalar types are bool, char, short, int, double. These map to JSON numbers, except 'bool' which maps to JSON 'true' and 'false'. (Use bool instead of BOOL.) 17 | Supported object types are NSString, NSNumber, NSData, NSDate, NSArray, NSDictionary. (NSData and NSDate are not native JSON; they will be automatically converted to/from strings in base64 and ISO date formats, respectively.) 18 | Additionally, a property's type can be a pointer to a CouchModel subclass. This provides references between model objects. The raw property value in the document must be a string whose value is interpreted as a document ID. */ 19 | @interface CouchModel : CouchDynamicObject 20 | { 21 | @private 22 | CouchDocument* _document; 23 | CFAbsoluteTime _changedTime; 24 | bool _autosaves :1; 25 | bool _isNew :1; 26 | bool _needsSave :1; 27 | 28 | NSMutableDictionary* _properties; // Cached property values, including changed values 29 | NSMutableSet* _changedNames; // Names of properties that have been changed but not saved 30 | NSMutableDictionary* _changedAttachments; 31 | } 32 | 33 | /** Returns the CouchModel associated with a CouchDocument, or creates & assigns one if necessary. 34 | If the CouchDocument already has an associated model, it's returned. Otherwise a new one is instantiated. 35 | If you call this on CouchModel itself, it'll delegate to the CouchModelFactory to decide what class to instantiate; this lets you map different classes to different "type" property values, for instance. 36 | If you call this method on a CouchModel subclass, it will always instantiate an instance of that class; e.g. [MyWidgetModel modelForDocument: doc] always creates a MyWidgetModel. */ 37 | + (id) modelForDocument: (CouchDocument*)document; 38 | 39 | /** Creates a new "untitled" model with a new unsaved document. 40 | The document won't be written to the database until -save is called. */ 41 | - (id) initWithNewDocumentInDatabase: (CouchDatabase*)database; 42 | 43 | /** Creates a new "untitled" model object with no document or database at all yet. 44 | Setting its .database property will cause it to create a CouchDocument. 45 | (This method is mostly here so that NSController objects can create CouchModels.) */ 46 | - (id) init; 47 | 48 | /** The document this item is associated with. Will be nil if it's new and unsaved. */ 49 | @property (readonly, retain) CouchDocument* document; 50 | 51 | /** The database the item's document belongs to. 52 | Setting this property will assign the item to a database, creating a document. 53 | Setting it to nil will delete its document from its database. */ 54 | @property (retain) CouchDatabase* database; 55 | 56 | /** Is this model new, never before saved? */ 57 | @property (readonly) bool isNew; 58 | 59 | #pragma mark - SAVING: 60 | 61 | /** Writes any changes to a new revision of the document, asynchronously. 62 | Does nothing and returns nil if no changes have been made. */ 63 | - (RESTOperation*) save; 64 | 65 | /** Should changes be saved back to the database automatically? 66 | Defaults to NO, requiring you to call -save manually. */ 67 | @property (nonatomic) bool autosaves; 68 | 69 | /** Does this model have unsaved changes? */ 70 | @property (readonly) bool needsSave; 71 | 72 | /** The document's current properties, in externalized JSON format. */ 73 | - (NSDictionary*) propertiesToSave; 74 | 75 | /** Deletes the document from the database. 76 | You can still use the model object afterwards, but it will refer to the deleted revision. */ 77 | - (RESTOperation*) deleteDocument; 78 | 79 | /** Deletes the document from the database via a PUT rather than a DELETE, saving 80 | the additionalProperties into the deleted document. 81 | You can still use the model object afterwards, but it will refer to the deleted revision. */ 82 | - (RESTOperation*) deleteDocumentWithAdditionalProperties:(NSDictionary *)additionalProperties; 83 | 84 | /** The time interval since the document was last changed externally (e.g. by a "pull" replication. 85 | This value can be used to highlight recently-changed objects in the UI. */ 86 | @property (readonly) NSTimeInterval timeSinceExternallyChanged; 87 | 88 | /** Bulk-saves changes to multiple model objects (which must all be in the same database). 89 | This invokes -[CouchDatabase putChanges:], which sends a single request to _bulk_docs. 90 | Any unchanged models in the array are ignored. 91 | @param models An array of CouchModel objects, which must all be in the same database. 92 | @return A RESTOperation that saves all changes, or nil if none of the models need saving. */ 93 | + (RESTOperation*) saveModels: (NSArray*)models; 94 | 95 | /** Resets the timeSinceExternallyChanged property to zero. */ 96 | - (void) markExternallyChanged; 97 | 98 | #pragma mark - PROPERTIES & ATTACHMENTS: 99 | 100 | /** Gets a property by name. 101 | You can use this for document properties that you haven't added @@property declarations for. */ 102 | - (id) getValueOfProperty: (NSString*)property; 103 | 104 | /** Sets a property by name. 105 | You can use this for document properties that you haven't added @@property declarations for. */ 106 | - (BOOL) setValue: (id)value ofProperty: (NSString*)property; 107 | 108 | 109 | /** The names of all attachments (array of strings). 110 | This reflects unsaved changes made by creating or deleting attachments. */ 111 | @property (readonly) NSArray* attachmentNames; 112 | 113 | /** Looks up the attachment with the given name (without fetching its contents). */ 114 | - (CouchAttachment*) attachmentNamed: (NSString*)name; 115 | 116 | /** Creates or updates an attachment (in memory). 117 | The attachment data will be written to the database at the same time as property changes are saved. 118 | @param name The attachment name. 119 | @param contentType The MIME type of the body. 120 | @param body The raw attachment data, or nil to delete the attachment. */ 121 | - (CouchAttachment*) createAttachmentWithName: (NSString*)name 122 | type: (NSString*)contentType 123 | body: (NSData*)body; 124 | 125 | /** Deletes (in memory) any existing attachment with the given name. 126 | The attachment will be deleted from the database at the same time as property changes are saved. */ 127 | - (void) removeAttachmentNamed: (NSString*)name; 128 | 129 | 130 | 131 | #pragma mark - PROTECTED (FOR SUBCLASSES TO OVERRIDE) 132 | 133 | /** Designated initializer. Do not call directly except from subclass initializers; to create a new instance call +modelForDocument: instead. 134 | @param document The document. Nil if this is created new (-init was called). */ 135 | - (id) initWithDocument: (CouchDocument*)document; 136 | 137 | /** The document ID to use when creating a new document. 138 | Default is nil, which means to assign no ID (the server will assign one). */ 139 | - (NSString*) idForNewDocumentInDatabase: (CouchDatabase*)db; 140 | 141 | /** Called when the model's properties are reloaded from the document. 142 | This happens both when initialized from a document, and after an external change. */ 143 | - (void) didLoadFromDocument; 144 | 145 | /** Returns the database in which to look up the document ID of a model-valued property. 146 | Defaults to the same database as the receiver's document. You should override this if a document property contains the ID of a document in a different database. */ 147 | - (CouchDatabase*) databaseForModelProperty: (NSString*)propertyName; 148 | 149 | @end 150 | -------------------------------------------------------------------------------- /Model/CouchModelFactory.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchModelFactory.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 11/22/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class CouchDocument; 11 | 12 | 13 | /** A configurable mapping from CouchDocument to CouchModel. 14 | It associates a model class with a value of the document's "type" property. */ 15 | @interface CouchModelFactory : NSObject 16 | { 17 | NSMutableDictionary* _typeDict; 18 | } 19 | 20 | /** Returns a global shared CouchModelFactory that's consulted by all databases. 21 | Mappings registered in this instance will be used as a fallback by all other instances if they don't have their own. */ 22 | + (CouchModelFactory*) sharedInstance; 23 | 24 | /** Given a document, attempts to return a CouchModel for it. 25 | If the document's modelObject property is set, it returns that value. 26 | If the document's "type" property has been registered, instantiates the associated class. 27 | Otherwise returns nil. */ 28 | - (id) modelForDocument: (CouchDocument*)document; 29 | 30 | /** Associates a value of the "type" property with a CouchModel subclass. 31 | @param classOrName Either a CouchModel subclass, or its class name as an NSString. 32 | @param type The value value of a document's "type" property that should indicate this class. */ 33 | - (void) registerClass: (id)classOrName forDocumentType: (NSString*)type; 34 | 35 | /** Returns the appropriate CouchModel subclass for this document. 36 | The default implementation just passes the document's "type" property value to -classForDocumentType:, but subclasses could override this to use different properties (or even the document ID) to decide. */ 37 | - (Class) classForDocument: (CouchDocument*)document; 38 | 39 | /** Looks up the CouchModel subclass that's been registered for a document type. */ 40 | - (Class) classForDocumentType: (NSString*)type; 41 | 42 | @end 43 | 44 | 45 | @interface CouchDatabase (CouchModelFactory) 46 | 47 | /** The CouchModel factory object to be used by this database. 48 | Every database has its own instance by default, but you can set this property to use a different one -- either to use a custom subclass, or to share a factory among multiple databases, or both. */ 49 | @property (retain) CouchModelFactory* modelFactory; 50 | @end -------------------------------------------------------------------------------- /Model/CouchModelFactory.m: -------------------------------------------------------------------------------- 1 | // 2 | // CouchModelFactory.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 11/22/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "CouchModelFactory.h" 10 | #import "CouchInternal.h" 11 | 12 | 13 | @implementation CouchModelFactory 14 | 15 | 16 | static CouchModelFactory* sSharedInstance; 17 | 18 | 19 | + (CouchModelFactory*) sharedInstance { 20 | static dispatch_once_t onceToken; 21 | dispatch_once(&onceToken, ^{ 22 | sSharedInstance = [[self alloc] init]; 23 | }); 24 | return sSharedInstance; 25 | } 26 | 27 | 28 | - (id)init { 29 | self = [super init]; 30 | if (self) { 31 | _typeDict = [[NSMutableDictionary alloc] init]; 32 | } 33 | return self; 34 | } 35 | 36 | 37 | - (void)dealloc { 38 | [_typeDict release]; 39 | [super dealloc]; 40 | } 41 | 42 | 43 | - (void) registerClass: (id)classOrName forDocumentType: (NSString*)type { 44 | [_typeDict setValue: classOrName forKey: type]; 45 | } 46 | 47 | 48 | - (Class) classForDocumentType: (NSString*)type { 49 | id klass = [_typeDict objectForKey: type]; 50 | if (!klass && self != sSharedInstance) 51 | return [sSharedInstance classForDocumentType: type]; 52 | if ([klass isKindOfClass: [NSString class]]) { 53 | NSString* className = klass; 54 | klass = NSClassFromString(className); 55 | NSAssert(klass, @"CouchModelFactory: no class named %@", className); 56 | } 57 | return klass; 58 | } 59 | 60 | 61 | - (Class) classForDocument: (CouchDocument*)document { 62 | NSString* type = [document propertyForKey: @"type"]; 63 | return type ? [self classForDocumentType: type] : nil; 64 | } 65 | 66 | 67 | - (id) modelForDocument: (CouchDocument*)document { 68 | CouchModel* model = document.modelObject; 69 | if (model) 70 | return model; 71 | return [[self classForDocument: document] modelForDocument: document]; 72 | } 73 | 74 | 75 | @end 76 | 77 | 78 | 79 | 80 | @implementation CouchDatabase (CouchModelFactory) 81 | 82 | - (CouchModelFactory*) modelFactory { 83 | if (!_modelFactory) 84 | _modelFactory = [[CouchModelFactory alloc] init]; 85 | return _modelFactory; 86 | } 87 | 88 | - (void) setModelFactory:(CouchModelFactory *)modelFactory { 89 | [_modelFactory autorelease]; 90 | _modelFactory = [modelFactory retain]; 91 | } 92 | 93 | @end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⚠️ This repo is obsolete. TouchDB is now known as [Couchbase Lite](https://www.couchbase.com/products/lite). 2 | 3 | ## CouchCocoa: An Objective-C API To TouchDB and Apache CouchDB™ 4 | 5 | CouchCocoa is a medium-level Objective-C API for working with [TouchDB][10] and [CouchDB][1] on iOS and Mac OS. By "medium-level" we mean: 6 | 7 | * It doesn't require knowledge of the HTTP API, only of CouchDB's architecture. You won't have to remember special paths or URL query parameters. 8 | * But it doesn't completely abstract away the fact that you're working with a database, the way CoreData does. You still work with CouchDB-style documents and queries, although there is a CouchModel class that does some of the dirty work of mapping between documents and native objects. 9 | 10 | This API is not the only way to access CouchDB or TouchDB; just the most convenient one. If you prefer, you can go down to the metal and talk to the HTTP API yourself using NSURLConnection. 11 | 12 | ### Kick The Tires! 13 | 14 | * [Peruse some simple code snippets!][7] 15 | * [Explore the API documentation!][8] 16 | 17 | ### Join Us 18 | 19 | * You can discuss this API or ask questions at the [Couchbase Mobile Google Group][3]. 20 | * You might also want to look at the [issue tracker][5] to see known problems and feature requests. 21 | 22 | ## Build Instructions 23 | 24 | ### Binary Builds 25 | 26 | Pre-built libraries are available. The latest build is always at [this address](http://files.couchbase.com/developer-previews/mobile/ios/couchcocoa/CouchCocoa.zip). If you need them, [earlier versions](http://files.couchbase.com/developer-previews/mobile/ios/couchcocoa/) are available too. 27 | 28 | ### Prerequisite 29 | 30 | Xcode 4.3 or later, with the SDK for iOS 4.3 or later. 31 | 32 | ### One-Time Repository Setup 33 | 34 | If you cloned the CouchCocoa Git repository, as opposed to downloading a precompiled framework, then you'll next need to initialize Git "submodules". This will clone the dependency JSONKit into the "vendor" subfolder: 35 | 36 | cd CouchCocoa 37 | git submodule init 38 | git submodule update 39 | 40 | ### Running The iOS Demo App 41 | 42 | Our iOS demo, "Grocery Sync", has [its own GitHub repository][12]. Check it out and look at its README for instructions. 43 | 44 | ### Running The Mac OS Demo Apps 45 | 46 | There are two simple Mac demo apps included in the Demo/ subfolder. One lets you edit a simple list of names and email addresses, the other is a shopping list. (They actually share most of the same source code; all the differences are in their model classes and .xib files, thanks to the magic of Cocoa bindings.) To run them: 47 | 48 | 0. Start a CouchDB server on localhost. (You can use a package manager like [HomeBrew][11] to install CouchDB.) 49 | 1. Open CouchCocoa.xcodeproj (in Xcode 4.2 or later) 50 | 2. Select "Demo-Addresses" or "Demo-Shopping" from the scheme pop-up in the toolbar 51 | 3. Press the Run button 52 | 53 | ### Building The Framework 54 | 55 | (You only need to do this if you checked out the CouchCocoa source code and want to build it yourself. If you downloaded a precompiled framework, just go onto the next section.) 56 | 57 | 1. Open CouchCocoa.xcodeproj 58 | 2. Select "Mac Framework" or "iOS Framework" from the scheme pop-up in the toolbar 59 | 3. Product > Build 60 | 61 | If you want to run the unit tests, first make sure a CouchDB server is running on localhost, then choose Product > Test. 62 | 63 | The framework will be located at: 64 | 65 | * Mac: build/CouchCocoa/Build/Products/Debug/CouchCocoa.framework 66 | * iOS: build/CouchCocoa/Build/Products/Debug-universal/CouchCocoa.framework 67 | 68 | (The exact location of `build` itself will depend on your Xcode preferences. It may be a subdirectory of the project folder, or it may be located down in an Xcode "DerivedData" folder. One way to find the framework is to open up the `Products` group in the project navigator, right-click on the appropriate `CouchCocoa.framework`, and choose "Show In Finder".) 69 | 70 | ## Using The Framework In Your Apps 71 | 72 | ### Mac OS: 73 | 74 | 1. Build the Mac framework (see above). 75 | 2. Copy CouchCocoa.framework somewhere, either into your project's folder or into a location shared between all your projects. 76 | 3. Open your Xcode project. 77 | 4. Drag the copied framework into the project window's file list. 78 | 5. Add the framework to your target (if you weren't already prompted to in the previous step.) 79 | 6. Edit your target and add a new Copy Files build phase. 80 | 7. Set the build phase's destination to Frameworks, and drag CouchCocoa.framework into its list from the main project file list. 81 | 82 | ### iOS: 83 | 84 | 1. Build the iOS framework (see above). 85 | 2. Copy CouchCocoa.framework somewhere, either into your project's folder or into a location shared between all your projects. 86 | 3. Open your Xcode project. 87 | 4. Drag the copied framework into the project window's file list. 88 | 5. Add the framework to your target (if you weren't already prompted to in the previous step.) 89 | 90 | ## License 91 | 92 | Released under the [Apache license, version 2.0][6]. 93 | 94 | Author: [Jens Alfke](https://github.com/snej/) 95 | 96 | With contributions from: [J Chris Anderson](https://github.com/jchris/), [David Venable](https://github.com/dlvenable), [Alex McArthur](https://github.com/alexmcarthur), [Jonathon Mah](https://github.com/jmah), [Pierre Metrailler](https://github.com/pimetrai), [Sven A. Schmidt](https://github.com/sas71), [Katrin Apel](https://github.com/kaalita). 97 | 98 | Copyright 2012, Couchbase, Inc. 99 | 100 | 101 | 102 | [1]: http://couchdb.apache.org/ 103 | [2]: https://github.com/schwa/trundle 104 | [3]: https://groups.google.com/group/mobile-couchbase 105 | [4]: http://www.couchbase.com/downloads/couchbase-single-server/community 106 | [5]: http://www.couchbase.org/issues/secure/IssueNavigator.jspa 107 | [6]: http://www.apache.org/licenses/LICENSE-2.0.html 108 | [7]: https://github.com/couchbaselabs/CouchCocoa/wiki/Example-Snippets 109 | [8]: http://couchbaselabs.github.com/CouchCocoa/docs/ 110 | [10]: https://github.com/couchbaselabs/TouchDB-iOS 111 | [11]: http://mxcl.github.com/homebrew/ 112 | [12]: https://github.com/couchbaselabs/iOS-Couchbase-Demo 113 | -------------------------------------------------------------------------------- /REST/REST.h: -------------------------------------------------------------------------------- 1 | // 2 | // REST.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/12/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 "RESTResource.h" 17 | #import "RESTOperation.h" 18 | #import "RESTBody.h" 19 | -------------------------------------------------------------------------------- /REST/RESTBase64.h: -------------------------------------------------------------------------------- 1 | // 2 | // RESTBase64.h 3 | // CouchCocoa 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 RESTBase64 : 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 | @end -------------------------------------------------------------------------------- /REST/RESTBase64.m: -------------------------------------------------------------------------------- 1 | // 2 | // RESTBase64.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 9/14/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "RESTBase64.h" 10 | 11 | // Based on public-domain source code by cyrus.najmabadi@gmail.com 12 | // taken from http://www.cocoadev.com/index.pl?BaseSixtyFour 13 | 14 | 15 | @implementation RESTBase64 16 | 17 | 18 | static const uint8_t kEncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 19 | static int8_t kDecodingTable[256]; 20 | 21 | + (void) initialize { 22 | if (self == [RESTBase64 class]) { 23 | memset(kDecodingTable, 0xFF, sizeof(kDecodingTable)); 24 | for (NSInteger i = 0; i < sizeof(kEncodingTable); i++) { 25 | kDecodingTable[kEncodingTable[i]] = i; 26 | } 27 | } 28 | } 29 | 30 | 31 | + (NSString*) encode: (const void*)input length: (size_t)length { 32 | if (input == NULL) 33 | return nil; 34 | NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4]; 35 | uint8_t* output = (uint8_t*)data.mutableBytes; 36 | 37 | for (NSInteger i = 0; i < length; i += 3) { 38 | NSInteger value = 0; 39 | for (NSInteger j = i; j < (i + 3); j++) { 40 | value <<= 8; 41 | 42 | if (j < length) { 43 | value |= ((const uint8_t*)input)[j]; 44 | } 45 | } 46 | 47 | NSInteger index = (i / 3) * 4; 48 | output[index + 0] = kEncodingTable[(value >> 18) & 0x3F]; 49 | output[index + 1] = kEncodingTable[(value >> 12) & 0x3F]; 50 | output[index + 2] = (i + 1) < length ? kEncodingTable[(value >> 6) & 0x3F] : '='; 51 | output[index + 3] = (i + 2) < length ? kEncodingTable[(value >> 0) & 0x3F] : '='; 52 | } 53 | 54 | return [[[NSString alloc] initWithData:data 55 | encoding:NSASCIIStringEncoding] autorelease]; 56 | } 57 | 58 | 59 | + (NSString*) encode: (NSData*)rawBytes { 60 | return [self encode: rawBytes.bytes length: rawBytes.length]; 61 | } 62 | 63 | 64 | + (NSData*) decode: (const char*)string length: (size_t)inputLength { 65 | if ((string == NULL) || (inputLength % 4 != 0)) { 66 | return nil; 67 | } 68 | 69 | while (inputLength > 0 && string[inputLength - 1] == '=') { 70 | inputLength--; 71 | } 72 | 73 | size_t outputLength = inputLength * 3 / 4; 74 | NSMutableData* data = [NSMutableData dataWithLength:outputLength]; 75 | uint8_t* output = data.mutableBytes; 76 | 77 | NSInteger inputPoint = 0; 78 | NSInteger outputPoint = 0; 79 | while (inputPoint < inputLength) { 80 | uint8_t i0 = string[inputPoint++]; 81 | uint8_t i1 = string[inputPoint++]; 82 | uint8_t i2 = inputPoint < inputLength ? string[inputPoint++] : 'A'; /* 'A' will decode to \0 */ 83 | uint8_t i3 = inputPoint < inputLength ? string[inputPoint++] : 'A'; 84 | 85 | if (kDecodingTable[i0] < 0 || kDecodingTable[i1] < 0 86 | || kDecodingTable[i2] < 0 || kDecodingTable[i3] < 0) 87 | return nil; 88 | 89 | output[outputPoint++] = (kDecodingTable[i0] << 2) | (kDecodingTable[i1] >> 4); 90 | if (outputPoint < outputLength) { 91 | output[outputPoint++] = ((kDecodingTable[i1] & 0xf) << 4) | (kDecodingTable[i2] >> 2); 92 | } 93 | if (outputPoint < outputLength) { 94 | output[outputPoint++] = ((kDecodingTable[i2] & 0x3) << 6) | kDecodingTable[i3]; 95 | } 96 | } 97 | 98 | return data; 99 | } 100 | 101 | 102 | + (NSData*) decode:(NSString*) string { 103 | NSData* ascii = [string dataUsingEncoding: NSASCIIStringEncoding]; 104 | return [self decode: ascii.bytes length: ascii.length]; 105 | } 106 | 107 | 108 | @end 109 | -------------------------------------------------------------------------------- /REST/RESTBody.h: -------------------------------------------------------------------------------- 1 | // 2 | // RESTBody.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 5/28/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 RESTResource; 18 | 19 | 20 | /** An HTTP request/response body. 21 | Consists of a content blob, and a set of HTTP entity headers. */ 22 | @interface RESTBody : NSObject 23 | { 24 | @protected 25 | NSData* _content; 26 | NSDictionary* _headers; 27 | RESTResource* _resource; 28 | id _fromJSON; 29 | } 30 | 31 | /** Returns a sub-dictionary of the input, containing only the HTTP 1.1 entity headers and their values. */ 32 | + (NSDictionary*) entityHeadersFrom: (NSDictionary*)headers; 33 | 34 | /** Initializes an instance with content and HTTP entity headers. */ 35 | - (id) initWithContent: (NSData*)content 36 | headers: (NSDictionary*)headers 37 | resource: (RESTResource*)resource; 38 | 39 | /** Initializes an instance with content and a Content-Type: header. */ 40 | - (id) initWithData: (NSData*)content contentType: (NSString*)contentType; 41 | 42 | /** The raw content. */ 43 | @property (readonly, copy) NSData* content; 44 | 45 | /** The HTTP headers, with standard capitalization (first letter of each word capitalized.) */ 46 | @property (readonly, copy) NSDictionary* headers; 47 | 48 | /** The owning RESTResource. */ 49 | @property (readonly, retain) RESTResource* resource; 50 | 51 | /** The value of the Content-Type: header. */ 52 | @property (readonly, copy) NSString* contentType; 53 | 54 | /** The value of the Etag: header. */ 55 | @property (readonly, copy) NSString* eTag; 56 | 57 | /** The value of the Last-Modified: header. */ 58 | @property (readonly, copy) NSString* lastModified; 59 | 60 | /** Content parsed as string. */ 61 | @property (readonly) NSString* asString; 62 | 63 | /** Parses the content as JSON and returns the result. 64 | This value is cached, so subsequent calls are cheap. */ 65 | @property (readonly) id fromJSON; 66 | 67 | @end 68 | 69 | 70 | /** A mutable subclass of RESTBody that allows the content and headers to be replaced. */ 71 | @interface RESTMutableBody : RESTBody 72 | 73 | @property (readwrite, copy) NSData* content; 74 | @property (readwrite, copy) NSDictionary* headers; 75 | @property (readwrite, copy) NSMutableDictionary* mutableHeaders; 76 | @property (readwrite, copy) NSString* contentType; 77 | @property (readwrite, retain) RESTResource* resource; 78 | 79 | @end 80 | 81 | 82 | @interface RESTBody (JSON) 83 | /** Converts an object to UTF-8-encoded JSON data. 84 | JSON 'fragments' (NSString / NSNumber) are allowed. Returns nil on nil input. */ 85 | + (NSData*) dataWithJSONObject: (id)obj; 86 | /** Converts an object to a JSON string. 87 | JSON 'fragments' (NSString / NSNumber) are allowed. Returns nil on nil input. */ 88 | + (NSString*) stringWithJSONObject: (id)obj; 89 | /** Converts an object to a pretty-printed JSON string. 90 | JSON 'fragments' (NSString / NSNumber) are allowed. Returns nil on nil input. */ 91 | + (NSString*) prettyStringWithJSONObject: (id)obj; 92 | /** Parses JSON data into a Foundation object tree. 93 | If parsing fails, returns nil. */ 94 | + (id) JSONObjectWithData: (NSData*)data; 95 | /** Parses a JSON string into a Foundation object tree. 96 | If parsing fails, returns nil. */ 97 | + (id) JSONObjectWithString: (NSString*)string; 98 | 99 | /** Converts an NSDate to a string in ISO-8601 format (standard JSON representation). */ 100 | + (NSString*) JSONObjectWithDate: (NSDate*)date; 101 | 102 | /** Parses a string in ISO-8601 date format into an NSDate. 103 | Returns nil if the string isn't parseable, or if it isn't a string at all. */ 104 | + (NSDate*) dateWithJSONObject: (id)jsonObject; 105 | 106 | /** Encodes NSData to a Base64 string, which can be stored in JSON. */ 107 | + (NSString*) base64WithData: (NSData*)data; 108 | 109 | /** Decodes a Base64 string to NSData. 110 | Returns nil if the string is not valid Base64, or is not a string at all. */ 111 | + (NSData*) dataWithBase64: (NSString*)base64; 112 | 113 | @end 114 | -------------------------------------------------------------------------------- /REST/RESTCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // RESTCache.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/17/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 RESTResource; 18 | 19 | 20 | /** An in-memory cache of RESTResource objects. 21 | It keeps track of all added resources as long as anything else has retained them, 22 | and it keeps a certain number of recently-accessed resources with no external references. 23 | It's intended for use by a parent resource, to cache its children. 24 | 25 | Important: 26 | * It should contain only direct sibling objects, as it assumes that their -relativePath property values are all different. 27 | * A RESTResource can belong to only one RESTCache at a time. */ 28 | @interface RESTCache : NSObject 29 | { 30 | @private 31 | #ifdef TARGET_OS_IPHONE 32 | NSMutableDictionary* _map; 33 | #else 34 | NSMapTable* _map; 35 | #endif 36 | NSCache* _cache; 37 | } 38 | 39 | - (id) init; 40 | - (id) initWithRetainLimit: (NSUInteger)retainLimit; 41 | 42 | /** Adds a resource to the cache. 43 | Does nothing if the resource is already in the cache. 44 | An exception is raised if the resource is already in a different cache. */ 45 | - (void) addResource: (RESTResource*)resource; 46 | 47 | /** Looks up a resource given its -relativePath property. */ 48 | - (RESTResource*) resourceWithRelativePath: (NSString*)relativePath; 49 | 50 | /** Removes a resource from the cache. 51 | Does nothing if the resource is not cached. 52 | An exception is raised if the resource is already in a different cache. */ 53 | - (void) forgetResource: (RESTResource*)resource; 54 | 55 | /** Removes all resources from the cache. */ 56 | - (void) forgetAllResources; 57 | 58 | /** Removes retained references to objects. 59 | All objects that don't have anything else retaining them will be removed from the cache. */ 60 | - (void) unretainResources; 61 | 62 | - (NSArray*) allCachedResources; 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /REST/RESTCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // RESTCache.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/17/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 "RESTCache.h" 17 | #import "RESTInternal.h" 18 | 19 | 20 | static const NSUInteger kDefaultRetainLimit = 50; 21 | 22 | 23 | @implementation RESTCache 24 | 25 | 26 | - (id)init { 27 | return [self initWithRetainLimit: kDefaultRetainLimit]; 28 | } 29 | 30 | 31 | - (id)initWithRetainLimit: (NSUInteger)retainLimit { 32 | self = [super init]; 33 | if (self) { 34 | #ifdef TARGET_OS_IPHONE 35 | // Construct a CFDictionary that doesn't retain its values: 36 | CFDictionaryValueCallBacks valueCB = kCFTypeDictionaryValueCallBacks; 37 | valueCB.retain = NULL; 38 | valueCB.release = NULL; 39 | _map = (NSMutableDictionary*)CFDictionaryCreateMutable( 40 | NULL, 100, &kCFCopyStringDictionaryKeyCallBacks, &valueCB); 41 | #else 42 | // Construct an NSMapTable that doesn't retain its values: 43 | _map = [[NSMapTable alloc] initWithKeyOptions: NSPointerFunctionsStrongMemory | 44 | NSPointerFunctionsObjectPersonality 45 | valueOptions: NSPointerFunctionsZeroingWeakMemory | 46 | NSPointerFunctionsObjectPersonality 47 | capacity: 100]; 48 | #endif 49 | if (retainLimit > 0) { 50 | _cache = [[NSCache alloc] init]; 51 | _cache.countLimit = retainLimit; 52 | } 53 | } 54 | return self; 55 | } 56 | 57 | 58 | - (void)dealloc { 59 | for (RESTResource* doc in _map.objectEnumerator) 60 | doc.owningCache = nil; 61 | [_map release]; 62 | // Calling -release on the cache right now is dangerous because it might already be 63 | // flushing itself (which may have triggered deallocation of my owner and hence myself), 64 | // and deallocing it in the midst of that will cause it to deadlock. So delay the release. 65 | // See 66 | [_cache autorelease]; 67 | [super dealloc]; 68 | } 69 | 70 | 71 | - (void) addResource: (RESTResource*)resource { 72 | resource.owningCache = self; 73 | NSString* key = resource.relativePath; 74 | NSAssert(![_map objectForKey: key], @"Caching duplicate items for '%@': %p, now %p", 75 | key, [_map objectForKey: key], resource); 76 | [_map setObject: resource forKey: key]; 77 | if (_cache) 78 | [_cache setObject: resource forKey: key]; 79 | else 80 | [[resource retain] autorelease]; 81 | } 82 | 83 | 84 | - (RESTResource*) resourceWithRelativePath: (NSString*)docID { 85 | RESTResource* doc = [_map objectForKey: docID]; 86 | if (doc && _cache && ![_cache objectForKey:docID]) 87 | [_cache setObject: doc forKey: docID]; // re-add doc to NSCache since it's recently used 88 | return doc; 89 | } 90 | 91 | 92 | - (void) forgetResource: (RESTResource*)resource { 93 | RESTCache* cache = resource.owningCache; 94 | if (cache) { 95 | NSAssert(cache == self, @"Removing object from the wrong cache"); 96 | resource.owningCache = nil; 97 | [_map removeObjectForKey: resource.relativePath]; 98 | } 99 | } 100 | 101 | 102 | - (void) resourceBeingDealloced:(RESTResource*)resource { 103 | [_map removeObjectForKey: resource.relativePath]; 104 | } 105 | 106 | 107 | - (NSArray*) allCachedResources { 108 | return _map.allValues; 109 | } 110 | 111 | 112 | - (void) unretainResources { 113 | [_cache removeAllObjects]; 114 | } 115 | 116 | 117 | - (void) forgetAllResources { 118 | [_map removeAllObjects]; 119 | [_cache removeAllObjects]; 120 | } 121 | 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /REST/RESTInternal.h: -------------------------------------------------------------------------------- 1 | // 2 | // RESTInternal.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/25/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 "REST.h" 17 | #import "RESTCache.h" 18 | 19 | 20 | void RESTWarn(NSString* format, ...) __attribute__((format(__NSString__, 1, 2)));; 21 | 22 | #define Warn RESTWarn 23 | 24 | extern BOOL gRESTWarnRaisesException; 25 | 26 | 27 | // Safe dynamic cast that returns nil if the object is not the expected class: 28 | #define $castIf(CLASSNAME,OBJ) ((CLASSNAME*)(RESTCastIf([CLASSNAME class],(OBJ)))) 29 | #define $castIfArrayOf(ITEMCLASSNAME,OBJ) RESTCastArrayOf([ITEMCLASSNAME class],(OBJ))) 30 | id RESTCastIf(Class,id); 31 | id RESTCastIfArrayOf(Class,id); 32 | 33 | 34 | // Object equality that correctly returns YES when both are nil: 35 | static inline BOOL $equal(id a, id b) {return a==b || [a isEqual: b];} 36 | 37 | 38 | @interface RESTOperation () 39 | + (NSError*) errorWithHTTPStatus: (int)httpStatus 40 | message: (NSString*)message 41 | URL: (NSURL*)url; 42 | @property (nonatomic, readonly) UInt8 retryCount; 43 | @end 44 | 45 | 46 | @interface RESTResource () 47 | - (void) setURL: (NSURL*)url; 48 | - (void) assignedRelativePath: (NSString*)relativePath; 49 | @property (readwrite, retain) RESTCache* owningCache; 50 | - (NSURLCredential*) credentialForOperation: (RESTOperation*)op; 51 | - (NSURLProtectionSpace*) protectionSpaceForOperation: (RESTOperation*)op; 52 | @end 53 | 54 | 55 | @interface RESTCache () 56 | - (void) resourceBeingDealloced:(RESTResource*)resource; 57 | @end 58 | 59 | 60 | @interface NSArray (RESTExtensions) 61 | - (NSArray*) rest_map: (id (^)(id obj))block; 62 | @end -------------------------------------------------------------------------------- /REST/RESTInternal.m: -------------------------------------------------------------------------------- 1 | // 2 | // RESTInternal.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 6/25/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 "RESTInternal.h" 17 | 18 | 19 | #define kWarningPrefix @"WARNING: " 20 | 21 | 22 | BOOL gRESTWarnRaisesException = NO; 23 | 24 | 25 | void RESTWarn( NSString *msg, ... ) 26 | { 27 | va_list args; 28 | va_start(args,msg); 29 | NSLogv([kWarningPrefix stringByAppendingString: msg], args); 30 | va_end(args); 31 | 32 | if (gRESTWarnRaisesException) { 33 | va_start(args,msg); 34 | [NSException raise: @"RESTWarning" 35 | format: msg 36 | arguments: args]; 37 | } 38 | } 39 | 40 | 41 | id RESTCastIf( Class requiredClass, id object ) 42 | { 43 | if( object && ! [object isKindOfClass: requiredClass] ) { 44 | Warn(@"$castIf: Expected %@, got %@ %@", requiredClass, [object class], object); 45 | object = nil; 46 | } 47 | return object; 48 | } 49 | 50 | NSArray* RESTCastIfArrayOf(Class itemClass, id object) 51 | { 52 | NSArray* array = $castIf(NSArray, object); 53 | for( id item in array ) { 54 | if (![item isKindOfClass: itemClass]) { 55 | Warn(@"$castIfArrayOf: Expected %@, got %@ %@", itemClass, [item class], item); 56 | return nil; 57 | } 58 | } 59 | return array; 60 | } 61 | 62 | 63 | @implementation NSArray (RESTExtensions) 64 | 65 | - (NSArray*) rest_map: (id (^)(id obj))block { 66 | NSMutableArray* mapped = [[NSMutableArray alloc] initWithCapacity: self.count]; 67 | for (id obj in self) { 68 | obj = block(obj); 69 | if (obj) 70 | [mapped addObject: obj]; 71 | } 72 | NSArray* result = [[mapped copy] autorelease]; 73 | [mapped release]; 74 | return result; 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /REST/RESTOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // RESTOperation.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 5/26/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 RESTBody, RESTResource; 18 | 19 | 20 | /** Error domain used for HTTP errors (status >= 300). The code is the HTTP status. */ 21 | extern NSString* const CouchHTTPErrorDomain; 22 | 23 | 24 | /** Type of block that's called when a RESTOperation completes (see -onComplete:). */ 25 | typedef void (^OnCompleteBlock)(); 26 | 27 | 28 | /** Represents an HTTP request to a RESTResource, and its response. 29 | Can be used either synchronously or asynchronously. Methods that return information about the 30 | response, such as -httpStatus or -body, will block if called before the response is available. 31 | Or you can explicitly block by calling -wait. 32 | On the other hand, to avoid blocking you can call -onCompletion: to schedule an Objective-C 33 | block to run when the response is complete. (Yes, the non-blocking mode takes a block... :) */ 34 | @interface RESTOperation : NSObject 35 | { 36 | @private 37 | RESTResource* _resource; 38 | NSURLRequest* _request; 39 | NSURLConnection* _connection; 40 | SInt8 _state; 41 | UInt8 _retryCount; 42 | BOOL _waiting; 43 | NSError* _error; 44 | 45 | NSHTTPURLResponse* _response; 46 | NSMutableData* _body; 47 | id _resultObject; 48 | 49 | NSMutableArray* _onCompletes; 50 | } 51 | 52 | /** Initializes a RESTOperation, but doesn't start loading it yet. 53 | Call -load, -wait or any synchronous method to start it. */ 54 | - (id) initWithResource: (RESTResource*)resource request: (NSURLRequest*)request; 55 | 56 | /** The RESTResource instance that created this operation. */ 57 | @property (readonly) RESTResource* resource; 58 | /** The target URL of this operation. 59 | (This is not necessarily the same as the URL of its resource! It's often the same, but it may have query parameters or sub-paths appended to it.) */ 60 | @property (readonly) NSURL* URL; 61 | /** The last component of the URL's path. */ 62 | @property (readonly) NSString* name; 63 | /** The HTTP method of the request. */ 64 | @property (readonly) NSString* method; 65 | /** The underlying URL request. */ 66 | @property (readonly) NSURLRequest* request; 67 | 68 | @property (readonly) BOOL isReadOnly; /**< Is this a GET or HEAD request? */ 69 | @property (readonly) BOOL isGET; /**< Is this a GET request? */ 70 | @property (readonly) BOOL isPUT; /**< Is this a PUT request? */ 71 | @property (readonly) BOOL isPOST; /**< Is this a POST request? */ 72 | @property (readonly) BOOL isDELETE; /**< Is this a DELETE request? */ 73 | 74 | /** Sets an HTTP request header. Must be called before loading begins! */ 75 | - (void) setValue: (NSString*)value forHeader: (NSString*)headerName; 76 | 77 | /** The HTTP request body. Cannot be changed after the operation starts. */ 78 | @property (copy) NSData* requestBody; 79 | 80 | #pragma mark LOADING: 81 | 82 | /** Sends the request, asynchronously. Subsequent calls do nothing. 83 | @return The receiver (self), to make it easy to say "return [op start];". */ 84 | - (RESTOperation*) start; 85 | 86 | /** Will call the given block when the request finishes. 87 | This method may be called multiple times; blocks will be called in the order added. 88 | @param onComplete The block to be called when the request finishes. 89 | @return YES if the block has been called by the time this method returns, NO if it will be called in the future. */ 90 | - (BOOL) onCompletion: (OnCompleteBlock)onComplete; 91 | 92 | /** Blocks till any pending network operation finishes (i.e. -isComplete becomes true.) 93 | -start will be called if it hasn't yet been. 94 | On completion, any pending onCompletion blocks are called first, before this method returns. 95 | The synchronous methods below all end up calling this one. 96 | @return YES on success, NO on error. */ 97 | - (BOOL) wait; 98 | 99 | /** Same as -wait but also returns any resulting error in the outError parameter. 100 | This is useful if the receiver is an intermediate value not accessible in a variable, for instance if you do something like 101 | [[resource GET] wait: &error] */ 102 | - (BOOL) wait: (NSError**)outError; 103 | 104 | /** Blocks until all of the given operations have finished. 105 | @param operations A set of RESTOperations. 106 | @return YES if all operations succeeded; NO if any of them failed. */ 107 | + (BOOL) wait: (NSSet*)operations; 108 | 109 | /** Stops an active operation. 110 | The operation will immediately complete, with error NSURLErrorCancelled in domain NSURLErrorDomain. 111 | Has no effect if the operation has already completed. */ 112 | - (void) cancel; 113 | 114 | #pragma mark RESPONSE: 115 | 116 | /** YES if the response is complete (whether successful or unsuccessful.) */ 117 | @property (readonly) BOOL isComplete; 118 | 119 | /** If the request has failed, this will be set to an NSError describing what went wrong; else it's nil. 120 | An HTTP response status of 300 or greater is considered an error and will cause this property to be set. 121 | This method does not block, but it won't be set to a non-nil value until the operation finishes. */ 122 | @property (readonly, retain) NSError* error; 123 | 124 | /** YES if there is no error and the HTTP status is <= 299 (Synchronous.) */ 125 | @property (readonly) BOOL isSuccessful; 126 | 127 | /** HTTP status code of the response (Synchronous.) 128 | Until the request finishes, this is zero. It's also zero if a lower-level network error occurred (like if the host couldn't be found or the TCP connection was reset.) */ 129 | @property (readonly) int httpStatus; 130 | 131 | /** Dictionary of HTTP response headers (Synchronous.) */ 132 | @property (readonly) NSDictionary* responseHeaders; 133 | 134 | /** The body of the response, with its entity headers (Synchronous.) */ 135 | @property (readonly) RESTBody* responseBody; 136 | 137 | /** The raw NSHTTPURLResponse object, in case you need it. */ 138 | @property (readonly) NSHTTPURLResponse* response; 139 | 140 | 141 | /** Object associated with this response. 142 | A client can store anything it wants here, typically a value parsed from or represented by the response body; often this property will be set by an onCompletion block. */ 143 | @property (retain) id resultObject; 144 | 145 | 146 | /** Debugging utility that returns a sort-of log of the HTTP request and response. */ 147 | - (NSString*) dump; 148 | 149 | @end 150 | 151 | 152 | 153 | /** Levels of logging that RESTResponses can perform. */ 154 | typedef enum { 155 | kRESTLogNothing = 0, 156 | kRESTLogRequestURLs, 157 | kRESTLogRequestHeaders 158 | } RESTLogLevel; 159 | 160 | /** The current level of logging used by all RESTResponses. */ 161 | extern RESTLogLevel gRESTLogLevel; 162 | -------------------------------------------------------------------------------- /Resources/CouchCocoa-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | org.couchbase.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSHumanReadableCopyright 24 | Copyright © 2011 Couchbase, Inc. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbaselabs/CouchCocoa/df2b7ca5c93ea761f935d1fa94fd9452d2df31b0/Resources/logo.png -------------------------------------------------------------------------------- /Test/CouchTestCase.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchTestCase.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 9/2/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class CouchServer, CouchDatabase; 11 | 12 | 13 | @interface CouchTestCase : SenTestCase 14 | { 15 | CouchServer* _server; 16 | CouchDatabase* _db; 17 | } 18 | 19 | @property (readonly) CouchDatabase* db; 20 | 21 | @end 22 | 23 | 24 | // Waits for a RESTOperation to complete and raises an assertion failure if it got an error. 25 | #define AssertWait(OP) ({RESTOperation* i_op = (OP);\ 26 | STAssertTrue([i_op wait], @"%@ failed: %@", i_op, i_op.error);\ 27 | i_op = i_op;}) 28 | 29 | 30 | -------------------------------------------------------------------------------- /Test/CouchTestCase.m: -------------------------------------------------------------------------------- 1 | // 2 | // CouchTestCase.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 9/2/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "CouchTestCase.h" 10 | #import "CouchInternal.h" 11 | #import "CouchDesignDocument.h" 12 | 13 | 14 | @implementation CouchTestCase 15 | 16 | 17 | - (void) setUp { 18 | gRESTWarnRaisesException = YES; 19 | [self raiseAfterFailure]; 20 | 21 | _server = [[CouchServer alloc] init]; // local server 22 | STAssertNotNil(_server, @"Couldn't create server object"); 23 | _server.tracksActiveOperations = YES; 24 | 25 | _db = [[_server databaseNamed: @"testdb_temporary"] retain]; 26 | STAssertNotNil(_db, @"Couldn't create database object"); 27 | RESTOperation* op = [_db create]; 28 | if (![op wait]) { 29 | NSLog(@"NOTE: DB '%@' exists; deleting and re-creating it for tests", _db.relativePath); 30 | STAssertEquals(op.httpStatus, 412, 31 | @"Unexpected error creating db: %@", op.error); 32 | AssertWait([_db DELETE]); 33 | AssertWait([_db create]); 34 | } 35 | 36 | gRESTLogLevel = kRESTLogRequestHeaders; // kRESTLogNothing; 37 | } 38 | 39 | 40 | - (void) tearDown { 41 | gRESTLogLevel = kRESTLogNothing; 42 | AssertWait([_db DELETE]); 43 | STAssertEquals(_server.activeOperations.count, (NSUInteger)0, nil); 44 | [_db release]; 45 | _db = nil; 46 | [_server release]; 47 | _server = nil; 48 | } 49 | 50 | 51 | @synthesize db = _db; 52 | 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Test/Resources/CouchCocoa-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | org.couchbase.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSHumanReadableCopyright 24 | Copyright © 2011 Couchbase, Inc. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Test/Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbaselabs/CouchCocoa/df2b7ca5c93ea761f935d1fa94fd9452d2df31b0/Test/Resources/logo.png -------------------------------------------------------------------------------- /Test/Test_DynamicObject.m: -------------------------------------------------------------------------------- 1 | // 2 | // Test_DynamicObject.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 8/27/11. 6 | // Copyright (c) 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "CouchDynamicObject.h" 10 | #import "CouchInternal.h" 11 | #import 12 | 13 | 14 | @interface TestDynamicObject : CouchDynamicObject 15 | { 16 | @public 17 | NSMutableDictionary* _dict; 18 | } 19 | - (id) initWithDictionary: (NSDictionary*)dict; 20 | @property (readwrite,copy) NSString *stringy; 21 | @property (readonly) int intey; 22 | @property (readwrite) short shorty; 23 | @property (readwrite) double doubley; 24 | @property (readwrite) bool booley; 25 | @end 26 | 27 | @implementation TestDynamicObject 28 | 29 | - (id) initWithDictionary: (NSDictionary*)dict { 30 | self = [super init]; 31 | if (self) {_dict = [dict mutableCopy];} 32 | return self; 33 | } 34 | 35 | - (void)dealloc { 36 | [_dict release]; 37 | [super dealloc]; 38 | } 39 | 40 | - (id) getValueOfProperty: (NSString*)property { 41 | return [_dict objectForKey: property]; 42 | } 43 | 44 | - (BOOL) setValue: (id)value ofProperty: (NSString*)property { 45 | [_dict setValue: value forKey: property]; 46 | return YES; 47 | } 48 | 49 | @dynamic stringy, intey, shorty, doubley, booley; 50 | 51 | @end 52 | 53 | 54 | @interface TestDynamicSubclass : TestDynamicObject 55 | @property (copy) NSData* dataey; 56 | @end 57 | 58 | @implementation TestDynamicSubclass 59 | @dynamic dataey; 60 | @end 61 | 62 | 63 | @interface Test_DynamicObject : SenTestCase 64 | @end 65 | 66 | 67 | @implementation Test_DynamicObject 68 | 69 | 70 | - (void) test0_Subclass { 71 | // It's important to run this test first, before the accessor methods are created for 72 | // TestDynamicObject by the other tests, because this one ensure the methods get attached to 73 | // the appropriate class in the hierarchy. 74 | //gCouchLogLevel = 2; 75 | NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: 76 | @"String value", @"stringy", 77 | [NSNumber numberWithInt: -6789], @"intey", 78 | [NSData data], @"dataey", nil]; 79 | TestDynamicSubclass *test = [[TestDynamicSubclass alloc] initWithDictionary: dict]; 80 | STAssertEqualObjects(test.stringy, @"String value", nil); 81 | STAssertEquals(test.intey, -6789, nil); 82 | STAssertEquals(test.doubley, 0.0, nil); 83 | STAssertEqualObjects(test.dataey, [NSData data], nil); 84 | 85 | test.stringy = nil; 86 | STAssertEqualObjects(test.stringy, nil, nil); 87 | test.doubley = 123.456; 88 | STAssertEquals(test.doubley, 123.456, nil); 89 | test.booley = true; 90 | STAssertEquals(test.booley, (bool)true, nil); 91 | test.dataey = nil; 92 | STAssertEqualObjects(test.dataey, nil, nil); 93 | } 94 | 95 | 96 | - (void) test1_EmptyDynamicObject { 97 | TestDynamicObject *test = [[TestDynamicObject alloc] initWithDictionary: 98 | [NSDictionary dictionary]]; 99 | STAssertTrue([test respondsToSelector: @selector(setStringy:)], nil); 100 | STAssertFalse([test respondsToSelector: NSSelectorFromString(@"setIntey:")], nil); 101 | STAssertFalse([test respondsToSelector: @selector(dataey)], nil); 102 | STAssertFalse([test respondsToSelector: NSSelectorFromString(@"size")], nil); 103 | STAssertEqualObjects(test.stringy, nil, nil); 104 | STAssertEquals(test.intey, 0, nil); 105 | STAssertEquals(test.doubley, 0.0, nil); 106 | STAssertEquals(test.booley, (bool)false, nil); 107 | [test release]; 108 | } 109 | 110 | 111 | - (void) test2_DynamicObject { 112 | NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: 113 | @"String value", @"stringy", 114 | [NSNumber numberWithInt: -6789], @"intey", nil]; 115 | TestDynamicObject *test = [[TestDynamicObject alloc] initWithDictionary: dict]; 116 | STAssertEqualObjects(test.stringy, @"String value", nil); 117 | STAssertEquals(test.intey, -6789, nil); 118 | STAssertEquals(test.doubley, 0.0, nil); 119 | 120 | test.stringy = nil; 121 | STAssertEqualObjects(test.stringy, nil, nil); 122 | test.doubley = 123.456; 123 | STAssertEquals(test.doubley, 123.456, nil); 124 | test.booley = true; 125 | STAssertEquals(test.booley, (bool)true, nil); 126 | 127 | NSDictionary* expected = [NSDictionary dictionaryWithObjectsAndKeys: 128 | [NSNumber numberWithBool: YES], @"booley", 129 | [NSNumber numberWithDouble: 123.456], @"doubley", 130 | [NSNumber numberWithInt: -6789], @"intey", nil]; 131 | STAssertEqualObjects(test->_dict, expected, nil); 132 | [test release]; 133 | } 134 | 135 | 136 | - (void) test3_intTypes { 137 | NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: 138 | [NSNumber numberWithShort: -6789], @"shorty", nil]; 139 | TestDynamicObject *test = [[TestDynamicObject alloc] initWithDictionary: dict]; 140 | STAssertEquals(test.shorty, (short)-6789, nil); 141 | test.shorty = 32767; 142 | STAssertEquals(test.shorty, (short)32767, nil); 143 | STAssertEqualObjects([test->_dict objectForKey: @"shorty"], [NSNumber numberWithShort: 32767], nil); 144 | test.shorty = -32768; 145 | STAssertEquals(test.shorty, (short)-32768, nil); 146 | STAssertEqualObjects([test->_dict objectForKey: @"shorty"], [NSNumber numberWithShort: -32768], nil); 147 | } 148 | 149 | 150 | @end 151 | -------------------------------------------------------------------------------- /Test/libcrypto-iphonesimulator.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbaselabs/CouchCocoa/df2b7ca5c93ea761f935d1fa94fd9452d2df31b0/Test/libcrypto-iphonesimulator.a -------------------------------------------------------------------------------- /Test/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbaselabs/CouchCocoa/df2b7ca5c93ea761f935d1fa94fd9452d2df31b0/Test/logo.png -------------------------------------------------------------------------------- /UI/iOS/CouchUITableSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // CouchUITableSource.h 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 8/2/11. 6 | // Copyright 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class CouchDocument, CouchLiveQuery, CouchQueryRow, RESTOperation; 11 | 12 | /** A UITableView data source driven by a CouchLiveQuery. */ 13 | @interface CouchUITableSource : NSObject 14 | 15 | @property (nonatomic, retain) IBOutlet UITableView* tableView; 16 | 17 | @property (retain) CouchLiveQuery* query; 18 | 19 | /** Rebuilds the table from the query's current .rows property. */ 20 | -(void) reloadFromQuery; 21 | 22 | 23 | #pragma mark Row Accessors: 24 | 25 | /** The current array of CouchQueryRows being used as the data source for the table. */ 26 | @property (nonatomic, readonly) NSArray* rows; 27 | 28 | /** Convenience accessor to get the row object for a given table row index. */ 29 | - (CouchQueryRow*) rowAtIndex: (NSUInteger)index; 30 | 31 | /** Convenience accessor to find the index path of the row with a given document. */ 32 | - (NSIndexPath*) indexPathForDocument: (CouchDocument*)document; 33 | 34 | /** Convenience accessor to return the document at a given index path. */ 35 | - (CouchDocument*) documentAtIndexPath: (NSIndexPath*)path; 36 | 37 | 38 | #pragma mark Displaying The Table: 39 | 40 | /** If non-nil, specifies the property name of the query row's value that will be used for the table row's visible label. 41 | If the row's value is not a dictionary, or if the property doesn't exist, the property will next be looked up in the document's properties. 42 | If this doesn't meet your needs for labeling rows, you should implement -couchTableSource:willUseCell:forRow: in the table's delegate. */ 43 | @property (copy) NSString* labelProperty; 44 | 45 | 46 | #pragma mark Editing The Table: 47 | 48 | /** Is the user allowed to delete rows by UI gestures? (Defaults to YES.) */ 49 | @property (nonatomic) BOOL deletionAllowed; 50 | 51 | /** Asynchronously deletes the documents at the given row indexes, animating the removal from the table. */ 52 | - (void) deleteDocumentsAtIndexes: (NSArray*)indexPaths; 53 | 54 | /** Asynchronously deletes the given documents, animating the removal from the table. */ 55 | - (void) deleteDocuments: (NSArray*)documents; 56 | 57 | @end 58 | 59 | 60 | /** Additional methods for the table view's delegate, that will be invoked by the CouchUITableSource. */ 61 | @protocol CouchUITableDelegate 62 | @optional 63 | 64 | /** Allows delegate to return its own custom cell, just like -tableView:cellForRowAtIndexPath:. 65 | If this returns nil the table source will create its own cell, as if this method were not implemented. */ 66 | - (UITableViewCell *)couchTableSource:(CouchUITableSource*)source 67 | cellForRowAtIndexPath:(NSIndexPath *)indexPath; 68 | 69 | /** Called after the query's results change, before the table view is reloaded. */ 70 | - (void)couchTableSource:(CouchUITableSource*)source 71 | willUpdateFromQuery:(CouchLiveQuery*)query; 72 | 73 | /** Called after the query's results change to update the table view. If this method is not implemented by the delegate, reloadData is called on the table view.*/ 74 | - (void)couchTableSource:(CouchUITableSource*)source 75 | updateFromQuery:(CouchLiveQuery*)query 76 | previousRows:(NSArray *)previousRows; 77 | 78 | /** Called from -tableView:cellForRowAtIndexPath: just before it returns, giving the delegate a chance to customize the new cell. */ 79 | - (void)couchTableSource:(CouchUITableSource*)source 80 | willUseCell:(UITableViewCell*)cell 81 | forRow:(CouchQueryRow*)row; 82 | 83 | /** Called if a CouchDB operation invoked by the source (e.g. deleting a document) fails. */ 84 | - (void)couchTableSource:(CouchUITableSource*)source 85 | operationFailed:(RESTOperation*)op; 86 | 87 | @end -------------------------------------------------------------------------------- /UI/iOS/CouchUITableSource.m: -------------------------------------------------------------------------------- 1 | // 2 | // CouchUITableSource.m 3 | // CouchCocoa 4 | // 5 | // Created by Jens Alfke on 8/2/11. 6 | // Copyright 2011 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "CouchUITableSource.h" 10 | #import "CouchInternal.h" 11 | 12 | 13 | @interface CouchUITableSource () 14 | { 15 | @private 16 | UITableView* _tableView; 17 | CouchLiveQuery* _query; 18 | NSMutableArray* _rows; 19 | NSString* _labelProperty; 20 | BOOL _deletionAllowed; 21 | } 22 | @end 23 | 24 | 25 | @implementation CouchUITableSource 26 | 27 | 28 | - (id)init { 29 | self = [super init]; 30 | if (self) { 31 | _deletionAllowed = YES; 32 | } 33 | return self; 34 | } 35 | 36 | 37 | - (void)dealloc { 38 | [_rows release]; 39 | [_query removeObserver: self forKeyPath: @"rows"]; 40 | [_query release]; 41 | [super dealloc]; 42 | } 43 | 44 | 45 | #pragma mark - 46 | #pragma mark ACCESSORS: 47 | 48 | 49 | @synthesize tableView=_tableView; 50 | @synthesize rows=_rows; 51 | 52 | 53 | - (CouchQueryRow*) rowAtIndex: (NSUInteger)index { 54 | return [_rows objectAtIndex: index]; 55 | } 56 | 57 | 58 | - (NSIndexPath*) indexPathForDocument: (CouchDocument*)document { 59 | NSString* documentID = document.documentID; 60 | NSUInteger index = 0; 61 | for (CouchQueryRow* row in _rows) { 62 | if ([row.documentID isEqualToString: documentID]) 63 | return [NSIndexPath indexPathForRow: index inSection: 0]; 64 | ++index; 65 | } 66 | return nil; 67 | } 68 | 69 | 70 | - (CouchDocument*) documentAtIndexPath: (NSIndexPath*)path { 71 | if (path.section == 0) 72 | return [[_rows objectAtIndex: path.row] document]; 73 | return nil; 74 | } 75 | 76 | 77 | - (id) tellDelegate: (SEL)selector withObject: (id)object { 78 | id delegate = _tableView.delegate; 79 | if ([delegate respondsToSelector: selector]) 80 | return [delegate performSelector: selector withObject: self withObject: object]; 81 | return nil; 82 | } 83 | 84 | 85 | #pragma mark - 86 | #pragma mark QUERY HANDLING: 87 | 88 | 89 | - (CouchLiveQuery*) query { 90 | return _query; 91 | } 92 | 93 | - (void) setQuery:(CouchLiveQuery *)query { 94 | if (query != _query) { 95 | [_query removeObserver: self forKeyPath: @"rows"]; 96 | [_query autorelease]; 97 | _query = [query retain]; 98 | [_query addObserver: self forKeyPath: @"rows" options: 0 context: NULL]; 99 | [self reloadFromQuery]; 100 | } 101 | } 102 | 103 | 104 | -(void) reloadFromQuery { 105 | CouchQueryEnumerator* rowEnum = _query.rows; 106 | if (rowEnum) { 107 | NSArray *oldRows = [_rows retain]; 108 | [_rows release]; 109 | _rows = [rowEnum.allObjects mutableCopy]; 110 | [self tellDelegate: @selector(couchTableSource:willUpdateFromQuery:) withObject: _query]; 111 | 112 | id delegate = _tableView.delegate; 113 | SEL selector = @selector(couchTableSource:updateFromQuery:previousRows:); 114 | if ([delegate respondsToSelector: selector]) { 115 | [delegate couchTableSource: self 116 | updateFromQuery: _query 117 | previousRows: oldRows]; 118 | } else { 119 | [self.tableView reloadData]; 120 | } 121 | [oldRows release]; 122 | } 123 | } 124 | 125 | 126 | - (void) observeValueForKeyPath: (NSString*)keyPath ofObject: (id)object 127 | change: (NSDictionary*)change context: (void*)context 128 | { 129 | if (object == _query) 130 | [self reloadFromQuery]; 131 | } 132 | 133 | 134 | #pragma mark - 135 | #pragma mark DATA SOURCE PROTOCOL: 136 | 137 | 138 | @synthesize labelProperty=_labelProperty; 139 | 140 | 141 | - (NSString*) labelForRow: (CouchQueryRow*)row { 142 | id value = row.value; 143 | if (_labelProperty) { 144 | if ([value isKindOfClass: [NSDictionary class]]) 145 | value = [value objectForKey: _labelProperty]; 146 | else 147 | value = nil; 148 | if (!value) 149 | value = [row.document propertyForKey: _labelProperty]; 150 | } 151 | return [value description]; 152 | } 153 | 154 | 155 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 156 | return _rows.count; 157 | } 158 | 159 | 160 | - (UITableViewCell *)tableView:(UITableView *)tableView 161 | cellForRowAtIndexPath:(NSIndexPath *)indexPath 162 | { 163 | // Allow the delegate to create its own cell: 164 | UITableViewCell* cell = [self tellDelegate: @selector(couchTableSource:cellForRowAtIndexPath:) 165 | withObject: indexPath]; 166 | if (!cell) { 167 | // ...if it doesn't, create a cell for it: 168 | cell = [tableView dequeueReusableCellWithIdentifier: @"CouchUITableDelegate"]; 169 | if (!cell) 170 | cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault 171 | reuseIdentifier: @"CouchUITableDelegate"] 172 | autorelease]; 173 | 174 | CouchQueryRow* row = [self rowAtIndex: indexPath.row]; 175 | cell.textLabel.text = [self labelForRow: row]; 176 | 177 | // Allow the delegate to customize the cell: 178 | id delegate = _tableView.delegate; 179 | if ([delegate respondsToSelector: @selector(couchTableSource:willUseCell:forRow:)]) 180 | [(id)delegate couchTableSource: self willUseCell: cell forRow: row]; 181 | } 182 | return cell; 183 | } 184 | 185 | 186 | #pragma mark - 187 | #pragma mark EDITING: 188 | 189 | 190 | @synthesize deletionAllowed=_deletionAllowed; 191 | 192 | 193 | - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { 194 | return _deletionAllowed; 195 | } 196 | 197 | 198 | - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { 199 | // Queries have a sort order so reordering doesn't generally make sense. 200 | return NO; 201 | } 202 | 203 | 204 | - (void) checkDelete: (RESTOperation*)op { 205 | if (!op.isSuccessful) { 206 | // If the delete failed, undo the table row deletion by reloading from the db: 207 | [self tellDelegate: @selector(couchTableSource:operationFailed:) withObject: op]; 208 | [self reloadFromQuery]; 209 | } 210 | } 211 | 212 | 213 | - (void)tableView:(UITableView *)tableView 214 | commitEditingStyle:(UITableViewCellEditingStyle)editingStyle 215 | forRowAtIndexPath:(NSIndexPath *)indexPath 216 | { 217 | if (editingStyle == UITableViewCellEditingStyleDelete) { 218 | // Delete the document from the database, asynchronously. 219 | RESTOperation* op = [[[self rowAtIndex:indexPath.row] document] DELETE]; 220 | [op onCompletion: ^{ [self checkDelete: op]; }]; 221 | [op start]; 222 | 223 | // Delete the row from the table data source. 224 | [_rows removeObjectAtIndex:indexPath.row]; 225 | [self.tableView deleteRowsAtIndexPaths: [NSArray arrayWithObject:indexPath] 226 | withRowAnimation: UITableViewRowAnimationFade]; 227 | } 228 | } 229 | 230 | 231 | - (void) deleteDocuments: (NSArray*)documents atIndexes: (NSArray*)indexPaths { 232 | RESTOperation* op = [_query.database deleteDocuments: documents]; 233 | [op onCompletion: ^{ [self checkDelete: op]; }]; 234 | 235 | NSMutableIndexSet* indexSet = [NSMutableIndexSet indexSet]; 236 | for (NSIndexPath* path in indexPaths) { 237 | if (path.section == 0) 238 | [indexSet addIndex: path.row]; 239 | } 240 | [_rows removeObjectsAtIndexes: indexSet]; 241 | 242 | [_tableView deleteRowsAtIndexPaths: indexPaths withRowAnimation: UITableViewRowAnimationFade]; 243 | } 244 | 245 | 246 | - (void) deleteDocumentsAtIndexes: (NSArray*)indexPaths { 247 | NSArray* docs = [indexPaths rest_map: ^(id path) {return [self documentAtIndexPath: path];}]; 248 | [self deleteDocuments: docs atIndexes: indexPaths]; 249 | } 250 | 251 | 252 | - (void) deleteDocuments: (NSArray*)documents { 253 | NSArray* paths = [documents rest_map: ^(id doc) {return [self indexPathForDocument: doc];}]; 254 | [self deleteDocuments: documents atIndexes: paths]; 255 | } 256 | 257 | 258 | @end 259 | --------------------------------------------------------------------------------