Skip to content
Browse files

Initial commit

  • Loading branch information...
0 parents commit 05d6d7e4071e5c7182f4a66aa0d03d6109b7cee1 @magiconair committed Aug 17, 2012
111 FMDatabase.h
@@ -0,0 +1,111 @@
+#import <Foundation/Foundation.h>
+#import "sqlite3.h"
+#import "FMResultSet.h"
+
+@interface FMDatabase : NSObject
+{
+ sqlite3* db;
+ NSString* databasePath;
+ BOOL logsErrors;
+ BOOL crashOnErrors;
+ BOOL inUse;
+ BOOL inTransaction;
+ BOOL traceExecution;
+ BOOL checkedOut;
+ int busyRetryTimeout;
+ BOOL shouldCacheStatements;
+ NSMutableDictionary *cachedStatements;
+}
+
+
++ (id)databaseWithPath:(NSString*)inPath;
+- (id)initWithPath:(NSString*)inPath;
+
+- (BOOL) open;
+- (void) close;
+- (BOOL) goodConnection;
+- (void) clearCachedStatements;
+
+// encryption methods. You need to have purchased the sqlite encryption extensions for these to work.
+- (BOOL) setKey:(NSString*)key;
+- (BOOL) rekey:(NSString*)key;
+
+
+- (NSString *) databasePath;
+
+- (NSString*) lastErrorMessage;
+
+- (int) lastErrorCode;
+- (BOOL) hadError;
+- (sqlite_int64) lastInsertRowId;
+
+- (sqlite3*) sqliteHandle;
+
+- (BOOL) executeUpdate:(NSString *)sql arguments:(va_list)args;
+- (BOOL) executeUpdate:(NSString*)sql, ...;
+
+- (id) executeQuery:(NSString *)sql arguments:(va_list)args;
+- (id) executeQuery:(NSString*)sql, ...;
+
+- (BOOL) rollback;
+- (BOOL) commit;
+- (BOOL) beginTransaction;
+- (BOOL) beginDeferredTransaction;
+
+- (BOOL)logsErrors;
+- (void)setLogsErrors:(BOOL)flag;
+
+- (BOOL)crashOnErrors;
+- (void)setCrashOnErrors:(BOOL)flag;
+
+- (BOOL)inUse;
+- (void)setInUse:(BOOL)value;
+
+- (BOOL)inTransaction;
+- (void)setInTransaction:(BOOL)flag;
+
+- (BOOL)traceExecution;
+- (void)setTraceExecution:(BOOL)flag;
+
+- (BOOL)checkedOut;
+- (void)setCheckedOut:(BOOL)flag;
+
+- (int)busyRetryTimeout;
+- (void)setBusyRetryTimeout:(int)newBusyRetryTimeout;
+
+- (BOOL)shouldCacheStatements;
+- (void)setShouldCacheStatements:(BOOL)value;
+
+- (NSMutableDictionary *)cachedStatements;
+- (void)setCachedStatements:(NSMutableDictionary *)value;
+
+
++ (NSString*) sqliteLibVersion;
+
+
+- (int)changes;
+
+@end
+
+@interface FMStatement : NSObject {
+ sqlite3_stmt *statement;
+ NSString *query;
+ long useCount;
+}
+
+
+- (void) close;
+- (void) reset;
+
+- (sqlite3_stmt *)statement;
+- (void)setStatement:(sqlite3_stmt *)value;
+
+- (NSString *)query;
+- (void)setQuery:(NSString *)value;
+
+- (long)useCount;
+- (void)setUseCount:(long)value;
+
+
+@end
+
711 FMDatabase.m
@@ -0,0 +1,711 @@
+#import "FMDatabase.h"
+#import "unistd.h"
+
+@implementation FMDatabase
+
++ (id)databaseWithPath:(NSString*)aPath {
+ return [[[FMDatabase alloc] initWithPath:aPath] autorelease];
+}
+
+- (id)initWithPath:(NSString*)aPath {
+ self = [super init];
+
+ if (self) {
+ databasePath = [aPath copy];
+ db = 0x00;
+ logsErrors = 0x00;
+ crashOnErrors = 0x00;
+ busyRetryTimeout = 0x00;
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ [self close];
+
+ [cachedStatements release];
+ [databasePath release];
+
+ [super dealloc];
+}
+
++ (NSString*) sqliteLibVersion {
+ return [NSString stringWithFormat:@"%s", sqlite3_libversion()];
+}
+
+- (NSString *) databasePath {
+ return databasePath;
+}
+
+- (sqlite3*) sqliteHandle {
+ return db;
+}
+
+- (BOOL) open {
+ int err = sqlite3_open( [databasePath fileSystemRepresentation], &db );
+ if(err != SQLITE_OK) {
+ NSLog(@"error opening!: %d", err);
+ return NO;
+ }
+
+ return YES;
+}
+
+- (void) close {
+
+ [self clearCachedStatements];
+
+ if (!db) {
+ return;
+ }
+
+ int rc;
+ BOOL retry;
+ int numberOfRetries = 0;
+ do {
+ retry = NO;
+ rc = sqlite3_close(db);
+ if (SQLITE_BUSY == rc) {
+ retry = YES;
+ usleep(20);
+ if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
+ NSLog(@"%s:%d", __FUNCTION__, __LINE__);
+ NSLog(@"Database busy, unable to close");
+ return;
+ }
+ }
+ else if (SQLITE_OK != rc) {
+ NSLog(@"error closing!: %d", rc);
+ }
+ }
+ while (retry);
+
+ db = nil;
+}
+
+- (void) clearCachedStatements {
+
+ NSEnumerator *e = [cachedStatements objectEnumerator];
+ FMStatement *cachedStmt;
+
+ while ((cachedStmt = [e nextObject])) {
+ [cachedStmt close];
+ }
+
+ [cachedStatements removeAllObjects];
+}
+
+- (FMStatement*) cachedStatementForQuery:(NSString*)query {
+ return [cachedStatements objectForKey:query];
+}
+
+- (void) setCachedStatement:(FMStatement*)statement forQuery:(NSString*)query {
+ //NSLog(@"setting query: %@", query);
+ query = [query copy]; // in case we got handed in a mutable string...
+ [statement setQuery:query];
+ [cachedStatements setObject:statement forKey:query];
+ [query release];
+}
+
+
+- (BOOL) rekey:(NSString*)key {
+#ifdef SQLITE_HAS_CODEC
+ if (!key) {
+ return NO;
+ }
+
+ int rc = sqlite3_rekey(db, [key UTF8String], strlen([key UTF8String]));
+
+ if (rc != SQLITE_OK) {
+ NSLog(@"error on rekey: %d", rc);
+ NSLog(@"%@", [self lastErrorMessage]);
+ }
+
+ return (rc == SQLITE_OK);
+#else
+ return NO;
+#endif
+}
+
+- (BOOL) setKey:(NSString*)key {
+#ifdef SQLITE_HAS_CODEC
+ if (!key) {
+ return NO;
+ }
+
+ int rc = sqlite3_key(db, [key UTF8String], strlen([key UTF8String]));
+
+ return (rc == SQLITE_OK);
+#else
+ return NO;
+#endif
+}
+
+- (BOOL) goodConnection {
+
+ if (!db) {
+ return NO;
+ }
+
+ FMResultSet *rs = [self executeQuery:@"select name from sqlite_master where type='table'"];
+
+ if (rs) {
+ [rs close];
+ return YES;
+ }
+
+ return NO;
+}
+
+- (void) compainAboutInUse {
+ NSLog(@"The FMDatabase %@ is currently in use.", self);
+
+ if (crashOnErrors) {
+ NSAssert1(false, @"The FMDatabase %@ is currently in use.", self);
+ }
+}
+
+- (NSString*) lastErrorMessage {
+ return [NSString stringWithUTF8String:sqlite3_errmsg(db)];
+}
+
+- (BOOL) hadError {
+ int lastErrCode = [self lastErrorCode];
+
+ return (lastErrCode > SQLITE_OK && lastErrCode < SQLITE_ROW);
+}
+
+- (int) lastErrorCode {
+ return sqlite3_errcode(db);
+}
+
+- (sqlite_int64) lastInsertRowId {
+
+ if (inUse) {
+ [self compainAboutInUse];
+ return NO;
+ }
+ [self setInUse:YES];
+
+ sqlite_int64 ret = sqlite3_last_insert_rowid(db);
+
+ [self setInUse:NO];
+
+ return ret;
+}
+
+- (void) bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt; {
+
+ if ((!obj) || ((NSNull *)obj == [NSNull null])) {
+ sqlite3_bind_null(pStmt, idx);
+ }
+
+ // FIXME - someday check the return codes on these binds.
+ else if ([obj isKindOfClass:[NSData class]]) {
+ sqlite3_bind_blob(pStmt, idx, [obj bytes], [obj length], SQLITE_STATIC);
+ }
+ else if ([obj isKindOfClass:[NSDate class]]) {
+ sqlite3_bind_double(pStmt, idx, [obj timeIntervalSince1970]);
+ }
+ else if ([obj isKindOfClass:[NSNumber class]]) {
+
+ if (strcmp([obj objCType], @encode(BOOL)) == 0) {
+ sqlite3_bind_int(pStmt, idx, ([obj boolValue] ? 1 : 0));
+ }
+ else if (strcmp([obj objCType], @encode(int)) == 0) {
+ sqlite3_bind_int64(pStmt, idx, [obj longValue]);
+ }
+ else if (strcmp([obj objCType], @encode(long)) == 0) {
+ sqlite3_bind_int64(pStmt, idx, [obj longValue]);
+ }
+ else if (strcmp([obj objCType], @encode(float)) == 0) {
+ sqlite3_bind_double(pStmt, idx, [obj floatValue]);
+ }
+ else if (strcmp([obj objCType], @encode(double)) == 0) {
+ sqlite3_bind_double(pStmt, idx, [obj doubleValue]);
+ }
+ else {
+ sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
+ }
+ }
+ else {
+ sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
+ }
+}
+
+- (id) executeQuery:(NSString *)sql arguments:(va_list)args {
+
+ if (inUse) {
+ [self compainAboutInUse];
+ return nil;
+ }
+
+ [self setInUse:YES];
+
+ FMResultSet *rs = nil;
+
+ int rc = 0x00;;
+ sqlite3_stmt *pStmt = 0x00;;
+ FMStatement *statement = 0x00;
+
+ if (traceExecution && sql) {
+ NSLog(@"%@ executeQuery: %@", self, sql);
+ }
+
+ if (shouldCacheStatements) {
+ statement = [self cachedStatementForQuery:sql];
+ pStmt = statement ? [statement statement] : 0x00;
+ }
+
+ int numberOfRetries = 0;
+ BOOL retry = NO;
+
+ if (!pStmt) {
+ do {
+ retry = NO;
+ rc = sqlite3_prepare_v2(db, [sql UTF8String], -1, &pStmt, 0);
+
+ if (SQLITE_BUSY == rc) {
+ retry = YES;
+ usleep(20);
+
+ if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
+ NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
+ NSLog(@"Database busy");
+ sqlite3_finalize(pStmt);
+ [self setInUse:NO];
+ return nil;
+ }
+ }
+ else if (SQLITE_OK != rc) {
+
+
+ if (logsErrors) {
+ NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+ NSLog(@"DB Query: %@", sql);
+ if (crashOnErrors) {
+#if defined(__BIG_ENDIAN__) && !TARGET_IPHONE_SIMULATOR
+ asm{ trap };
+#endif
+ NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+ }
+ }
+
+ sqlite3_finalize(pStmt);
+
+ [self setInUse:NO];
+ return nil;
+ }
+ }
+ while (retry);
+ }
+
+ id obj;
+ int idx = 0;
+ int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)
+
+ while (idx < queryCount) {
+ obj = va_arg(args, id);
+
+ if (traceExecution) {
+ NSLog(@"obj: %@", obj);
+ }
+
+ idx++;
+
+ [self bindObject:obj toColumn:idx inStatement:pStmt];
+ }
+
+ if (idx != queryCount) {
+ NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)");
+ sqlite3_finalize(pStmt);
+ [self setInUse:NO];
+ return nil;
+ }
+
+ [statement retain]; // to balance the release below
+
+ if (!statement) {
+ statement = [[FMStatement alloc] init];
+ [statement setStatement:pStmt];
+
+ if (shouldCacheStatements) {
+ [self setCachedStatement:statement forQuery:sql];
+ }
+ }
+
+ // the statement gets close in rs's dealloc or [rs close];
+ rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
+ [rs setQuery:sql];
+
+ statement.useCount = statement.useCount + 1;
+
+ [statement release];
+
+ [self setInUse:NO];
+
+ return rs;
+}
+
+- (id) executeQuery:(NSString*)sql, ... {
+ va_list args;
+ va_start(args, sql);
+
+ id result = [self executeQuery:sql arguments:args];
+
+ va_end(args);
+ return result;
+}
+
+
+- (BOOL) executeUpdate:(NSString*)sql arguments:(va_list)args {
+
+ if (inUse) {
+ [self compainAboutInUse];
+ return NO;
+ }
+
+ [self setInUse:YES];
+
+ int rc = 0x00;
+ sqlite3_stmt *pStmt = 0x00;
+ FMStatement *cachedStmt = 0x00;
+
+ if (traceExecution && sql) {
+ NSLog(@"%@ executeUpdate: %@", self, sql);
+ }
+
+ if (shouldCacheStatements) {
+ cachedStmt = [self cachedStatementForQuery:sql];
+ pStmt = cachedStmt ? [cachedStmt statement] : 0x00;
+ }
+
+ int numberOfRetries = 0;
+ BOOL retry = NO;
+
+ if (!pStmt) {
+
+ do {
+ retry = NO;
+ rc = sqlite3_prepare_v2(db, [sql UTF8String], -1, &pStmt, 0);
+ if (SQLITE_BUSY == rc) {
+ retry = YES;
+ usleep(20);
+
+ if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
+ NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
+ NSLog(@"Database busy");
+ sqlite3_finalize(pStmt);
+ [self setInUse:NO];
+ return NO;
+ }
+ }
+ else if (SQLITE_OK != rc) {
+
+
+ if (logsErrors) {
+ NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+ NSLog(@"DB Query: %@", sql);
+ if (crashOnErrors) {
+#if defined(__BIG_ENDIAN__) && !TARGET_IPHONE_SIMULATOR
+ asm{ trap };
+#endif
+ NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+ }
+ }
+
+ sqlite3_finalize(pStmt);
+ [self setInUse:NO];
+
+ return NO;
+ }
+ }
+ while (retry);
+ }
+
+
+ id obj;
+ int idx = 0;
+ int queryCount = sqlite3_bind_parameter_count(pStmt);
+
+ while (idx < queryCount) {
+
+ obj = va_arg(args, id);
+
+ if (traceExecution) {
+ NSLog(@"obj: %@", obj);
+ }
+
+ idx++;
+
+ [self bindObject:obj toColumn:idx inStatement:pStmt];
+ }
+
+ if (idx != queryCount) {
+ NSLog(@"Error: the bind count is not correct for the # of variables (%@) (executeUpdate)", sql);
+ sqlite3_finalize(pStmt);
+ [self setInUse:NO];
+ return NO;
+ }
+
+ /* Call sqlite3_step() to run the virtual machine. Since the SQL being
+ ** executed is not a SELECT statement, we assume no data will be returned.
+ */
+ numberOfRetries = 0;
+ do {
+ rc = sqlite3_step(pStmt);
+ retry = NO;
+
+ if (SQLITE_BUSY == rc) {
+ // this will happen if the db is locked, like if we are doing an update or insert.
+ // in that case, retry the step... and maybe wait just 10 milliseconds.
+ retry = YES;
+ usleep(20);
+
+ if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
+ NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
+ NSLog(@"Database busy");
+ retry = NO;
+ }
+ }
+ else if (SQLITE_DONE == rc || SQLITE_ROW == rc) {
+ // all is well, let's return.
+ }
+ else if (SQLITE_ERROR == rc) {
+ NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_ERROR", rc, sqlite3_errmsg(db));
+ NSLog(@"DB Query: %@", sql);
+ }
+ else if (SQLITE_MISUSE == rc) {
+ // uh oh.
+ NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_MISUSE", rc, sqlite3_errmsg(db));
+ NSLog(@"DB Query: %@", sql);
+ }
+ else {
+ // wtf?
+ NSLog(@"Unknown error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(db));
+ NSLog(@"DB Query: %@", sql);
+ }
+
+ } while (retry);
+
+ assert( rc!=SQLITE_ROW );
+
+
+ if (shouldCacheStatements && !cachedStmt) {
+ cachedStmt = [[FMStatement alloc] init];
+
+ [cachedStmt setStatement:pStmt];
+
+ [self setCachedStatement:cachedStmt forQuery:sql];
+
+ [cachedStmt release];
+ }
+
+ if (cachedStmt) {
+ cachedStmt.useCount = cachedStmt.useCount + 1;
+ rc = sqlite3_reset(pStmt);
+ }
+ else {
+ /* Finalize the virtual machine. This releases all memory and other
+ ** resources allocated by the sqlite3_prepare() call above.
+ */
+ rc = sqlite3_finalize(pStmt);
+ }
+
+ [self setInUse:NO];
+
+ return (rc == SQLITE_OK);
+}
+
+- (BOOL) executeUpdate:(NSString*)sql, ... {
+ va_list args;
+ va_start(args, sql);
+
+ BOOL result = [self executeUpdate:sql arguments:args];
+
+ va_end(args);
+ return result;
+}
+
+
+
+- (BOOL) rollback {
+ BOOL b = [self executeUpdate:@"ROLLBACK TRANSACTION;"];
+ if (b) {
+ inTransaction = NO;
+ }
+ return b;
+}
+
+- (BOOL) commit {
+ BOOL b = [self executeUpdate:@"COMMIT TRANSACTION;"];
+ if (b) {
+ inTransaction = NO;
+ }
+ return b;
+}
+
+- (BOOL) beginDeferredTransaction {
+ BOOL b = [self executeUpdate:@"BEGIN DEFERRED TRANSACTION;"];
+ if (b) {
+ inTransaction = YES;
+ }
+ return b;
+}
+
+- (BOOL) beginTransaction {
+ BOOL b = [self executeUpdate:@"BEGIN EXCLUSIVE TRANSACTION;"];
+ if (b) {
+ inTransaction = YES;
+ }
+ return b;
+}
+
+- (BOOL)logsErrors {
+ return logsErrors;
+}
+- (void)setLogsErrors:(BOOL)flag {
+ logsErrors = flag;
+}
+
+- (BOOL)crashOnErrors {
+ return crashOnErrors;
+}
+- (void)setCrashOnErrors:(BOOL)flag {
+ crashOnErrors = flag;
+}
+
+- (BOOL)inUse {
+ return inUse || inTransaction;
+}
+
+- (void) setInUse:(BOOL)b {
+ inUse = b;
+}
+
+- (BOOL)inTransaction {
+ return inTransaction;
+}
+- (void)setInTransaction:(BOOL)flag {
+ inTransaction = flag;
+}
+
+- (BOOL)traceExecution {
+ return traceExecution;
+}
+- (void)setTraceExecution:(BOOL)flag {
+ traceExecution = flag;
+}
+
+- (BOOL)checkedOut {
+ return checkedOut;
+}
+- (void)setCheckedOut:(BOOL)flag {
+ checkedOut = flag;
+}
+
+
+- (int)busyRetryTimeout {
+ return busyRetryTimeout;
+}
+- (void)setBusyRetryTimeout:(int)newBusyRetryTimeout {
+ busyRetryTimeout = newBusyRetryTimeout;
+}
+
+
+- (BOOL)shouldCacheStatements {
+ return shouldCacheStatements;
+}
+
+- (void)setShouldCacheStatements:(BOOL)value {
+
+ shouldCacheStatements = value;
+
+ if (shouldCacheStatements && !cachedStatements) {
+ [self setCachedStatements:[NSMutableDictionary dictionary]];
+ }
+
+ if (!shouldCacheStatements) {
+ [self setCachedStatements:nil];
+ }
+}
+
+- (NSMutableDictionary *) cachedStatements {
+ return cachedStatements;
+}
+
+- (void)setCachedStatements:(NSMutableDictionary *)value {
+ if (cachedStatements != value) {
+ [cachedStatements release];
+ cachedStatements = [value retain];
+ }
+}
+
+
+- (int)changes {
+ return(sqlite3_changes(db));
+}
+
+@end
+
+
+
+@implementation FMStatement
+
+- (void)dealloc {
+ [self close];
+ [query release];
+ [super dealloc];
+}
+
+
+- (void) close {
+ if (statement) {
+ sqlite3_finalize(statement);
+ statement = 0x00;
+ }
+}
+
+- (void) reset {
+ if (statement) {
+ sqlite3_reset(statement);
+ }
+}
+
+- (sqlite3_stmt *) statement {
+ return statement;
+}
+
+- (void)setStatement:(sqlite3_stmt *)value {
+ statement = value;
+}
+
+- (NSString *) query {
+ return query;
+}
+
+- (void)setQuery:(NSString *)value {
+ if (query != value) {
+ [query release];
+ query = [value retain];
+ }
+}
+
+- (long)useCount {
+ return useCount;
+}
+
+- (void)setUseCount:(long)value {
+ if (useCount != value) {
+ useCount = value;
+ }
+}
+
+- (NSString*) description {
+ return [NSString stringWithFormat:@"%@ %d hit(s) for query %@", [super description], useCount, query];
+}
+
+
+@end
+
27 FMDatabaseAdditions.h
@@ -0,0 +1,27 @@
+//
+// FMDatabaseAdditions.h
+// fmkit
+//
+// Created by August Mueller on 10/30/05.
+// Copyright 2005 Flying Meat Inc.. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+@interface FMDatabase (FMDatabaseAdditions)
+
+
+- (int) intForQuery:(NSString*)objs, ...;
+- (long) longForQuery:(NSString*)objs, ...;
+- (BOOL) boolForQuery:(NSString*)objs, ...;
+- (double) doubleForQuery:(NSString*)objs, ...;
+- (NSString*) stringForQuery:(NSString*)objs, ...;
+- (NSData*) dataForQuery:(NSString*)objs, ...;
+
+// Notice that there's no dataNoCopyForQuery:.
+// That would be a bad idea, because we close out the result set, and then what
+// happens to the data that we just didn't copy? Who knows, not I.
+
+- (id)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments;
+- (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
+
+@end
99 FMDatabaseAdditions.m
@@ -0,0 +1,99 @@
+//
+// FMDatabaseAdditions.m
+// fmkit
+//
+// Created by August Mueller on 10/30/05.
+// Copyright 2005 Flying Meat Inc.. All rights reserved.
+//
+
+#import "FMDatabase.h"
+#import "FMDatabaseAdditions.h"
+
+@implementation FMDatabase (FMDatabaseAdditions)
+
+#define RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(type, sel) \
+va_list args; \
+va_start(args, query); \
+FMResultSet *resultSet = [self executeQuery:query arguments:args]; \
+va_end(args); \
+if (![resultSet next]) { return (type)0; } \
+type ret = [resultSet sel:0]; \
+[resultSet close]; \
+[resultSet setParentDB:nil]; \
+return ret;
+
+
+- (NSString*)stringForQuery:(NSString*)query, ...; {
+ RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSString *, stringForColumnIndex);
+}
+
+- (int)intForQuery:(NSString*)query, ...; {
+ RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(int, intForColumnIndex);
+}
+
+- (long)longForQuery:(NSString*)query, ...; {
+ RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(long, longForColumnIndex);
+}
+
+- (BOOL)boolForQuery:(NSString*)query, ...; {
+ RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(BOOL, boolForColumnIndex);
+}
+
+- (double)doubleForQuery:(NSString*)query, ...; {
+ RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(double, doubleForColumnIndex);
+}
+
+- (NSData*)dataForQuery:(NSString*)query, ...; {
+ RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSData *, dataForColumnIndex);
+}
+
+
+//From Phong Long:
+//sometimes you want to be able generate queries programatically
+//with an arbitrary number of arguments, as well as be able to bind
+//them properly. this method allows you to pass in a query string with any
+//number of ?, then you pass in an appropriate number of objects in an NSArray
+//to executeQuery:arguments:
+
+//this technique is being implemented as described by Matt Gallagher at
+//http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html
+
+- (id)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments {
+
+ id returnObject;
+
+ //also need make sure that everything in arguments is an Obj-C object
+ //or else argList will be the wrong size
+ NSUInteger argumentsCount = [arguments count];
+ char *argList = (char *)malloc(sizeof(id *) * argumentsCount);
+ [arguments getObjects:(id *)argList];
+
+ returnObject = [self executeQuery:sql arguments:argList];
+
+ free(argList);
+
+ return returnObject;
+}
+
+- (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments {
+
+ BOOL returnBool;
+
+ //also need make sure that everything in arguments is an Obj-C object
+ //or else argList will be the wrong size
+ NSUInteger argumentsCount = [arguments count];
+ char *argList = (char *)malloc(sizeof(id *) * argumentsCount);
+ [arguments getObjects:(id *)argList];
+
+ returnBool = [self executeUpdate:sql arguments:argList];
+
+ free(argList);
+
+ return returnBool;
+
+}
+
+
+
+
+@end
72 FMResultSet.h
@@ -0,0 +1,72 @@
+#import <Foundation/Foundation.h>
+#import "sqlite3.h"
+
+@class FMDatabase;
+@class FMStatement;
+
+@interface FMResultSet : NSObject {
+ FMDatabase *parentDB;
+ FMStatement *statement;
+
+ NSString *query;
+ NSMutableDictionary *columnNameToIndexMap;
+ BOOL columnNamesSetup;
+}
+
+
++ (id) resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB;
+
+- (void) close;
+
+- (NSString *)query;
+- (void)setQuery:(NSString *)value;
+
+- (FMStatement *)statement;
+- (void)setStatement:(FMStatement *)value;
+
+- (void)setParentDB:(FMDatabase *)newDb;
+
+- (BOOL) next;
+- (BOOL) hasAnotherRow;
+
+- (int) columnIndexForName:(NSString*)columnName;
+- (NSString*) columnNameForIndex:(int)index;
+
+- (int) intForColumn:(NSString*)columnName;
+- (int) intForColumnIndex:(int)columnIdx;
+
+- (long) longForColumn:(NSString*)columnName;
+- (long) longForColumnIndex:(int)columnIdx;
+
+- (long long int) longLongIntForColumn:(NSString*)columnName;
+- (long long int) longLongIntForColumnIndex:(int)columnIdx;
+
+- (BOOL) boolForColumn:(NSString*)columnName;
+- (BOOL) boolForColumnIndex:(int)columnIdx;
+
+- (double) doubleForColumn:(NSString*)columnName;
+- (double) doubleForColumnIndex:(int)columnIdx;
+
+- (NSString*) stringForColumn:(NSString*)columnName;
+- (NSString*) stringForColumnIndex:(int)columnIdx;
+
+- (NSDate*) dateForColumn:(NSString*)columnName;
+- (NSDate*) dateForColumnIndex:(int)columnIdx;
+
+- (NSData*) dataForColumn:(NSString*)columnName;
+- (NSData*) dataForColumnIndex:(int)columnIdx;
+
+/*
+If you are going to use this data after you iterate over the next row, or after you close the
+result set, make sure to make a copy of the data first (or just use dataForColumn:/dataForColumnIndex:)
+If you don't, you're going to be in a world of hurt when you try and use the data.
+*/
+- (NSData*) dataNoCopyForColumn:(NSString*)columnName;
+- (NSData*) dataNoCopyForColumnIndex:(int)columnIdx;
+
+- (BOOL) columnIndexIsNull:(int)columnIdx;
+- (BOOL) columnIsNull:(NSString*)columnName;
+
+- (void) kvcMagic:(id)object;
+
+@end
319 FMResultSet.m
@@ -0,0 +1,319 @@
+#import "FMResultSet.h"
+#import "FMDatabase.h"
+#import "unistd.h"
+
+@interface FMResultSet (Private)
+- (NSMutableDictionary *)columnNameToIndexMap;
+- (void)setColumnNameToIndexMap:(NSMutableDictionary *)value;
+@end
+
+@implementation FMResultSet
+
++ (id) resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB {
+
+ FMResultSet *rs = [[FMResultSet alloc] init];
+
+ [rs setStatement:statement];
+ [rs setParentDB:aDB];
+
+ return [rs autorelease];
+}
+
+- (void)dealloc {
+ [self close];
+
+ [query release];
+ query = nil;
+
+ [columnNameToIndexMap release];
+ columnNameToIndexMap = nil;
+
+ [super dealloc];
+}
+
+- (void) close {
+
+ [statement reset];
+ [statement release];
+ statement = nil;
+
+ // we don't need this anymore... (i think)
+ //[parentDB setInUse:NO];
+ parentDB = nil;
+}
+
+- (void) setupColumnNames {
+
+ if (!columnNameToIndexMap) {
+ [self setColumnNameToIndexMap:[NSMutableDictionary dictionary]];
+ }
+
+ int columnCount = sqlite3_column_count(statement.statement);
+
+ int columnIdx = 0;
+ for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
+ [columnNameToIndexMap setObject:[NSNumber numberWithInt:columnIdx]
+ forKey:[[NSString stringWithUTF8String:sqlite3_column_name(statement.statement, columnIdx)] lowercaseString]];
+ }
+ columnNamesSetup = YES;
+}
+
+- (void) kvcMagic:(id)object {
+
+ int columnCount = sqlite3_column_count(statement.statement);
+
+ int columnIdx = 0;
+ for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
+
+ const char *c = (const char *)sqlite3_column_text(statement.statement, columnIdx);
+
+ // check for a null row
+ if (c) {
+ NSString *s = [NSString stringWithUTF8String:c];
+
+ [object setValue:s forKey:[NSString stringWithUTF8String:sqlite3_column_name(statement.statement, columnIdx)]];
+ }
+ }
+}
+
+- (BOOL) next {
+
+ int rc;
+ BOOL retry;
+ int numberOfRetries = 0;
+ do {
+ retry = NO;
+
+ rc = sqlite3_step(statement.statement);
+
+ if (SQLITE_BUSY == rc) {
+ // this will happen if the db is locked, like if we are doing an update or insert.
+ // in that case, retry the step... and maybe wait just 10 milliseconds.
+ retry = YES;
+ usleep(20);
+
+ if ([parentDB busyRetryTimeout] && (numberOfRetries++ > [parentDB busyRetryTimeout])) {
+
+ NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [parentDB databasePath]);
+ NSLog(@"Database busy");
+ break;
+ }
+ }
+ else if (SQLITE_DONE == rc || SQLITE_ROW == rc) {
+ // all is well, let's return.
+ }
+ else if (SQLITE_ERROR == rc) {
+ NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([parentDB sqliteHandle]));
+ break;
+ }
+ else if (SQLITE_MISUSE == rc) {
+ // uh oh.
+ NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([parentDB sqliteHandle]));
+ break;
+ }
+ else {
+ // wtf?
+ NSLog(@"Unknown error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([parentDB sqliteHandle]));
+ break;
+ }
+
+ } while (retry);
+
+
+ if (rc != SQLITE_ROW) {
+ [self close];
+ }
+
+ return (rc == SQLITE_ROW);
+}
+
+- (BOOL) hasAnotherRow {
+ return sqlite3_errcode([parentDB sqliteHandle]) == SQLITE_ROW;
+}
+
+- (int) columnIndexForName:(NSString*)columnName {
+
+ if (!columnNamesSetup) {
+ [self setupColumnNames];
+ }
+
+ columnName = [columnName lowercaseString];
+
+ NSNumber *n = [columnNameToIndexMap objectForKey:columnName];
+
+ if (n) {
+ return [n intValue];
+ }
+
+ NSLog(@"Warning: I could not find the column named '%@'.", columnName);
+
+ return -1;
+}
+
+
+
+- (int) intForColumn:(NSString*)columnName {
+ return [self intForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (int) intForColumnIndex:(int)columnIdx {
+ return sqlite3_column_int(statement.statement, columnIdx);
+}
+
+- (long) longForColumn:(NSString*)columnName {
+ return [self longForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (long) longForColumnIndex:(int)columnIdx {
+ return (long)sqlite3_column_int64(statement.statement, columnIdx);
+}
+
+- (long long int) longLongIntForColumn:(NSString*)columnName {
+ return [self longLongIntForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (long long int) longLongIntForColumnIndex:(int)columnIdx {
+ return sqlite3_column_int64(statement.statement, columnIdx);
+}
+
+- (BOOL) boolForColumn:(NSString*)columnName {
+ return [self boolForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (BOOL) boolForColumnIndex:(int)columnIdx {
+ return ([self intForColumnIndex:columnIdx] != 0);
+}
+
+- (double) doubleForColumn:(NSString*)columnName {
+ return [self doubleForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (double) doubleForColumnIndex:(int)columnIdx {
+ return sqlite3_column_double(statement.statement, columnIdx);
+}
+
+- (NSString*) stringForColumnIndex:(int)columnIdx {
+
+ if (sqlite3_column_type(statement.statement, columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+ return nil;
+ }
+
+ const char *c = (const char *)sqlite3_column_text(statement.statement, columnIdx);
+
+ if (!c) {
+ // null row.
+ return nil;
+ }
+
+ return [NSString stringWithUTF8String:c];
+}
+
+- (NSString*) stringForColumn:(NSString*)columnName {
+ return [self stringForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (NSDate*) dateForColumn:(NSString*)columnName {
+ return [self dateForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (NSDate*) dateForColumnIndex:(int)columnIdx {
+
+ if (sqlite3_column_type(statement.statement, columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+ return nil;
+ }
+
+ return [NSDate dateWithTimeIntervalSince1970:[self doubleForColumnIndex:columnIdx]];
+}
+
+
+- (NSData*) dataForColumn:(NSString*)columnName {
+ return [self dataForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (NSData*) dataForColumnIndex:(int)columnIdx {
+
+ if (sqlite3_column_type(statement.statement, columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+ return nil;
+ }
+
+ int dataSize = sqlite3_column_bytes(statement.statement, columnIdx);
+
+ NSMutableData *data = [NSMutableData dataWithLength:dataSize];
+
+ memcpy([data mutableBytes], sqlite3_column_blob(statement.statement, columnIdx), dataSize);
+
+ return data;
+}
+
+
+- (NSData*) dataNoCopyForColumn:(NSString*)columnName {
+ return [self dataNoCopyForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (NSData*) dataNoCopyForColumnIndex:(int)columnIdx {
+
+ if (sqlite3_column_type(statement.statement, columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+ return nil;
+ }
+
+ int dataSize = sqlite3_column_bytes(statement.statement, columnIdx);
+
+ NSData *data = [NSData dataWithBytesNoCopy:(void *)sqlite3_column_blob(statement.statement, columnIdx) length:dataSize freeWhenDone:NO];
+
+ return data;
+}
+
+
+- (BOOL) columnIndexIsNull:(int)columnIdx {
+ return sqlite3_column_type(statement.statement, columnIdx) == SQLITE_NULL;
+}
+
+- (BOOL) columnIsNull:(NSString*)columnName {
+ return [self columnIndexIsNull:[self columnIndexForName:columnName]];
+}
+
+
+// returns autoreleased NSString containing the name of the column in the result set
+- (NSString*) columnNameForIndex:(int)index {
+ return [NSString stringWithUTF8String: sqlite3_column_name(statement.statement, index)];
+}
+
+- (void)setParentDB:(FMDatabase *)newDb {
+ parentDB = newDb;
+}
+
+
+- (NSString *)query {
+ return query;
+}
+
+- (void)setQuery:(NSString *)value {
+ [value retain];
+ [query release];
+ query = value;
+}
+
+- (NSMutableDictionary *)columnNameToIndexMap {
+ return columnNameToIndexMap;
+}
+
+- (void)setColumnNameToIndexMap:(NSMutableDictionary *)value {
+ [value retain];
+ [columnNameToIndexMap release];
+ columnNameToIndexMap = value;
+}
+
+- (FMStatement *) statement {
+ return statement;
+}
+
+- (void)setStatement:(FMStatement *)value {
+ if (statement != value) {
+ [statement release];
+ statement = [value retain];
+ }
+}
+
+
+
+@end
453 map2sqlite.m
@@ -0,0 +1,453 @@
+//
+// map2sqlite.m
+//
+// Copyright (c) 2009, Frank Schroeder, SharpMind GbR
+// Copyright (c) 2008-2009, Route-Me Contributors (RMTileKey function)
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+//
+// map2sqlite - import utility for the RMDBTileSource
+//
+// map2sqlite creates an sqlite database with tiles that is
+// compatible with the RMDBTileSource of the route-me project.
+//
+// It supports importing of OpenStreetmap and ArcGis compatible
+// directory structures of tiles.
+//
+// OpenStreetMap tile structure
+//
+// <zoom>/<col>/<row>.png
+//
+// zoom, col, row: decimal values
+//
+// ArcGIS tile structure
+//
+// L<zoom>/R<row>/C<col>.png
+//
+// zoom: decimal value
+// col, row: hexadecimal values
+//
+// The following tables are created and populated.
+//
+// table "preferences" - contains the map meta data as name/value pairs
+//
+// SQL: create table preferences(name text primary key, value text)
+//
+// The preferences table must at least contain the following
+// values for the tile source to function properly.
+//
+// * map.minZoom - minimum supported zoom level
+// * map.maxZoom - maximum supported zoom level
+// * map.tileSideLength - tile size in pixels
+//
+// Optionally it can contain the following values
+//
+// Coverage area:
+// * map.coverage.topLeft.latitude
+// * map.coverage.topLeft.longitude
+// * map.coverage.bottomRight.latitude
+// * map.coverage.bottomRight.longitude
+// * map.coverage.center.latitude
+// * map.coverage.center.longitude
+//
+// Attribution:
+// * map.shortName
+// * map.shortAttribution
+// * map.longDescription
+// * map.longAttribution
+//
+// table "tiles" - contains the tile images
+//
+// SQL: create table tiles(tilekey integer primary key, image blob)
+//
+// The tile images are stored in the "image" column as a blob.
+// The primary key of the table is the "tilekey" which is computed
+// with the RMTileKey function (found in RMTile.h)
+//
+// uint64_t RMTileKey(RMTile tile);
+//
+
+#import <Foundation/Foundation.h>
+#import "FMDatabase.h"
+#import "FMDatabaseAdditions.h"
+
+#define FMDBQuickCheck(SomeBool) { if (!(SomeBool)) { NSLog(@"Failure on line %d", __LINE__); return 123; } }
+#define FMDBErrorCheck(db) { if ([db hadError]) { NSLog(@"DB error %d on line %d: %@", [db lastErrorCode], __LINE__, [db lastErrorMessage]); } }
+#define NSStringFromPoint(p) ([NSString stringWithFormat:@"{x=%1.6f,y=%1.6f}", (p).x, (p).y])
+#define NSStringFromSize(s) ([NSString stringWithFormat:@"{w=%1.1f,h=%1.1f}", (s).width, (s).height])
+#define NSStringFromRect(r) ([NSString stringWithFormat:@"{x=%1.1f,y=%1.1f,w=%1.1f,h=%1.1f}", (r).origin.x, (r).origin.y, (r).size.width, (r).size.height])
+
+// version of this program
+#define kVersion @"1.0"
+
+// mandatory preference keys
+#define kMinZoomKey @"map.minZoom"
+#define kMaxZoomKey @"map.maxZoom"
+#define kTileSideLengthKey @"map.tileSideLength"
+
+// optional preference keys for the coverage area
+#define kCoverageTopLeftLatitudeKey @"map.coverage.topLeft.latitude"
+#define kCoverageTopLeftLongitudeKey @"map.coverage.topLeft.longitude"
+#define kCoverageBottomRightLatitudeKey @"map.coverage.bottomRight.latitude"
+#define kCoverageBottomRightLongitudeKey @"map.coverage.bottomRight.longitude"
+#define kCoverageCenterLatitudeKey @"map.coverage.center.latitude"
+#define kCoverageCenterLongitudeKey @"map.coverage.center.longitude"
+
+// optional preference keys for the attribution
+#define kShortNameKey @"map.shortName"
+#define kLongDescriptionKey @"map.longDescription"
+#define kShortAttributionKey @"map.shortAttribution"
+#define kLongAttributionKey @"map.longAttribution"
+
+
+
+/* ----------------------------------------------------------------------
+ * Helper functions
+ */
+
+/*
+ * Calculates the top left coordinate of a tile.
+ * (assumes OpenStreetmap tiles)
+ */
+CGPoint pointForTile(int row, int col, int zoom) {
+ float lon = col / pow(2.0, zoom) * 360.0 - 180;
+ float n = M_PI - 2.0 * M_PI * row / pow(2.0, zoom);
+ float lat = 180.0 / M_PI * atan(0.5 * (exp(n) - exp(-n)));
+
+ return CGPointMake(lon, lat);
+}
+
+/*
+ * Prints usage information.
+ */
+void printUsage() {
+ NSLog(@"Usage: map2sqlite -db <db file> [-mapdir <map directory>]");
+}
+
+/*
+ * Converts a hexadecimal string into an integer value.
+ */
+NSUInteger scanHexInt(NSString* s) {
+ NSUInteger value;
+
+ NSScanner* scanner = [NSScanner scannerWithString:s];
+ [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"LRC"]];
+ if ([scanner scanHexInt:&value]) {
+ return value;
+ } else {
+ NSLog(@"not a hex int %@", s);
+ return -1;
+ }
+}
+
+
+
+/*
+ * Creates a unique single key for a map tile.
+ *
+ * Copyright (c) 2008-2009, Route-Me Contributors
+ */
+uint64_t RMTileKey(int tileZoom, int tileX, int tileY)
+{
+ uint64_t zoom = (uint64_t) tileZoom & 0xFFLL; // 8bits, 256 levels
+ uint64_t x = (uint64_t) tileX & 0xFFFFFFFLL; // 28 bits
+ uint64_t y = (uint64_t) tileY & 0xFFFFFFFLL; // 28 bits
+
+ uint64_t key = (zoom << 56) | (x << 28) | (y << 0);
+
+ return key;
+}
+
+/* ----------------------------------------------------------------------
+ * Database functions
+ */
+
+/*
+ * Creates an empty sqlite database
+ */
+FMDatabase* createDB(NSString* dbFile) {
+ // delete the old db file
+ [[NSFileManager defaultManager] removeFileAtPath:dbFile handler:nil];
+
+ FMDatabase* db = [FMDatabase databaseWithPath:dbFile];
+ NSLog(@"Creating %@", dbFile);
+ if (![db open]) {
+ NSLog(@"Could not create %@.", dbFile);
+ return nil;
+ }
+
+ return db;
+}
+
+/*
+ * Executes a query with error check and returns the result set.
+ */
+FMResultSet* executeQuery(FMDatabase* db, NSString* sql, ...) {
+ va_list args;
+ va_start(args, sql);
+ FMResultSet* rs = [db executeQuery:sql arguments:args];
+ va_end(args);
+ FMDBErrorCheck(db);
+
+ return rs;
+}
+
+/*
+ * Executes an update query with error check.
+ */
+void executeUpdate(FMDatabase* db, NSString* sql, ...) {
+ va_list args;
+ va_start(args, sql);
+ [db executeUpdate:sql arguments:args];
+ va_end(args);
+ FMDBErrorCheck(db);
+}
+
+/* ----------------------------------------------------------------------
+ * Preference functions
+ */
+
+void addStringAsPref(FMDatabase* db, NSString* name, NSString* value) {
+ executeUpdate(db, @"insert into preferences (name, value) values (?,?)", name, value);
+}
+
+void addFloatAsPref(FMDatabase* db, NSString* name, float value) {
+ addStringAsPref(db, name, [NSString stringWithFormat:@"%f", value]);
+}
+
+void addIntAsPref(FMDatabase* db, NSString* name, int value) {
+ addStringAsPref(db, name, [NSString stringWithFormat:@"%d", value]);
+}
+
+void createPrefs(FMDatabase* db) {
+ executeUpdate(db, @"create table preferences(name text primary key, value text)");
+}
+
+/* ----------------------------------------------------------------------
+ * Main functions
+ */
+
+/*
+ * Creates the "tiles" table and imports a given directory structure
+ * into the table.
+ */
+void createMapDB(FMDatabase* db, NSString* mapDir) {
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+
+ // import the tiles
+ executeUpdate(db, @"create table tiles(tilekey integer primary key, zoom integer, row integer, col integer, image blob)");
+
+ // OpenStreetMap tile structure
+ //
+ // <zoom>/<col>/<row>.png
+ //
+ // zoom, col, row: decimal values
+ //
+ // ArcGIS tile structure
+ //
+ // L<zoom>/R<row>/C<col>.png
+ //
+ // zoom: decimal value
+ // col, row: hexadecimal values
+ //
+ int minZoom = INT_MAX;
+ int maxZoom = INT_MIN;
+ NSLog(@"Importing map tiles at %@", mapDir);
+ for (NSString* f in [fileManager subpathsAtPath:mapDir]) {
+ if ([[[f pathExtension] lowercaseString] isEqualToString:@"png"]) {
+ NSArray* comp = [f componentsSeparatedByString:@"/"];
+ NSUInteger zoom, row, col;
+
+ // openstreetmap or ArcGis tiles?
+ if ([[comp objectAtIndex:0] characterAtIndex:0] == 'L') {
+ zoom = [[[comp objectAtIndex:0] substringFromIndex:1] intValue];
+ row = scanHexInt([comp objectAtIndex:1]);
+ col = scanHexInt([[comp objectAtIndex:2] stringByDeletingPathExtension]);
+ } else {
+ zoom = [[comp objectAtIndex:0] intValue];
+ col = [[comp objectAtIndex:1] intValue];
+ row = [[[comp objectAtIndex:2] stringByDeletingPathExtension] intValue];
+ }
+
+ // update min and max zoom
+ minZoom = fmin(minZoom, zoom);
+ maxZoom = fmax(maxZoom, zoom);
+
+ NSData* image = [[NSData alloc] initWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", mapDir, f]];
+ if (image) {
+ executeUpdate(db, @"insert into tiles (tilekey, zoom, row, col, image) values (?, ?, ?, ?, ?)",
+ [NSNumber numberWithLongLong:RMTileKey(zoom, col, row)],
+ [NSNumber numberWithInt:zoom],
+ [NSNumber numberWithInt:row],
+ [NSNumber numberWithInt:col],
+ image);
+ [image release];
+ } else {
+ NSLog(@"Could not read %@", f);
+ }
+ }
+ }
+
+ // add mandatory map meta data to the preferences table
+ addIntAsPref(db, kMinZoomKey, minZoom);
+ addIntAsPref(db, kMaxZoomKey, maxZoom);
+ addIntAsPref(db, kTileSideLengthKey, 256);
+
+ // add coverage area and center
+ // print map dimensions per zoom level
+ FMResultSet* rs = executeQuery(db, @"select min(row) min_row, min(col) min_col, max(row) max_row, max(col) max_col from tiles where zoom = ?", [NSNumber numberWithInt:maxZoom]);
+ while ([rs next]) {
+ int minRow = [rs intForColumn:@"min_row"];
+ int minCol = [rs intForColumn:@"min_col"];
+ int maxRow = [rs intForColumn:@"max_row"];
+ int maxCol = [rs intForColumn:@"max_col"];
+ CGPoint topLeft = pointForTile(minRow, minCol, maxZoom);
+ CGPoint bottomRight = pointForTile(maxRow + 1, maxCol + 1, maxZoom);
+ addFloatAsPref(db, kCoverageTopLeftLatitudeKey, topLeft.y);
+ addFloatAsPref(db, kCoverageTopLeftLongitudeKey, topLeft.x);
+ addFloatAsPref(db, kCoverageBottomRightLatitudeKey, bottomRight.y);
+ addFloatAsPref(db, kCoverageBottomRightLongitudeKey, bottomRight.x);
+
+ // this prolly works only for the northern hemisphere
+ // I'm just too lazy to do it right for now
+ addFloatAsPref(db, kCoverageCenterLatitudeKey, topLeft.y + (bottomRight.y - topLeft.y)/2);
+ addFloatAsPref(db, kCoverageCenterLongitudeKey, topLeft.x + (bottomRight.x - topLeft.x)/2);
+ }
+ [rs close];
+
+}
+
+/*
+ * Displays some statistics about the tiles in the imported database.
+ */
+void showMapDBStats(FMDatabase* db, NSString* dbFile, NSString* mapDir) {
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+
+ // print some map statistics
+ // print some map statistics
+ FMResultSet* rs = executeQuery(db, @"select count(*) count, min(zoom) min_zoom, max(zoom) max_zoom from tiles");
+ if ([rs next]) {
+ int count = [rs intForColumn:@"count"];
+ int minZoom = [rs intForColumn:@"min_zoom"];
+ int maxZoom = [rs intForColumn:@"max_zoom"];
+ unsigned long long fileSize = [[[fileManager fileAttributesAtPath:dbFile traverseLink:YES] objectForKey:NSFileSize] unsignedLongLongValue];
+
+ NSLog(@"\n");
+ NSLog(@"Map statistics");
+ NSLog(@"--------------");
+ NSLog(@"map db: %@", dbFile);
+ NSLog(@"file size: %qi bytes", fileSize);
+ NSLog(@"tile directory: %@", mapDir);
+ NSLog(@"number of tiles: %d", count);
+ NSLog(@"zoom levels: %d - %d", minZoom, maxZoom);
+ }
+ [rs close];
+
+ // print map dimensions per zoom level
+ rs = executeQuery(db, @"select zoom, count(zoom) count, min(row) min_row, min(col) min_col, max(row) max_row, max(col) max_col from tiles group by zoom");
+ while ([rs next]) {
+ int zoom = [rs intForColumn:@"zoom"];
+ int count = [rs intForColumn:@"count"];
+ int minRow = [rs intForColumn:@"min_row"];
+ int minCol = [rs intForColumn:@"min_col"];
+ int maxRow = [rs intForColumn:@"max_row"];
+ int maxCol = [rs intForColumn:@"max_col"];
+ CGPoint topLeft = pointForTile(minRow, minCol, zoom);
+ CGPoint bottomRight = pointForTile(maxRow + 1, maxCol + 1, zoom);
+
+ NSLog(@"zoom level %2d: %6d tiles, (%6d,%6d)x(%6d,%6d), %@x%@",
+ zoom,
+ count,
+ minRow,
+ minCol,
+ maxRow,
+ maxCol,
+ NSStringFromPoint(topLeft),
+ NSStringFromPoint(bottomRight));
+ }
+ [rs close];
+}
+
+/*
+ * main method
+ */
+int main (int argc, const char * argv[]) {
+ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+
+ // print the version
+ NSLog(@"map2sqlite %@\n", kVersion);
+
+ // get the command line args
+ NSUserDefaults *args = [NSUserDefaults standardUserDefaults];
+
+ // command line args
+ NSString* dbFile = [args stringForKey:@"db"];
+ NSString* mapDir = [args stringForKey:@"mapdir"];
+
+ // check command line args
+ if (dbFile == nil) {
+ printUsage();
+ [pool release];
+ return 1;
+ }
+
+ // check that the map directory exists
+ if (mapDir != nil) {
+ BOOL isDir;
+ if (![fileManager fileExistsAtPath:mapDir isDirectory:&isDir] && isDir) {
+ NSLog(@"Map directory does not exist: %@", mapDir);
+ [pool release];
+ return 1;
+ }
+ }
+
+ // delete the old db
+ [fileManager removeFileAtPath:dbFile handler:nil];
+
+ // create the db
+ FMDatabase* db = createDB(dbFile);
+ if (db == nil) {
+ NSLog(@"Error creating database %@", dbFile);
+ [pool release];
+ return 1;
+ }
+
+ // cache the statements as we're using them a lot
+ db.shouldCacheStatements = YES;
+
+ // create the preferences table
+ createPrefs(db);
+
+ // import the map
+ if (mapDir != nil) {
+ createMapDB(db, mapDir);
+ showMapDBStats(db, dbFile, mapDir);
+ }
+
+ // cleanup
+ [db close];
+ [pool release];
+ return 0;
+}
BIN map2sqlite.xcodeproj/TemplateIcon.icns
Binary file not shown.
248 map2sqlite.xcodeproj/project.pbxproj
@@ -0,0 +1,248 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 45;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 0105B4220FDB9CF8005F4C4D /* libsqlite3.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 0105B4210FDB9CF8005F4C4D /* libsqlite3.0.dylib */; };
+ 0105B42C0FDB9D15005F4C4D /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 0105B4270FDB9D15005F4C4D /* FMDatabase.m */; };
+ 0105B42D0FDB9D15005F4C4D /* FMDatabaseAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 0105B4290FDB9D15005F4C4D /* FMDatabaseAdditions.m */; };
+ 0105B42E0FDB9D15005F4C4D /* FMResultSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 0105B42B0FDB9D15005F4C4D /* FMResultSet.m */; };
+ 8DD76F9A0486AA7600D96B5E /* map2sqlite.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* map2sqlite.m */; settings = {ATTRIBUTES = (); }; };
+ 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 8DD76F9E0486AA7600D96B5E /* CopyFiles */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 8;
+ dstPath = /usr/share/man/man1/;
+ dstSubfolderSpec = 0;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 1;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 0105B4210FDB9CF8005F4C4D /* libsqlite3.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.0.dylib; path = usr/lib/libsqlite3.0.dylib; sourceTree = SDKROOT; };
+ 0105B4260FDB9D15005F4C4D /* FMDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabase.h; sourceTree = "<group>"; };
+ 0105B4270FDB9D15005F4C4D /* FMDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabase.m; sourceTree = "<group>"; };
+ 0105B4280FDB9D15005F4C4D /* FMDatabaseAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabaseAdditions.h; sourceTree = "<group>"; };
+ 0105B4290FDB9D15005F4C4D /* FMDatabaseAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabaseAdditions.m; sourceTree = "<group>"; };
+ 0105B42A0FDB9D15005F4C4D /* FMResultSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMResultSet.h; sourceTree = "<group>"; };
+ 0105B42B0FDB9D15005F4C4D /* FMResultSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMResultSet.m; sourceTree = "<group>"; };
+ 08FB7796FE84155DC02AAC07 /* map2sqlite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = map2sqlite.m; sourceTree = "<group>"; };
+ 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
+ 32A70AAB03705E1F00C91783 /* map2sqlite_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = map2sqlite_Prefix.pch; sourceTree = "<group>"; };
+ 8DD76FA10486AA7600D96B5E /* map2sqlite */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = map2sqlite; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 8DD76F9B0486AA7600D96B5E /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */,
+ 0105B4220FDB9CF8005F4C4D /* libsqlite3.0.dylib in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 0105B4250FDB9D01005F4C4D /* fmdb */ = {
+ isa = PBXGroup;
+ children = (
+ 0105B4260FDB9D15005F4C4D /* FMDatabase.h */,
+ 0105B4270FDB9D15005F4C4D /* FMDatabase.m */,
+ 0105B4280FDB9D15005F4C4D /* FMDatabaseAdditions.h */,
+ 0105B4290FDB9D15005F4C4D /* FMDatabaseAdditions.m */,
+ 0105B42A0FDB9D15005F4C4D /* FMResultSet.h */,
+ 0105B42B0FDB9D15005F4C4D /* FMResultSet.m */,
+ );
+ name = fmdb;
+ sourceTree = "<group>";
+ };
+ 08FB7794FE84155DC02AAC07 /* map2sqlite */ = {
+ isa = PBXGroup;
+ children = (
+ 0105B4250FDB9D01005F4C4D /* fmdb */,
+ 08FB7795FE84155DC02AAC07 /* Source */,
+ C6859EA2029092E104C91782 /* Documentation */,
+ 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */,
+ 1AB674ADFE9D54B511CA2CBB /* Products */,
+ );
+ name = map2sqlite;
+ sourceTree = "<group>";
+ };
+ 08FB7795FE84155DC02AAC07 /* Source */ = {
+ isa = PBXGroup;
+ children = (
+ 32A70AAB03705E1F00C91783 /* map2sqlite_Prefix.pch */,
+ 08FB7796FE84155DC02AAC07 /* map2sqlite.m */,
+ );
+ name = Source;
+ sourceTree = "<group>";
+ };
+ 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = {
+ isa = PBXGroup;
+ children = (
+ 0105B4210FDB9CF8005F4C4D /* libsqlite3.0.dylib */,
+ 08FB779EFE84155DC02AAC07 /* Foundation.framework */,
+ );
+ name = "External Frameworks and Libraries";
+ sourceTree = "<group>";
+ };
+ 1AB674ADFE9D54B511CA2CBB /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 8DD76FA10486AA7600D96B5E /* map2sqlite */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ C6859EA2029092E104C91782 /* Documentation */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Documentation;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 8DD76F960486AA7600D96B5E /* map2sqlite */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "map2sqlite" */;
+ buildPhases = (
+ 8DD76F990486AA7600D96B5E /* Sources */,
+ 8DD76F9B0486AA7600D96B5E /* Frameworks */,
+ 8DD76F9E0486AA7600D96B5E /* CopyFiles */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = map2sqlite;
+ productInstallPath = "$(HOME)/bin";
+ productName = map2sqlite;
+ productReference = 8DD76FA10486AA7600D96B5E /* map2sqlite */;
+ productType = "com.apple.product-type.tool";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 08FB7793FE84155DC02AAC07 /* Project object */ = {
+ isa = PBXProject;
+ buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "map2sqlite" */;
+ compatibilityVersion = "Xcode 3.1";
+ hasScannedForEncodings = 1;
+ mainGroup = 08FB7794FE84155DC02AAC07 /* map2sqlite */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 8DD76F960486AA7600D96B5E /* map2sqlite */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 8DD76F990486AA7600D96B5E /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 8DD76F9A0486AA7600D96B5E /* map2sqlite.m in Sources */,
+ 0105B42C0FDB9D15005F4C4D /* FMDatabase.m in Sources */,
+ 0105B42D0FDB9D15005F4C4D /* FMDatabaseAdditions.m in Sources */,
+ 0105B42E0FDB9D15005F4C4D /* FMResultSet.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 1DEB927508733DD40010E9CD /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ COPY_PHASE_STRIP = NO;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_ENABLE_FIX_AND_CONTINUE = YES;
+ GCC_MODEL_TUNING = G5;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = map2sqlite_Prefix.pch;
+ INSTALL_PATH = /usr/local/bin;
+ PRODUCT_NAME = map2sqlite;
+ };
+ name = Debug;
+ };
+ 1DEB927608733DD40010E9CD /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ GCC_MODEL_TUNING = G5;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = map2sqlite_Prefix.pch;
+ INSTALL_PATH = /usr/local/bin;
+ PRODUCT_NAME = map2sqlite;
+ };
+ name = Release;
+ };
+ 1DEB927908733DD40010E9CD /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+ GCC_C_LANGUAGE_STANDARD = c99;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ PREBINDING = NO;
+ SDKROOT = macosx10.5;
+ };
+ name = Debug;
+ };
+ 1DEB927A08733DD40010E9CD /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+ GCC_C_LANGUAGE_STANDARD = c99;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ PREBINDING = NO;
+ SDKROOT = macosx10.5;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "map2sqlite" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1DEB927508733DD40010E9CD /* Debug */,
+ 1DEB927608733DD40010E9CD /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "map2sqlite" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1DEB927908733DD40010E9CD /* Debug */,
+ 1DEB927A08733DD40010E9CD /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 08FB7793FE84155DC02AAC07 /* Project object */;
+}
10 map2sqlite_Prefix.pch
@@ -0,0 +1,10 @@
+//
+// Prefix header for all source files of the 'map2sqlite' target in the 'map2sqlite' project.
+//
+
+#ifdef __OBJC__
+ #import <Foundation/Foundation.h>
+#endif
+
+#define START_TIMER(t) NSDate* t = [NSDate date];
+#define STOP_TIMER(t, msg) NSLog(@"%@: %0.2f seconds", (msg), -[t timeIntervalSinceNow]);

0 comments on commit 05d6d7e

Please sign in to comment.
Something went wrong with that request. Please try again.