Permalink
Browse files

Improvements to C API

* Now actually thread-safe.
* Less data copying.
  • Loading branch information...
1 parent 46185fa commit 27dbf42120ea40b732fbb93b373ac18940e88911 @snej snej committed Mar 5, 2012
Showing with 75 additions and 39 deletions.
  1. +5 −4 Source/TDC.h
  2. +70 −35 Source/TDC.m
View
@@ -24,6 +24,7 @@ typedef struct {
const char** headerValues;
size_t contentLength;
const void* content;
+ void* _private;
} TDCMIME;
@@ -51,10 +52,10 @@ TDCMIME* TDCMIMECreate(unsigned headerCount,
void TDCMIMEFree(TDCMIME* mime);
-/** Identifies the base directory in which TouchDB should store data files.
- This directory must exist and must be writeable.
- This must be called once (and only once), before the first call to TDCSendRequest. */
-void TDCSetBaseDirectory(const char* path);
+/** Initializes the TDC API.
+ This must be called once (and only once), before the first call to TDCSendRequest.
+ @param dataDirectoryPath The base directory in which TouchDB should store data files. This directory's parent directory must exist and be writeable. */
+void TDCInitialize(const char* dataDirectoryPath);
/** Synchronously calls a TouchDB REST API method.
View
@@ -14,6 +14,7 @@
#import <string.h>
+static NSLock* sLock;
static NSString* sServerDir;
static TDServer* sServer;
@@ -85,22 +86,42 @@ static void FreeStringList(unsigned count, const char** stringList) {
}
+// Creates a TDCMIME whose body comes from an NSData object, without copying the body.
+static TDCMIME* TDCMIMECreateWithNSData(unsigned headerCount,
+ const char** headerNames,
+ const char** headerValues,
+ NSData* content)
+{
+ TDCMIME* mime = TDCMIMECreate(headerCount, headerNames, headerValues,
+ content.length, content.bytes, NO);
+ if (mime)
+ mime->_private = [content retain];
+ return mime;
+}
+
+
void TDCMIMEFree(TDCMIME* mime) {
if (!mime)
return;
FreeStringList(mime->headerCount, mime->headerNames);
FreeStringList(mime->headerCount, mime->headerValues);
- free((void*)mime->content);
+ if (mime->_private) // _private field points to NSData that owns the content ptr
+ [(NSData*)mime->_private release];
+ else
+ free((void*)mime->content);
free(mime);
}
-void TDCSetBaseDirectory(const char* path) {
+void TDCInitialize(const char* dataDirectoryPath) {
assert(!sServerDir);
- sServerDir = [CToNSString(path) retain];
+ sServerDir = [CToNSString(dataDirectoryPath) retain];
+ assert(sServerDir);
+ sLock = [[NSLock alloc] init];
}
+// Creates an NSURLRequest from a method, URL, headers and body.
static NSURLRequest* CreateRequest(NSString* method,
NSString* urlStr,
TDCMIME* headersAndBody)
@@ -133,6 +154,44 @@ void TDCSetBaseDirectory(const char* path) {
}
+// Actually runs the pre-parsed request through a TDRouter. This method is thread-safe.
+static TDResponse* RunRequest(NSURLRequest* request) {
+ assert(sLock);
+ [sLock lock];
+ @try {
+ // Create TDServer on first call:
+ if (!sServer) {
+ assert(sServerDir);
+ NSError* error;
+ sServer = [[TDServer alloc] initWithDirectory: sServerDir error: &error];
+ if (!sServer) {
+ Warn(@"Unable to create TouchDB server: %@", error);
+ return nil;
+ }
+ }
+
+ TDRouter* router = [[[TDRouter alloc] initWithServer: sServer
+ request: request] autorelease];
+ __block bool finished = false;
+ router.onFinished = ^{finished = true;};
+ [router start];
+ while (!finished) {
+ if (![[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
+ beforeDate: [NSDate dateWithTimeIntervalSinceNow: 5]])
+ break;
+ }
+
+ return finished ? router.response : nil;
+ } @catch (NSException *x) {
+ Warn(@"TDCSendRequest caught %@", x);
+ return nil;
+ } @finally {
+ [sLock unlock];
+ }
+}
+
+
+// Converts a TDResponse object to a TDCMIME structure.
static TDCMIME* CreateMIMEFromTDResponse(TDResponse* response) {
NSDictionary* headers = response.headers;
NSArray* headerNames = headers.allKeys;
@@ -143,9 +202,7 @@ void TDCSetBaseDirectory(const char* path) {
cHeaderNames[i] = [name UTF8String];
cHeaderValues[i] = [[headers objectForKey: name] UTF8String];
}
- NSData* content = response.body.asJSON;
- return TDCMIMECreate(headerCount, cHeaderNames, cHeaderValues,
- content.length, content.bytes, YES);
+ return TDCMIMECreateWithNSData(headerCount, cHeaderNames, cHeaderValues, response.body.asJSON);
}
@@ -157,48 +214,26 @@ int TDCSendRequest(const char* method,
@autoreleasepool {
*outResponse = NULL;
- // Create TDServer on first call:
- if (!sServer) {
- assert(sServerDir);
- NSError* error;
- sServer = [[TDServer alloc] initWithDirectory: sServerDir error: &error];
- if (!sServer) {
- Warn(@"Unable to create TouchDB server: %@", error);
- TDCMIMEFree(headersAndBody);
- return 500;
- }
- }
-
- // Create an NSURLRequest:
NSURLRequest* request = CreateRequest(CToNSString(method),
CToNSString(url),
headersAndBody);
TDCMIMEFree(headersAndBody);
if (!request)
return 400;
- // Create & run the router:
- TDRouter* router = [[[TDRouter alloc] initWithServer: sServer
- request: request] autorelease];
- __block bool finished = false;
- router.onFinished = ^{finished = true;};
- [router start];
- while (!finished) {
- if (![[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
- beforeDate: [NSDate dateWithTimeIntervalSinceNow: 5]])
- break;
- }
-
- // Return the response:
- *outResponse = CreateMIMEFromTDResponse(router.response);
- return router.response.status;
+ TDResponse* response = RunRequest(request);
+ if (!response)
+ return 500;
+ *outResponse = CreateMIMEFromTDResponse(response);
+ return response.status;
}
}
+
TestCase(TDCSendRequest) {
- TDCSetBaseDirectory("/tmp/TDCTest");
+ TDCInitialize("/tmp/TDCTest");
TDCMIME* response;
int status = TDCSendRequest("GET", "touchdb:///", NULL, &response);

0 comments on commit 27dbf42

Please sign in to comment.