Browse files

GNUstep compatibility (work in progress)

* Added a compatibility header TDGNUstep.h to declare stuff that isn't available in that environment.
* Abstracted JSON API in a TDJSON class so we can use a different one where NSJSONSerialization isn't available.
* Use <TouchDB/...> syntax for #imports.
* GNUstep doesn't have CoreFoundation, so work around that.
* GNUstep objects to assigning 'nil' to a non-id pointer lvalue; this usually shows up with those NSError** parameters.
* Commenting out TDReachability for now, since GNUstep doesn't have SystemConfiguration.
  • Loading branch information...
1 parent fa74ade commit 82f4a7f30096f75c12844b6e4b621893ddaf8673 @snej snej committed Feb 28, 2012
Showing with 899 additions and 147 deletions.
  1. +42 −0 Demo-Mac/EmptyGNUstepApp.m
  2. +0 −1 Demo-Mac/Frameworks/README
  3. +1 −1 Demo-Mac/TouchServ.m
  4. +6 −5 Source/ChangeTracker/TDChangeTracker.m
  5. +1 −2 Source/ChangeTracker/TDConnectionChangeTracker.m
  6. +3 −1 Source/ChangeTracker/TDSocketChangeTracker.m
  7. +16 −0 Source/TDBase64.h
  8. +115 −0 Source/TDBase64.m
  9. +11 −4 Source/TDBlobStore.h
  10. +30 −16 Source/TDBlobStore.m
  11. +2 −2 Source/TDBlobStore_Tests.m
  12. +6 −6 Source/TDBody.m
  13. +17 −16 Source/TDCollateJSON.m
  14. +1 −1 Source/TDDatabase+Attachments.h
  15. +1 −1 Source/TDDatabase+Insertion.h
  16. +2 −2 Source/TDDatabase+Insertion.m
  17. +1 −1 Source/TDDatabase+LocalDocs.h
  18. +4 −4 Source/TDDatabase+LocalDocs.m
  19. +1 −1 Source/TDDatabase+Replication.h
  20. +9 −9 Source/TDDatabase.m
  21. +3 −3 Source/TDDatabase_Tests.m
  22. +81 −0 Source/TDGNUstep.h
  23. +86 −0 Source/TDGNUstep.m
  24. +1 −1 Source/TDInternal.h
  25. +53 −0 Source/TDJSON.h
  26. +19 −0 Source/TDJSON.m
  27. +4 −1 Source/TDMisc.h
  28. +60 −3 Source/TDMisc.m
  29. +1 −1 Source/TDMultipartDownloader.m
  30. +1 −1 Source/TDMultipartWriter.m
  31. +1 −1 Source/TDPuller.h
  32. +2 −2 Source/TDPuller.m
  33. +1 −1 Source/TDPusher.h
  34. +5 −5 Source/TDPusher.m
  35. +7 −0 Source/TDReachability.h
  36. +53 −0 Source/TDRemoteRequest.h
  37. +175 −0 Source/TDRemoteRequest.m
  38. +1 −1 Source/TDReplicator.m
  39. +1 −1 Source/TDReplicatorManager.h
  40. +1 −1 Source/TDReplicatorManager.m
  41. +2 −2 Source/TDRevision.m
  42. +14 −27 Source/TDRouter+Handlers.m
  43. +1 −1 Source/TDRouter.h
  44. +17 −5 Source/TDRouter.m
  45. +3 −3 Source/TDRouter_Tests.m
  46. +1 −1 Source/TDSequenceMap.h
  47. +3 −3 Source/TDServer.m
  48. +5 −5 Source/TDView.m
  49. +6 −0 Source/TouchDBPrefix.h
  50. +20 −4 TouchDB.xcodeproj/project.pbxproj
  51. +0 −1 TouchServ
  52. +2 −0 vendor/google-toolbox-for-mac/GTMDefines.h
View
42 Demo-Mac/EmptyGNUstepApp.m
@@ -0,0 +1,42 @@
+//
+// EmptyGNUstepApp.m
+// TouchDB
+//
+// Created by Jens Alfke on 2/27/12.
+// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <TouchDB/TouchDB.h>
+#import <TouchDB/TDRouter.h>
+
+#if DEBUG
+#import "Logging.h"
+#else
+#define Warn NSLog
+#define Log NSLog
+#endif
+
+
+int main (int argc, const char * argv[])
+{
+ @autoreleasepool {
+#if DEBUG
+ EnableLog(YES);
+ EnableLogTo(TDListener, YES);
+#endif
+
+ NSError* error;
+ TDServer* server = [[TDServer alloc] initWithDirectory: @"/tmp/touchdbserver" error: &error];
+ if (error) {
+ Warn(@"FATAL: Error initializing TouchDB: %@", error);
+ exit(1);
+ }
+ NSLog(@"Started server %@", server);
+
+ [[NSRunLoop currentRunLoop] run];
+
+ }
+ return 0;
+}
+
View
1 Demo-Mac/Frameworks/README
@@ -1 +0,0 @@
-Copy or symlink CouchCocoa.framework into this directory.
View
2 Demo-Mac/TouchServ.m
@@ -1,5 +1,5 @@
//
-// main.m
+// TouchServ.m
// TouchServ
//
// Created by Jens Alfke on 1/16/12.
View
11 Source/ChangeTracker/TDChangeTracker.m
@@ -64,7 +64,7 @@ - (id)initWithDatabaseURL: (NSURL*)databaseURL
}
- (NSString*) databaseName {
- return _databaseURL.lastPathComponent;
+ return _databaseURL.path.lastPathComponent;
}
- (NSString*) changesFeedPath {
@@ -76,10 +76,11 @@ - (NSString*) changesFeedPath {
[path appendFormat: @"&since=%@", TDEscapeURLParam([_lastSequenceID description])];
if (_filterName) {
[path appendFormat: @"&filter=%@", TDEscapeURLParam(_filterName)];
- [_filterParameters enumerateKeysAndObjectsUsingBlock: ^(id key, id value, BOOL *stop) {
+ for (NSString* key in _filterParameters) {
+ id value = [_filterParameters objectForKey: key];
[path appendFormat: @"&%@=%@", TDEscapeURLParam(key),
TDEscapeURLParam([value description])];
- }];
+ }
}
return path;
@@ -147,7 +148,7 @@ - (BOOL) receivedChange: (NSDictionary*)change {
- (BOOL) receivedChunk: (NSData*)chunk {
if (chunk.length > 1) {
- id change = [NSJSONSerialization JSONObjectWithData: chunk options: 0 error: nil];
+ id change = [TDJSON JSONObjectWithData: chunk options: 0 error: NULL];
if (![self receivedChange: change]) {
Warn(@"Received unparseable change line from server: %@", [chunk my_UTF8ToString]);
return NO;
@@ -159,7 +160,7 @@ - (BOOL) receivedChunk: (NSData*)chunk {
- (BOOL) receivedPollResponse: (NSData*)body {
if (!body)
return NO;
- id changeObj = [NSJSONSerialization JSONObjectWithData: body options: 0 error: nil];
+ id changeObj = [TDJSON JSONObjectWithData: body options: 0 error: NULL];
NSDictionary* changeDict = $castIf(NSDictionary, changeObj);
NSArray* changes = $castIf(NSArray, [changeDict objectForKey: @"results"]);
if (!changes)
View
3 Source/ChangeTracker/TDConnectionChangeTracker.m
@@ -34,7 +34,6 @@ - (BOOL) start {
request.timeoutInterval = 6.02e23;
_connection = [[NSURLConnection connectionWithRequest: request delegate: self] retain];
- [_connection start];
LogTo(ChangeTracker, @"%@: Started... <%@>", self, request.URL);
return YES;
}
@@ -96,7 +95,7 @@ - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// In continuous mode, break input into lines and parse each as JSON:
for (;;) {
const char* start = _inputBuffer.bytes;
- const char* eol = strnstr(start, "\n", _inputBuffer.length);
+ const char* eol = memchr(start, '\n', _inputBuffer.length);
if (!eol)
break; // Wait till we have a complete line
ptrdiff_t lineLength = eol - start;
View
4 Source/ChangeTracker/TDSocketChangeTracker.m
@@ -18,6 +18,8 @@
#import "TDSocketChangeTracker.h"
#import "TDBase64.h"
+#import <string.h>
+
enum {
kStateStatus,
@@ -122,7 +124,7 @@ - (BOOL) failUnparseable: (NSString*)line {
- (BOOL) readLine {
const char* start = _inputBuffer.bytes;
- const char* crlf = strnstr(start, "\r\n", _inputBuffer.length);
+ const char* crlf = memmem(start, _inputBuffer.length, "\r\n", 2);
if (!crlf)
return NO; // Wait till we have a complete line
ptrdiff_t lineLength = crlf - start;
View
16 Source/TDBase64.h
@@ -0,0 +1,16 @@
+//
+// TDBase64.h
+// TouchDB
+//
+// Created by Jens Alfke on 9/14/11.
+// Copyright (c) 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface TDBase64 : NSObject
++ (NSString*) encode:(const void*) input length:(size_t) length;
++ (NSString*) encode:(NSData*) rawBytes;
++ (NSData*) decode:(const char*) string length:(size_t) inputLength;
++ (NSData*) decode:(NSString*) string;
+@end
View
115 Source/TDBase64.m
@@ -0,0 +1,115 @@
+//
+// TDBase64.m
+// TouchDB
+//
+// Created by Jens Alfke on 9/14/11.
+// Copyright (c) 2011 Couchbase, Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+// except in compliance with the License. You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+// either express or implied. See the License for the specific language governing permissions
+// and limitations under the License.
+
+#import "TDBase64.h"
+
+// Based on public-domain source code by cyrus.najmabadi@gmail.com
+// taken from http://www.cocoadev.com/index.pl?BaseSixtyFour
+
+
+@implementation TDBase64
+
+
+static const uint8_t kEncodingTable[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static int8_t kDecodingTable[256];
+
++ (void) initialize {
+ if (self == [TDBase64 class]) {
+ memset(kDecodingTable, 0xFF, sizeof(kDecodingTable));
+ for (NSUInteger i = 0; i < sizeof(kEncodingTable); i++) {
+ kDecodingTable[kEncodingTable[i]] = (int8_t)i;
+ }
+ }
+}
+
+
++ (NSString*) encode: (const void*)input length: (size_t)length {
+ if (input == NULL)
+ return nil;
+ NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
+ uint8_t* output = (uint8_t*)data.mutableBytes;
+
+ for (NSUInteger i = 0; i < length; i += 3) {
+ NSInteger value = 0;
+ for (NSUInteger j = i; j < (i + 3); j++) {
+ value <<= 8;
+
+ if (j < length) {
+ value |= ((const uint8_t*)input)[j];
+ }
+ }
+
+ NSInteger index = (i / 3) * 4;
+ output[index + 0] = kEncodingTable[(value >> 18) & 0x3F];
+ output[index + 1] = kEncodingTable[(value >> 12) & 0x3F];
+ output[index + 2] = (i + 1) < length ? kEncodingTable[(value >> 6) & 0x3F] : '=';
+ output[index + 3] = (i + 2) < length ? kEncodingTable[(value >> 0) & 0x3F] : '=';
+ }
+
+ return [[[NSString alloc] initWithData:data
+ encoding:NSASCIIStringEncoding] autorelease];
+}
+
+
++ (NSString*) encode: (NSData*)rawBytes {
+ return [self encode: rawBytes.bytes length: rawBytes.length];
+}
+
+
++ (NSData*) decode: (const char*)string length: (size_t)inputLength {
+ if ((string == NULL) || (inputLength % 4 != 0)) {
+ return nil;
+ }
+
+ while (inputLength > 0 && string[inputLength - 1] == '=') {
+ inputLength--;
+ }
+
+ size_t outputLength = inputLength * 3 / 4;
+ NSMutableData* data = [NSMutableData dataWithLength:outputLength];
+ uint8_t* output = data.mutableBytes;
+
+ NSUInteger inputPoint = 0;
+ NSUInteger outputPoint = 0;
+ while (inputPoint < inputLength) {
+ uint8_t i0 = string[inputPoint++];
+ uint8_t i1 = string[inputPoint++];
+ uint8_t i2 = inputPoint < inputLength ? string[inputPoint++] : 'A'; /* 'A' will decode to \0 */
+ uint8_t i3 = inputPoint < inputLength ? string[inputPoint++] : 'A';
+
+ if (kDecodingTable[i0] < 0 || kDecodingTable[i1] < 0
+ || kDecodingTable[i2] < 0 || kDecodingTable[i3] < 0)
+ return nil;
+
+ output[outputPoint++] = (uint8_t)((kDecodingTable[i0] << 2) | (kDecodingTable[i1] >> 4));
+ if (outputPoint < outputLength) {
+ output[outputPoint++] = (uint8_t)(((kDecodingTable[i1] & 0xf) << 4) | (kDecodingTable[i2] >> 2));
+ }
+ if (outputPoint < outputLength) {
+ output[outputPoint++] = (uint8_t)(((kDecodingTable[i2] & 0x3) << 6) | kDecodingTable[i3]);
+ }
+ }
+
+ return data;
+}
+
+
++ (NSData*) decode:(NSString*) string {
+ NSData* ascii = [string dataUsingEncoding: NSASCIIStringEncoding];
+ return [self decode: ascii.bytes length: ascii.length];
+}
+
+
+@end
View
15 Source/TDBlobStore.h
@@ -7,12 +7,19 @@
//
#import <Foundation/Foundation.h>
+
+#ifdef GNUSTEP
+#import <openssl/md5.h>
+#import <openssl/sha.h>
+#else
+#define COMMON_DIGEST_FOR_OPENSSL
#import <CommonCrypto/CommonDigest.h>
+#endif
/** Key identifying a data blob. This happens to be a SHA-1 digest. */
typedef struct TDBlobKey {
- uint8_t bytes[CC_SHA1_DIGEST_LENGTH];
+ uint8_t bytes[SHA_DIGEST_LENGTH];
} TDBlobKey;
@@ -48,7 +55,7 @@ typedef struct TDBlobKey {
typedef struct {
- uint8_t bytes[CC_MD5_DIGEST_LENGTH];
+ uint8_t bytes[MD5_DIGEST_LENGTH];
} TDMD5Key;
@@ -59,8 +66,8 @@ typedef struct {
NSString* _tempPath;
NSFileHandle* _out;
UInt64 _length;
- CC_SHA1_CTX _shaCtx;
- CC_MD5_CTX _md5Ctx;
+ SHA_CTX _shaCtx;
+ MD5_CTX _md5Ctx;
TDBlobKey _blobKey;
TDMD5Key _MD5Digest;
}
View
46 Source/TDBlobStore.m
@@ -15,6 +15,13 @@
#import "TDBlobStore.h"
#import "TDMisc.h"
+#import <ctype.h>
+
+
+#ifdef GNUSTEP
+#define NSDataReadingMappedIfSafe NSMappedRead
+#define NSDataWritingAtomic NSAtomicWrite
+#endif
#define kFileExtension "blob"
@@ -51,7 +58,10 @@ - (void)dealloc {
+ (TDBlobKey) keyForBlob: (NSData*)blob {
NSCParameterAssert(blob);
TDBlobKey key;
- CC_SHA1(blob.bytes, (CC_LONG)blob.length, key.bytes);
+ SHA_CTX ctx;
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, blob.bytes, blob.length);
+ SHA1_Final(key.bytes, &ctx);
return key;
}
@@ -99,15 +109,15 @@ + (BOOL) getKey: (TDBlobKey*)outKey forFilename: (NSString*)filename {
- (NSData*) blobForKey: (TDBlobKey)key {
NSString* path = [self pathForKey: key];
- return [NSData dataWithContentsOfFile: path options: NSDataReadingMappedIfSafe error: nil];
+ return [NSData dataWithContentsOfFile: path options: NSDataReadingMappedIfSafe error: NULL];
}
- (NSInputStream*) blobInputStreamForKey: (TDBlobKey)key
length: (UInt64*)outLength
{
NSString* path = [self pathForKey: key];
if (outLength)
- *outLength = [[[NSFileManager defaultManager] attributesOfItemAtPath: path error: nil]
+ *outLength = [[[NSFileManager defaultManager] attributesOfItemAtPath: path error: NULL]
fileSize];
return [NSInputStream inputStreamWithFileAtPath: path];
}
@@ -130,7 +140,7 @@ - (BOOL) storeBlob: (NSData*)blob
- (NSArray*) allKeys {
NSArray* blob = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: _path
- error: nil];
+ error: NULL];
if (!blob)
return nil;
return [blob my_map: ^(id filename) {
@@ -146,7 +156,7 @@ - (NSArray*) allKeys {
- (NSUInteger) count {
NSUInteger n = 0;
NSFileManager* fmgr = [NSFileManager defaultManager];
- for (NSString* filename in [fmgr contentsOfDirectoryAtPath: _path error: nil]) {
+ for (NSString* filename in [fmgr contentsOfDirectoryAtPath: _path error: NULL]) {
if ([[self class] getKey: NULL forFilename: filename])
++n;
}
@@ -157,10 +167,10 @@ - (NSUInteger) count {
- (UInt64) totalDataSize {
UInt64 total = 0;
NSFileManager* fmgr = [NSFileManager defaultManager];
- for (NSString* filename in [fmgr contentsOfDirectoryAtPath: _path error: nil]) {
+ for (NSString* filename in [fmgr contentsOfDirectoryAtPath: _path error: NULL]) {
if ([[self class] getKey: NULL forFilename: filename]) {
NSString* itemPath = [_path stringByAppendingPathComponent: filename];
- NSDictionary* attrs = [fmgr attributesOfItemAtPath: itemPath error: nil];
+ NSDictionary* attrs = [fmgr attributesOfItemAtPath: itemPath error: NULL];
if (attrs)
total += attrs.fileSize;
}
@@ -171,7 +181,7 @@ - (UInt64) totalDataSize {
- (NSUInteger) deleteBlobsExceptWithKeys: (NSSet*)keysToKeep {
NSFileManager* fmgr = [NSFileManager defaultManager];
- NSArray* blob = [fmgr contentsOfDirectoryAtPath: _path error: nil];
+ NSArray* blob = [fmgr contentsOfDirectoryAtPath: _path error: NULL];
if (!blob)
return 0;
NSUInteger numDeleted = 0;
@@ -195,6 +205,9 @@ - (NSUInteger) deleteBlobsExceptWithKeys: (NSSet*)keysToKeep {
- (NSString*) tempDir {
if (!_tempDir) {
// Find a temporary directory suitable for files that will be moved into the store:
+#ifdef GNUSTEP
+ _tempDir = [NSTemporaryDirectory() copy];
+#else
NSError* error;
NSURL* parentURL = [NSURL fileURLWithPath: _path isDirectory: YES];
NSURL* tempDirURL = [[NSFileManager defaultManager]
@@ -206,6 +219,7 @@ - (NSString*) tempDir {
Log(@"TDBlobStore %@ created tempDir %@", _path, _tempDir);
if (!_tempDir)
Warn(@"TDBlobStore: Unable to create temp dir: %@", error);
+#endif
}
return _tempDir;
}
@@ -224,8 +238,8 @@ - (id) initWithStore: (TDBlobStore*)store {
self = [super init];
if (self) {
_store = store;
- CC_SHA1_Init(&_shaCtx);
- CC_MD5_Init(&_md5Ctx);
+ SHA1_Init(&_shaCtx);
+ MD5_Init(&_md5Ctx);
// Open a temporary file in the store's temporary directory:
NSString* filename = [TDCreateUUID() stringByAppendingPathExtension: @"blobtmp"];
@@ -248,8 +262,8 @@ - (void) appendData: (NSData*)data {
[_out writeData: data];
NSUInteger dataLen = data.length;
_length += dataLen;
- CC_SHA1_Update(&_shaCtx, data.bytes, (CC_LONG)dataLen);
- CC_MD5_Update(&_md5Ctx, data.bytes, (CC_LONG)dataLen);
+ SHA1_Update(&_shaCtx, data.bytes, dataLen);
+ MD5_Update(&_md5Ctx, data.bytes, dataLen);
}
- (void) closeFile {
@@ -261,8 +275,8 @@ - (void) closeFile {
- (void) finish {
Assert(_out, @"Already finished");
[self closeFile];
- CC_SHA1_Final(_blobKey.bytes, &_shaCtx);
- CC_MD5_Final(_MD5Digest.bytes, &_md5Ctx);
+ SHA1_Final(_blobKey.bytes, &_shaCtx);
+ MD5_Final(_MD5Digest.bytes, &_md5Ctx);
}
- (BOOL) install {
@@ -272,7 +286,7 @@ - (BOOL) install {
// Move temp file to correct location in blob store:
NSString* dstPath = [_store pathForKey: _blobKey];
if ([[NSFileManager defaultManager] moveItemAtPath: _tempPath
- toPath: dstPath error:nil]) {
+ toPath: dstPath error:NULL]) {
[_tempPath release];
_tempPath = nil;
} else {
@@ -286,7 +300,7 @@ - (BOOL) install {
- (void) cancel {
[self closeFile];
if (_tempPath) {
- [[NSFileManager defaultManager] removeItemAtPath: _tempPath error: nil];
+ [[NSFileManager defaultManager] removeItemAtPath: _tempPath error: NULL];
[_tempPath release];
_tempPath = nil;
}
View
4 Source/TDBlobStore_Tests.m
@@ -12,15 +12,15 @@
static TDBlobStore* createStore(void) {
NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent: @"TDBlobStoreTest"];
- [[NSFileManager defaultManager] removeItemAtPath: path error: nil];
+ [[NSFileManager defaultManager] removeItemAtPath: path error: NULL];
NSError* error;
TDBlobStore* store = [[TDBlobStore alloc] initWithPath: path error: &error];
CAssert(store, @"Couldn't create TDBlobStore: %@", error);
return [store autorelease];
}
static void deleteStore(TDBlobStore* store) {
- [[NSFileManager defaultManager] removeItemAtPath: store.path error: nil];
+ [[NSFileManager defaultManager] removeItemAtPath: store.path error: NULL];
}
View
12 Source/TDBody.m
@@ -57,7 +57,7 @@ + (TDBody*) bodyWithJSON: (NSData*)json {
- (BOOL) isValidJSON {
// Yes, this is just like asObject except it doesn't warn.
if (!_object && !_error) {
- _object = [[NSJSONSerialization JSONObjectWithData: _json options: 0 error: nil] copy];
+ _object = [[TDJSON JSONObjectWithData: _json options: 0 error: NULL] copy];
if (!_object) {
_error = YES;
}
@@ -67,7 +67,7 @@ - (BOOL) isValidJSON {
- (NSData*) asJSON {
if (!_json && !_error) {
- _json = [[NSJSONSerialization dataWithJSONObject: _object options: 0 error: nil] copy];
+ _json = [[TDJSON dataWithJSONObject: _object options: 0 error: NULL] copy];
if (!_json) {
Warn(@"TDBody: couldn't convert to JSON");
_error = YES;
@@ -79,9 +79,9 @@ - (NSData*) asJSON {
- (NSData*) asPrettyJSON {
id props = self.asObject;
if (props) {
- NSData* json = [NSJSONSerialization dataWithJSONObject: props
- options: NSJSONWritingPrettyPrinted
- error: nil];
+ NSData* json = [TDJSON dataWithJSONObject: props
+ options: TDJSONWritingPrettyPrinted
+ error: NULL];
if (json) {
NSMutableData* mjson = [[json mutableCopy] autorelease];
[mjson appendBytes: "\n" length: 1];
@@ -98,7 +98,7 @@ - (NSString*) asJSONString {
- (id) asObject {
if (!_object && !_error) {
NSError* error = nil;
- _object = [[NSJSONSerialization JSONObjectWithData: _json options: 0 error: &error] copy];
+ _object = [[TDJSON JSONObjectWithData: _json options: 0 error: &error] copy];
if (!_object) {
Warn(@"TDBody: couldn't parse JSON: %@ (error=%@)", [_json my_UTF8ToString], error);
_error = YES;
View
33 Source/TDCollateJSON.m
@@ -132,7 +132,7 @@ static int compareStringsASCII(const char** in1, const char** in2) {
}
-static CFStringRef createCFStringFromJSON(const char** in) {
+static NSString* createStringFromJSON(const char** in) {
// Scan the JSON string to find its end and whether it contains escapes:
const char* start = ++*in;
unsigned escapes = 0;
@@ -150,7 +150,7 @@ static CFStringRef createCFStringFromJSON(const char** in) {
*in = str + 1;
size_t length = str - start;
- CFAllocatorRef deallocator;
+ BOOL freeWhenDone = NO;
if (escapes > 0) {
length -= escapes;
char* buf = malloc(length);
@@ -163,23 +163,24 @@ static CFStringRef createCFStringFromJSON(const char** in) {
}
CAssertEq(dst-buf, (int)length);
start = buf;
- deallocator = NULL; // means "use system deallocator", i.e. free()
- } else {
- deallocator = kCFAllocatorNull;
+ freeWhenDone = YES;
}
- CFStringRef cfstr = CFStringCreateWithBytesNoCopy(NULL, (const UInt8*)start, length,
- kCFStringEncodingUTF8, NO, deallocator);
- CAssert(cfstr != NULL, @"Failed to convert to string: start=%p, length=%u", start, length);
- return cfstr;
+
+ NSString* nsstr = [[NSString alloc] initWithBytesNoCopy: (void*)start
+ length: length
+ encoding: NSUTF8StringEncoding
+ freeWhenDone: freeWhenDone];
+ CAssert(nsstr != nil, @"Failed to convert to string: start=%p, length=%u", start, length);
+ return nsstr;
}
static int compareStringsUnicode(const char** in1, const char** in2) {
- CFStringRef str1 = createCFStringFromJSON(in1);
- CFStringRef str2 = createCFStringFromJSON(in2);
- int result = (int) CFStringCompare(str1, str2, kCFCompareAnchored | kCFCompareLocalized);
- CFRelease(str1);
- CFRelease(str2);
+ NSString* str1 = createStringFromJSON(in1);
+ NSString* str2 = createStringFromJSON(in2);
+ int result = (int)[str1 localizedCompare: str2];
+ [str1 release];
+ [str2 release];
return result;
}
@@ -265,7 +266,7 @@ int TDCollateJSON(void *context,
// encodes an object to a C string in JSON format. JSON fragments are allowed.
static const char* encode(id obj) {
NSArray* wrapped = $array(obj);
- NSData* data = [NSJSONSerialization dataWithJSONObject: wrapped options: 0 error: nil];
+ NSData* data = [TDJSON dataWithJSONObject: wrapped options: 0 error: NULL];
CAssert(data);
data = [data subdataWithRange: NSMakeRange(1, data.length-2)]; // strip the brackets
return [[data my_UTF8ToString] UTF8String];
@@ -353,7 +354,7 @@ static void testEscape(const char* source, char decoded) {
}
TestCase(TDCollateUnicodeStrings) {
- // Make sure that NSJSONSerialization never creates escape sequences we can't parse.
+ // Make sure that TDJSON never creates escape sequences we can't parse.
// That includes "\unnnn" for non-ASCII chars, and "\t", "\b", etc.
RequireTestCase(TDCollateConvertEscape);
void* mode = kTDCollateJSON_Unicode;
View
2 Source/TDDatabase+Attachments.h
@@ -6,7 +6,7 @@
// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
//
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
@class TDBlobStoreWriter;
View
2 Source/TDDatabase+Insertion.h
@@ -6,7 +6,7 @@
// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
//
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
@protocol TDValidationContext;
View
4 Source/TDDatabase+Insertion.m
@@ -15,7 +15,7 @@
#import "TDDatabase+Insertion.h"
#import "TDDatabase+Attachments.h"
-#import "TDRevision.h"
+#import <TouchDB/TDRevision.h>
#import "TDInternal.h"
#import "TDMisc.h"
@@ -144,7 +144,7 @@ - (NSData*) encodeDocumentJSON: (TDRevision*)rev {
}
NSError* error;
- NSData* json = [NSJSONSerialization dataWithJSONObject: properties options:0 error: &error];
+ NSData* json = [TDJSON dataWithJSONObject: properties options:0 error: &error];
[properties release];
Assert(json, @"Unable to serialize %@ to JSON: %@", rev, error);
return json;
View
2 Source/TDDatabase+LocalDocs.h
@@ -6,7 +6,7 @@
// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
//
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
@interface TDDatabase (LocalDocs)
View
8 Source/TDDatabase+LocalDocs.m
@@ -14,7 +14,7 @@
// and limitations under the License.
#import "TDDatabase+LocalDocs.h"
-#import "TDRevision.h"
+#import <TouchDB/TDRevision.h>
#import "TDBody.h"
#import "TDInternal.h"
@@ -38,9 +38,9 @@ - (TDRevision*) getLocalDocumentWithID: (NSString*)docID
if (json.length==0 || (json.length==2 && memcmp(json.bytes, "{}", 2)==0))
properties = $mdict(); // workaround for issue #44
else {
- properties = [NSJSONSerialization JSONObjectWithData: json
- options:NSJSONReadingMutableContainers
- error: nil];
+ properties = [TDJSON JSONObjectWithData: json
+ options:TDJSONReadingMutableContainers
+ error: NULL];
if (!properties)
return nil;
}
View
2 Source/TDDatabase+Replication.h
@@ -6,7 +6,7 @@
// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
//
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
@class TDReplicator;
View
18 Source/TDDatabase.m
@@ -13,10 +13,10 @@
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
#import "TDDatabase+Attachments.h"
#import "TDInternal.h"
-#import "TDRevision.h"
+#import <TouchDB/TDRevision.h>
#import "TDCollateJSON.h"
#import "TDBlobStore.h"
#import "TDPuller.h"
@@ -46,10 +46,10 @@ - (NSString*) attachmentStorePath {
+ (TDDatabase*) createEmptyDBAtPath: (NSString*)path {
- if (!removeItemIfExists(path, nil))
+ if (!removeItemIfExists(path, NULL))
return nil;
TDDatabase *db = [[[self alloc] initWithPath: path] autorelease];
- if (!removeItemIfExists(db.attachmentStorePath, nil))
+ if (!removeItemIfExists(db.attachmentStorePath, NULL))
return nil;
if (![db open])
return nil;
@@ -311,7 +311,7 @@ - (void) dealloc {
- (UInt64) totalDataSize {
- NSDictionary* attrs = [[NSFileManager defaultManager] attributesOfItemAtPath: _path error: nil];
+ NSDictionary* attrs = [[NSFileManager defaultManager] attributesOfItemAtPath: _path error: NULL];
if (!attrs)
return 0;
return attrs.fileSize + _attachments.totalDataSize;
@@ -394,7 +394,7 @@ - (SequenceNumber) lastSequence {
static NSData* appendDictToJSON(NSData* json, NSDictionary* dict) {
if (!dict.count)
return json;
- NSData* extraJson = [NSJSONSerialization dataWithJSONObject: dict options:0 error:nil];
+ NSData* extraJson = [TDJSON dataWithJSONObject: dict options:0 error: NULL];
if (!extraJson)
return nil;
size_t jsonLength = json.length;
@@ -495,9 +495,9 @@ - (NSDictionary*) documentPropertiesFromJSON: (NSData*)json
[rev release];
if (json==nil || (json.length==2 && memcmp(json.bytes, "{}", 2)==0))
return extra; // optimization, and workaround for issue #44
- NSMutableDictionary* docProperties = [NSJSONSerialization JSONObjectWithData: json
- options: NSJSONReadingMutableContainers
- error: nil];
+ NSMutableDictionary* docProperties = [TDJSON JSONObjectWithData: json
+ options: TDJSONReadingMutableContainers
+ error: NULL];
[docProperties addEntriesFromDictionary: extra];
return docProperties;
}
View
6 Source/TDDatabase_Tests.m
@@ -14,13 +14,13 @@
// and limitations under the License.
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
#import "TDDatabase+Attachments.h"
#import "TDDatabase+Insertion.h"
#import "TDDatabase+LocalDocs.h"
#import "TDDatabase+Replication.h"
#import "TDBody.h"
-#import "TDRevision.h"
+#import <TouchDB/TDRevision.h>
#import "TDBlobStore.h"
#import "TDBase64.h"
#import "TDInternal.h"
@@ -154,7 +154,7 @@
TestCase(TDDatabase_EmptyDoc) {
- // Test case for issue #44, which is caused by a bug in NSJSONSerialization.
+ // Test case for issue #44, which is caused by a bug in TDJSON.
TDDatabase* db = createDB();
TDRevision* rev = putDoc(db, $dict());
TDQueryOptions options = kDefaultTDQueryOptions;
View
81 Source/TDGNUstep.h
@@ -0,0 +1,81 @@
+//
+// TDGNUstep.h
+// TouchDB
+//
+// Created by Jens Alfke on 2/27/12.
+// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
+//
+
+#ifdef GNUSTEP
+
+/* Stuff that's in iOS / OS X but not GNUstep or Linux */
+
+#define _GNU_SOURCE
+
+#import <Foundation/Foundation.h>
+
+
+#ifndef NS_BLOCKS_AVAILABLE
+#define NS_BLOCKS_AVAILABLE 1
+#endif
+
+
+typedef int32_t SInt32;
+typedef uint32_t UInt32;
+typedef int64_t SInt64;
+typedef uint64_t UInt64;
+typedef int8_t SInt8;
+typedef uint8_t UInt8;
+
+
+// in BSD but not Linux:
+int digittoint(int c);
+
+
+#define NSRunLoopCommonModes NSDefaultRunLoopMode
+
+
+typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);
+
+@interface NSArray (GNUstep)
+- (NSArray *)sortedArrayUsingComparator:(NSComparator)cmptr;
+@end
+
+
+@interface NSMutableArray (GNUstep)
+- (void)sortUsingComparator:(NSComparator)cmptr;
+@end
+
+
+enum {
+ NSDataReadingMappedIfSafe = 1UL << 0,
+ NSDataReadingUncached = 1UL << 1,
+};
+typedef NSUInteger NSDataReadingOptions;
+
+enum {
+ NSDataSearchBackwards = 1UL << 0,
+ NSDataSearchAnchored = 1UL << 1
+};
+typedef NSUInteger NSDataSearchOptions;
+
+@interface NSData (GNUstep)
++ (id)dataWithContentsOfFile:(NSString *)path options:(NSDataReadingOptions)readOptionsMask error:(NSError **)errorPtr;
+- (NSRange)rangeOfData:(NSData *)dataToFind options:(NSDataSearchOptions)mask range:(NSRange)searchRange;
+@end
+
+
+enum {
+ NSFileWriteFileExistsError = 99999 //TEMP
+};
+
+
+@protocol NSURLConnectionDelegate <NSObject>
+@end
+
+
+@protocol NSStreamDelegate <NSObject>
+@end
+
+
+#endif // GNUSTEP
View
86 Source/TDGNUstep.m
@@ -0,0 +1,86 @@
+//
+// TDGNUstep.m
+// TouchDB
+//
+// Created by Jens Alfke on 2/27/12.
+// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
+//
+
+#import "TDGNUstep.h"
+#import <Foundation/Foundation.h>
+
+
+@interface NSError (GNUstep)
++ (NSError*) _last;
+@end
+
+
+
+int digittoint(int c) {
+ if (isdigit(c))
+ return c - '0';
+ else if (c >= 'A' && c <= 'F')
+ return 10 + c - 'A';
+ else if (c >= 'a' && c <= 'f')
+ return 10 + c - 'a';
+ else
+ return 0;
+}
+
+
+
+static NSComparisonResult callComparator(id a, id b, void* context) {
+ return ((NSComparator)context)(a, b);
+}
+
+@implementation NSArray (GNUstep)
+
+- (NSArray *)sortedArrayUsingComparator:(NSComparator)cmptr {
+ return [self sortedArrayUsingFunction: &callComparator context: cmptr];
+}
+
+@end
+
+@implementation NSMutableArray (GNUstep)
+
+- (void)sortUsingComparator:(NSComparator)cmptr {
+ [self sortUsingFunction: &callComparator context: cmptr];
+}
+
+@end
+
+
+
+@implementation NSData (GNUstep)
+
++ (id)dataWithContentsOfFile:(NSString *)path
+ options:(NSDataReadingOptions)options
+ error:(NSError **)errorPtr
+{
+ NSData* data;
+ if (options & NSDataReadingMappedIfSafe)
+ data = [self dataWithContentsOfMappedFile: path];
+ else
+ data = [self dataWithContentsOfFile: path];
+ if (!data && errorPtr)
+ *errorPtr = [NSError _last];
+ return data;
+}
+
+- (NSRange)rangeOfData:(NSData *)dataToFind
+ options:(NSDataSearchOptions)options
+ range:(NSRange)searchRange
+{
+ NSParameterAssert(dataToFind);
+ NSParameterAssert(options == 0); // not implemented yet
+ const void* myBytes = self.bytes;
+ NSUInteger patternLen = dataToFind.length;
+ if (patternLen == 0)
+ return NSMakeRange(NSNotFound, 0);
+ const void* start = memmem(myBytes, self.length, dataToFind.bytes, patternLen);
+ if (!start)
+ return NSMakeRange(NSNotFound, 0);
+ return NSMakeRange(start - myBytes, patternLen);
+}
+
+@end
View
2 Source/TDInternal.h
@@ -6,7 +6,7 @@
// Copyright (c) 2011 Couchbase, Inc. All rights reserved.
//
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
#import "TDDatabase+Attachments.h"
#import "TDView.h"
#import "TDServer.h"
View
53 Source/TDJSON.h
@@ -0,0 +1,53 @@
+//
+// TDJSON.h
+// TouchDB
+//
+// Created by Jens Alfke on 2/27/12.
+// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+// Conditional compilation for JSONKit and/or NSJSONSerialization.
+// If the app supports OS versions prior to NSJSONSerialization, we'll do a runtime
+// test for it and use it if present, otherwise fall back to JSONKit.
+#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED
+#define USE_NSJSON (__IPHONE_OS_VERSION_MIN_REQUIRED >= 50000)
+#else
+#define USE_NSJSON (MAC_OS_X_VERSION_MIN_REQUIRED >= 1070)
+#endif
+
+
+enum {
+ TDJSONReadingMutableContainers = (1UL << 0),
+ TDJSONReadingMutableLeaves = (1UL << 1),
+ TDJSONReadingAllowFragments = (1UL << 2)
+};
+typedef NSUInteger TDJSONReadingOptions;
+
+enum {
+ TDJSONWritingPrettyPrinted = (1UL << 0)
+};
+typedef NSUInteger TDJSONWritingOptions;
+
+
+#if USE_NSJSON
+
+#define TDJSON NSJSONSerialization
+
+#else
+
+@interface TDJSON : NSObject
+
++ (NSData *)dataWithJSONObject:(id)obj
+ options:(TDJSONWritingOptions)opt
+ error:(NSError **)error;
+
++ (id)JSONObjectWithData:(NSData *)data
+ options:(TDJSONReadingOptions)opt
+ error:(NSError **)error;
+
+@end
+
+#endif // USE_NSJSON
View
19 Source/TDJSON.m
@@ -0,0 +1,19 @@
+//
+// TDJSON.m
+// TouchDB
+//
+// Created by Jens Alfke on 2/27/12.
+// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
+//
+
+#import "TDJSON.h"
+
+
+#if ! USE_NSJSON
+
+@implementation TDJSON
+
+@end
+
+
+#endif // ! USE_NSJSON
View
5 Source/TDMisc.h
@@ -7,7 +7,7 @@
//
#import <Foundation/Foundation.h>
-#import "TDRevision.h"
+#import <TouchDB/TDRevision.h>
extern NSString* const TDHTTPErrorDomain;
@@ -29,3 +29,6 @@ NSString* TDEscapeURLParam( NSString* param );
/** Returns YES if this error appears to be due to the computer being offline or the remote host being unreachable. */
BOOL TDIsOfflineError( NSError* error );
+
+/** Returns the input URL without the query string or fragment identifier, just ending with the path. */
+NSURL* TDURLWithoutQuery( NSURL* url );
View
63 Source/TDMisc.m
@@ -16,23 +16,42 @@
#import "TDMisc.h"
#import "CollectionUtils.h"
+
+
+#ifdef GNUSTEP
+#import <openssl/sha.h>
+#import <uuid/uuid.h> // requires installing "uuid-dev" package on Ubuntu
+#else
+#define COMMON_DIGEST_FOR_OPENSSL
#import <CommonCrypto/CommonDigest.h>
+#endif
NSString* const TDHTTPErrorDomain = @"TDHTTP";
NSString* TDCreateUUID() {
+#ifdef GNUSTEP
+ uuid_t uuid;
+ uuid_generate(uuid);
+ char cstr[37];
+ uuid_unparse_lower(uuid, cstr);
+ return [[[NSString alloc] initWithCString: cstr encoding: NSASCIIStringEncoding] autorelease];
+#else
CFUUIDRef uuid = CFUUIDCreate(NULL);
NSString* str = NSMakeCollectable(CFUUIDCreateString(NULL, uuid));
CFRelease(uuid);
return [str autorelease];
+#endif
}
NSString* TDHexSHA1Digest( NSData* input ) {
- unsigned char digest[CC_SHA1_DIGEST_LENGTH];
- CC_SHA1(input.bytes, (CC_LONG)input.length, digest);
+ unsigned char digest[SHA_DIGEST_LENGTH];
+ SHA_CTX ctx;
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, input.bytes, input.length);
+ SHA1_Final(digest, &ctx);
char hex[2*sizeof(digest) + 1];
char *dst = &hex[0];
for( size_t i=0; i<sizeof(digest); i+=1 )
@@ -59,20 +78,33 @@ NSComparisonResult TDSequenceCompare( SequenceNumber a, SequenceNumber b) {
NSString* TDEscapeID( NSString* docOrRevID ) {
+#ifdef GNUSTEP
+ docOrRevID = [docOrRevID stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
+ docOrRevID = [docOrRevID stringByReplacingOccurrencesOfString: @"&" withString: @"%26"];
+ docOrRevID = [docOrRevID stringByReplacingOccurrencesOfString: @"/" withString: @"%2F"];
+ return docOrRevID;
+#else
CFStringRef escaped = CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)docOrRevID,
NULL, (CFStringRef)@"&/",
kCFStringEncodingUTF8);
return [NSMakeCollectable(escaped) autorelease];
+#endif
}
NSString* TDEscapeURLParam( NSString* param ) {
+#ifdef GNUSTEP
+ param = [param stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
+ param = [param stringByReplacingOccurrencesOfString: @"&" withString: @"%26"];
+ return param;
+#else
CFStringRef escaped = CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)param,
NULL, (CFStringRef)@"&",
kCFStringEncodingUTF8);
return [NSMakeCollectable(escaped) autorelease];
+#endif
}
@@ -82,6 +114,31 @@ BOOL TDIsOfflineError( NSError* error ) {
if ($equal(domain, NSURLErrorDomain))
return code == NSURLErrorDNSLookupFailed
|| code == NSURLErrorNotConnectedToInternet
- || code == NSURLErrorInternationalRoamingOff;
+#ifndef GNUSTEP
+ || code == NSURLErrorInternationalRoamingOff
+#endif
+ ;
return NO;
}
+
+
+NSURL* TDURLWithoutQuery( NSURL* url ) {
+#if GNUSTEP
+ CAssert(NO, @"UNIMPLEMENTED"); //TEMP
+#else
+ // Strip anything after the URL's path (i.e. the query string)
+ CFURLRef cfURL = (CFURLRef)url;
+ CFRange range = CFURLGetByteRangeForComponent(cfURL, kCFURLComponentResourceSpecifier, NULL);
+ if (range.length == 0) {
+ return url;
+ } else {
+ CFIndex size = CFURLGetBytes(cfURL, NULL, 0);
+ if (size > 8000)
+ return url; // give up
+ UInt8 bytes[size];
+ CFURLGetBytes(cfURL, bytes, size);
+ cfURL = CFURLCreateWithBytes(NULL, bytes, range.location - 1, kCFStringEncodingUTF8, NULL);
+ return [NSMakeCollectable(cfURL) autorelease];
+ }
+#endif
+}
View
2 Source/TDMultipartDownloader.m
@@ -71,7 +71,7 @@ - (void) clearConnection {
- (BOOL) parseJSONBuffer {
- id document = [NSJSONSerialization JSONObjectWithData: _jsonBuffer options: 0 error: nil];
+ id document = [TDJSON JSONObjectWithData: _jsonBuffer options: 0 error: NULL];
setObj(&_jsonBuffer, nil);
if (![document isKindOfClass: [NSDictionary class]]) {
Warn(@"%@: received unparseable JSON data '%@'",
View
2 Source/TDMultipartWriter.m
@@ -85,7 +85,7 @@ - (void) addData: (NSData*)data {
- (BOOL) addFile: (NSString*)path {
- NSDictionary* info = [[NSFileManager defaultManager] attributesOfItemAtPath: path error: nil];
+ NSDictionary* info = [[NSFileManager defaultManager] attributesOfItemAtPath: path error: NULL];
if (!info)
return NO;
if (![super addFile: path])
View
2 Source/TDPuller.h
@@ -7,7 +7,7 @@
//
#import "TDReplicator.h"
-#import "TDRevision.h"
+#import <TouchDB/TDRevision.h>
@class TDChangeTracker, TDSequenceMap;
View
4 Source/TDPuller.m
@@ -16,7 +16,7 @@
#import "TDPuller.h"
#import "TDDatabase+Insertion.h"
#import "TDDatabase+Replication.h"
-#import "TDRevision.h"
+#import <TouchDB/TDRevision.h>
#import "TDChangeTracker.h"
#import "TDBatcher.h"
#import "TDMultipartDownloader.h"
@@ -328,6 +328,6 @@ - (void) dealloc {
static NSString* joinQuotedEscaped(NSArray* strings) {
if (strings.count == 0)
return @"[]";
- NSData* json = [NSJSONSerialization dataWithJSONObject: strings options: 0 error: NULL];
+ NSData* json = [TDJSON dataWithJSONObject: strings options: 0 error: NULL];
return TDEscapeURLParam([json my_UTF8ToString]);
}
View
2 Source/TDPusher.h
@@ -7,7 +7,7 @@
//
#import "TDPuller.h"
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
/** Replicator that pushes to a remote CouchDB. */
View
10 Source/TDPusher.m
@@ -14,9 +14,9 @@
// and limitations under the License.
#import "TDPusher.h"
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
#import "TDDatabase+Insertion.h"
-#import "TDRevision.h"
+#import <TouchDB/TDRevision.h>
#import "TDMultipartUploader.h"
#import "TDInternal.h"
#import "TDMisc.h"
@@ -42,7 +42,7 @@ - (BOOL) isPush {
- (TDFilterBlock) filter {
- return _filterName ? [_db filterNamed: _filterName] : nil;
+ return _filterName ? [_db filterNamed: _filterName] : NULL;
}
@@ -195,7 +195,7 @@ - (void) processInbox: (TDRevisionList*)changes {
}
lastInboxSequence = rev.sequence;
Assert([properties objectForKey: @"_id"]);
- return [properties autorelease];
+ return (id)[properties autorelease];
}];
// Post the revisions to the destination. "new_edits":false means that the server should
@@ -287,7 +287,7 @@ static int findCommonAncestor(TDRevision* rev, NSArray* possibleRevIDs) {
if (!ancestorID)
return 0;
int generation;
- if (![TDRevision parseRevID: ancestorID intoGeneration: &generation andSuffix: nil])
+ if (![TDRevision parseRevID: ancestorID intoGeneration: &generation andSuffix: NULL])
generation = 0;
return generation;
}
View
7 Source/TDReachability.h
@@ -7,7 +7,14 @@
//
#import <Foundation/Foundation.h>
+
+#ifdef GNUSTEP
+typedef uint32_t SCNetworkReachabilityFlags;
+typedef void* SCNetworkReachabilityRef;
+typedef void* CFRunLoopRef;
+#else
#import <SystemConfiguration/SCNetworkReachability.h>
+#endif
typedef void (^TDReachabilityOnChangeBlock)(void);
View
53 Source/TDRemoteRequest.h
@@ -0,0 +1,53 @@
+//
+// TDRemoteRequest.h
+// TouchDB
+//
+// Created by Jens Alfke on 12/15/11.
+// Copyright (c) 2011 Couchbase, Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+/** The signature of the completion block called by a TDRemoteRequest.
+ @param result On success, a 'result' object; by default this is the TDRemoteRequest iself, but subclasses may return something else. On failure, this will likely be nil.
+ @param error The error, if any, else nil. */
+typedef void (^TDRemoteRequestCompletionBlock)(id result, NSError* error);
+
+
+/** Asynchronous HTTP request; a fairly simple wrapper around NSURLConnection that calls a completion block when ready. */
+@interface TDRemoteRequest : NSObject <NSURLConnectionDelegate
+#if TARGET_OS_IPHONE
+ , NSURLConnectionDataDelegate
+#endif
+ >
+{
+ @protected
+ NSMutableURLRequest* _request;
+ TDRemoteRequestCompletionBlock _onCompletion;
+ NSURLConnection* _connection;
+ UInt8 _retryCount;
+}
+
+/** Creates and starts a request; when finished, the onCompletion block will be called. */
+- (id) initWithMethod: (NSString*)method URL: (NSURL*)url body: (id)body
+ onCompletion: (TDRemoteRequestCompletionBlock)onCompletion;
+
+// protected:
+- (void) setupRequest: (NSMutableURLRequest*)request withBody: (id)body;
+- (void) start;
+- (void) clearConnection;
+- (void) cancelWithStatus: (int)status;
+- (void) respondWithResult: (id)result error: (NSError*)error;
+
+@end
+
+
+/** A request that parses its response body as JSON.
+ The parsed object will be returned as the first parameter of the completion block. */
+@interface TDRemoteJSONRequest : TDRemoteRequest
+{
+ @private
+ NSMutableData* _jsonBuffer;
+}
+@end
View
175 Source/TDRemoteRequest.m
@@ -0,0 +1,175 @@
+//
+// TDRemoteRequest.m
+// TouchDB
+//
+// Created by Jens Alfke on 12/15/11.
+// Copyright (c) 2011 Couchbase, Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+// except in compliance with the License. You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+// either express or implied. See the License for the specific language governing permissions
+// and limitations under the License.
+
+#import "TDRemoteRequest.h"
+#import "TDMisc.h"
+#import "TDMultipartReader.h"
+#import "TDBlobStore.h"
+
+
+// Max number of retry attempts for a transient failure
+#define kMaxRetries 2
+
+
+@implementation TDRemoteRequest
+
+
+- (id) initWithMethod: (NSString*)method URL: (NSURL*)url body: (id)body
+ onCompletion: (TDRemoteRequestCompletionBlock)onCompletion
+{
+ self = [super init];
+ if (self) {
+ LogTo(RemoteRequest, @"%@: Starting...", self);
+ _onCompletion = [onCompletion copy];
+ _request = [[NSMutableURLRequest alloc] initWithURL: url];
+ _request.HTTPMethod = method;
+ _request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
+ [self setupRequest: _request withBody: body];
+ [self start];
+ }
+ return self;
+}
+
+
+- (void) setupRequest: (NSMutableURLRequest*)request withBody: (id)body {
+}
+
+
+- (void) start {
+ Assert(!_connection);
+ _connection = [[NSURLConnection connectionWithRequest: _request delegate: self] retain];
+ [_connection start];
+}
+
+
+- (void) clearConnection {
+ [_request release];
+ _request = nil;
+ [_connection autorelease];
+ _connection = nil;
+}
+
+
+- (void)dealloc {
+ [self clearConnection];
+ [_onCompletion release];
+ [super dealloc];
+}
+
+
+- (NSString*) description {
+ return $sprintf(@"%@[%@ %@]", [self class], _request.HTTPMethod, _request.URL);
+}
+
+
+- (void) respondWithResult: (id)result error: (NSError*)error {
+ Assert(result || error);
+ LogTo(RemoteRequest, @"%@: Calling completion block...", self);
+ _onCompletion(result, error);
+}
+
+
+- (void) cancelWithStatus: (int)status {
+ [_connection cancel];
+
+ if (status >= 500 && status != 501 && status <= 504 && _retryCount < kMaxRetries) {
+ // Retry on Internal Server Error, Bad Gateway, Service Unavailable or Gateway Timeout:
+ NSTimeInterval delay = 1<<_retryCount;
+ ++_retryCount;
+ LogTo(RemoteRequest, @"%@: Will retry in %g sec", self, delay);
+ [_connection autorelease];
+ _connection = nil;
+ [self performSelector: @selector(start) withObject: nil afterDelay: delay];
+ return;
+ }
+
+ [self connection: _connection didFailWithError: TDHTTPError(status, _request.URL)];
+}
+
+
+#pragma mark - NSURLCONNECTION DELEGATE:
+
+
+- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
+ int status = (int) ((NSHTTPURLResponse*)response).statusCode;
+ LogTo(RemoteRequest, @"%@: Got response, status %d", self, status);
+ if (status >= 300)
+ [self cancelWithStatus: status];
+}
+
+- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
+ LogTo(RemoteRequest, @"%@: Got %lu bytes", self, (unsigned long)data.length);
+}
+
+- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
+ Log(@"%@: Got error %@", self, error);
+ [self clearConnection];
+ [self respondWithResult: nil error: error];
+}
+
+- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
+ [self clearConnection];
+ [self respondWithResult: self error: nil];
+}
+
+- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
+ willCacheResponse:(NSCachedURLResponse *)cachedResponse
+{
+ return nil;
+}
+
+@end
+
+
+
+
+@implementation TDRemoteJSONRequest
+
+- (void) setupRequest: (NSMutableURLRequest*)request withBody: (id)body {
+ [request setValue: @"application/json" forHTTPHeaderField: @"Accept"];
+ if (body) {
+ request.HTTPBody = [TDJSON dataWithJSONObject: body options: 0 error: NULL];
+ [request addValue: @"application/json" forHTTPHeaderField: @"Content-Type"];
+ }
+}
+
+- (void) clearConnection {
+ [_jsonBuffer release];
+ _jsonBuffer = nil;
+ [super clearConnection];
+}
+
+- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
+ [super connection: connection didReceiveData: data];
+ if (!_jsonBuffer)
+ _jsonBuffer = [[NSMutableData alloc] initWithCapacity: MAX(data.length, 8192u)];
+ [_jsonBuffer appendData: data];
+}
+
+- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
+ id result = nil;
+ if (_jsonBuffer)
+ result = [TDJSON JSONObjectWithData: _jsonBuffer options: 0 error: NULL];
+ NSError* error = nil;
+ if (!result) {
+ Warn(@"%@: %@ %@ returned unparseable data '%@'",
+ self, _request.HTTPMethod, _request.URL, [_jsonBuffer my_UTF8ToString]);
+ error = TDHTTPError(502, _request.URL);
+ }
+ [self clearConnection];
+ [self respondWithResult: result error: error];
+}
+
+@end
View
2 Source/TDReplicator.m
@@ -16,7 +16,7 @@
#import "TDReplicator.h"
#import "TDPusher.h"
#import "TDPuller.h"
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
#import "TDRemoteRequest.h"
#import "TDBatcher.h"
#import "TDReachability.h"
View
2 Source/TDReplicatorManager.h
@@ -6,7 +6,7 @@
// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
//
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
@class TDServer;
View
2 Source/TDReplicatorManager.m
@@ -10,7 +10,7 @@
#import "TDReplicatorManager.h"
#import "TDServer.h"
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
#import "TDDatabase+Insertion.h"
#import "TDDatabase+Replication.h"
#import "TDPusher.h"
View
4 Source/TDRevision.m
@@ -13,7 +13,7 @@
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
-#import "TDRevision.h"
+#import <TouchDB/TDRevision.h>
#import "TDBody.h"
#import "TDMisc.h"
@@ -95,7 +95,7 @@ + (BOOL) parseRevID: (NSString*)revID intoGeneration: (int*)outNum andSuffix:(NS
{
NSScanner* scanner = [[NSScanner alloc] initWithString: revID];
scanner.charactersToBeSkipped = nil;
- BOOL parsed = [scanner scanInt: outNum] && [scanner scanString: @"-" intoString: nil];
+ BOOL parsed = [scanner scanInt: outNum] && [scanner scanString: @"-" intoString: NULL];
if (outSuffix)
*outSuffix = [revID substringFromIndex: scanner.scanLocation];
[scanner release];
View
41 Source/TDRouter+Handlers.m
@@ -14,14 +14,14 @@
// and limitations under the License.
#import "TDRouter.h"
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
#import "TDDatabase+Attachments.h"
#import "TDDatabase+Insertion.h"
#import "TDDatabase+LocalDocs.h"
#import "TDDatabase+Replication.h"
#import "TDView.h"
#import "TDBody.h"
-#import "TDRevision.h"
+#import <TouchDB/TDRevision.h>
#import "TDServer.h"
#import "TDReplicator.h"
#import "TDReplicatorManager.h"
@@ -49,20 +49,7 @@ @implementation TDRouter (Handlers)
- (void) setResponseLocation: (NSURL*)url {
// Strip anything after the URL's path (i.e. the query string)
- CFURLRef cfURL = (CFURLRef)url;
- CFRange range = CFURLGetByteRangeForComponent(cfURL, kCFURLComponentResourceSpecifier, NULL);
- if (range.length == 0) {
- [_response setValue: url.absoluteString ofHeader: @"Location"];
- } else {
- CFIndex size = CFURLGetBytes(cfURL, NULL, 0);
- if (size > 8000)
- return;
- UInt8 bytes[size];
- CFURLGetBytes(cfURL, bytes, size);
- cfURL = CFURLCreateWithBytes(NULL, bytes, range.location - 1, kCFStringEncodingUTF8, NULL);
- [_response setValue: (id)CFURLGetString(cfURL) ofHeader: @"Location"];
- CFRelease(cfURL);
- }
+ [_response setValue: TDURLWithoutQuery(url).absoluteString ofHeader: @"Location"];
}
@@ -352,13 +339,13 @@ - (TDStatus) do_POST_revs_diff: (TDDatabase*)db {
}
// Add the possible ancestors for each missing revision:
- [diffs enumerateKeysAndObjectsUsingBlock: ^(NSString* docID, NSMutableDictionary* docInfo,
- BOOL *stop) {
+ for (NSString* docID in diffs) {
+ NSMutableDictionary* docInfo = [diffs objectForKey: docID];
int maxGen = 0;
NSString* maxRevID = nil;
for (NSString* revID in [docInfo objectForKey: @"missing"]) {
int gen;
- if ([TDRevision parseRevID: revID intoGeneration: &gen andSuffix: nil] && gen > maxGen) {
+ if ([TDRevision parseRevID: revID intoGeneration: &gen andSuffix: NULL] && gen > maxGen) {
maxGen = gen;
maxRevID = revID;
}
@@ -368,7 +355,7 @@ - (TDStatus) do_POST_revs_diff: (TDDatabase*)db {
[rev release];
if (ancestors)
[docInfo setObject: ancestors forKey: @"possible_ancestors"];
- }];
+ }
_response.bodyObject = diffs;
return 200;
@@ -431,8 +418,8 @@ - (NSDictionary*) responseBodyForChangesWithConflicts: (NSArray*)changes since:
- (void) sendContinuousChange: (TDRevision*)rev {
NSDictionary* changeDict = [self changeDictForRev: rev];
- NSMutableData* json = [[NSJSONSerialization dataWithJSONObject: changeDict
- options: 0 error: nil] mutableCopy];
+ NSMutableData* json = [[TDJSON dataWithJSONObject: changeDict
+ options: 0 error: NULL] mutableCopy];
[json appendBytes: "\n" length: 1];
_onDataAvailable(json);
[json release];
@@ -449,8 +436,8 @@ - (void) dbChanged: (NSNotification*)n {
Log(@"TDRouter: Sending longpoll response");
[self sendResponse];
NSDictionary* body = [self responseBodyForChanges: $array(rev) since: 0];
- _onDataAvailable([NSJSONSerialization dataWithJSONObject: body
- options: 0 error: nil]);
+ _onDataAvailable([TDJSON dataWithJSONObject: body
+ options: 0 error: NULL]);
_onFinished();
[self stop];
} else {
@@ -539,9 +526,9 @@ - (NSString*) setResponseEtag: (TDRevision*)rev {
if (!queryStr)
return nil;
NSData* queryData = [queryStr dataUsingEncoding: NSUTF8StringEncoding];
- return $castIfArrayOf(NSString, [NSJSONSerialization JSONObjectWithData: queryData
+ return $castIfArrayOf(NSString, [TDJSON JSONObjectWithData: queryData
options: 0
- error: nil]);
+ error: NULL]);
}
@@ -593,7 +580,7 @@ - (TDStatus) do_GET: (TDDatabase*)db docID: (NSString*)docID {
} else {
// ?open_revs=[...] returns an array of revisions of the document:
- NSArray* openRevs = $castIf(NSArray, [self jsonQuery: @"open_revs" error: nil]);
+ NSArray* openRevs = $castIf(NSArray, [self jsonQuery: @"open_revs" error: NULL]);
if (!openRevs)
return 400;
result = [NSMutableArray arrayWithCapacity: openRevs.count];
View
2 Source/TDRouter.h
@@ -6,7 +6,7 @@
// Copyright (c) 2011 Couchbase, Inc. All rights reserved.
//
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
@class TDServer, TDResponse, TDBody;
View
22 Source/TDRouter.m
@@ -21,7 +21,9 @@
#import "TDMultipartWriter.h"
#import "TDReplicatorManager.h"
#import "TDInternal.h"
+#ifndef GNUSTEP
#import <objc/message.h>
+#endif
extern double TouchDBVersionNumber; // Defined in generated TouchDB_vers.c
@@ -112,9 +114,9 @@ - (id) jsonQuery: (NSString*)param error: (NSError**)outError {
NSString* value = [self query: param];
if (!value)
return nil;
- id result = [NSJSONSerialization
+ id result = [TDJSON
JSONObjectWithData: [value dataUsingEncoding: NSUTF8StringEncoding]
- options: NSJSONReadingAllowFragments error: outError];
+ options: TDJSONReadingAllowFragments error: outError];
if (!result)
Warn(@"TDRouter: invalid JSON in query param ?%@=%@", param, value);
return result;
@@ -129,8 +131,8 @@ - (BOOL) cacheWithEtag: (NSString*)etag {
- (NSDictionary*) bodyAsDictionary {
- return $castIf(NSDictionary, [NSJSONSerialization JSONObjectWithData: _request.HTTPBody
- options: 0 error: nil]);
+ return $castIf(NSDictionary, [TDJSON JSONObjectWithData: _request.HTTPBody
+ options: 0 error: NULL]);
}
@@ -200,7 +202,11 @@ - (TDStatus) openDB {
static NSArray* splitPath( NSURL* url ) {
// Unfortunately can't just call url.path because that converts %2F to a '/'.
+#if GNUSTEP
+ NSString* pathString = [url.path copy]; //TEMP
+#else
NSString* pathString = NSMakeCollectable(CFURLCopyPath((CFURLRef)url));
+#endif
NSMutableArray* path = $marray();
for (NSString* comp in [pathString componentsSeparatedByString: @"/"]) {
if ([comp length] > 0) {
@@ -320,7 +326,13 @@ - (TDStatus) route {
@"TDRouter(Handlers) is missing -- app may be linked without -ObjC linker flag.");
sel = @selector(do_UNKNOWN);
}
+
+#ifdef GNUSTEP
+ IMP fn = objc_msg_lookup(self, sel);
+ return (TDStatus) fn(self, sel, _db, docID, attachmentName);
+#else
return (TDStatus) objc_msgSend(self, sel, _db, docID, attachmentName);
+#endif
}
@@ -432,7 +444,7 @@ - (void) setMultipartBody: (NSArray*)parts type: (NSString*)type {
boundary: nil];
for (id part in parts) {
if (![part isKindOfClass: [NSData class]]) {
- part = [NSJSONSerialization dataWithJSONObject: part options: 0 error: nil];
+ part = [TDJSON dataWithJSONObject: part options: 0 error: NULL];
[mp setNextPartsHeaders: $dict({@"Content-Type", @"application/json"})];
}
[mp addData: part];
View
6 Source/TDRouter_Tests.m
@@ -14,7 +14,7 @@
// and limitations under the License.
#import "TDRouter.h"
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
#import "TDBody.h"
#import "TDServer.h"
#import "TDBase64.h"
@@ -41,7 +41,7 @@
[request setValue: [headers objectForKey: header] forHTTPHeaderField: header];
if (bodyObj) {
NSError* error = nil;
- request.HTTPBody = [NSJSONSerialization dataWithJSONObject: bodyObj options:0 error:&error];
+ request.HTTPBody = [TDJSON dataWithJSONObject: bodyObj options:0 error:&error];
CAssertNil(error);
}
TDRouter* router = [[[TDRouter alloc] initWithServer: server request: request] autorelease];
@@ -67,7 +67,7 @@ static id ParseJSONResponse(TDResponse* response) {
jsonStr = [[[NSString alloc] initWithData: json encoding: NSUTF8StringEncoding] autorelease];
CAssert(jsonStr);
NSError* error;
- result = [NSJSONSerialization JSONObjectWithData: json options: 0 error: &error];
+ result = [TDJSON JSONObjectWithData: json options: 0 error: &error];
CAssert(result, @"Couldn't parse JSON response: %@", error);
}
return result;
View
2 Source/TDSequenceMap.h
@@ -6,7 +6,7 @@
// Copyright (c) 2012 Couchbase, Inc. All rights reserved.
//
-#import "TDRevision.h"
+#import <TouchDB/TDRevision.h>
/** A data structure representing a type of array that allows object values to be added to the end, and removed in arbitrary order; it's used by the replicator to keep track of which revisions have been transferred and what sequences to checkpoint. */
View
6 Source/TDServer.m
@@ -14,7 +14,7 @@
// and limitations under the License.
#import "TDServer.h"
-#import "TDDatabase.h"
+#import <TouchDB/TDDatabase.h>
#import "TDReplicatorManager.h"
#import "TDInternal.h"
@@ -38,7 +38,7 @@ + (void) initialize {
#if DEBUG
+ (TDServer*) createEmptyAtPath: (NSString*)path {
- [[NSFileManager defaultManager] removeItemAtPath: path error: nil];
+ [[NSFileManager defaultManager] removeItemAtPath: path error: NULL];
NSError* error;
TDServer* server = [[self alloc] initWithDirectory: path error: &error];
Assert(server, @"Failed to create server at %@: %@", path, error);
@@ -139,7 +139,7 @@ - (BOOL) deleteDatabaseNamed: (NSString*)name {
- (NSArray*) allDatabaseNames {
- NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: _dir error: nil];
+ NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: _dir error: NULL];
files = [files pathsMatchingExtensions: $array(kDBExtension)];
return [files my_map: ^(id filename) {
return [[filename stringByDeletingPathExtension]
View
10 Source/TDView.m
@@ -134,13 +134,13 @@ - (void) databaseClosing {
static NSString* toJSONString( id object ) {
if (!object)
return nil;
- // NSJSONSerialization won't write fragments, so if I get one wrap it in an array first:
+ // TDJSON won't write fragments, so if I get one wrap it in an array first:
BOOL wrapped = NO;
if (![object isKindOfClass: [NSDictionary class]] && ![object isKindOfClass: [NSArray class]]) {
wrapped = YES;
object = $array(object);
}
- NSData* json = [NSJSONSerialization dataWithJSONObject: object options: 0 error: nil];
+ NSData* json = [TDJSON dataWithJSONObject: object options: 0 error: NULL];
if (wrapped)
json = [json subdataWithRange: NSMakeRange(1, json.length - 2)];
return [json my_UTF8ToString];
@@ -150,9 +150,9 @@ - (void) databaseClosing {