Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First commit into Git.

  • Loading branch information...
commit c9be12651ff3c8d642779b16a5028b44f8a28979 0 parents
@snej snej authored
Showing with 9,795 additions and 0 deletions.
  1. +8 −0 .gitignore
  2. +3 −0  .gitmodules
  3. +14 −0 Couch/Couch.h
  4. +38 −0 Couch/CouchAttachment.h
  5. +128 −0 Couch/CouchAttachment.m
  6. +32 −0 Couch/CouchChangeTracker.h
  7. +212 −0 Couch/CouchChangeTracker.m
  8. +87 −0 Couch/CouchDatabase.h
  9. +251 −0 Couch/CouchDatabase.m
  10. +59 −0 Couch/CouchDesignDocument.h
  11. +106 −0 Couch/CouchDesignDocument.m
  12. +53 −0 Couch/CouchDocument.h
  13. +239 −0 Couch/CouchDocument.m
  14. +50 −0 Couch/CouchInternal.h
  15. +7 −0 Couch/CouchPrefix.pch
  16. +110 −0 Couch/CouchQuery.h
  17. +247 −0 Couch/CouchQuery.m
  18. +23 −0 Couch/CouchResource.h
  19. +70 −0 Couch/CouchResource.m
  20. +61 −0 Couch/CouchRevision.h
  21. +196 −0 Couch/CouchRevision.m
  22. +40 −0 Couch/CouchServer.h
  23. +92 −0 Couch/CouchServer.m
  24. +1,055 −0 CouchCocoa.xcodeproj/project.pbxproj
  25. +7 −0 CouchCocoa.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  26. +28 −0 Demo/CouchDemo-Info.plist
  27. +2,669 −0 Demo/CouchDemo.xib
  28. +23 −0 Demo/DemoAppController.h
  29. +47 −0 Demo/DemoAppController.m
  30. +26 −0 Demo/DemoItem.h
  31. +99 −0 Demo/DemoItem.m
  32. +27 −0 Demo/DemoQuery.h
  33. +97 −0 Demo/DemoQuery.m
  34. +1,723 −0 Doxyfile
  35. +11 −0 REST/REST.h
  36. +66 −0 REST/RESTBody.h
  37. +209 −0 REST/RESTBody.m
  38. +48 −0 REST/RESTCache.h
  39. +89 −0 REST/RESTCache.m
  40. +29 −0 REST/RESTInternal.h
  41. +56 −0 REST/RESTInternal.m
  42. +129 −0 REST/RESTOperation.h
  43. +345 −0 REST/RESTOperation.m
  44. +126 −0 REST/RESTResource.h
  45. +275 −0 REST/RESTResource.m
  46. +30 −0 Resources/CouchCocoa-Info.plist
  47. +2 −0  Resources/en.lproj/InfoPlist.strings
  48. BIN  Resources/logo.png
  49. +325 −0 Test/Test_Couch.m
  50. +127 −0 Test/Test_REST.m
  51. +1 −0  Vendor/JSONKit
8 .gitignore
@@ -0,0 +1,8 @@
+.DS_Store
+*.swp
+*.pbxuser
+*.perspectivev3
+*.mode1v3
+xcuserdata/
+build/
+Documentation/
3  .gitmodules
@@ -0,0 +1,3 @@
+[submodule "Vendor/JSONKit"]
+ path = Vendor/JSONKit
+ url = git://github.com/johnezang/JSONKit.git
14 Couch/Couch.h
@@ -0,0 +1,14 @@
+//
+// Couch.h
+// CouchCocoa
+//
+// Created by Jens Alfke on 6/12/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchServer.h"
+#import "CouchDatabase.h"
+#import "CouchQuery.h"
+#import "CouchDocument.h"
+#import "CouchRevision.h"
+#import "CouchAttachment.h"
38 Couch/CouchAttachment.h
@@ -0,0 +1,38 @@
+//
+// CouchAttachment.h
+// CouchCocoa
+//
+// Created by Jens Alfke on 5/26/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchResource.h"
+@class CouchDocument;
+
+
+/** A binary attachment to a document. */
+@interface CouchAttachment : CouchResource
+{
+ @private
+ NSString* _contentType;
+}
+
+- (id) initWithDocument: (CouchDocument*)document
+ name: (NSString*)name
+ type: (NSString*)contentType;
+
+@property (readonly) CouchDocument* document;
+@property (readonly) NSString* name;
+@property (readonly, copy) NSString* contentType;
+
+/** Asynchronous setter for the body. (Use inherited -GET to get it.) */
+- (RESTOperation*) PUT: (NSData*)body contentType: (NSString*)contentType;
+
+/** Asynchronous setter for the body. (Use inherited -GET to get it.) */
+- (RESTOperation*) PUT: (NSData*)body;
+
+/** Synchronous accessor for the body.
+ If the getter fails, it returns nil. The setter fails silently. */
+@property (copy) NSData* body;
+
+@end
128 Couch/CouchAttachment.m
@@ -0,0 +1,128 @@
+//
+// CouchAttachment.m
+// CouchCocoa
+//
+// Created by Jens Alfke on 5/26/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchAttachment.h"
+#import "CouchDocument.h"
+#import "RESTInternal.h"
+
+
+@interface CouchAttachment ()
+@property (readwrite, copy) NSString* contentType;
+@end
+
+
+@implementation CouchAttachment
+
+
+- (id) initWithDocument: (CouchDocument*)document
+ name: (NSString*)name
+ type: (NSString*)contentType
+{
+ NSParameterAssert(contentType);
+ self = [super initWithParent: document relativePath: name];
+ if (self) {
+ _contentType = [contentType copy];
+ }
+ return self;
+}
+
+
+- (void)dealloc {
+ [_contentType release];
+ [super dealloc];
+}
+
+
+@synthesize contentType = _contentType;
+
+
+- (NSString*) name {
+ return self.relativePath;
+}
+
+
+- (CouchDocument*) document {
+ return (CouchDocument*)self.parent;
+}
+
+
+#pragma mark -
+#pragma mark BODY
+
+
+- (BOOL) contentsAreJSON {
+ return NO; // overridden from CouchResource
+}
+
+
+- (RESTOperation*) PUT: (NSData*)body contentType: (NSString*)contentType {
+ if (!contentType) {
+ contentType = _contentType;
+ NSParameterAssert(contentType != nil);
+ }
+ NSDictionary* params = [NSDictionary dictionaryWithObject: contentType
+ forKey: @"Content-Type"];
+ return [self PUT: body parameters: params];
+}
+
+
+- (RESTOperation*) PUT: (NSData*)body {
+ return [self PUT: body contentType: _contentType];
+}
+
+
+- (NSData*) body {
+ RESTOperation* op = [self GET];
+ if ([op wait])
+ return op.responseBody.content;
+ else {
+ Warn(@"Synchronous CouchAttachment.body getter failed: %@", op.error);
+ return nil;
+ }
+}
+
+
+- (void) setBody: (NSData*)body {
+ RESTOperation* op = [self PUT: body];
+ if (![op wait])
+ Warn(@"Synchronous CouchAttachment.body setter failed: %@", op.error);
+}
+
+
+- (NSMutableURLRequest*) requestWithMethod: (NSString*)method
+ parameters: (NSDictionary*)parameters {
+ if ([method isEqualToString: @"PUT"] || [method isEqualToString: @"DELETE"]) {
+ // Add a ?rev= query param with the current document revision:
+ NSString* revisionID = self.document.currentRevisionID;
+ if (revisionID) {
+ NSMutableDictionary* nuParams = [[parameters mutableCopy] autorelease];
+ if (!nuParams)
+ nuParams = [NSMutableDictionary dictionary];
+ [nuParams setObject: revisionID forKey: @"?rev"];
+ parameters = nuParams;
+ }
+ }
+ return [super requestWithMethod: method parameters: parameters];
+}
+
+
+- (NSError*) op: (RESTOperation*)op willCompleteWithError: (NSError*)error {
+ error = [super operation: op willCompleteWithError: error];
+
+ if (!error && op.isSuccessful) {
+ // Capture changes to the contentType made by GETs and PUTs:
+ if (op.isGET)
+ self.contentType = [op.responseHeaders objectForKey: @"Content-Type"];
+ else if (op.isPUT)
+ self.contentType = [op.request valueForHTTPHeaderField: @"Content-Type"];
+ }
+ return error;
+}
+
+
+@end
32 Couch/CouchChangeTracker.h
@@ -0,0 +1,32 @@
+//
+// CouchChangeTracker.h
+// CouchCocoa
+//
+// Created by Jens Alfke on 6/20/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+@class CouchDatabase;
+
+
+/** Reads the continuous-mode _changes feed of a database, and sends the individual lines to -[CouchDatabase receivedChangeChunk:].
+ This class is used internally by CouchDatabase and you shouldn't need to use it yourself. */
+@interface CouchChangeTracker : NSObject <NSStreamDelegate>
+{
+ @private
+ CouchDatabase* _database;
+ NSUInteger _lastSequenceNo;
+ NSInputStream* _trackingInput;
+ NSOutputStream* _trackingOutput;
+ NSString* _trackingRequest;
+
+ NSMutableData* _inputBuffer;
+ int _state;
+}
+
+- (id)initWithDatabase: (CouchDatabase*)database sequenceNumber: (NSUInteger)lastSequenceNo;
+- (BOOL) start;
+- (void) stop;
+
+@end
212 Couch/CouchChangeTracker.m
@@ -0,0 +1,212 @@
+//
+// CouchChangeTracker.m
+// CouchCocoa
+//
+// Created by Jens Alfke on 6/20/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchChangeTracker.h"
+
+#import "CouchDatabase.h"
+#import "CouchInternal.h"
+
+
+// <http://wiki.apache.org/couchdb/HTTP_database_API#Changes>
+
+
+enum {
+ kStateStatus,
+ kStateHeaders,
+ kStateChunks
+};
+
+
+#define kLoggingEnabled NO
+#define LOG if(!kLoggingEnabled) ; else NSLog
+
+
+@implementation CouchChangeTracker
+
+
+- (id)initWithDatabase: (CouchDatabase*)database sequenceNumber: (NSUInteger)lastSequenceNo {
+ NSParameterAssert(database);
+ self = [super init];
+ if (self) {
+ _database = [database retain];
+ _lastSequenceNo = lastSequenceNo;
+ }
+
+ return self;
+}
+
+
+- (BOOL) start {
+ NSURL* url = _database.URL;
+ _trackingRequest = [[NSString stringWithFormat:
+ @"GET /%@/_changes?feed=continuous&heartbeat=300000&since=%u HTTP/1.1\r\n"
+ @"Host: %@\r\n"
+ @"\r\n",
+ _database.relativePath, _lastSequenceNo, url.host] copy];
+ LOG(@"CouchChangeTracker: Starting with request:\n%@", _trackingRequest);//TEMP
+
+ /* Why are we using raw TCP streams rather than NSURLConnection? Good question.
+ NSURLConnection seems to have some kind of but with reading the output of _changes, maybe
+ because it's chunked and the stream doesn't close afterwards. At any rate, at least on
+ OS X 10.6.7, the delegate never receives any notification of a response. The workaround
+ is to act as a dumb HTTP parser and do the job ourselves. */
+
+#if TARGET_OS_IPHONE
+ CFReadStreamRef cfInputStream = NULL;
+ CFWriteStreamRef cfOutputStream = NULL;
+ CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)url.host, url.port.intValue,
+ &cfInputStream, &cfOutputStream);
+ if (!cfInputStream)
+ return NO;
+ _trackingInput = (NSInputStream*)cfInputStream;
+ _trackingOutput = (NSOutputStream*)cfOutputStream;
+#else
+ [NSStream getStreamsToHost: [NSHost hostWithName: url.host]
+ port: url.port.intValue
+ inputStream: &_trackingInput outputStream: &_trackingOutput];
+ if (!_trackingOutput)
+ return NO;
+ [_trackingInput retain];
+ [_trackingOutput retain];
+#endif
+
+ _state = kStateStatus;
+
+ _inputBuffer = [[NSMutableData alloc] initWithCapacity: 1024];
+
+ [_trackingOutput setDelegate: self];
+ [_trackingOutput scheduleInRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];
+ [_trackingOutput open];
+ [_trackingInput setDelegate: self];
+ [_trackingInput scheduleInRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];
+ [_trackingInput open];
+ return YES;
+}
+
+
+- (void) stop {
+ LOG(@"CouchChangeTracker: stop");
+ [_trackingInput close];
+ [_trackingInput release];
+ _trackingInput = nil;
+
+ [_trackingOutput close];
+ [_trackingOutput release];
+ _trackingOutput = nil;
+
+ [_inputBuffer release];
+ _inputBuffer = nil;
+}
+
+
+- (BOOL) readLine {
+ const char* start = _inputBuffer.bytes;
+ const char* crlf = strnstr(start, "\r\n", _inputBuffer.length);
+ if (!crlf)
+ return NO; // Wait till we have a complete line
+ ptrdiff_t lineLength = crlf - start;
+ NSString* line = [[[NSString alloc] initWithBytes: start
+ length: lineLength
+ encoding: NSUTF8StringEncoding] autorelease];
+ LOG(@"CouchChangeTracker: LINE: \"%@\"", line);//TEMP
+ if (line) {
+ switch (_state) {
+ case kStateStatus: {
+ // Read the HTTP response status line:
+ if (![line hasPrefix: @"HTTP/1.1 200 "]) {
+ Warn(@"_changes response: %@", line);
+ [self stop];
+ return NO;
+ }
+ _state = kStateHeaders;
+ break;
+ }
+ case kStateHeaders:
+ if (line.length == 0)
+ _state = kStateChunks;
+ break;
+ case kStateChunks: {
+ if (line.length == 0)
+ break; // There's an empty line between chunks
+ NSScanner* scanner = [NSScanner scannerWithString: line];
+ unsigned chunkLength;
+ if (![scanner scanHexInt: &chunkLength]) {
+ Warn(@"Failed to parse _changes chunk length '%@'", line);
+ [self stop];
+ return NO;
+ }
+ if (_inputBuffer.length < lineLength + 2 + chunkLength)
+ return NO; // Don't read the chunk till it's complete
+
+ NSData* chunk = [_inputBuffer subdataWithRange: NSMakeRange(lineLength + 2,
+ chunkLength)];
+ [_inputBuffer replaceBytesInRange: NSMakeRange(0, lineLength + 2 + chunkLength)
+ withBytes: NULL length: 0];
+ // Finally! Send the line to the database to parse:
+ [_database receivedChangeLine: chunk];
+ return YES;
+ }
+ }
+ } else {
+ Warn(@"Couldn't read line from _changes");
+ }
+
+ // Remove the parsed line:
+ [_inputBuffer replaceBytesInRange: NSMakeRange(0, lineLength + 2) withBytes: NULL length: 0];
+ return YES;
+}
+
+
+- (void)stream: (NSInputStream*)stream handleEvent: (NSStreamEvent)eventCode {
+ switch (eventCode) {
+ case NSStreamEventHasSpaceAvailable: {
+ LOG(@"CouchChangeTracker: HasSpaceAvailable %@", stream);
+ if (_trackingRequest) {
+ const char* buffer = [_trackingRequest UTF8String];
+ NSUInteger written = [(NSOutputStream*)stream write: (void*)buffer maxLength: strlen(buffer)];
+ NSAssert(written == strlen(buffer), @"Output stream didn't write entire request");
+ // FIX: It's unlikely but possible that the stream won't take the entire request; need to
+ // write the rest later.
+ [_trackingRequest release];
+ _trackingRequest = nil;
+ }
+ break;
+ }
+ case NSStreamEventHasBytesAvailable: {
+ LOG(@"CouchChangeTracker: HasBytesAvailable %@", stream);
+ while ([stream hasBytesAvailable]) {
+ uint8_t buffer[1024];
+ NSInteger bytesRead = [stream read: buffer maxLength: sizeof(buffer)];
+ if (bytesRead > 0) {
+ [_inputBuffer appendBytes: buffer length: bytesRead];
+ LOG(@"CouchChangeTracker: read %ld bytes", (long)bytesRead);
+ }
+ }
+ while (_inputBuffer && [self readLine])
+ ;
+ break;
+ }
+ case NSStreamEventEndEncountered:
+ LOG(@"CouchChangeTracker: EndEncountered %@", stream);
+ if (_inputBuffer.length > 0)
+ Warn(@"CouchChangeTracker connection closed with unparsed data in buffer");
+ [self stop];
+ break;
+ case NSStreamEventErrorOccurred:
+ LOG(@"CouchChangeTracker: ErrorEncountered %@", stream);
+ [self stop];
+ break;
+
+ default:
+ LOG(@"CouchChangeTracker: Event %lx on %@", (long)eventCode, stream);
+ break;
+ }
+}
+
+
+@end
87 Couch/CouchDatabase.h
@@ -0,0 +1,87 @@
+//
+// CouchDatabase.h
+// CouchCocoa
+//
+// Created by Jens Alfke on 5/26/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchResource.h"
+@class RESTCache, CouchChangeTracker, CouchDocument, CouchDesignDocument, CouchQuery, CouchServer;
+struct CouchViewDefinition;
+
+
+/** Type of block that's called when the database changes. */
+typedef void (^OnDatabaseChangeBlock)(CouchDocument*);
+
+
+/** A CouchDB database; contains CouchDocuments.
+ The CouchServer is the factory object for CouchDatabases. */
+@interface CouchDatabase : CouchResource
+{
+ @private
+ RESTCache* _docCache;
+ CouchChangeTracker* _tracker;
+ NSUInteger _lastSequenceNumber;
+ OnDatabaseChangeBlock _onChange;
+}
+
+@property (readonly) CouchServer* server;
+
+/** Creates the database on the server. */
+- (RESTOperation*) create;
+
+/** Gets the current total number of documents. (Synchronous) */
+- (NSInteger) getDocumentCount;
+
+/** Instantiates a CouchDocument object with the given ID.
+ Makes no server calls; a document with that ID doesn't even need to exist yet.
+ CouchDocuments are cached, so there will never be more than one instance (in this database)
+ at a time with the same documentID. */
+- (CouchDocument*) documentWithID: (NSString*)docID;
+
+/** Creates a CouchDocument object with no current ID.
+ The first time you PUT to that document, it will be created on the server (via a POST). */
+- (CouchDocument*) untitledDocument;
+
+/** Returns a query that will fetch all documents in the database. */
+- (CouchQuery*) getAllDocuments;
+
+/** Returns a query that will fetch the documents with the given IDs. */
+- (CouchQuery*) getDocumentsWithIDs: (NSArray*)docIDs;
+
+/** Bulk-writes multiple documents in one HTTP call.
+ Documents that don't exist on the server yet will be created. */
+- (RESTOperation*) putChanges: (NSArray*)properties toRevisions: (NSArray*)revisions;
+
+#pragma mark QUERIES & DESIGN DOCUMENTS:
+
+/** Returns a query that runs custom map/reduce functions.
+ This is very slow compared to a precompiled view and should only be used for testing. */
+- (CouchQuery*) slowQueryWithViewDefinition:(struct CouchViewDefinition)definition;
+
+/** Convenience method that creates a custom query from a JavaScript map function. */
+- (CouchQuery*) slowQueryWithMapFunction: (NSString*)mapFunctionSource;
+
+/** Instantiates a CouchDesignDocument object with the given ID.
+ Makes no server calls; a design document with that ID doesn't even need to exist yet.
+ CouchDesignDocuments are cached, so there will never be more than one instance (in this database) at a time with the same name. */
+- (CouchDesignDocument*) designDocumentWithName: (NSString*)name;
+
+#pragma mark CHANGE TRACKING:
+
+/** Controls whether document change-tracking is enabled.
+ It's off by default. Turning it on creates a long-lived socket connection to the database, and will post potentially a lot of notifications, so don't turn it on unless you're actually going to use the notifications. */
+@property BOOL tracksChanges;
+
+/** The last change sequence number received from the database.
+ If this is not known yet, the current value will be fetched via a synchronous query.
+ You can save the current value on quit, and restore it on relaunch before enabling change tracking, to get notifications of all changes that have occurred in the meantime. */
+@property NSUInteger lastSequenceNumber;
+
+/** The given block will be called every time a document change notification is received.
+ It's not currently possible to register more than one block; each call overwrites the last. */
+- (void) onChange: (OnDatabaseChangeBlock)block;
+
+
+@end
251 Couch/CouchDatabase.m
@@ -0,0 +1,251 @@
+//
+// CouchDatabase.m
+// CouchCocoa
+//
+// Created by Jens Alfke on 5/26/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchDatabase.h"
+#import "RESTCache.h"
+#import "CouchChangeTracker.h"
+#import "CouchDesignDocument.h"
+#import "CouchInternal.h"
+
+#import "JSONKit.h"
+
+
+NSString* const kCouchDocumentChangeNotification = @"CouchDocumentChange";
+
+/** Number of CouchDocument objects to cache in memory */
+static const NSUInteger kDocRetainLimit = 50;
+
+
+@implementation CouchDatabase
+
+
+- (void)dealloc {
+ self.tracksChanges = NO;
+ [super dealloc];
+}
+
+
+- (CouchServer*) server {
+ return (CouchServer*)[self parent];
+}
+
+
+- (CouchDatabase*) database {
+ return self;
+}
+
+
+- (RESTOperation*) create {
+ return [self PUT: nil parameters: nil];
+}
+
+
+- (NSInteger) getDocumentCount {
+ id count = [[self GET] representedValueForKey: @"doc_count"]; // synchronous
+ return [count isKindOfClass: [NSNumber class]] ? [count intValue] : -1;
+}
+
+
+- (CouchDocument*) documentWithID: (NSString*)docID {
+ CouchDocument* doc = (CouchDocument*) [_docCache resourceWithRelativePath: docID];
+ if (!doc) {
+ if ([docID hasPrefix: @"_design/"]) // Create a design doc when appropriate
+ doc = [[CouchDesignDocument alloc] initWithParent: self relativePath: docID];
+ else
+ doc = [[CouchDocument alloc] initWithParent: self relativePath: docID];
+ if (!doc)
+ return nil;
+ if (!_docCache)
+ _docCache = [[RESTCache alloc] initWithRetainLimit: kDocRetainLimit];
+ [_docCache addResource: doc];
+ [doc release];
+ }
+ return doc;
+}
+
+
+- (CouchDesignDocument*) designDocumentWithName: (NSString*)name {
+ return (CouchDesignDocument*)[self documentWithID: [@"_design/" stringByAppendingString: name]];
+}
+
+
+- (CouchDocument*) untitledDocument {
+ return [[[CouchDocument alloc] initUntitledWithParent: self] autorelease];
+}
+
+
+- (void) documentAssignedID: (CouchDocument*)document {
+ if (!_docCache)
+ _docCache = [[RESTCache alloc] init];
+ [_docCache addResource: document];
+}
+
+
+- (RESTOperation*) putChanges: (NSArray*)properties toRevisions: (NSArray*)revisions {
+ // http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API
+ NSUInteger nRevisions = revisions.count;
+ NSAssert(properties.count == nRevisions, @"Mismatched array counts");
+ NSMutableArray* entries = [NSMutableArray arrayWithCapacity: nRevisions];
+ for (NSUInteger i=0; i<nRevisions; i++) {
+ CouchRevision* revision = [revisions objectAtIndex: i];
+ NSMutableDictionary* props = [[properties objectAtIndex: i] mutableCopy];
+ [props setObject: revision.documentID forKey: @"_id"];
+ [props setObject: revision.revisionID forKey: @"_rev"];
+ [entries addObject: props];
+ [props release];
+ }
+ NSDictionary* body = [NSDictionary dictionaryWithObject: entries forKey: @"docs"];
+
+ RESTResource* bulkDocs = [[[RESTResource alloc] initWithParent: self
+ relativePath: @"_bulk_docs"] autorelease];
+ RESTOperation* op = [bulkDocs POSTJSON: body parameters: nil];
+ [op onCompletion: ^{
+ if (op.isSuccessful) {
+ NSArray* responses = $castIf(NSArray, op.responseBody.fromJSON);
+ op.representedObject = responses;
+ int i = 0;
+ for (id response in responses) {
+ CouchRevision* revision = [revisions objectAtIndex: i++];
+ [revision.document bulkSaveCompleted: $castIf(NSDictionary, response)];
+ }
+ }
+ }];
+ return op;
+}
+
+
+#pragma mark -
+#pragma mark QUERIES
+
+
+- (CouchQuery*) getAllDocuments {
+ CouchQuery *query = [[[CouchQuery alloc] initWithParent: self relativePath: @"_all_docs"] autorelease];
+ query.prefetch = YES;
+ return query;
+}
+
+
+- (CouchQuery*) getDocumentsWithIDs: (NSArray*)docIDs {
+ CouchQuery *query = [self getAllDocuments];
+ query.keys = docIDs;
+ return query;
+}
+
+
+- (CouchQuery*) slowQueryWithViewDefinition:(struct CouchViewDefinition)definition
+{
+ return [[[CouchFunctionQuery alloc] initWithDatabase: self
+ viewDefinition: definition] autorelease];
+}
+
+- (CouchQuery*) slowQueryWithMapFunction: (NSString*)mapFunctionSource {
+ CouchViewDefinition defn = {mapFunctionSource, nil, kCouchLanguageJavaScript};
+ return [self slowQueryWithViewDefinition: defn];
+}
+
+
+
+#pragma mark -
+#pragma mark TRACKING CHANGES:
+
+
+- (NSUInteger) lastSequenceNumber {
+ if (_lastSequenceNumber == 0) {
+ // Don't know the current sequence number, so ask for it:
+ id seqObj = [[self GET] representedValueForKey: @"update_seq"]; // synchronous
+ if ([seqObj isKindOfClass: [NSNumber class]])
+ _lastSequenceNumber = [seqObj intValue];
+ }
+ return _lastSequenceNumber;
+}
+
+
+- (void) setLastSequenceNumber:(NSUInteger)lastSequenceNumber {
+ _lastSequenceNumber = lastSequenceNumber;
+}
+
+
+// <http://wiki.apache.org/couchdb/HTTP_database_API#Changes>
+
+
+static NSString* const kTrackingPath = @"_changes?feed=continuous";
+
+
+- (void) onChange: (OnDatabaseChangeBlock)block {
+ NSAssert(!_onChange, @"Sorry, only one onChange handler at a time"); // TODO: Allow multiple onChange blocks!
+ _onChange = [block copy];
+}
+
+
+- (void) receivedChange: (NSDictionary*)change
+{
+ // Get & check sequence number:
+ NSNumber* sequenceObj = $castIf(NSNumber, [change objectForKey: @"seq"]);
+ if (!sequenceObj)
+ return;
+ NSUInteger sequence = [sequenceObj intValue];
+ if (sequence <= _lastSequenceNumber)
+ return;
+
+ // Get document:
+ NSString* docID = [change objectForKey: @"id"];
+ CouchDocument* document = [self documentWithID: docID];
+
+ // Notify!
+ if ([document notifyChanged: change]) {
+ if (_onChange)
+ _onChange(document);
+ NSNotification* n = [NSNotification notificationWithName: kCouchDocumentChangeNotification
+ object: self
+ userInfo: change];
+ [[NSNotificationCenter defaultCenter] postNotification: n];
+ } else {
+ NSLog(@" CouchDatabase change with seq=%lu already known", (unsigned long)sequence);
+ }
+
+ _lastSequenceNumber = sequence;
+}
+
+
+- (void) receivedChangeLine: (NSData*)chunk {
+ NSString* line = [[[NSString alloc] initWithData: chunk encoding:NSUTF8StringEncoding] autorelease];
+ if (!line) {
+ Warn(@"Couldn't parse UTF-8 from _changes");
+ return;
+ }
+ if (line.length == 0 || [line isEqualToString: @"\n"])
+ return;
+ NSDictionary* change = $castIf(NSDictionary, [line objectFromJSONString]);
+ if (change) {
+ NSLog(@"**** CHANGED: %@", line);
+ [self receivedChange: change];
+ } else {
+ Warn(@"Received unparseable change line from server: %@", line);
+ }
+}
+
+
+- (BOOL) tracksChanges {
+ return _tracker != nil;
+}
+
+
+- (void) setTracksChanges: (BOOL)track {
+ if (track && !_tracker) {
+ _tracker = [[CouchChangeTracker alloc] initWithDatabase: self
+ sequenceNumber: _lastSequenceNumber];
+ [_tracker start];
+ } else if (!track && _tracker) {
+ [_tracker stop];
+ [_tracker release];
+ _tracker = nil;
+ }
+}
+
+
+@end
59 Couch/CouchDesignDocument.h
@@ -0,0 +1,59 @@
+//
+// CouchDesignDocument.h
+// CouchCocoa
+//
+// Created by Jens Alfke on 6/8/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchDocument.h"
+@class CouchQuery;
+
+
+/** Language parameter for JavaScript map and reduce functions. */
+extern NSString* const kCouchLanguageJavaScript;
+
+
+/** A C structure used to package up the definition of a view.
+ All the fields are autoreleased, so you don't need to retain or release them. */
+typedef struct CouchViewDefinition {
+ NSString* mapFunction; /**< The source code of the map function. Never nil. */
+ NSString* reduceFunction; /**< The source code of the reduce function, or nil if none. */
+ NSString* language; /**< Programming language; defaults to kCouchLanguageJavaScript. */
+} CouchViewDefinition;
+
+
+/** A Design Document is a special document type that contains things like map/reduce functions, and the code and static resources for CouchApps. */
+@interface CouchDesignDocument : CouchDocument
+{
+ @private
+ NSMutableDictionary* _views;
+ BOOL _changed;
+}
+
+/** Creates a query for the given named view. */
+- (CouchQuery*) queryViewNamed: (NSString*)viewName;
+
+/** Fetches and returns the names of all the views defined in this design document.
+ The first call fetches the entire design document synchronously; subsequent calls are cached. */
+@property (readonly) NSArray* viewNames;
+
+/** Fetches the map and/or reduce functions defining the given named view.
+ If there is no view by that name, the "mapFunction" field of the result will be nil.
+ The first call fetches the entire design document synchronously; subsequent calls are cached. */
+- (CouchViewDefinition) getViewNamed: (NSString*)viewName;
+
+/** Creates, updates or deletes a view given map and/or reduce functions.
+ Does not send the changes to the server until you call -saveChanges.
+ @param definition Pointer to the view definition, or NULL to delete the view.
+ @param viewName The name of the view. */
+- (BOOL) setDefinition: (const CouchViewDefinition*)definition
+ ofViewNamed: (NSString*)viewName;
+
+/** Have the contents of the design document been changed in-memory but not yet saved? */
+@property (readonly) BOOL changed;
+
+/** Saves changes, asynchronously. If there are no current changes, returns nil. */
+- (RESTOperation*) saveChanges;
+
+@end
106 Couch/CouchDesignDocument.m
@@ -0,0 +1,106 @@
+//
+// CouchDesignDocument.m
+// CouchCocoa
+//
+// Created by Jens Alfke on 6/8/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchDesignDocument.h"
+#import "CouchInternal.h"
+
+
+NSString* const kCouchLanguageJavaScript = @"javascript";
+
+
+@interface CouchDesignDocument ()
+@property (readwrite) BOOL changed;
+@end
+
+
+@implementation CouchDesignDocument
+
+
+- (CouchQuery*) queryViewNamed: (NSString*)viewName {
+ NSString* path = [@"_view/" stringByAppendingString: viewName];
+ return [[[CouchQuery alloc] initWithParent: self relativePath: path] autorelease];
+}
+
+
+- (NSDictionary*) properties {
+ return self.currentRevision.properties;
+}
+
+
+/** Returns a dictionary mapping view names to the dictionaries defining them (as in the design document's JSON source.)
+ The first call fetches the entire design document; subsequent calls are cached. */
+- (NSDictionary*) views {
+ //FIX: How/when to invalidate the cache?
+ if (!_views) {
+ if (![[self GET] wait])
+ return nil;
+ NSDictionary* views = $castIf(NSDictionary, [self.properties objectForKey: @"views"]);
+ if (views)
+ _views = [views mutableCopy];
+ else
+ _views = [[NSMutableDictionary alloc] init];
+ }
+ return _views;
+}
+
+- (NSArray*) viewNames {
+ return [self.views allKeys];
+}
+
+- (CouchViewDefinition) getViewNamed: (NSString*)viewName
+{
+ CouchViewDefinition defn = {nil, nil, nil};
+ NSDictionary* view = $castIf(NSDictionary, [self.views objectForKey: viewName]);
+ if (view) {
+ defn.mapFunction = [view objectForKey: @"map"];
+ defn.reduceFunction = [view objectForKey: @"reduce"];
+ defn.language = [self.properties objectForKey: @"language"];
+ if (!defn.language)
+ defn.language = kCouchLanguageJavaScript;
+ }
+ return defn;
+}
+
+- (BOOL) setDefinition: (const CouchViewDefinition*)definition
+ ofViewNamed: (NSString*)viewName
+{
+ if (!self.views)
+ return NO;
+
+ if (definition) {
+ NSParameterAssert(definition->mapFunction);
+ NSDictionary* viewDefinition = [NSDictionary dictionaryWithObjectsAndKeys:
+ definition->mapFunction, @"map",
+ definition->reduceFunction, @"reduce", // may be nil
+ nil];
+ [_views setObject: viewDefinition forKey: viewName];
+ //TODO: Remember the language
+ } else {
+ [_views removeObjectForKey: viewName];
+ }
+ self.changed = YES;
+ return YES;
+}
+
+
+@synthesize changed=_changed;
+
+
+- (RESTOperation*) saveChanges {
+ if (!_changed)
+ return nil;
+
+ NSMutableDictionary* newProps = [[self.properties mutableCopy] autorelease];
+ [newProps setObject: _views forKey: @"views"];
+ self.changed = NO;
+ return [self.currentRevision putProperties: newProps];
+ // TODO: What about conflicts?
+}
+
+
+@end
53 Couch/CouchDocument.h
@@ -0,0 +1,53 @@
+//
+// CouchDocument.h
+// CouchCocoa
+//
+// Created by Jens Alfke on 5/26/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchResource.h"
+@class CouchAttachment, CouchDatabase, CouchRevision;
+
+
+/** A CouchDB document, aka "record" aka "row".
+ Note: Never alloc/init a CouchDocument directly. Instead get it from the database by calling -documentWithID: or -untitledDocument. */
+@interface CouchDocument : CouchResource
+{
+ @private
+ BOOL _isDeleted;
+ NSString* _currentRevisionID;
+ CouchRevision* _currentRevision;
+}
+
+
+@property (readonly) NSString* documentID;
+@property (readonly) BOOL isDeleted;
+
+#pragma mark REVISIONS:
+
+@property (readonly, copy) NSString* currentRevisionID;
+
+- (CouchRevision*) currentRevision;
+- (CouchRevision*) revisionWithID: (NSString*)revisionID;
+
+- (NSArray*) getRevisionHistory;
+
+#pragma mark PROPERTIES:
+
+/** These are the app-defined properties of the document, without the CouchDB-defined special properties whose names begin with "_".
+ (If you want the entire set of properties returned by the server, use the inherited -representedObject property.)
+ This is shorthand for self.currentRevision.properties. */
+@property (readonly, copy) NSDictionary* properties;
+
+/** Shorthand for [self.properties objectForKey: key]. */
+- (id) propertyForKey: (NSString*)key;
+
+/** Updates the document with new properties.
+ This is asynchronous. Watch response for conflicts! */
+- (RESTOperation*) putProperties: (NSDictionary*)properties;
+
+@end
+
+
+extern NSString* const kCouchDocumentChangeNotification;
239 Couch/CouchDocument.m
@@ -0,0 +1,239 @@
+//
+// CouchDocument.m
+// CouchCocoa
+//
+// Created by Jens Alfke on 5/26/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchDocument.h"
+#import "CouchInternal.h"
+
+
+@interface CouchDocument ()
+@property (readwrite) BOOL isDeleted;
+@end
+
+
+@implementation CouchDocument
+
+
+- (void)dealloc {
+ [_currentRevisionID release];
+ [_currentRevision release];
+ [super dealloc];
+}
+
+
+- (NSString*) documentID {
+ return self.relativePath;
+}
+
+
+#pragma mark REVISIONS:
+
+
+@synthesize isDeleted=_isDeleted, currentRevisionID=_currentRevisionID;
+
+
+- (void) setCurrentRevisionID:(NSString *)revisionID {
+ if (![revisionID isEqualToString: _currentRevisionID]) {
+ [_currentRevisionID release];
+ _currentRevisionID = [revisionID copy];
+ [_currentRevision autorelease];
+ _currentRevision = nil;
+ }
+}
+
+
+- (CouchRevision*) revisionWithID: (NSString*)revisionID {
+ NSParameterAssert(revisionID);
+ if ([revisionID isEqualToString: _currentRevisionID])
+ return self.currentRevision;
+ return [[[CouchRevision alloc] initWithDocument: self revisionID: revisionID] autorelease];
+}
+
+
+- (CouchRevision*) currentRevision {
+ if (!_currentRevision) {
+ if (_currentRevisionID)
+ _currentRevision = [[CouchRevision alloc] initWithDocument: self
+ revisionID: _currentRevisionID];
+ else if (self.relativePath)
+ _currentRevision = [[CouchRevision alloc] initWithOperation: [self GET]];
+ }
+ return _currentRevision;
+}
+
+
+- (NSArray*) getRevisionHistory {
+ RESTOperation* op = [self sendHTTP: @"GET"
+ parameters: [NSDictionary dictionaryWithObjectsAndKeys:
+ @"true", @"?revs", nil]];
+ if (![op wait])
+ return nil;
+ NSDictionary* revisions = $castIf(NSDictionary,
+ [op.responseBody.fromJSON objectForKey: @"_revisions"]);
+ NSArray* revIDs = [revisions objectForKey: @"ids"];
+ int start = [$castIf(NSNumber, [revisions objectForKey: @"start"]) intValue];
+ if (start < 1 || start < revIDs.count)
+ return nil;
+ NSMutableArray* revs = [NSMutableArray arrayWithCapacity: revIDs.count];
+ for (NSString* revID in revIDs) {
+ revID = [NSString stringWithFormat: @"%i-%@", start--, revID];
+ // Server returns revs in reverse order, but I want to return them forwards
+ [revs insertObject: [self revisionWithID: revID] atIndex: 0];
+ }
+ return revs;
+}
+
+
+#pragma mark PROPERTIES:
+
+
+- (NSDictionary*) properties {
+ return self.currentRevision.properties;
+}
+
+
+- (id) propertyForKey: (NSString*)key {
+ return [self.currentRevision propertyForKey: key];
+}
+
+
+- (RESTOperation*) putProperties: (NSDictionary*)properties {
+ NSParameterAssert(properties != nil);
+ for (NSString* key in properties)
+ NSAssert1(![key hasPrefix: @"_"], @"Illegal property key '%@'", key);
+
+ if (_currentRevisionID) {
+ NSMutableDictionary* newProperties = [[properties mutableCopy] autorelease];
+ [newProperties setObject: _currentRevisionID forKey: @"_rev"];
+ properties = newProperties;
+ }
+
+ return [self PUTJSON: properties parameters: nil];
+}
+
+
+#pragma mark -
+#pragma mark CHANGES:
+
+
+- (BOOL) notifyChanged: (NSDictionary*)change {
+ // Get revision:
+ NSArray* changeList = $castIf(NSArray, [change objectForKey: @"changes"]);
+ NSDictionary* changeDict = $castIf(NSDictionary, changeList.lastObject);
+ // TODO: Can there ever be more than one object in the list? What does that mean?
+ NSString* rev = $castIf(NSString, [changeDict objectForKey: @"rev"]);
+ if (!rev)
+ return NO;
+
+ if ([_currentRevisionID isEqualToString: rev])
+ return NO;
+ self.currentRevisionID = rev;
+
+ if ([[change objectForKey: @"deleted"] isEqual: (id)kCFBooleanTrue])
+ self.isDeleted = YES;
+
+ NSNotification* n = [NSNotification notificationWithName: kCouchDocumentChangeNotification
+ object: self
+ userInfo: change];
+ [[NSNotificationCenter defaultCenter] postNotification: n];
+ return YES;
+}
+
+
+- (void) updateFromSaveResponse: (NSDictionary*)response {
+ NSString* docID = [response objectForKey: @"id"];
+ NSString* rev = [response objectForKey: @"rev"];
+
+ NSString* myDocID = self.documentID;
+ if (myDocID) {
+ if (![docID isEqualToString: myDocID]) {
+ Warn(@"Document ID mismatch: id='%@' for %@", docID, self);
+ return;
+ }
+ } else {
+ if (!docID) {
+ Warn(@"No document ID received for saving untitled %@", self);
+ return;
+ }
+ }
+
+ self.currentRevisionID = rev;
+
+ NSMutableDictionary* rep = [self.representedObject mutableCopy];
+ if (!rep)
+ rep = [[NSMutableDictionary alloc] init];
+ [rep setObject: rev forKey: @"_rev"];
+ [rep setObject: docID forKey: @"_id"];
+ self.representedObject = rep;
+ [rep release];
+}
+
+
+#pragma mark -
+#pragma mark OPERATION HANDLING:
+
+
+- (NSMutableURLRequest*) requestWithMethod: (NSString*)method
+ parameters: (NSDictionary*)parameters {
+ if ([method isEqualToString: @"DELETE"]) {
+ NSString* revision = self.currentRevisionID;
+ if (revision) {
+ // Add a ?rev= query param with the current document revision:
+ NSMutableDictionary* nuParams = [[parameters mutableCopy] autorelease];
+ if (!nuParams)
+ nuParams = [NSMutableDictionary dictionary];
+ [nuParams setObject: revision forKey: @"?rev"];
+ parameters = nuParams;
+ }
+ }
+ return [super requestWithMethod: method parameters: parameters];
+}
+
+
+- (NSError*) operation: (RESTOperation*)op willCompleteWithError: (NSError*)error {
+ error = [super operation: op willCompleteWithError: error];
+
+ if (op.httpStatus < 300) {
+ // On a PUT or DELETE, update my eTag from the documnt's new revision:
+ if (op.isPUT || op.isDELETE) {
+ NSString* rev = [op representedValueForKey: @"rev"];
+ self.currentRevisionID = rev;
+ if (rev) {
+ NSMutableDictionary* rep = [self.representedObject mutableCopy];
+ if (rep) {
+ [rep setObject: rev forKey: @"_rev"];
+ self.representedObject = rep;
+ [rep release];
+ }
+ }
+
+ if (op.isDELETE)
+ self.isDeleted = YES;
+ }
+ } else if (op.httpStatus == 404 && op.isGET) {
+ // Check whether it's been deleted:
+ if ([[op representedValueForKey: @"reason"] isEqualToString: @"deleted"])
+ self.isDeleted = YES;
+ }
+ return error;
+}
+
+
+- (void) createdByPOST: (RESTOperation*)op {
+ [super createdByPOST: op]; //FIX: Should update relativePath directly from 'id' instead
+ [self updateFromSaveResponse: $castIf(NSDictionary, op.representedObject)];
+ [self.database documentAssignedID: self];
+}
+
+
+- (void) bulkSaveCompleted: (NSDictionary*) result {
+ if (![result objectForKey: @"error"])
+ [self updateFromSaveResponse: result];
+}
+
+
+@end
50 Couch/CouchInternal.h
@@ -0,0 +1,50 @@
+//
+// CouchInternal.h
+// CouchCocoa
+//
+// Created by Jens Alfke on 6/26/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "Couch.h"
+#import "RESTInternal.h"
+
+
+@interface CouchDatabase (Private)
+- (void) documentAssignedID: (CouchDocument*)document;
+- (void) receivedChangeLine: (NSData*)chunk;
+@end
+
+
+@interface CouchDocument (Private)
+- (void) bulkSaveCompleted: (NSDictionary*) result;
+@property (readwrite, copy) id representedObject;
+- (BOOL) notifyChanged: (NSDictionary*)change;
+@end
+
+
+@interface CouchResource (Private)
+/** Are this resource's contents always expected to be JSON?
+ Default implementation returns YES; overridden by CouchAttachment to return NO. */
+@property (readonly) BOOL contentsAreJSON;
+@end
+
+
+@interface CouchRevision (Private)
+- (id) initWithDocument: (CouchDocument*)document revisionID: (NSString*)revisionID;
+- (id) initWithDocument: (CouchDocument*)document contents: (NSDictionary*)contents;
+- (id) initWithOperation: (RESTOperation*)operation;
+@end
+
+
+/** A query that allows custom map and reduce functions to be supplied at runtime.
+ Usually created by calling -[CouchDatabase slowQueryWithMapFunction:]. */
+@interface CouchFunctionQuery : CouchQuery
+{
+ NSDictionary* _viewDefinition;
+}
+
+- (id) initWithDatabase: (CouchDatabase*)db
+ viewDefinition: (struct CouchViewDefinition)definition;
+
+@end
7 Couch/CouchPrefix.pch
@@ -0,0 +1,7 @@
+//
+// Prefix header for all source files in the 'Couch' project
+//
+
+#ifdef __OBJC__
+#import <Foundation/Foundation.h>
+#endif
110 Couch/CouchQuery.h
@@ -0,0 +1,110 @@
+//
+// CouchQuery.h
+// CouchCocoa
+//
+// Created by Jens Alfke on 5/30/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchResource.h"
+@class CouchDatabase;
+@class CouchDocument;
+@class CouchDesignDocument;
+@class CouchQueryEnumerator;
+@class CouchQueryRow;
+
+
+/** Represents a CouchDB 'view', or a view-like resource like _all_documents. */
+@interface CouchQuery : CouchResource
+{
+ @private
+ NSUInteger _limit, _skip;
+ id _startKey, _endKey;
+ BOOL _descending, _prefetch;
+ NSArray *_keys;
+}
+
+/** The design document that contains this view. */
+@property (readonly) CouchDesignDocument* designDocument;
+
+/** The maximum number of rows to return. Default value is 0, meaning 'unlimited'. */
+@property NSUInteger limit;
+
+/** The number of initial rows to skip. Default value is 0.
+ Should only be used with small values. For efficient paging, use startkey and limit.*/
+@property NSUInteger skip;
+
+/** Should the rows be returned in descending key order? Default value is NO. */
+@property BOOL descending;
+
+/** If non-nil, the key value to start at. */
+@property (copy) id startKey;
+
+/** If non-nil, the key value to end after. */
+@property (copy) id endKey;
+
+/** If non-nil, the query will fetch only the rows with the given keys. */
+@property (copy) NSArray* keys;
+
+/** If set to YES, the results will include the entire document contents of the associated rows.
+ These can be accessed via CouchQueryRow's -documentContents property. */
+@property BOOL prefetch;
+
+
+/** Sends the query to the server and returns an enumerator over the result rows.
+ This is currently synchronous (blocks until the response is complete) but may not remain so. */
+- (CouchQueryEnumerator*) rows;
+
+/** Same as -rows, except returns nil if the query results have not changed since the last time
+ it was evaluated. (Synchronous) */
+- (CouchQueryEnumerator*) rowsIfChanged;
+
+@end
+
+
+/** Enumerator on a CouchQuery's result rows.
+ The objects returned are instances of CouchQueryRow. */
+@interface CouchQueryEnumerator : NSEnumerator
+{
+ @private
+ CouchQuery* _query;
+ NSArray* _rows;
+ NSUInteger _totalCount;
+ NSUInteger _nextRow;
+}
+
+/** The number of rows returned in this enumerator */
+@property (readonly) NSUInteger count;
+
+/** The total number of rows in the query (excluding options like limit, skip, etc.) */
+@property (readonly) NSUInteger totalCount;
+
+/** The next result row. This is the same as -nextObject but with a checked return type. */
+- (CouchQueryRow*) nextRow;
+
+/** Random access to a row in the result */
+- (CouchQueryRow*) rowAtIndex: (NSUInteger)index;
+
+@end
+
+
+/** A result row from a CouchDB view query. */
+@interface CouchQueryRow : NSObject
+{
+ @private
+ CouchQuery* _query;
+ id _result;
+}
+
+@property (readonly) CouchQuery* query;
+@property (readonly) id key;
+@property (readonly) id value;
+
+/** The document this row was mapped from. */
+@property (readonly) CouchDocument* document;
+
+/** The contents of the document this row was mapped from.
+ To get this, you must have set the -prefetch property on the query; else this will be nil. */
+@property (readonly) NSDictionary* documentContents;
+
+@end
247 Couch/CouchQuery.m
@@ -0,0 +1,247 @@
+//
+// CouchQuery.m
+// CouchCocoa
+//
+// Created by Jens Alfke on 5/30/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+// <http://wiki.apache.org/couchdb/HTTP_view_API#Querying_Options>
+
+
+#import "CouchQuery.h"
+#import "CouchDesignDocument.h"
+#import "CouchInternal.h"
+
+#import "JSONKit.h"
+
+
+@interface CouchQueryEnumerator ()
+- (id) initWithQuery: (CouchQuery*)query op: (RESTOperation*)op;
+@end
+
+
+@interface CouchQueryRow ()
+- (id) initWithQuery: (CouchQuery*)query result: (id)result;
+@end
+
+
+
+@implementation CouchQuery
+
+
+@synthesize limit=_limit, skip=_skip, descending=_descending, startKey=_startKey, endKey=_endKey,
+ prefetch=_prefetch, keys=_keys;
+
+
+- (CouchDesignDocument*) designDocument {
+ // The relativePath to a view URL will look like "_design/DOCNAME/_view/VIEWNAME"
+ NSArray* path = [self.relativePath componentsSeparatedByString: @"/"];
+ if (path.count >= 4 && [[path objectAtIndex: 0] isEqualToString: @"_design"])
+ return [self.database designDocumentWithName: [path objectAtIndex: 1]];
+ else
+ return nil;
+}
+
+
+- (NSDictionary*) jsonToPost {
+ if (_keys)
+ return [NSDictionary dictionaryWithObject: _keys forKey: @"keys"];
+ else
+ return nil;
+}
+
+
+- (NSMutableDictionary*) requestParams {
+ NSMutableDictionary* params = [NSMutableDictionary dictionary];
+ if (_limit)
+ [params setObject: [NSNumber numberWithUnsignedLong: _limit] forKey: @"?limit"];
+ if (_skip)
+ [params setObject: [NSNumber numberWithUnsignedLong: _skip] forKey: @"?skip"];
+ if (_startKey)
+ [params setObject: [_startKey JSONString] forKey: @"?startkey"];
+ if (_endKey)
+ [params setObject: [_endKey JSONString] forKey: @"?endkey"];
+ if (_descending)
+ [params setObject: @"true" forKey: @"?descending"];
+ if (_prefetch)
+ [params setObject: @"true" forKey: @"?include_docs"];
+ return params;
+}
+
+
+- (RESTOperation*) createResponse {
+ NSDictionary* params = self.requestParams;
+ NSDictionary* json = self.jsonToPost;
+ if (json)
+ return [self POSTJSON: json parameters: params];
+ else
+ return [self sendHTTP: @"GET" parameters: params];
+}
+
+
+- (CouchQueryEnumerator*) rows {
+ self.representedObject = nil; // Disallow cached response
+ return [self rowsIfChanged];
+}
+
+
+- (CouchQueryEnumerator*) rowsIfChanged {
+ RESTOperation* op = [self createResponse];
+ if (op.isSuccessful && op.httpStatus == 304)
+ return nil; // unchanged
+ NSArray* rows = $castIf(NSArray, [op representedValueForKey: @"rows"]); // BLOCKING
+ if (!rows) {
+ Warn(@"Error getting %@: %@", self, op.error);
+ return nil;
+ }
+ return [[[CouchQueryEnumerator alloc] initWithQuery: self op: op] autorelease];
+}
+
+
+@end
+
+
+
+
+@implementation CouchFunctionQuery
+
+
+- (id) initWithDatabase: (CouchDatabase*)db
+ viewDefinition: (struct CouchViewDefinition)definition
+{
+ self = [super initWithParent: db relativePath: @"_temp_view"];
+ if (self != nil) {
+ _viewDefinition = [[NSDictionary alloc] initWithObjectsAndKeys:
+ definition.mapFunction, @"map",
+ definition.reduceFunction, @"reduce", // may be nil
+ nil];
+ }
+ return self;
+}
+
+
+- (void) dealloc
+{
+ [_viewDefinition release];
+ [super dealloc];
+}
+
+
+- (NSDictionary*) jsonToPost {
+ return _viewDefinition;
+}
+
+
+@end
+
+
+
+
+@implementation CouchQueryEnumerator
+
+
+@synthesize totalCount=_totalCount;
+
+
+- (id) initWithQuery: (CouchQuery*)query op: (RESTOperation*)op {
+ self = [super init];
+ if (self) {
+ _query = [query retain];
+ _rows = [$castIf(NSArray, [op representedValueForKey: @"rows"]) retain]; // BLOCKING
+ if (!_rows) {
+ [self release];
+ return nil;
+ }
+ _totalCount = [[op representedValueForKey: @"total_rows"] intValue];
+ }
+ return self;
+}
+
+
+- (void) dealloc
+{
+ [_query release];
+ [_rows release];
+ [super dealloc];
+}
+
+
+- (NSUInteger) count {
+ return _rows.count;
+}
+
+
+- (CouchQueryRow*) rowAtIndex: (NSUInteger)index {
+ return [[[CouchQueryRow alloc] initWithQuery: _query
+ result: [_rows objectAtIndex:index]]
+ autorelease];
+}
+
+
+- (CouchQueryRow*) nextRow {
+ if (_nextRow >= _rows.count)
+ return nil;
+ return [self rowAtIndex:_nextRow++];
+}
+
+
+- (id) nextObject {
+ return [self nextRow];
+}
+
+
+@end
+
+
+
+
+@implementation CouchQueryRow
+
+
+- (id) initWithQuery: (CouchQuery*)query result: (id)result {
+ self = [super init];
+ if (self) {
+ if (![result isKindOfClass: [NSDictionary class]]) {
+ Warn(@"Unexpected row value in view results: %@", result);
+ [self release];
+ return nil;
+ }
+ _query = [query retain];
+ _result = [result retain];
+ }
+ return self;
+}
+
+
+@synthesize query=_query;
+
+- (id) key {return [_result objectForKey: @"key"];}
+- (id) value {return [_result objectForKey: @"value"];}
+- (NSString*) documentID {return [_result objectForKey: @"id"];}
+- (NSDictionary*) documentContents {return [_result objectForKey: @"doc"];}
+
+
+- (CouchDocument*) document {
+ NSString* docID = [_result objectForKey: @"id"];
+ if (!docID) {
+ Warn(@"Unexpected missing id in view results: %@", _result);
+ return nil;
+ }
+ CouchDocument* doc = [_query.database documentWithID: docID];
+
+ id docContents = [_result objectForKey: @"doc"];
+ if (docContents)
+ doc.representedObject = docContents;
+ return doc;
+}
+
+
+- (NSString*) description {
+ return [NSString stringWithFormat: @"%@[key=%@; value=%@; id=%@]",
+ [self class],
+ [self.key JSONString], [self.value JSONString], self.documentID];
+}
+
+
+@end
23 Couch/CouchResource.h
@@ -0,0 +1,23 @@
+//
+// CouchResource.h
+// CouchCocoa
+//
+// Created by Jens Alfke on 5/29/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "RESTResource.h"
+@class CouchDatabase;
+
+
+/** NSError domain string used for errors returned from the CouchDB server. */
+extern NSString* const kCouchDBErrorDomain;
+
+
+/** Superclass of CouchDB model classes. Adds Couch-specific error handling to RESTResource. */
+@interface CouchResource : RESTResource
+
+/** The owning database. */
+@property (readonly) CouchDatabase* database;
+
+@end
70 Couch/CouchResource.m
@@ -0,0 +1,70 @@
+//
+// CouchResource.m
+// CouchCocoa
+//
+// Created by Jens Alfke on 5/29/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchResource.h"
+#import "RESTInternal.h"
+
+
+NSString* const kCouchDBErrorDomain = @"CouchDB";
+
+
+@implementation CouchResource
+
+
+- (CouchDatabase*) database {
+ return [(CouchResource*)self.parent database];
+ // No, this is not an infinite regress. CouchDatabase overrides this to return self.
+}
+
+
+- (BOOL) contentsAreJSON {
+ return YES; // CouchAttachment overrides this to return NO
+}
+
+
+- (NSError*) operation: (RESTOperation*)op willCompleteWithError: (NSError*)error {
+ error = [super operation: op willCompleteWithError: error];
+
+ int httpStatus = op.httpStatus;
+ if (httpStatus == 0)
+ return error; // Some kind non-HTTP error
+
+
+ if (httpStatus < 300) {
+ if (self.contentsAreJSON) {
+ // On success, store the parsed JSON in the op's representedObject:
+ id json = op.responseBody.fromJSON;
+ op.representedObject = json;
+ // and remember the response ETag to allow future conditional GETs:
+ [self cacheRepresentedObject: json forResponse: op];
+ }
+
+ } else if (httpStatus >= 400) {
+ NSDictionary* json = $castIf(NSDictionary, op.responseBody.fromJSON);
+ if (json) {
+ // Interpret extra error info in JSON body of op:
+ NSString* errorName = [[json objectForKey: @"error"] description];
+ if (errorName) {
+ NSString* reason = [[json objectForKey: @"reason"] description];
+ if (reason)
+ reason = [NSString stringWithFormat: @"%@: %@", errorName, reason];
+ NSDictionary* info = [NSDictionary dictionaryWithObjectsAndKeys:
+ error, NSUnderlyingErrorKey,
+ errorName, NSLocalizedFailureReasonErrorKey,
+ reason, NSLocalizedDescriptionKey,
+ nil];
+ error = [NSError errorWithDomain: kCouchDBErrorDomain code: error.code
+ userInfo: info];
+ }
+ }
+ }
+ return error;
+}
+
+
+@end
61 Couch/CouchRevision.h
@@ -0,0 +1,61 @@
+//
+// CouchRevision.h
+// CouchCocoa
+//
+// Created by Jens Alfke on 6/28/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchResource.h"
+@class CouchAttachment, CouchDocument, RESTOperation;
+
+/** A single revision of a CouchDocument. */
+@interface CouchRevision : CouchResource
+{
+ @private
+ NSDictionary* _properties;
+ BOOL _isDeleted;
+}
+
+@property (readonly) CouchDocument* document;
+@property (readonly) NSString* documentID;
+@property (readonly) NSString* revisionID;
+
+/** Is this the current/latest revision of its document? */
+@property (readonly) BOOL isCurrent;
+
+/** Does this revision mark the deletion of its document? */
+@property (readonly) BOOL isDeleted;
+
+#pragma mark PROPERTIES
+
+/** These are the app-defined properties of the document, without the CouchDB-defined special properties whose names begin with "_". */
+@property (readonly, copy) NSDictionary* properties;
+
+/** Shorthand for [self.properties objectForKey: key]. */
+- (id) propertyForKey: (NSString*)key;
+
+/** The document as returned from the server and parsed from JSON.
+ Keys beginning with "_" are defined and reserved by CouchDB; others are app-specific.
+ For most purposes you probably want to use the -properties property instead. */
+@property (readonly) NSDictionary* fromJSON;
+
+
+/** Creates a new revision with the given properties. This is asynchronous. Watch response for conflicts! */
+- (RESTOperation*) putProperties: (NSDictionary*)properties;
+
+
+#pragma mark ATTACHMENTS
+
+/** The names of all attachments (array of strings). */
+@property (readonly) NSArray* attachmentNames;
+
+/** Looks up the attachment with the given name (without fetching its contents). */
+- (CouchAttachment*) attachmentNamed: (NSString*)name;
+
+/** Creates a new attachment object, but doesn't write it to the database yet.
+ To actually create the attachment, you'll need to call -PUT on the CouchAttachment. */
+- (CouchAttachment*) createAttachmentWithName: (NSString*)name type: (NSString*)contentType;
+
+
+@end
196 Couch/CouchRevision.m
@@ -0,0 +1,196 @@
+//
+// CouchRevision.m
+// CouchCocoa
+//
+// Created by Jens Alfke on 6/28/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchRevision.h"
+
+#import "CouchInternal.h"
+
+
+@implementation CouchRevision
+
+
+- (id) initWithDocument: (CouchDocument*)document revisionID: (NSString*)revisionID {
+ NSParameterAssert(document);
+ if (revisionID)
+ return [super initWithParent: document
+ relativePath: [@"?rev=" stringByAppendingString: revisionID]];
+ else
+ return [super initUntitledWithParent: document];
+}
+
+
+- (id) initWithDocument: (CouchDocument*)document contents: (NSDictionary*)contents {
+ NSParameterAssert(document);
+ NSParameterAssert(contents);
+ NSString* revisionID = $castIf(NSString, [contents objectForKey: @"_rev"]);
+ if (!revisionID) {
+ [self release];
+ return nil;
+ }
+
+ self = [self initWithDocument: document revisionID: revisionID];
+ if (self) {
+ self.representedObject = [[contents copy] autorelease];
+ _isDeleted = [$castIf(NSNumber, [contents objectForKey: @"_deleted"]) boolValue];
+ }
+ return self;
+}
+
+
+- (id) initWithOperation: (RESTOperation*)operation {
+ NSParameterAssert(operation);
+ // Have to block to find out the revision ID. :(
+ BOOL isDeleted = NO;
+ if (![operation wait]) {
+ // Check whether it's been deleted:
+ if (operation.httpStatus == 404 &&
+ [[operation representedValueForKey: @"reason"] isEqualToString: @"deleted"]) {
+ isDeleted = YES;
+ } else {
+ Warn(@"CouchRevision initWithOperation failed: %@ on %@", operation.error, operation);
+ [self release];
+ return nil;
+ }
+ }
+ self = [self initWithDocument: $castIf(CouchDocument, operation.resource)
+ contents: operation.representedObject];
+ if (self) {
+ _isDeleted = isDeleted;
+ }
+ return self;
+}
+
+
+- (id) initWithParent: (RESTResource*)parent relativePath: (NSString*)relativePath {
+ NSAssert(NO, @"Wrong initializer for CouchRevision");
+ return nil;
+}
+
+
+- (void)dealloc {
+ [_properties release];
+ [super dealloc];
+}
+
+
+- (NSURL*) URL {
+ if (!self.relativePath)
+ return self.parent.URL;
+ // My relativePath is a query string, not a path component
+ NSString* urlStr = self.parent.URL.absoluteString;
+ urlStr = [urlStr stringByAppendingString: self.relativePath];
+ return [NSURL URLWithString: urlStr];
+}
+
+
+- (CouchDocument*) document {
+ return (CouchDocument*) self.parent;
+}
+
+
+- (NSString*) documentID {
+ return self.parent.relativePath;
+}
+
+
+- (BOOL) isCurrent {
+ return [self.revisionID isEqualToString: self.document.currentRevisionID];
+}
+
+
+@synthesize isDeleted=_isDeleted;
+
+
+- (NSString*) revisionID {
+ return [self.relativePath substringFromIndex: 5];
+}
+
+
+#pragma mark -
+#pragma mark CONTENTS / PROPERTIES:
+
+
+- (NSDictionary*) fromJSON {
+ NSDictionary* json = self.representedObject;
+ if (!json) {
+ [[self GET] wait]; // synchronous!
+ json = self.representedObject;
+ }
+ return json;
+}
+
+
+- (NSDictionary*) properties {
+ if (!_properties) {
+ NSDictionary* rep = [self fromJSON];
+ if (rep) {
+ NSMutableDictionary* props = [[NSMutableDictionary alloc] init];
+ for (NSString* key in rep) {
+ if (![key hasPrefix: @"_"])
+ [props setObject: [rep objectForKey: key] forKey: key];
+ }
+ _properties = [props copy];
+ [props release];
+ }
+ }
+ return _properties;
+}
+
+
+- (id) propertyForKey: (NSString*)key {
+ if ([key hasPrefix: @"_"])
+ return nil;
+ return [self.fromJSON objectForKey: key];
+}
+
+
+- (RESTOperation*) putProperties: (NSDictionary*)properties {
+ NSParameterAssert(properties != nil);
+ for (NSString* key in properties)
+ NSAssert1(![key hasPrefix: @"_"], @"Illegal property key '%@'", key);
+
+ return [self PUTJSON: properties parameters: nil];
+}
+
+
+#pragma mark -
+#pragma mark ATTACHMENTS:
+
+
+- (NSDictionary*) attachmentMetadata {
+ return $castIf(NSDictionary, [self.fromJSON objectForKey: @"_attachments"]);
+}
+
+
+- (NSDictionary*) attachmentMetadataFor: (NSString*)name {
+ return $castIf(NSDictionary, [self.attachmentMetadata objectForKey: name]);
+}
+
+
+- (NSArray*) attachmentNames {
+ return [self.attachmentMetadata allKeys];
+}
+
+
+- (CouchAttachment*) attachmentNamed: (NSString*)name {
+ NSDictionary* metadata = [self attachmentMetadataFor: name];
+ NSString* type = $castIf(NSString, [metadata objectForKey: @"content_type"]);
+ if (!type)
+ return nil;
+ return [[[CouchAttachment alloc] initWithDocument: self.document name: name type: type] autorelease];
+}
+
+
+- (CouchAttachment*) createAttachmentWithName: (NSString*)name type: (NSString*)contentType {
+ if ([self attachmentMetadataFor: name])
+ return nil;
+ return [[[CouchAttachment alloc] initWithDocument: self.document name: name type: contentType] autorelease];
+}
+
+
+@end
40 Couch/CouchServer.h
@@ -0,0 +1,40 @@
+//
+// CouchServer.h
+// CouchCocoa
+//
+// Created by Jens Alfke on 5/26/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchResource.h"
+@class CouchDatabase, RESTCache;
+
+
+/** The top level of a CouchDB server. Contains CouchDatabases. */
+@interface CouchServer : CouchResource
+{
+ @private
+ RESTCache* _dbCache;
+}
+
+/** Initialize given a server URL. */
+- (id) initWithURL: (NSURL*)url;
+
+/** Without a URL, connects to localhost on default port 5984. */
+- (id) init;
+
+/** Fetches the server's current version string. (Synchronous) */
+- (NSString*) getVersion: (NSError**)outError;
+
+/** Returns an array of unique-ID strings generated by the server. (Synchronous) */
+- (NSArray*) generateUUIDs: (NSUInteger)count;
+
+/** Returns array of CouchDatabase objects representing all the databases on the server. (Synchronous) */
+- (NSArray*) getDatabases;
+
+/** Just creates a CouchDatabase object; makes no calls to the server.
+ The database doesn't need to exist (you can call -create on it afterwards to create it.)
+ Multiple calls with the same name will return the same CouchDatabase instance. */
+- (CouchDatabase*) databaseNamed: (NSString*)name;
+
+@end
92 Couch/CouchServer.m
@@ -0,0 +1,92 @@
+//
+// CouchServer.m
+// CouchCocoa
+//
+// Created by Jens Alfke on 5/26/11.
+// Copyright 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import "CouchServer.h"
+
+#import "CouchInternal.h"
+#import "RESTCache.h"
+
+
+static NSString* const kLocalServerURL = @"http://127.0.0.1:5984/";
+
+
+@implementation CouchServer
+
+
+- (id) initWithURL: (NSURL*)url {
+ return [super initWithURL: url];
+}
+
+
+/** Without URL, connects to localhost on default port */
+- (id) init {
+ return [self initWithURL:[NSURL URLWithString: kLocalServerURL]];
+}
+
+
+- (void)dealloc {
+ [_dbCache release];
+ [super dealloc];
+}
+
+
+- (RESTResource*) childWithPath: (NSString*)name {
+ return [[[CouchResource alloc] initWithParent: self relativePath: name] autorelease];
+}
+
+
+- (NSString*) getVersion: (NSError**)outError {
+ RESTOperation* op = [self GET];
+ [op wait];
+ if (outError)
+ *outError = op.error;
+ return [[op representedValueForKey: @"version"] description]; // Blocks!
+}
+
+
+- (NSArray*) generateUUIDs: (NSUInteger)count {
+ NSDictionary* params = [NSDictionary dictionaryWithObject:
+ [NSNumber numberWithUnsignedLong: count]
+ forKey: @"?count"];
+ RESTOperation* op = [[self childWithPath: @"_uuids"] sendHTTP: @"GET" parameters: params];
+ return [op representedValueForKey: @"uuids"];
+}
+
+
+- (NSArray*) getDatabases {
+ RESTOperation* op = [[self childWithPath: @"_all_dbs"] GET];
+ NSArray* names = $castIf(NSArray, op.representedObject); // Blocks!
+ if (!names)
+ return nil;
+
+ NSMutableArray* databases = [NSMutableArray array];
+ for (id name in names) {
+ if (![name isKindOfClass:[NSString class]])
+ return nil;
+ [databases addObject: [self databaseNamed: name]];
+ }
+ return databases;
+}
+
+
+- (CouchDatabase*) databaseNamed: (NSString*)name {
+ CouchDatabase* db = (CouchDatabase*) [_dbCache resourceWithRelativePath: name];
+ if (!db) {
+ db = [[CouchDatabase alloc] initWithParent: self relativePath: name];
+ if (!db)
+ return nil;
+ if (!_dbCache)
+ _dbCache = [[RESTCache alloc] initWithRetainLimit: 0];
+ [_dbCache addResource: db];
+ [db release];
+ }
+ return db;
+}
+
+
+@end
1,055 CouchCocoa.xcodeproj/project.pbxproj
@@ -0,0 +1,1055 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 2730940E13C68E080093857D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2730940C13C68E080093857D /* InfoPlist.strings */; };
+ 2739BF2D13BCE53B004829CD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2739BF2C13BCE53B004829CD /* Foundation.framework */; };
+ 2739BF3713BCE53C004829CD /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 272E9D8313A2EBDF009F18E9 /* SenTestingKit.framework */; };
+ 2739BF3913BCE53C004829CD /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2739BF3813BCE53C004829CD /* UIKit.framework */; };
+ 2739BF3A13BCE53C004829CD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2739BF2C13BCE53B004829CD /* Foundation.framework */; };
+ 2739BF3C13BCE53C004829CD /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2739BF3B13BCE53C004829CD /* CoreGraphics.framework */; };
+ 2739BF3F13BCE53C004829CD /* libCouch.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2739BF2B13BCE53B004829CD /* libCouch.a */; };
+ 2739BF5013BCE560004829CD /* RESTResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B231B13916F1400DDD950 /* RESTResource.m */; };
+ 2739BF5113BCE562004829CD /* RESTOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B22FE138F32CB00DDD950 /* RESTOperation.m */; };
+ 2739BF5213BCE565004829CD /* RESTBody.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A57B3C1397E6FB002776DB /* RESTBody.m */; };
+ 2739BF5313BCE568004829CD /* RESTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 2781242C13AC265A0051A99D /* RESTCache.m */; };
+ 2739BF5413BCE56D004829CD /* RESTInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DA430513B659A900BBADB7 /* RESTInternal.m */; };
+ 2739BF5513BCE5A5004829CD /* CouchServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B22F2138F1F5F00DDD950 /* CouchServer.m */; };
+ 2739BF5613BCE5A8004829CD /* CouchDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B22F5138F269200DDD950 /* CouchDatabase.m */; };
+ 2739BF5713BCE5BD004829CD /* CouchDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B22F8138F2AA600DDD950 /* CouchDocument.m */; };
+ 2739BF5813BCE5BD004829CD /* CouchRevision.m in Sources */ = {isa = PBXBuildFile; fileRef = 2739BF2113BAB412004829CD /* CouchRevision.m */; };
+ 2739BF5913BCE5BD004829CD /* CouchAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B22FB138F320A00DDD950 /* CouchAttachment.m */; };
+ 2739BF5A13BCE5BD004829CD /* CouchDesignDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 27BB781713A08B520069ABA7 /* CouchDesignDocument.m */; };
+ 2739BF5B13BCE5BD004829CD /* CouchQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B275F1394225600DDD950 /* CouchQuery.m */; };
+ 2739BF5C13BCE5BD004829CD /* CouchResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B24CB1392EE3600DDD950 /* CouchResource.m */; };
+ 2739BF5D13BCE5BD004829CD /* CouchChangeTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 2781244513AFA6CD0051A99D /* CouchChangeTracker.m */; };
+ 2739BF5E13BCE5CD004829CD /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 2781242313AC1B2F0051A99D /* JSONKit.m */; };
+ 27A577BB13970A3B002776DB /* DemoQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A577A713970959002776DB /* DemoQuery.m */; };
+ 27A5799E13974DD7002776DB /* DemoItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A577DE13970D94002776DB /* DemoItem.m */; };
+ 27A579B313974E41002776DB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27A5792913974B65002776DB /* Cocoa.framework */; };
+ 27CDEBEA13C675C900C979BB /* Test_REST.m in Sources */ = {isa = PBXBuildFile; fileRef = 272E9D9313A2EBE0009F18E9 /* Test_REST.m */; };
+ 27CDEBEB13C675CD00C979BB /* Test_Couch.m in Sources */ = {isa = PBXBuildFile; fileRef = 270A663A13A5B36900791F4A /* Test_Couch.m */; };
+ 27CDEC0413C67C9B00C979BB /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 272E9D8313A2EBDF009F18E9 /* SenTestingKit.framework */; };
+ 27CDEC0513C67C9B00C979BB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27A5792913974B65002776DB /* Cocoa.framework */; };
+ 27CDEC0813C67C9B00C979BB /* Couch.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27CDEBF113C67C9B00C979BB /* Couch.framework */; };
+ 27CDEC1913C67D0300C979BB /* RESTResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B231B13916F1400DDD950 /* RESTResource.m */; };
+ 27CDEC1A13C67D0300C979BB /* RESTOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B22FE138F32CB00DDD950 /* RESTOperation.m */; };
+ 27CDEC1B13C67D0300C979BB /* RESTBody.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A57B3C1397E6FB002776DB /* RESTBody.m */; };
+ 27CDEC1C13C67D0300C979BB /* RESTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 2781242C13AC265A0051A99D /* RESTCache.m */; };
+ 27CDEC1D13C67D0300C979BB /* RESTInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DA430513B659A900BBADB7 /* RESTInternal.m */; };
+ 27CDEC1E13C67D0300C979BB /* CouchServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B22F2138F1F5F00DDD950 /* CouchServer.m */; };
+ 27CDEC1F13C67D0300C979BB /* CouchDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B22F5138F269200DDD950 /* CouchDatabase.m */; };
+ 27CDEC2013C67D0300C979BB /* CouchDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B22F8138F2AA600DDD950 /* CouchDocument.m */; };
+ 27CDEC2113C67D0300C979BB /* CouchRevision.m in Sources */ = {isa = PBXBuildFile; fileRef = 2739BF2113BAB412004829CD /* CouchRevision.m */; };
+ 27CDEC2213C67D0300C979BB /* CouchAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B22FB138F320A00DDD950 /* CouchAttachment.m */; };
+ 27CDEC2313C67D0300C979BB /* CouchDesignDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 27BB781713A08B520069ABA7 /* CouchDesignDocument.m */; };
+ 27CDEC2413C67D0300C979BB /* CouchQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B275F1394225600DDD950 /* CouchQuery.m */; };
+ 27CDEC2513C67D0300C979BB /* CouchResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B24CB1392EE3600DDD950 /* CouchResource.m */; };
+ 27CDEC2613C67D0300C979BB /* CouchChangeTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 2781244513AFA6CD0051A99D /* CouchChangeTracker.m */; };
+ 27CDEC2713C67D0300C979BB /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 2781242313AC1B2F0051A99D /* JSONKit.m */; };
+ 27CDEC2813C67D1100C979BB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27CDEBF613C67C9B00C979BB /* Foundation.framework */; };
+ 27CDEC2913C67D3500C979BB /* RESTResource.h in Headers */ = {isa = PBXBuildFile; fileRef = 278B231A13916F1400DDD950 /* RESTResource.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 27CDEC2A13C67D3500C979BB /* RESTOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 278B22FD138F32CB00DDD950 /* RESTOperation.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 27CDEC2B13C67D3500C979BB /* RESTBody.h in Headers */ = {isa = PBXBuildFile; fileRef = 27A57B3B1397E6FB002776DB /* RESTBody.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 27CDEC2C13C67D3500C979BB /* RESTCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 2781242B13AC265A0051A99D /* RESTCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 27CDEC2D13C67D3500C979BB /* REST.h in Headers */ = {isa = PBXBuildFile; fileRef = 270A664413A5BA4600791F4A /* REST.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 27CDEC2E13C67D3500C979BB /* CouchServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 278B22F1138F1F5F00DDD950 /* CouchServer.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 27CDEC2F13C67D3500C979BB /* CouchDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = 278B22F4138F269200DDD950 /* CouchDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 27CDEC3013C67D3500C979BB /* CouchDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = 278B22F7138F2AA600DDD950 /* CouchDocument.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 27CDEC3113C67D3500C979BB /* CouchRevision.h in Headers */ = {isa = PBXBuildFile; fileRef = 2739BF2013BAB411004829CD /* CouchRevision.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 27CDEC3213C67D3500C979BB /* CouchAttachment.h in Headers */ = {isa = PBXBuildFile; fileRef = 278B22FA138F320A00DDD950 /* CouchAttachment.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 27CDEC3313C67D3500C979BB /* CouchDesignDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = 27BB781613A08B520069ABA7 /* CouchDesignDocument.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 27CDEC3413C67D3500C979BB /* CouchQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = 278B275E1394225600DDD950 /* CouchQuery.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 27CDEC3513C67D3500C979BB /* CouchResource.h in Headers */ = {isa = PBXBuildFile; fileRef = 278B24CA1392EE3600DDD950 /* CouchResource.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 27CDEC3613C67D3500C979BB /* Couch.h in Headers */ = {isa = PBXBuildFile; fileRef = 270A663C13A5B3DF00791F4A /* Couch.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 27CDEC3713C67E1600C979BB /* Test_REST.m in Sources */ = {isa = PBXBuildFile; fileRef = 272E9D9313A2EBE0009F18E9 /* Test_REST.m */; };
+ 27CDEC3813C67E1A00C979BB /* Test_Couch.m in Sources */ = {isa = PBXBuildFile; fileRef = 270A663A13A5B36900791F4A /* Test_Couch.m */; };
+ 27CDEC3A13C6841E00C979BB /* Couch.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27CDEBF113C67C9B00C979BB /* Couch.framework */; };
+ 27EF148C1396D8CC0052913E /* DemoAppController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF148B1396D8CC0052913E /* DemoAppController.m */; };
+ 27EF14B31396DD3B0052913E /* CouchDemo.xib in Resources */ = {isa = PBXBuildFile; fileRef = 27EF14B21396DD3B0052913E /* CouchDemo.xib */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 2739BF3D13BCE53C004829CD /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 2739BF2A13BCE53B004829CD;
+ remoteInfo = "iOS Library";
+ };
+ 27CDEC0613C67C9B00C979BB /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 27CDEBF013C67C9B00C979BB;
+ remoteInfo = CouchAPI;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 27A57840139748D2002776DB /* CopyFiles */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 270A663A13A5B36900791F4A /* Test_Couch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = Test_Couch.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
+ 270A663C13A5B3DF00791F4A /* Couch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Couch.h; sourceTree = "<group>"; };
+ 270A664413A5BA4600791F4A /* REST.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = REST.h; sourceTree = "<group>"; };
+ 272E9D8313A2EBDF009F18E9 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; };
+ 272E9D9313A2EBE0009F18E9 /* Test_REST.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Test_REST.m; sourceTree = "<group>"; };
+ 2730940D13C68E080093857D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+ 273175A313CBA6B7000FF426 /* Doxyfile */ = {isa = PBXFileReference; explicitFileType = text.script; path = Doxyfile; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.sh; };
+ 27333BCA13B7E61700EF5A10 /* CouchInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CouchInternal.h; sourceTree = "<group>"; };
+ 2739BF2013BAB411004829CD /* CouchRevision.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CouchRevision.h; sourceTree = "<group>"; };
+ 2739BF2113BAB412004829CD /* CouchRevision.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CouchRevision.m; sourceTree = "<group>"; };
+ 2739BF2B13BCE53B004829CD /* libCouch.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCouch.a; sourceTree = BUILT_PRODUCTS_DIR; };
+ 2739BF2C13BCE53B004829CD /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+ 2739BF3613BCE53C004829CD /* iOS LibraryTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "iOS LibraryTests.octest"; path = "iOS Tests.octest"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 2739BF3813BCE53C004829CD /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
+ 2739BF3B13BCE53C004829CD /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
+ 2781242213AC1B2F0051A99D /* JSONKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JSONKit.h; path = Vendor/JSONKit/JSONKit.h; sourceTree = SOURCE_ROOT; };
+ 2781242313AC1B2F0051A99D /* JSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JSONKit.m; path = Vendor/JSONKit/JSONKit.m; sourceTree = SOURCE_ROOT; };
+ 2781242B13AC265A0051A99D /* RESTCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RESTCache.h; sourceTree = "<group>"; };
+ 2781242C13AC265A0051A99D /* RESTCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RESTCache.m; sourceTree = "<group>"; };
+ 2781244413AFA6CD0051A99D /* CouchChangeTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CouchChangeTracker.h; sourceTree = "<group>"; };
+ 2781244513AFA6CD0051A99D /* CouchChangeTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = CouchChangeTracker.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
+ 278B22F1138F1F5F00DDD950 /* CouchServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CouchServer.h; sourceTree = "<group>"; };
+ 278B22F2138F1F5F00DDD950 /* CouchServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CouchServer.m; sourceTree = "<group>"; };
+ 278B22F4138F269200DDD950 /* CouchDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CouchDatabase.h; sourceTree = "<group>"; };
+ 278B22F5138F269200DDD950 /* CouchDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = CouchDatabase.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
+ 278B22F7138F2AA600DDD950 /* CouchDocument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CouchDocument.h; sourceTree = "<group>"; };
+ 278B22F8138F2AA600DDD950 /* CouchDocument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CouchDocument.m; sourceTree = "<group>"; };
+ 278B22FA138F320A00DDD950 /* CouchAttachment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CouchAttachment.h; sourceTree = "<group>"; };
+ 278B22FB138F320A00DDD950 /* CouchAttachment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = CouchAttachment.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
+ 278B22FD138F32CB00DDD950 /* RESTOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RESTOperation.h; sourceTree = "<group>"; };
+ 278B22FE138F32CB00DDD950 /* RESTOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RESTOperation.m; sourceTree = "<group>"; };
+ 278B231A13916F1400DDD950 /* RESTResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RESTResource.h; sourceTree = "<group>"; };
+ 278B231B13916F1400DDD950 /* RESTResource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RESTResource.m; sourceTree = "<group>"; };
+ 278B24CA1392EE3600DDD950 /* CouchResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CouchResource.h; sourceTree = "<group>"; };
+ 278B24CB1392EE3600DDD950 /* CouchResource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CouchResource.m; sourceTree = "<group>"; };
+ 278B275E1394225600DDD950 /* CouchQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CouchQuery.h; sourceTree = "<group>"; };
+ 278B275F1394225600DDD950 /* CouchQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = CouchQuery.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
+ 27A577A613970959002776DB /* DemoQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoQuery.h; sourceTree = "<group>"; };
+ 27A577A713970959002776DB /* DemoQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoQuery.m; sourceTree = "<group>"; };
+ 27A577DD13970D94002776DB /* DemoItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoItem.h; sourceTree = "<group>"; };
+ 27A577DE13970D94002776DB /* DemoItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoItem.m; sourceTree = "<group>"; };
+ 27A5792913974B65002776DB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
+ 27A57B3B1397E6FB002776DB /* RESTBody.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RESTBody.h; sourceTree = "<group>"; };
+ 27A57B3C1397E6FB002776DB /* RESTBody.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RESTBody.m; sourceTree = "<group>"; };
+ 27BB781613A08B520069ABA7 /* CouchDesignDocument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CouchDesignDocument.h; sourceTree = "<group>"; };
+ 27BB781713A08B520069ABA7 /* CouchDesignDocument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CouchDesignDocument.m; sourceTree = "<group>"; };
+ 27CDEBF113C67C9B00C979BB /* Couch.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Couch.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 27CDEBF413C67C9B00C979BB /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
+ 27CDEBF513C67C9B00C979BB /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
+ 27CDEBF613C67C9B00C979BB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+ 27CDEBF913C67C9B00C979BB /* CouchCocoa-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "CouchCocoa-Info.plist"; sourceTree = "<group>"; };
+ 27CDEC0313C67C9B00C979BB /* CouchAPITests-Mac.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "CouchAPITests-Mac.octest"; path = "Mac Tests.octest"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 27CDEC3913C6806400C979BB /* CouchPrefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CouchPrefix.pch; sourceTree = "<group>"; };
+ 27DA430413B659A900BBADB7 /* RESTInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RESTInternal.h; sourceTree = "<group>"; };
+ 27DA430513B659A900BBADB7 /* RESTInternal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RESTInternal.m; sourceTree = "<group>"; };
+ 27EF14771396D7BA0052913E /* CouchDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CouchDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 27EF14791396D7BA0052913E /* CouchDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "CouchDemo-Info.plist"; sourceTree = "<group>"; };
+ 27EF148A1396D8CC0052913E /* DemoAppController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoAppController.h; sourceTree = "<group>"; };
+ 27EF148B1396D8CC0052913E /* DemoAppController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoAppController.m; sourceTree = "<group>"; };
+ 27EF14B21396DD3B0052913E /* CouchDemo.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CouchDemo.xib; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 2739BF2813BCE53B004829CD /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 2739BF2D13BCE53B004829CD /* Foundation.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 2739BF3213BCE53C004829CD /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 2739BF3713BCE53C004829CD /* SenTestingKit.framework in Frameworks */,
+ 2739BF3913BCE53C004829CD /* UIKit.framework in Frameworks */,
+ 2739BF3A13BCE53C004829CD /* Foundation.framework in Frameworks */,
+ 2739BF3C13BCE53C004829CD /* CoreGraphics.framework in Frameworks */,
+ 2739BF3F13BCE53C004829CD /* libCouch.a in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 27CDEBED13C67C9B00C979BB /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 27CDEC2813C67D1100C979BB /* Foundation.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 27CDEBFF13C67C9B00C979BB /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 27CDEC0413C67C9B00C979BB /* SenTestingKit.framework in Frameworks */,
+ 27CDEC0513C67C9B00C979BB /* Cocoa.framework in Frameworks */,
+ 27CDEC0813C67C9B00C979BB /* Couch.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 27EF14751396D7BA0052913E /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 27CDEC3A13C6841E00C979BB /* Couch.framework in Frameworks */,
+ 27A579B313974E41002776DB /* Cocoa.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 08FB7794FE84155DC02AAC07 /* Couch */ = {
+ isa = PBXGroup;
+ children = (
+ 278B231E1391724E00DDD950 /* REST */,
+ 08FB7795FE84155DC02AAC07 /* Couch */,
+ 272E9D8B13A2EBE0009F18E9 /* Test */,
+ 27EF147F1396D7CD0052913E /* Demo */,
+ 273175A313CBA6B7000FF426 /* Doxyfile */,
+ 27A57A51139751B2002776DB /* JSONKit */,
+ 27CDEBF713C67C9B00C979BB /* Resources */,
+ 272E9D8213A2EBDF009F18E9 /* Frameworks */,
+ 1AB674ADFE9D54B511CA2CBB /* Products */,
+ );
+ name = Couch;
+ sourceTree = "<group>";
+ };
+ 08FB7795FE84155DC02AAC07 /* Couch */ = {
+ isa = PBXGroup;
+ children = (
+ 278B22F1138F1F5F00DDD950 /* CouchServer.h */,
+ 278B22F2138F1F5F00DDD950 /* CouchServer.m */,
+ 278B22F4138F269200DDD950 /* CouchDatabase.h */,
+ 278B22F5138F269200DDD950 /* CouchDatabase.m */,
+ 278B22F7138F2AA600DDD950 /* CouchDocument.h */,
+ 278B22F8138F2AA600DDD950 /* CouchDocument.m */,
+ 2739BF2013BAB411004829CD /* CouchRevision.h */,
+ 2739BF2113BAB412004829CD /* CouchRevision.m */,
+ 278B22FA138F320A00DDD950 /* CouchAttachment.h */,
+ 278B22FB138F320A00DDD950 /* CouchAttachment.m */,
+ 27BB781613A08B520069ABA7 /* CouchDesignDocument.h */,
+ 27BB781713A08B520069ABA7 /* CouchDesignDocument.m */,
+ 278B275E1394225600DDD950 /* CouchQuery.h */,
+ 278B275F1394225600DDD950 /* CouchQuery.m */,
+ 278B24CA1392EE3600DDD950 /* CouchResource.h */,
+ 278B24CB1392EE3600DDD950 /* CouchResource.m */,
+ 270A663C13A5B3DF00791F4A /* Couch.h */,
+ 27333BCB13B7E70100EF5A10 /* Internal */,
+ );
+ path = Couch;
+ sourceTree = "<group>";
+ };
+ 1AB674ADFE9D54B511CA2CBB /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 27EF14771396D7BA0052913E /* CouchDemo.app */,
+ 2739BF2B13BCE53B004829CD /* libCouch.a */,
+ 2739BF3613BCE53C004829CD /* iOS LibraryTests.octest */,
+ 27CDEBF113C67C9B00C979BB /* Couch.framework */,
+ 27CDEC0313C67C9B00C979BB /* CouchAPITests-Mac.octest */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ 272E9D8213A2EBDF009F18E9 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 272E9D8313A2EBDF009F18E9 /* SenTestingKit.framework */,
+ 27A5792913974B65002776DB /* Cocoa.framework */,
+ 2739BF2C13BCE53B004829CD /* Foundation.framework */,
+ 2739BF3813BCE53C004829CD /* UIKit.framework */,
+ 2739BF3B13BCE53C004829CD /* CoreGraphics.framework */,
+ 27CDEBF313C67C9B00C979BB /* Other Frameworks */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ 272E9D8B13A2EBE0009F18E9 /* Test */ = {
+ isa = PBXGroup;
+ children = (
+ 272E9D9313A2EBE0009F18E9 /* Test_REST.m */,
+ 270A663A13A5B36900791F4A /* Test_Couch.m */,
+ );
+ path = Test;
+ sourceTree = "<group>";
+ };
+ 27333BCB13B7E70100EF5A10 /* Internal */ = {
+ isa = PBXGroup;
+ children = (
+ 27333BCA13B7E61700EF5A10 /* CouchInternal.h */,
+ 2781244413AFA6CD0051A99D /* CouchChangeTracker.h */,
+ 2781244513AFA6CD0051A99D /* CouchChangeTracker.m */,
+ 27CDEC3913C6806400C979BB /* CouchPrefix.pch */,
+ );
+ name = Internal;
+ sourceTree = "<group>";
+ };
+ 27333BCC13B7EB0000EF5A10 /* Internal */ = {
+ isa = PBXGroup;
+ children = (
+ 27DA430413B659A900BBADB7 /* RESTInternal.h */,
+ 27DA430513B659A900BBADB7 /* RESTInternal.m */,
+ );
+ name = Internal;
+ sourceTree = "<group>";
+ };
+ 278B231E1391724E00DDD950 /* REST */ = {
+ isa = PBXGroup;
+ children = (
+ 278B231A13916F1400DDD950 /* RESTResource.h */,
+ 278B231B13916F1400DDD950 /* RESTResource.m */,
+ 278B22FD138F32CB00DDD950 /* RESTOperation.h */,
+ 278B22FE138F32CB00DDD950 /* RESTOperation.m */,
+ 27A57B3B1397E6FB002776DB /* RESTBody.h */,
+ 27A57B3C1397E6FB002776DB /* RESTBody.m */,
+ 2781242B13AC265A0051A99D /* RESTCache.h */,
+ 2781242C13AC265A0051A99D /* RESTCache.m */,
+ 270A664413A5BA4600791F4A /* REST.h */,
+ 27333BCC13B7EB0000EF5A10 /* Internal */,
+ );
+ path = REST;
+ sourceTree = "<group>";
+ };
+ 27A57A51139751B2002776DB /* JSONKit */ = {
+ isa = PBXGroup;
+ children = (
+ 2781242213AC1B2F0051A99D /* JSONKit.h */,
+ 2781242313AC1B2F0051A99D /* JSONKit.m */,
+ );
+ name = JSONKit;
+ path = Vendor/JSONKit;
+ sourceTree = "<group>";
+ };
+ 27CDEBF313C67C9B00C979BB /* Other Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 27CDEBF413C67C9B00C979BB /* AppKit.framework */,
+ 27CDEBF513C67C9B00C979BB /* CoreData.framework */,
+ 27CDEBF613C67C9B00C979BB /* Foundation.framework */,
+ );
+ name = "Other Frameworks";
+ sourceTree = "<group>";
+ };
+ 27CDEBF713C67C9B00C979BB /* Resources */ = {
+ isa = PBXGroup;
+ children = (
+ 27CDEBF913C67C9B00C979BB /* CouchCocoa-Info.plist */,
+ 2730940C13C68E080093857D /* InfoPlist.strings */,
+ );
+ path = Resources;
+ sourceTree = "<group>";
+ };
+ 27EF147F1396D7CD0052913E /* Demo */ = {
+ isa = PBXGroup;
+ children = (
+ 27A577DD13970D94002776DB /* DemoItem.h */,
+ 27A577DE13970D94002776DB /* DemoItem.m */,
+ 27A577A613970959002776DB /* DemoQuery.h */,
+ 27A577A713970959002776DB /* DemoQuery.m */,
+ 27EF148A1396D8CC0052913E /* DemoAppController.h */,
+ 27EF148B1396D8CC0052913E /* DemoAppController.m */,
+ 27EF14B21396DD3B0052913E /* CouchDemo.xib */,
+ 27EF14791396D7BA0052913E /* CouchDemo-Info.plist */,
+ );
+ path = Demo;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ 2739BF2913BCE53B004829CD /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 27CDEBEE13C67C9B00C979BB /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 27CDEC2913C67D3500C979BB /* RESTResource.h in Headers */,
+ 27CDEC2A13C67D3500C979BB /* RESTOperation.h in Headers */,
+ 27CDEC2B13C67D3500C979BB /* RESTBody.h in Headers */,
+ 27CDEC2C13C67D3500C979BB /* RESTCache.h in Headers */,
+ 27CDEC2D13C67D3500C979BB /* REST.h in Headers */,
+ 27CDEC2E13C67D3500C979BB /* CouchServer.h in Headers */,
+ 27CDEC2F13C67D3500C979BB /* CouchDatabase.h in Headers */,
+ 27CDEC3013C67D3500C979BB /* CouchDocument.h in Headers */,
+ 27CDEC3113C67D3500C979BB /* CouchRevision.h in Headers */,
+ 27CDEC3213C67D3500C979BB /* CouchAttachment.h in Headers */,
+ 27CDEC3313C67D3500C979BB /* CouchDesignDocument.h in Headers */,
+ 27CDEC3413C67D3500C979BB /* CouchQuery.h in Headers */,
+ 27CDEC3513C67D3500C979BB /* CouchResource.h in Headers */,
+ 27CDEC3613C67D3500C979BB /* Couch.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXLegacyTarget section */
+ 2731759A13CBA57B000FF426 /* Documentation */ = {
+ isa = PBXLegacyTarget;
+ buildArgumentsString = "";
+ buildConfigurationList = 2731759B13CBA57B000FF426 /* Build configuration list for PBXLegacyTarget "Documentation" */;
+ buildPhases = (
+ );
+ buildToolPath = /opt/local/bin/doxygen;
+ buildWorkingDirectory = "";
+ dependencies = (
+ );
+ name = Documentation;
+ passBuildSettingsInEnvironment = 0;
+ productName = Documentation;
+ };
+/* End PBXLegacyTarget section */
+
+/* Begin PBXNativeTarget section */
+ 2739BF2A13BCE53B004829CD /* iOS Library */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 2739BF4A13BCE53C004829CD /* Build configuration list for PBXNativeTarget "iOS Library" */;
+ buildPhases = (
+ 2739BF2713BCE53B004829CD /* Sources */,
+ 2739BF2813BCE53B004829CD /* Frameworks */,
+ 2739BF2913BCE53B004829CD /* Headers */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "iOS Library";
+ productName = "iOS Library";
+ productReference = 2739BF2B13BCE53B004829CD /* libCouch.a */;
+ productType = "com.apple.product-type.library.static";