Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

First full commit

Added a completed readme, licanse information, the ncMeta Project
classes and a sample app Xcode project.
  • Loading branch information...
commit 1babd538ec14364eaddc8aefb46a6c08aa5ec67a 1 parent fe0d7d3
@timschroedernet authored
Showing with 8,445 additions and 4 deletions.
  1. +9 −0 LICENSE.md
  2. +128 −4 README.md
  3. +25 −0 TSAppRecord.h
  4. +43 −0 TSAppRecord.m
  5. +23 −0 TSDBConnector.h
  6. +201 −0 TSDBConnector.m
  7. +18 −0 TSDBConnectorDelegate.h
  8. +25 −0 TSDBMonitorController.h
  9. +132 −0 TSDBMonitorController.m
  10. +17 −0 TSDBMonitorDelegate.h
  11. +24 −0 TSNCMetaController.h
  12. +137 −0 TSNCMetaController.m
  13. +19 −0 TSNCMetaControllerDelegate.h
  14. +21 −0 TSWindowMonitorController.h
  15. +156 −0 TSWindowMonitorController.m
  16. +18 −0 TSWindowMonitorControllerDelegate.h
  17. +444 −0 ncMeta App/ncMeta.xcodeproj/project.pbxproj
  18. +133 −0 ncMeta App/ncMeta/FMDatabase.h
  19. +1,127 −0 ncMeta App/ncMeta/FMDatabase.m
  20. +33 −0 ncMeta App/ncMeta/FMDatabaseAdditions.h
  21. +155 −0 ncMeta App/ncMeta/FMDatabaseAdditions.m
  22. +75 −0 ncMeta App/ncMeta/FMDatabasePool.h
  23. +244 −0 ncMeta App/ncMeta/FMDatabasePool.m
  24. +38 −0 ncMeta App/ncMeta/FMDatabaseQueue.h
  25. +176 −0 ncMeta App/ncMeta/FMDatabaseQueue.m
  26. +102 −0 ncMeta App/ncMeta/FMResultSet.h
  27. +423 −0 ncMeta App/ncMeta/FMResultSet.m
  28. +20 −0 ncMeta App/ncMeta/TSAppDelegate.h
  29. +194 −0 ncMeta App/ncMeta/TSAppDelegate.m
  30. +25 −0 ncMeta App/ncMeta/TSAppRecord.h
  31. +43 −0 ncMeta App/ncMeta/TSAppRecord.m
  32. +23 −0 ncMeta App/ncMeta/TSDBConnector.h
  33. +201 −0 ncMeta App/ncMeta/TSDBConnector.m
  34. +18 −0 ncMeta App/ncMeta/TSDBConnectorDelegate.h
  35. +25 −0 ncMeta App/ncMeta/TSDBMonitorController.h
  36. +132 −0 ncMeta App/ncMeta/TSDBMonitorController.m
  37. +17 −0 ncMeta App/ncMeta/TSDBMonitorDelegate.h
  38. +24 −0 ncMeta App/ncMeta/TSNCMetaController.h
  39. +137 −0 ncMeta App/ncMeta/TSNCMetaController.m
  40. +19 −0 ncMeta App/ncMeta/TSNCMetaControllerDelegate.h
  41. +21 −0 ncMeta App/ncMeta/TSWindowMonitorController.h
  42. +156 −0 ncMeta App/ncMeta/TSWindowMonitorController.m
  43. +18 −0 ncMeta App/ncMeta/TSWindowMonitorControllerDelegate.h
  44. +41 −0 ncMeta App/ncMeta/en.lproj/Credits.rtf
  45. +2 −0  ncMeta App/ncMeta/en.lproj/InfoPlist.strings
  46. +3,314 −0 ncMeta App/ncMeta/en.lproj/MainMenu.xib
  47. +14 −0 ncMeta App/ncMeta/main.m
  48. BIN  ncMeta App/ncMeta/menuicon.png
  49. BIN  ncMeta App/ncMeta/menuicon@2x.png
  50. BIN  ncMeta App/ncMeta/menuicon_red.png
  51. BIN  ncMeta App/ncMeta/menuicon_red@2x.png
  52. +38 −0 ncMeta App/ncMeta/ncMeta-Info.plist
  53. +7 −0 ncMeta App/ncMeta/ncMeta-Prefix.pch
  54. +10 −0 ncMeta App/ncMeta/ncMeta.entitlements
  55. BIN  ncMeta App/ncMeta/ncMeta.iconset/icon_128x128.png
  56. BIN  ncMeta App/ncMeta/ncMeta.iconset/icon_128x128@2x.png
  57. BIN  ncMeta App/ncMeta/ncMeta.iconset/icon_16x16.png
  58. BIN  ncMeta App/ncMeta/ncMeta.iconset/icon_16x16@2x.png
  59. BIN  ncMeta App/ncMeta/ncMeta.iconset/icon_256x256.png
  60. BIN  ncMeta App/ncMeta/ncMeta.iconset/icon_256x256@2x.png
  61. BIN  ncMeta App/ncMeta/ncMeta.iconset/icon_32x32.png
  62. BIN  ncMeta App/ncMeta/ncMeta.iconset/icon_32x32@2x.png
  63. BIN  ncMeta App/ncMeta/ncMeta.iconset/icon_512x512.png
  64. BIN  ncMeta App/ncMeta/ncMeta.iconset/icon_512x512@2x.png
View
9 LICENSE.md
@@ -0,0 +1,9 @@
+###ncMeta Project License
+
+Copyright (c) 2012 Tim Schröder
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
132 README.md
@@ -1,6 +1,130 @@
-ncMeta
-======
+#ncMeta Project
-Cocoa classes to have fun with Mountain Lion's Notification Center
+by Tim Schröder (see my [website](http://www.timschroeder.net), or my [blog](http://blog.timschroeder.net), or follow me on [Twitter](http://www.twitter.com/timschroedernet)).
-Test
+ncMeta Project is a set of classes for having fun with Mountain Lion’s [Notification Center](http://www.apple.com/osx/whats-new/features.html#notification). In its present form, it offers the functionality
+
+* to monitor notifications being delivered to the Notification Center by any app;
+* to monitor Notification Center being invoked (and thereby brought to front) by the user.
+
+I developed the ncMeta Project to facilitate interaction with Mountain Lion’s Notification Center beyond the limited functionality presently offered by the [NSUserNotificationCenter class](https://developer.apple.com/library/mac/#documentation/Foundation/Reference/NSUserNotificationCenter_Class/Reference/Reference.html). The ncMeta Project doesn’t use any hacks or non-public methods.
+
+###How does it work?
+
+The ncMeta Project consists of several classes, most of which are described below. However, you’ll typically only have to interact with the [TSNCMetaController][] class and the [TSNCMetaControllerDelegate Protocol][]. A sandboxed sample application project (ncMeta App) is available. For more information on what the sample application does, see my [blog](http://blog.timschroeder.net).
+
+Behind the scenes, the ncMeta Project reads from the sqlite database Notification Center uses to store information about notifications and the apps which deliver them. To monitor when Notification Center is in front, it sets a timer and regularly checks if its window (which doesn’t look like a window) is the front-most window.
+
+###Usage
+
+To use the ncMeta Project in your application, first
+
+* put all ncMeta files into your Xcode project,
+* put the fmdb classes mentioned below into your Xcode project,
+* if you want to sandbox your app, please read the [sandbox caveats][] below,
+* link to the CoreServices framework and to the libsqlite3.dylib library,
+* read the [General Caveats][] (see below).
+
+Second, read and follow the documentation of the [TSNCMetaController][] class and the [TSNCMetaControllerDelegate Protocol][]. Off you go!
+
+###General Caveats
+
+* ncMeta Project will only compile with Xcode 4.4+ and needs ARC to be enabled.
+* ncMeta Project uses the [fmdb framework](https://github.com/ccgus/fmdb) and needs the following .h and .m files to be present in order to compile: FMDatabase, FMDatabaseAdditions, FMDatabasePool, FMDatabaseQueue, FMResultSet.
+* ncMeta Project is work in progress.
+
+###Sandbox Caveats
+
+You can run the ncMeta Project classes in a sandbox. Add the temporary entitlement key `com.apple.security.temporary-exception.files.home-relative-path.read-write` with a value of `/Library/Application Support/NotificationCenter/` to your entitlements plist file, or ask the user for access to that directory via an open dialog and store the security-scoped URL you receive for later access. Though ncMeta doesn’t write to Notification Center’s database, I’ve experienced issues with the [fmdb framework](https://github.com/ccgus/fmdb) corrupting Notification Center’s database when having only read-only access.
+
+###License
+
+The license for ncMeta Project is contained in the „license.md“ file.
+
+###Support and so on
+
+There is no support for ncMeta Project. For feature requests and bug reports, please use GitHub’s issue tracker. I’ll see what I can do.
+
+***
+
+##TSNCMetaController
+
+TSNCMetaController is the only class you’ll have to interact with. As it provides interaction with an external ressource, the Notification Center, it has been designed as a singleton class.
+
+###Usage
+
+Connect to the TSNCMetaController singleton using code like this:
+
+ TSNCMetaController *ncController = [TSNCMetaController sharedController];
+
+Before using TSNCMetaController, you’ll have to set its `delegate` property, as most communication between the controller class and your application will be uni-directional, facilitated by the [TSNCMetaControllerDelegate Protocol][]. So, to start monitor operations after having connected to the TSNCMetaController singlton, use this code:
+
+ [ncController setDelegate:self];
+ [ncController startOperations];
+
+You’ll then receive information about Notification Center events via the [TSNCMetaControllerDelegate Protocol][].
+
+###Public Methods
+
+######-(void) startOperations
+
+Begins monitoring Notification Center’s behaviour, i.e. if it is brought to front or if new notifications are delivered to it. If one of this happens, the delegate is called via the [TSNCMetaControllerDelegate Protocol][].
+
+######-(void) stopOperations
+Stops monitoring Notification Center’s behaviour. This method should be called when the app terminates.
+
+######- (void)resetCount
+
+If invoked, resets the internal count on how many notification have been delivered to the Notification Center by apps. This method is suitable to be called after the Notification Center has become visible.
+
+######-(void) setDelegate:(id)delegate
+
+Sets TSNCMetaController’s delegate. The delegate must conform to the [TSNCMetaControllerDelegate Protocol][].
+
+***
+
+##TSNCMetaControllerDelegate Protocol
+
+The TSNCMetaControllerDelegate Protocol is used to inform the TSNCMetaController delegate about things happening to the Notification Center.
+
+######-(void) hasBecomeVisible
+
+This delegate method is called when the Notification Center has become visible on the user’s screen.
+
+######-(void) hasBecomeInvisible
+
+This delegate method is called when the Notification Center has been hidden by the user.
+
+######-(void) newNotifications:(NSArray*)newNotifications
+
+This delegate method is called when new notifications have been delivered to and by the Notification Center. The array passed contains an object of the type [TSAppRecord][] for every app which has delivered a notification.
+
+***
+
+##TSAppRecord
+
+TSAppRecord is the model class used to pass information on notifications delivered to the Notification Center by an app. It contains a number of public properties and one public method.
+
+###Public Properties
+
+######@property (strong) NSString *bundle_id
+
+Contains the bundle id of the app which has delivered a notification to the Notification Center, e.g. `com.apple.mail`.
+
+######@property (assign) NSInteger show_count
+
+Contains the maximum number of notifications that are shown in the Notification Center for the app, as set by the user in System Preferences.
+
+######@property (assign) BOOL shown
+
+Is `YES` if the user has enabled Notification Center notifications for this app in System Preferences, `NO` if the user has disabled this behaviour.
+
+######@property (assign) NSInteger increase
+
+Contains the number of notifications this app has delivered to the Notification Center since the last reset of this property. Calling TSNCMetaController’s -resetCount method resets this property for all TSAppRecord instances present. The value of this property may be greater than the value the `show_count` property returns.
+
+###Public Methods
+
+######-(NSString*) displayTitle
+
+This convenience method returns the localized title of the application whose notification info is contained in a TSAppRecord instance.
View
25 TSAppRecord.h
@@ -0,0 +1,25 @@
+//
+// TSAppRecord.h
+// ncMeta
+//
+// Created by Tim Schröder on 09.08.12.
+// Copyright (c) 2012 Tim Schröder. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface TSAppRecord : NSObject
+
+-(BOOL) hasNote:(NSInteger)note;
+-(NSString*) displayTitle;
+
+@property (strong) NSString *app_id;
+@property (strong) NSString *bundle_id;
+@property (assign) NSInteger show_count;
+@property (assign) NSInteger increase;
+@property (assign) BOOL shown;
+@property (strong) NSMutableArray *noteArray;
+@property (assign) NSInteger oldIncrease;
+@property (strong) NSMutableArray *oldNoteArray;
+
+@end
View
43 TSAppRecord.m
@@ -0,0 +1,43 @@
+//
+// TSAppRecord.m
+// ncMeta
+//
+// Created by Tim Schröder on 09.08.12.
+// Copyright (c) 2012 Tim Schröder. All rights reserved.
+//
+
+#import "TSAppRecord.h"
+
+
+@implementation TSAppRecord
+
+-(id)init
+{
+ if (self = [super init]) {
+ self.noteArray = [NSMutableArray arrayWithCapacity:0];
+ self.oldNoteArray = [NSMutableArray arrayWithCapacity:0];
+ }
+ return self;
+}
+
+-(BOOL) hasNote:(NSInteger)note
+{
+ BOOL result = NO;
+ for (NSNumber *num in self.noteArray) {
+ if (note == [num integerValue]) result = YES;
+ }
+ for (NSNumber *num in self.oldNoteArray) {
+ if (note == [num integerValue]) result = YES;
+ }
+ return result;
+}
+
+-(NSString*) displayTitle
+{
+ NSString *path = [[NSWorkspace sharedWorkspace]absolutePathForAppBundleWithIdentifier:[self bundle_id]];
+ NSString *title = [[NSFileManager defaultManager] displayNameAtPath:path];
+ return title;
+}
+
+
+@end
View
23 TSDBConnector.h
@@ -0,0 +1,23 @@
+//
+// TSDBConnector.h
+// ncMeta
+//
+// Created by Tim Schröder on 08.08.12.
+// Copyright (c) 2012 Tim Schröder. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface TSDBConnector : NSObject
+
++ (TSDBConnector *)sharedConnector;
+- (void)checkForChange;
+- (void)resetCount;
+- (void)performInitialFetch;
+
+@property (strong) NSMutableArray *appArray;
+@property (assign) BOOL initializing;
+@property (strong) NSString *path;
+@property (assign) id delegate;
+
+@end
View
201 TSDBConnector.m
@@ -0,0 +1,201 @@
+//
+// TSDBConnector.m
+// ncMeta
+//
+// Created by Tim Schröder on 08.08.12.
+// Copyright (c) 2012 Tim Schröder. All rights reserved.
+//
+
+#import "TSDBConnector.h"
+#import "FMDatabase.h"
+#import "TSAppDelegate.h"
+#import "TSAppRecord.h"
+#import "TSDBConnectorDelegate.h"
+
+
+@implementation TSDBConnector
+
+static TSDBConnector *_sharedConnector = nil;
+
+
+#pragma mark -
+#pragma mark Singleton Methods
+
++ (TSDBConnector *)sharedConnector
+{
+ if (!_sharedConnector) {
+ _sharedConnector = [[super allocWithZone:NULL] init];
+ }
+ return _sharedConnector;
+}
+
++ (id)allocWithZone:(NSZone *)zone
+{
+ return [self sharedConnector];
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+ return self;
+}
+
+
+#pragma mark -
+#pragma mark Housekeeping Methods
+
+-(id)init
+{
+ if (self=[super init]) {
+ self.appArray = [NSMutableArray arrayWithCapacity:0];
+ }
+ return self;
+}
+
+
+#pragma mark -
+#pragma mark Public Methods
+
+-(void)checkForChange
+{
+ // Get full path for NC database
+ NSString *filename;
+ NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.path
+ error:nil];
+ for (NSString *file in files) {
+ if ([file hasSuffix:@".db"]) filename = file;
+ }
+ if (!filename) return;
+ NSString *filePath = [self.path stringByAppendingFormat:@"%@", filename];
+
+ // Access database
+ FMDatabase *db = [FMDatabase databaseWithPath:filePath];
+ if (!db) return;
+ if ([db open]) {
+
+ // Get/Update App Data
+ FMResultSet *entries = [db executeQuery:@"SELECT app_id, bundleid, show_count, flags FROM app_info"];
+ while ([entries next]) {
+ NSString *app_id = [entries stringForColumn:@"app_id"];
+ NSString *bundle_id = [entries stringForColumn:@"bundleid"];
+ NSInteger show_count = [entries intForColumn:@"show_count"];
+ NSInteger flags = [entries intForColumn:@"flags"];
+ BOOL shown = NO;
+ shown = (!(flags&1));
+
+ // Check if we already have the app in our array
+ BOOL alreadyThere = NO;
+ TSAppRecord *alreadyThereApp;
+ for (TSAppRecord *app in self.appArray) {
+ if (!alreadyThere) {
+ NSString *compare_bundle_id = [app bundle_id];
+ if ([bundle_id isEqualToString:compare_bundle_id]) {
+ alreadyThere = YES;
+ alreadyThereApp = app;
+ }
+ }
+ }
+
+ // No, app isn't already there, add it
+ if (!alreadyThere) {
+ // Hinzufügen
+ TSAppRecord *newRecord = [[TSAppRecord alloc] init];
+ newRecord.app_id = app_id;
+ newRecord.bundle_id = bundle_id;
+ newRecord.shown = shown;
+ alreadyThereApp.increase = 0;
+ newRecord.show_count = show_count;
+ [self.appArray addObject:newRecord];
+ } else {
+
+ // Yes, app is there, update its record
+ alreadyThereApp.shown = shown;
+ alreadyThereApp.show_count = show_count;
+ alreadyThereApp.oldIncrease = alreadyThereApp.increase;
+ }
+ }
+
+ // Get Number of notifications shown from each app
+ NSMutableArray *tempNoteArray = [NSMutableArray arrayWithCapacity:0];
+ FMResultSet *notifications = [db executeQuery:@"SELECT app_id, note_id FROM presented_notifications ORDER BY sort_order"];
+ while ([notifications next]) {
+ // Bring all notifications from DB to our array
+ NSString *app_id = [notifications stringForColumn:@"app_id"];
+ if (app_id) {
+ NSInteger note_id = [notifications intForColumn:@"note_id"];
+ [tempNoteArray addObject:[NSNumber numberWithInteger:note_id]];
+ for (TSAppRecord *app in self.appArray) {
+ if ([app_id isEqualToString:app.app_id]) {
+
+ if (self.initializing) {
+ [app.oldNoteArray addObject:[NSNumber numberWithInteger:note_id]];
+ } else {
+ // Check if notification is already in our array
+ if (![app hasNote:note_id]) {
+ // No, add notification to our array and increase the notification count
+ app.increase = app.increase + 1;
+ [app.noteArray addObject:[NSNumber numberWithInteger:note_id]];
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Check for notifications which are in our array but not anymore in NC
+ for (TSAppRecord *app in self.appArray) {
+ NSMutableArray *delArray = [NSMutableArray arrayWithCapacity:0];
+ for (NSNumber *note in app.noteArray) {
+ BOOL numberThere = NO;
+ for (NSNumber *compareNote in tempNoteArray) {
+ if ([compareNote integerValue] == [note integerValue]) numberThere = YES;
+ }
+ if (!numberThere) [delArray addObject:note];
+ }
+ NSInteger delCount = [delArray count];
+ app.increase = app.increase - delCount;
+ if (app.increase < 0) app.increase = 0;
+ [app.noteArray removeObjectsInArray:delArray];
+ }
+
+ // Check for Increase of shown Notifications
+ BOOL increase = NO;
+ NSMutableArray *infoArray = [NSMutableArray arrayWithCapacity:0];
+ for (TSAppRecord *app in self.appArray) {
+ if ((!self.initializing) && (app.shown)) {
+ if ((app.increase > 0) || (app.increase != app.oldIncrease)) {
+ increase = YES;
+ [infoArray addObject:app];
+ }
+ }
+ }
+
+ if (increase) {
+ if (self.delegate &&
+ [self.delegate conformsToProtocol:@protocol(TSDBConnectorDelegate)] &&
+ [self.delegate respondsToSelector:@selector(newNotifications:)]) {
+ [self.delegate newNotifications:infoArray];
+ }
+ }
+ [db close];
+ }
+}
+
+- (void)resetCount
+{
+ for (TSAppRecord *app in self.appArray) {
+ app.increase = 0;
+ app.oldIncrease = 0;
+ [app.oldNoteArray addObjectsFromArray:app.noteArray];
+ [app.noteArray removeAllObjects];
+ }
+}
+
+- (void) performInitialFetch
+{
+ self.initializing = YES;
+ [self checkForChange];
+ self.initializing = NO;
+}
+
+
+@end
View
18 TSDBConnectorDelegate.h
@@ -0,0 +1,18 @@
+//
+// TSDBConnectorDelegate.h
+// ncMeta
+//
+// Created by Tim Schröder on 14.08.12.
+// Copyright (c) 2012 Tim Schröder. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@protocol TSDBConnectorDelegate <NSObject>
+
+@required
+
+-(void)newNotifications:(NSArray*)array;
+
+
+@end
View
25 TSDBMonitorController.h
@@ -0,0 +1,25 @@
+//
+// TSDBMonitorController.h
+// ncMeta
+//
+// Created by Tim Schröder on 06.08.12.
+// Copyright (c) 2012 Tim Schröder. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@interface TSDBMonitorController : NSObject
+{
+ FSEventStreamRef monitorStream;
+ BOOL monitoringIsActive;
+}
+
++ (TSDBMonitorController *)sharedController;
+- (BOOL)startMonitoring;
+- (void)stopMonitoring;
+
+@property (strong) NSURL *monitorURL;
+@property (assign) id delegate;
+
+@end
View
132 TSDBMonitorController.m
@@ -0,0 +1,132 @@
+//
+// TSDBMonitorController.m
+// ncMeta
+//
+// Created by Tim Schröder on 06.08.12.
+// Copyright (c) 2012 Tim Schröder. All rights reserved.
+//
+
+#import "TSDBMonitorController.h"
+#import "TSDBMonitorDelegate.h"
+#import <CoreServices/CoreServices.h>
+
+
+@implementation TSDBMonitorController
+
+NSDate *_lastChangeDate;
+
+static TSDBMonitorController *_sharedController = nil;
+
+#pragma mark -
+#pragma mark Singleton Methods
+
++ (TSDBMonitorController *)sharedController
+{
+ if (!_sharedController) {
+ _sharedController = [[super allocWithZone:NULL] init];
+ }
+ return _sharedController;
+}
+
++ (id)allocWithZone:(NSZone *)zone
+{
+ return [self sharedController];
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+ return self;
+}
+
+
+#pragma mark -
+#pragma mark Overriden Methods
+
+-(id)init
+{
+ if (self = [super init]) {
+ monitoringIsActive = NO;
+ _lastChangeDate = nil;
+ }
+ return (self);
+}
+
+
+#pragma mark -
+#pragma mark Monitoring Callback Method
+
+void fsevents_callback(ConstFSEventStreamRef streamRef,
+ void *userData,
+ size_t numEvents,
+ void *eventPaths,
+ const FSEventStreamEventFlags eventFlags[],
+ const FSEventStreamEventId eventIds[])
+{
+ id delegate = [[TSDBMonitorController sharedController] delegate];
+ if (delegate &&
+ [delegate conformsToProtocol:@protocol(TSDBMonitorDelegate)] &&
+ [delegate respondsToSelector:@selector(fileHasChanged)]) {
+ [delegate fileHasChanged];
+ }
+}
+
+
+#pragma mark -
+#pragma mark Monitoring Administration Methods
+
+-(FSEventStreamRef)startMonitoringForStream:(FSEventStreamRef)stream withPath:(NSString*)path
+{
+ NSArray *pathsToWatch = [NSArray arrayWithObject:path];
+ void *appPointer = (__bridge void*)self;
+ FSEventStreamContext context = {0, appPointer, NULL, NULL, NULL};
+ NSTimeInterval latency = 3.0;
+ stream = FSEventStreamCreate(NULL,
+ &fsevents_callback,
+ &context,
+ (__bridge CFArrayRef) pathsToWatch,
+ kFSEventStreamEventIdSinceNow,
+ (CFAbsoluteTime) latency,
+ kFSEventStreamCreateFlagUseCFTypes
+ );
+ FSEventStreamScheduleWithRunLoop(stream,
+ CFRunLoopGetCurrent(),
+ kCFRunLoopDefaultMode);
+ FSEventStreamStart (stream);
+ return stream;
+}
+
+-(void)stopMonitoringForStream:(FSEventStreamRef)stream
+{
+ if (stream != NULL) {
+ FSEventStreamStop (stream);
+ FSEventStreamInvalidate(stream);
+ FSEventStreamRelease (stream);
+ }
+}
+
+
+#pragma mark -
+#pragma mark 'Public' Methods
+
+-(BOOL)startMonitoring
+// Will return YES if monitoring was started successfully, NO if starting failed
+{
+ // Return if we're already running or if we don't have an URL
+ if ((monitoringIsActive) || (!self.monitorURL)) return NO;
+
+ // Start Monitoring
+ monitorStream = [self startMonitoringForStream:monitorStream withPath:[self.monitorURL path]];
+ monitoringIsActive = YES;
+ return YES;
+}
+
+-(void)stopMonitoring
+{
+ // Don't stop if we aren't monitoring
+ if ((!monitoringIsActive) || (!monitorStream)) return;
+ [self stopMonitoringForStream:monitorStream];
+ monitoringIsActive = NO;
+}
+
+
+@end
View
17 TSDBMonitorDelegate.h
@@ -0,0 +1,17 @@
+//
+// TSDBMonitorDelegate.h
+// ncMeta
+//
+// Created by Tim Schröder on 13.08.12.
+// Copyright (c) 2012 Tim Schröder. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@protocol TSDBMonitorDelegate <NSObject>
+
+@required
+
+-(void)fileHasChanged;
+
+@end
View
24 TSNCMetaController.h
@@ -0,0 +1,24 @@
+//
+// TSNCMetaController.h
+// ncMeta
+//
+// Created by Tim Schröder on 14.08.12.
+// Copyright (c) 2012 Tim Schröder. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "TSDBMonitorDelegate.h"
+#import "TSWindowMonitorControllerDelegate.h"
+#import "TSDBConnectorDelegate.h"
+
+@interface TSNCMetaController : NSObject <TSDBMonitorDelegate, TSWindowMonitorControllerDelegate, TSDBConnectorDelegate>
+
++ (TSNCMetaController *)sharedController;
+- (void)startOperations;
+- (void)stopOperations;
+- (void)resetCount;
+
+@property (assign) id delegate;
+
+
+@end
View
137 TSNCMetaController.m
@@ -0,0 +1,137 @@
+//
+// TSNCMetaController.m
+// ncMeta
+//
+// Created by Tim Schröder on 14.08.12.
+// Copyright (c) 2012 Tim Schröder. All rights reserved.
+//
+
+#import "TSNCMetaController.h"
+#import "TSDBMonitorController.h"
+#import "TSWindowMonitorController.h"
+#import "TSNCMetaControllerDelegate.h"
+#import "TSDBConnector.h"
+
+#define monitorURLString @"/Library/Application Support/NotificationCenter"
+
+@implementation TSNCMetaController
+
+static TSNCMetaController *_sharedController = nil;
+
+#pragma mark -
+#pragma mark Singleton Methods
+
++ (TSNCMetaController *)sharedController
+{
+ if (!_sharedController) {
+ _sharedController = [[super allocWithZone:NULL] init];
+ }
+ return _sharedController;
+}
+
++ (id)allocWithZone:(NSZone *)zone
+{
+ return [self sharedController];
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+ return self;
+}
+
+
+#pragma mark -
+#pragma mark Public Methods
+
+- (void)startOperations
+{
+ // Prepare Monitoring Path
+ NSString *home = NSHomeDirectory();
+ NSArray *pathArray = [home componentsSeparatedByString:@"/"];
+ NSString *absolutePath;
+ if ([pathArray count] > 2) {
+ absolutePath = [NSString stringWithFormat:@"/%@/%@", [pathArray objectAtIndex:1], [pathArray objectAtIndex:2]];
+ }
+ NSString *path = [NSString stringWithFormat:@"%@%@", absolutePath, monitorURLString];
+
+ // Initialize Database Connector
+ NSString *dirPath = [path stringByAppendingString:@"/"];
+ TSDBConnector *dbConnector = [TSDBConnector sharedConnector];
+ [dbConnector setPath:dirPath];
+ [dbConnector setDelegate:self];
+ [dbConnector performInitialFetch];
+
+ // Start NC Database Monitoring
+ NSURL *URL = [NSURL fileURLWithPath:path];
+ TSDBMonitorController *dbMonitor = [TSDBMonitorController sharedController];
+ [dbMonitor setMonitorURL:URL];
+ [dbMonitor setDelegate:self];
+ [dbMonitor startMonitoring];
+
+ // Start NC Window Monitoring
+ TSWindowMonitorController *windowMonitor = [TSWindowMonitorController sharedController];
+ [windowMonitor setDelegate:self];
+ [windowMonitor startMonitoring];
+}
+
+- (void)stopOperations
+{
+ [[TSDBMonitorController sharedController] stopMonitoring];
+}
+
+- (void)resetCount
+{
+ [[TSDBConnector sharedConnector] resetCount];
+}
+
+
+#pragma mark -
+#pragma mark - TSWindowMonitorControllerDelegate Protocol Methods
+
+-(void)NCDidShow
+{
+ // Send info to delegate
+ if (self.delegate &&
+ [self.delegate conformsToProtocol:@protocol(TSNCMetaControllerDelegate)] &&
+ [self.delegate respondsToSelector:@selector(hasBecomeVisible)]) {
+ [self.delegate hasBecomeVisible];
+ }
+}
+
+-(void)NCDidHide
+{
+ // Reset DB info
+ [[TSDBConnector sharedConnector] resetCount];
+
+ // Send info to delegate
+ if (self.delegate &&
+ [self.delegate conformsToProtocol:@protocol(TSNCMetaControllerDelegate)] &&
+ [self.delegate respondsToSelector:@selector(hasBecomeInvisible)]) {
+ [self.delegate hasBecomeInvisible];
+ }
+}
+
+
+#pragma mark -
+#pragma mark TSMonitorDelegate Protocol Methods
+
+-(void)fileHasChanged
+{
+ [[TSDBConnector sharedConnector] checkForChange];
+}
+
+
+#pragma mark -
+#pragma mark TSDBConnector Protocol Methods
+
+-(void)newNotifications:(NSArray*)array
+{
+ if (self.delegate &&
+ [self.delegate conformsToProtocol:@protocol(TSNCMetaControllerDelegate)] &&
+ [self.delegate respondsToSelector:@selector(newNotifications:)]) {
+ [self.delegate newNotifications:array];
+ }
+}
+
+
+@end
View
19 TSNCMetaControllerDelegate.h
@@ -0,0 +1,19 @@
+//
+// TSNCMetaControllerDelegate.h
+// ncMeta
+//
+// Created by Tim Schröder on 14.08.12.
+// Copyright (c) 2012 Tim Schröder. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@protocol TSNCMetaControllerDelegate <NSObject>
+
+@required
+
+-(void)hasBecomeVisible;
+-(void)hasBecomeInvisible;
+-(void)newNotifications:(NSArray*)newNotifications;
+
+@end
View
21 TSWindowMonitorController.h
@@ -0,0 +1,21 @@
+//
+// TSWindowMonitorController.h
+// ncMeta
+//
+// Created by Tim Schröder on 14.08.12.
+// Copyright (c) 2012 Tim Schröder. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface TSWindowMonitorController : NSObject
+
++ (TSWindowMonitorController *)sharedController;
+- (void) startMonitoring;
+- (void) stopMonitoring;
+
+@property (assign) id delegate;
+@property (strong) NSTimer *timer;
+@property (assign) BOOL lastVisibleState;
+
+@end
View
156 TSWindowMonitorController.m
@@ -0,0 +1,156 @@
+//
+// TSWindowMonitorController.m
+// ncMeta
+//
+// Created by Tim Schröder on 14.08.12.
+// Copyright (c) 2012 Tim Schröder. All rights reserved.
+//
+
+#import "TSWindowMonitorController.h"
+#import "TSWindowMonitorControllerDelegate.h"
+
+#define updateInterval 1.0
+#define ncURI @"com.apple.notificationcenterui"
+
+@implementation TSWindowMonitorController
+
+static TSWindowMonitorController *_sharedController = nil;
+
+#pragma mark -
+#pragma mark Singleton Methods
+
++ (TSWindowMonitorController *)sharedController
+{
+ if (!_sharedController) {
+ _sharedController = [[super allocWithZone:NULL] init];
+ }
+ return _sharedController;
+}
+
++ (id)allocWithZone:(NSZone *)zone
+{
+ return [self sharedController];
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+ return self;
+}
+
+
+#pragma mark -
+#pragma mark Internal Methods
+
+// Retrieve PID of Notification Center
+-(NSNumber*)getNCPID
+{
+ ProcessSerialNumber psn = { 0, kCurrentProcess };
+ NSNumber *pid;
+ BOOL finished = NO;
+ do {
+ // Get next process
+ OSStatus err;
+ err = GetNextProcess(&psn);
+ if (err == procNotFound) {
+ finished = YES;
+ } else {
+ // Get info about process
+ CFDictionaryRef dictRef;
+ UInt32 ui = kProcessDictionaryIncludeAllInformationMask;
+ dictRef = ProcessInformationCopyDictionary (&psn, ui);
+ NSDictionary *dict = (__bridge NSDictionary*)dictRef;
+ NSString *bundleIdentifier = [dict objectForKey:(__bridge NSString*)kCFBundleIdentifierKey];
+ if ([bundleIdentifier isEqualToString:ncURI]) {
+ finished = YES;
+ // Get PID
+ pid_t _pid;
+ err = GetProcessPID (&psn, &_pid);
+ pid = [NSNumber numberWithInteger:(NSInteger)_pid];
+ }
+ }
+
+ } while (!finished);
+ return pid;
+}
+
+
+#pragma mark -
+#pragma mark NSTimer callback
+
+// Check if NC window has become (in-)visible since last check
+-(void)checkWindows:(id)timer
+{
+ BOOL windowVisible = NO;
+ NSNumber *number = (NSNumber*)[timer userInfo];
+ NSInteger pid = [(NSNumber*)number integerValue];
+ CFArrayRef arrayRef = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
+ NSArray *windows = (__bridge NSMutableArray *)arrayRef;
+ for (NSDictionary *window in windows) {
+ NSNumber *pidCompare = [window objectForKey:(__bridge NSString*)kCGWindowOwnerPID];
+ NSInteger intCompare = [pidCompare integerValue];
+
+ // Have we found the NC window pid?
+ if (pid == intCompare) {
+
+ // Exclude notifications which may be present on the desktop
+ CGRect cRect;
+ CFDictionaryRef dict = (__bridge CFDictionaryRef)([window objectForKey:(__bridge NSString*)kCGWindowBounds]);
+ CGRectMakeWithDictionaryRepresentation (dict, &cRect);
+ NSRect rect = NSRectFromCGRect(cRect);
+ NSRect screenFrame = [[[NSScreen screens] objectAtIndex:0] frame];
+ if ((screenFrame.size.width==rect.size.width) && (screenFrame.size.height=rect.size.height)) windowVisible = YES;
+ }
+ }
+ CFRelease (arrayRef);
+
+ // Check if we need to send info to delegate
+ if (windowVisible) {
+ if (!self.lastVisibleState) {
+ self.lastVisibleState = YES;
+
+ // Send info to delegate
+ if (self.delegate &&
+ [self.delegate conformsToProtocol:@protocol(TSWindowMonitorControllerDelegate)] &&
+ [self.delegate respondsToSelector:@selector(NCDidShow)]) {
+ [self.delegate NCDidShow];
+ }
+ }
+ } else {
+ if (self.lastVisibleState) {
+ self.lastVisibleState = NO;
+
+ // Send info to delegate
+ if (self.delegate &&
+ [self.delegate conformsToProtocol:@protocol(TSWindowMonitorControllerDelegate)] &&
+ [self.delegate respondsToSelector:@selector(NCDidHide)]) {
+ [self.delegate NCDidHide];
+ }
+ }
+ }
+}
+
+
+#pragma mark -
+#pragma mark Public Methods
+
+- (void) startMonitoring
+{
+ self.lastVisibleState = NO;
+
+ // Init Timer
+ if (self.timer) [self.timer invalidate];
+ self.timer = [NSTimer scheduledTimerWithTimeInterval:updateInterval
+ target:self
+ selector:@selector(checkWindows:)
+ userInfo:[self getNCPID]
+ repeats:YES];
+}
+
+- (void) stopMonitoring
+{
+ [self.timer invalidate];
+ self.timer = nil;
+}
+
+
+@end
View
18 TSWindowMonitorControllerDelegate.h
@@ -0,0 +1,18 @@
+//
+// TSWindowMonitorControllerDelegate.h
+// ncMeta
+//
+// Created by Tim Schröder on 14.08.12.
+// Copyright (c) 2012 Tim Schröder. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@protocol TSWindowMonitorControllerDelegate <NSObject>
+
+@required
+
+-(void)NCDidShow;
+-(void)NCDidHide;
+
+@end
View
444 ncMeta App/ncMeta.xcodeproj/project.pbxproj
@@ -0,0 +1,444 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 222410C915D40EEB00AC2E93 /* TSAppRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = 222410C815D40EEB00AC2E93 /* TSAppRecord.m */; };
+ 22A317A115D25498002725D9 /* ncMeta.iconset in Resources */ = {isa = PBXBuildFile; fileRef = 22A317A015D25498002725D9 /* ncMeta.iconset */; };
+ 22A317B815D296F8002725D9 /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 22A317AF15D296F8002725D9 /* FMDatabase.m */; };
+ 22A317B915D296F8002725D9 /* FMDatabaseAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 22A317B115D296F8002725D9 /* FMDatabaseAdditions.m */; };
+ 22A317BA15D296F8002725D9 /* FMDatabasePool.m in Sources */ = {isa = PBXBuildFile; fileRef = 22A317B315D296F8002725D9 /* FMDatabasePool.m */; };
+ 22A317BB15D296F8002725D9 /* FMDatabaseQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 22A317B515D296F8002725D9 /* FMDatabaseQueue.m */; };
+ 22A317BC15D296F8002725D9 /* FMResultSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 22A317B715D296F8002725D9 /* FMResultSet.m */; };
+ 22A317BE15D29B51002725D9 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 22A317BD15D29B50002725D9 /* libsqlite3.dylib */; };
+ 22B4DA0815D0761200D890FF /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22B4DA0715D0761200D890FF /* CoreServices.framework */; };
+ 22B4DA0915D076E000D890FF /* TSDBMonitorController.m in Sources */ = {isa = PBXBuildFile; fileRef = 22B4DA0515D075F900D890FF /* TSDBMonitorController.m */; };
+ 22C4CEB315D305310088F4F9 /* TSDBConnector.m in Sources */ = {isa = PBXBuildFile; fileRef = 22C4CEB215D305310088F4F9 /* TSDBConnector.m */; };
+ 22DB4A3B15D6864100F1A926 /* menuicon_red.png in Resources */ = {isa = PBXBuildFile; fileRef = 22DB4A3715D6864100F1A926 /* menuicon_red.png */; };
+ 22DB4A3C15D6864100F1A926 /* menuicon_red@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 22DB4A3815D6864100F1A926 /* menuicon_red@2x.png */; };
+ 22DB4A3D15D6864100F1A926 /* menuicon.png in Resources */ = {isa = PBXBuildFile; fileRef = 22DB4A3915D6864100F1A926 /* menuicon.png */; };
+ 22DB4A3E15D6864100F1A926 /* menuicon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 22DB4A3A15D6864100F1A926 /* menuicon@2x.png */; };
+ 22EB2FD715DAC91800DC6094 /* TSWindowMonitorController.m in Sources */ = {isa = PBXBuildFile; fileRef = 22EB2FD615DAC91800DC6094 /* TSWindowMonitorController.m */; };
+ 22EC6F8015DAC09900BF101E /* TSNCMetaController.m in Sources */ = {isa = PBXBuildFile; fileRef = 22EC6F7F15DAC09900BF101E /* TSNCMetaController.m */; };
+ 22EE65A715D0610C0072CEDA /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22EE65A615D0610C0072CEDA /* Cocoa.framework */; };
+ 22EE65B115D0610C0072CEDA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 22EE65AF15D0610C0072CEDA /* InfoPlist.strings */; };
+ 22EE65B315D0610C0072CEDA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 22EE65B215D0610C0072CEDA /* main.m */; };
+ 22EE65B715D0610C0072CEDA /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 22EE65B515D0610C0072CEDA /* Credits.rtf */; };
+ 22EE65BA15D0610C0072CEDA /* TSAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 22EE65B915D0610C0072CEDA /* TSAppDelegate.m */; };
+ 22EE65BD15D0610D0072CEDA /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 22EE65BB15D0610D0072CEDA /* MainMenu.xib */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 222410C715D40EEB00AC2E93 /* TSAppRecord.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSAppRecord.h; sourceTree = "<group>"; };
+ 222410C815D40EEB00AC2E93 /* TSAppRecord.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSAppRecord.m; sourceTree = "<group>"; };
+ 22A317A015D25498002725D9 /* ncMeta.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; path = ncMeta.iconset; sourceTree = "<group>"; };
+ 22A317AE15D296F8002725D9 /* FMDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabase.h; sourceTree = "<group>"; };
+ 22A317AF15D296F8002725D9 /* FMDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabase.m; sourceTree = "<group>"; };
+ 22A317B015D296F8002725D9 /* FMDatabaseAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabaseAdditions.h; sourceTree = "<group>"; };
+ 22A317B115D296F8002725D9 /* FMDatabaseAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabaseAdditions.m; sourceTree = "<group>"; };
+ 22A317B215D296F8002725D9 /* FMDatabasePool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabasePool.h; sourceTree = "<group>"; };
+ 22A317B315D296F8002725D9 /* FMDatabasePool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabasePool.m; sourceTree = "<group>"; };
+ 22A317B415D296F8002725D9 /* FMDatabaseQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabaseQueue.h; sourceTree = "<group>"; };
+ 22A317B515D296F8002725D9 /* FMDatabaseQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabaseQueue.m; sourceTree = "<group>"; };
+ 22A317B615D296F8002725D9 /* FMResultSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMResultSet.h; sourceTree = "<group>"; };
+ 22A317B715D296F8002725D9 /* FMResultSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMResultSet.m; sourceTree = "<group>"; };
+ 22A317BD15D29B50002725D9 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; };
+ 22B4DA0515D075F900D890FF /* TSDBMonitorController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TSDBMonitorController.m; sourceTree = "<group>"; };
+ 22B4DA0615D075F900D890FF /* TSDBMonitorController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TSDBMonitorController.h; sourceTree = "<group>"; };
+ 22B4DA0715D0761200D890FF /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; };
+ 22C4CEB115D305310088F4F9 /* TSDBConnector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSDBConnector.h; sourceTree = "<group>"; };
+ 22C4CEB215D305310088F4F9 /* TSDBConnector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSDBConnector.m; sourceTree = "<group>"; };
+ 22DA4E3B15D19AA8007148F3 /* ncMeta.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = ncMeta.entitlements; sourceTree = "<group>"; };
+ 22DB4A3715D6864100F1A926 /* menuicon_red.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menuicon_red.png; sourceTree = "<group>"; };
+ 22DB4A3815D6864100F1A926 /* menuicon_red@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menuicon_red@2x.png"; sourceTree = "<group>"; };
+ 22DB4A3915D6864100F1A926 /* menuicon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menuicon.png; sourceTree = "<group>"; };
+ 22DB4A3A15D6864100F1A926 /* menuicon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menuicon@2x.png"; sourceTree = "<group>"; };
+ 22EB2FD515DAC91800DC6094 /* TSWindowMonitorController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSWindowMonitorController.h; sourceTree = "<group>"; };
+ 22EB2FD615DAC91800DC6094 /* TSWindowMonitorController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSWindowMonitorController.m; sourceTree = "<group>"; };
+ 22EB2FD915DAC93000DC6094 /* TSWindowMonitorControllerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TSWindowMonitorControllerDelegate.h; sourceTree = "<group>"; };
+ 22EB2FDA15DAEC1F00DC6094 /* TSDBConnectorDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TSDBConnectorDelegate.h; sourceTree = "<group>"; };
+ 22EC6F7215D9AB7A00BF101E /* TSDBMonitorDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TSDBMonitorDelegate.h; sourceTree = "<group>"; };
+ 22EC6F7E15DAC09900BF101E /* TSNCMetaController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSNCMetaController.h; sourceTree = "<group>"; };
+ 22EC6F7F15DAC09900BF101E /* TSNCMetaController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSNCMetaController.m; sourceTree = "<group>"; };
+ 22EC6F8215DAC6DD00BF101E /* TSNCMetaControllerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TSNCMetaControllerDelegate.h; sourceTree = "<group>"; };
+ 22EE65A215D0610C0072CEDA /* ncMeta.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ncMeta.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 22EE65A615D0610C0072CEDA /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
+ 22EE65A915D0610C0072CEDA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
+ 22EE65AB15D0610C0072CEDA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+ 22EE65AE15D0610C0072CEDA /* ncMeta-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ncMeta-Info.plist"; sourceTree = "<group>"; };
+ 22EE65B015D0610C0072CEDA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+ 22EE65B215D0610C0072CEDA /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ 22EE65B415D0610C0072CEDA /* ncMeta-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ncMeta-Prefix.pch"; sourceTree = "<group>"; };
+ 22EE65B615D0610C0072CEDA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = "<group>"; };
+ 22EE65B815D0610C0072CEDA /* TSAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TSAppDelegate.h; sourceTree = "<group>"; };
+ 22EE65B915D0610C0072CEDA /* TSAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TSAppDelegate.m; sourceTree = "<group>"; };
+ 22EE65BC15D0610D0072CEDA /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 22EE659F15D0610C0072CEDA /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 22A317BE15D29B51002725D9 /* libsqlite3.dylib in Frameworks */,
+ 22B4DA0815D0761200D890FF /* CoreServices.framework in Frameworks */,
+ 22EE65A715D0610C0072CEDA /* Cocoa.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 222410C515D40EDB00AC2E93 /* Model */ = {
+ isa = PBXGroup;
+ children = (
+ 222410C715D40EEB00AC2E93 /* TSAppRecord.h */,
+ 222410C815D40EEB00AC2E93 /* TSAppRecord.m */,
+ );
+ name = Model;
+ sourceTree = "<group>";
+ };
+ 22A317AD15D296DE002725D9 /* fmdb */ = {
+ isa = PBXGroup;
+ children = (
+ 22A317AE15D296F8002725D9 /* FMDatabase.h */,
+ 22A317AF15D296F8002725D9 /* FMDatabase.m */,
+ 22A317B015D296F8002725D9 /* FMDatabaseAdditions.h */,
+ 22A317B115D296F8002725D9 /* FMDatabaseAdditions.m */,
+ 22A317B215D296F8002725D9 /* FMDatabasePool.h */,
+ 22A317B315D296F8002725D9 /* FMDatabasePool.m */,
+ 22A317B415D296F8002725D9 /* FMDatabaseQueue.h */,
+ 22A317B515D296F8002725D9 /* FMDatabaseQueue.m */,
+ 22A317B615D296F8002725D9 /* FMResultSet.h */,
+ 22A317B715D296F8002725D9 /* FMResultSet.m */,
+ );
+ name = fmdb;
+ sourceTree = "<group>";
+ };
+ 22EB2FDB15DAECB100DC6094 /* NC Database Classes */ = {
+ isa = PBXGroup;
+ children = (
+ 22C4CEB115D305310088F4F9 /* TSDBConnector.h */,
+ 22C4CEB215D305310088F4F9 /* TSDBConnector.m */,
+ 22EB2FDA15DAEC1F00DC6094 /* TSDBConnectorDelegate.h */,
+ 22B4DA0615D075F900D890FF /* TSDBMonitorController.h */,
+ 22B4DA0515D075F900D890FF /* TSDBMonitorController.m */,
+ 22EC6F7215D9AB7A00BF101E /* TSDBMonitorDelegate.h */,
+ );
+ name = "NC Database Classes";
+ sourceTree = "<group>";
+ };
+ 22EB2FDC15DAECCC00DC6094 /* NC Window Classes */ = {
+ isa = PBXGroup;
+ children = (
+ 22EB2FD515DAC91800DC6094 /* TSWindowMonitorController.h */,
+ 22EB2FD615DAC91800DC6094 /* TSWindowMonitorController.m */,
+ 22EB2FD915DAC93000DC6094 /* TSWindowMonitorControllerDelegate.h */,
+ );
+ name = "NC Window Classes";
+ sourceTree = "<group>";
+ };
+ 22EE659715D0610C0072CEDA = {
+ isa = PBXGroup;
+ children = (
+ 22EE65AC15D0610C0072CEDA /* ncMeta */,
+ 22EE65A515D0610C0072CEDA /* Frameworks */,
+ 22EE65A315D0610C0072CEDA /* Products */,
+ );
+ sourceTree = "<group>";
+ };
+ 22EE65A315D0610C0072CEDA /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 22EE65A215D0610C0072CEDA /* ncMeta.app */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ 22EE65A515D0610C0072CEDA /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 22EE65A615D0610C0072CEDA /* Cocoa.framework */,
+ 22EE65A815D0610C0072CEDA /* Other Frameworks */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ 22EE65A815D0610C0072CEDA /* Other Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 22A317BD15D29B50002725D9 /* libsqlite3.dylib */,
+ 22B4DA0715D0761200D890FF /* CoreServices.framework */,
+ 22EE65A915D0610C0072CEDA /* AppKit.framework */,
+ 22EE65AB15D0610C0072CEDA /* Foundation.framework */,
+ );
+ name = "Other Frameworks";
+ sourceTree = "<group>";
+ };
+ 22EE65AC15D0610C0072CEDA /* ncMeta */ = {
+ isa = PBXGroup;
+ children = (
+ 22A317AD15D296DE002725D9 /* fmdb */,
+ 222410C515D40EDB00AC2E93 /* Model */,
+ 22EB2FDC15DAECCC00DC6094 /* NC Window Classes */,
+ 22EB2FDB15DAECB100DC6094 /* NC Database Classes */,
+ 22EC6F7E15DAC09900BF101E /* TSNCMetaController.h */,
+ 22EC6F7F15DAC09900BF101E /* TSNCMetaController.m */,
+ 22EC6F8215DAC6DD00BF101E /* TSNCMetaControllerDelegate.h */,
+ 22EE65B815D0610C0072CEDA /* TSAppDelegate.h */,
+ 22EE65B915D0610C0072CEDA /* TSAppDelegate.m */,
+ 22EE65AD15D0610C0072CEDA /* Supporting Files */,
+ );
+ path = ncMeta;
+ sourceTree = "<group>";
+ };
+ 22EE65AD15D0610C0072CEDA /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 22DB4A3715D6864100F1A926 /* menuicon_red.png */,
+ 22DB4A3815D6864100F1A926 /* menuicon_red@2x.png */,
+ 22DB4A3915D6864100F1A926 /* menuicon.png */,
+ 22DB4A3A15D6864100F1A926 /* menuicon@2x.png */,
+ 22EE65BB15D0610D0072CEDA /* MainMenu.xib */,
+ 22A317A015D25498002725D9 /* ncMeta.iconset */,
+ 22DA4E3B15D19AA8007148F3 /* ncMeta.entitlements */,
+ 22EE65AE15D0610C0072CEDA /* ncMeta-Info.plist */,
+ 22EE65AF15D0610C0072CEDA /* InfoPlist.strings */,
+ 22EE65B215D0610C0072CEDA /* main.m */,
+ 22EE65B415D0610C0072CEDA /* ncMeta-Prefix.pch */,
+ 22EE65B515D0610C0072CEDA /* Credits.rtf */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 22EE65A115D0610C0072CEDA /* ncMeta */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 22EE65C015D0610D0072CEDA /* Build configuration list for PBXNativeTarget "ncMeta" */;
+ buildPhases = (
+ 22EE659E15D0610C0072CEDA /* Sources */,
+ 22EE659F15D0610C0072CEDA /* Frameworks */,
+ 22EE65A015D0610C0072CEDA /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = ncMeta;
+ productName = ncMeta;
+ productReference = 22EE65A215D0610C0072CEDA /* ncMeta.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 22EE659915D0610C0072CEDA /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ CLASSPREFIX = TS;
+ LastUpgradeCheck = 0440;
+ ORGANIZATIONNAME = "Tim Schröder";
+ };
+ buildConfigurationList = 22EE659C15D0610C0072CEDA /* Build configuration list for PBXProject "ncMeta" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = 22EE659715D0610C0072CEDA;
+ productRefGroup = 22EE65A315D0610C0072CEDA /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 22EE65A115D0610C0072CEDA /* ncMeta */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 22EE65A015D0610C0072CEDA /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 22EE65B115D0610C0072CEDA /* InfoPlist.strings in Resources */,
+ 22EE65B715D0610C0072CEDA /* Credits.rtf in Resources */,
+ 22EE65BD15D0610D0072CEDA /* MainMenu.xib in Resources */,
+ 22A317A115D25498002725D9 /* ncMeta.iconset in Resources */,
+ 22DB4A3B15D6864100F1A926 /* menuicon_red.png in Resources */,
+ 22DB4A3C15D6864100F1A926 /* menuicon_red@2x.png in Resources */,
+ 22DB4A3D15D6864100F1A926 /* menuicon.png in Resources */,
+ 22DB4A3E15D6864100F1A926 /* menuicon@2x.png in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 22EE659E15D0610C0072CEDA /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 22EE65B315D0610C0072CEDA /* main.m in Sources */,
+ 22EE65BA15D0610C0072CEDA /* TSAppDelegate.m in Sources */,
+ 22B4DA0915D076E000D890FF /* TSDBMonitorController.m in Sources */,
+ 22A317B815D296F8002725D9 /* FMDatabase.m in Sources */,
+ 22A317B915D296F8002725D9 /* FMDatabaseAdditions.m in Sources */,
+ 22A317BA15D296F8002725D9 /* FMDatabasePool.m in Sources */,
+ 22A317BB15D296F8002725D9 /* FMDatabaseQueue.m in Sources */,
+ 22A317BC15D296F8002725D9 /* FMResultSet.m in Sources */,
+ 22C4CEB315D305310088F4F9 /* TSDBConnector.m in Sources */,
+ 222410C915D40EEB00AC2E93 /* TSAppRecord.m in Sources */,
+ 22EC6F8015DAC09900BF101E /* TSNCMetaController.m in Sources */,
+ 22EB2FD715DAC91800DC6094 /* TSWindowMonitorController.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ 22EE65AF15D0610C0072CEDA /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 22EE65B015D0610C0072CEDA /* en */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "<group>";
+ };
+ 22EE65B515D0610C0072CEDA /* Credits.rtf */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 22EE65B615D0610C0072CEDA /* en */,
+ );
+ name = Credits.rtf;
+ sourceTree = "<group>";
+ };
+ 22EE65BB15D0610D0072CEDA /* MainMenu.xib */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 22EE65BC15D0610D0072CEDA /* en */,
+ );
+ name = MainMenu.xib;
+ sourceTree = "<group>";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 22EE65BE15D0610D0072CEDA /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ARCHS = "$(ARCHS_STANDARD_64_BIT)";
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.8;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = macosx;
+ };
+ name = Debug;
+ };
+ 22EE65BF15D0610D0072CEDA /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ARCHS = "$(ARCHS_STANDARD_64_BIT)";
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = YES;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.8;
+ SDKROOT = macosx;
+ };
+ name = Release;
+ };
+ 22EE65C115D0610D0072CEDA /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_ENTITLEMENTS = ncMeta/ncMeta.entitlements;
+ CODE_SIGN_IDENTITY = "Mac Developer";
+ COMBINE_HIDPI_IMAGES = YES;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(SRCROOT)\"",
+ );
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "ncMeta/ncMeta-Prefix.pch";
+ INFOPLIST_FILE = "ncMeta/ncMeta-Info.plist";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE = "";
+ WRAPPER_EXTENSION = app;
+ };
+ name = Debug;
+ };
+ 22EE65C215D0610D0072CEDA /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_ENTITLEMENTS = ncMeta/ncMeta.entitlements;
+ CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application";
+ COMBINE_HIDPI_IMAGES = YES;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(SRCROOT)\"",
+ );
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "ncMeta/ncMeta-Prefix.pch";
+ INFOPLIST_FILE = "ncMeta/ncMeta-Info.plist";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE = "";
+ WRAPPER_EXTENSION = app;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 22EE659C15D0610C0072CEDA /* Build configuration list for PBXProject "ncMeta" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 22EE65BE15D0610D0072CEDA /* Debug */,
+ 22EE65BF15D0610D0072CEDA /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 22EE65C015D0610D0072CEDA /* Build configuration list for PBXNativeTarget "ncMeta" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 22EE65C115D0610D0072CEDA /* Debug */,
+ 22EE65C215D0610D0072CEDA /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 22EE659915D0610C0072CEDA /* Project object */;
+}
View
133 ncMeta App/ncMeta/FMDatabase.h
@@ -0,0 +1,133 @@
+#import <Foundation/Foundation.h>
+#import "sqlite3.h"
+#import "FMResultSet.h"
+#import "FMDatabasePool.h"
+
+
+#if ! __has_feature(objc_arc)
+ #define FMDBAutorelease(__v) ([__v autorelease]);
+ #define FMDBReturnAutoreleased FMDBAutorelease
+
+ #define FMDBRetain(__v) ([__v retain]);
+ #define FMDBReturnRetained FMDBRetain
+
+ #define FMDBRelease(__v) ([__v release]);
+#else
+ // -fobjc-arc
+ #define FMDBAutorelease(__v)
+ #define FMDBReturnAutoreleased(__v) (__v)
+
+ #define FMDBRetain(__v)
+ #define FMDBReturnRetained(__v) (__v)
+
+ #define FMDBRelease(__v)
+#endif
+
+
+@interface FMDatabase : NSObject {
+
+ sqlite3* _db;
+ NSString* _databasePath;
+ BOOL _logsErrors;
+ BOOL _crashOnErrors;
+ BOOL _traceExecution;
+ BOOL _checkedOut;
+ BOOL _shouldCacheStatements;
+ BOOL _isExecutingStatement;
+ BOOL _inTransaction;
+ int _busyRetryTimeout;
+
+ NSMutableDictionary *_cachedStatements;
+ NSMutableSet *_openResultSets;
+ NSMutableSet *_openFunctions;
+
+}
+
+
+@property (assign) BOOL traceExecution;
+@property (assign) BOOL checkedOut;
+@property (assign) int busyRetryTimeout;
+@property (assign) BOOL crashOnErrors;
+@property (assign) BOOL logsErrors;
+@property (retain) NSMutableDictionary *cachedStatements;
+
+
++ (id)databaseWithPath:(NSString*)inPath;
+- (id)initWithPath:(NSString*)inPath;
+
+- (BOOL)open;
+#if SQLITE_VERSION_NUMBER >= 3005000
+- (BOOL)openWithFlags:(int)flags;
+#endif
+- (BOOL)close;
+- (BOOL)goodConnection;
+- (void)clearCachedStatements;
+- (void)closeOpenResultSets;
+- (BOOL)hasOpenResultSets;
+
+// 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;
+- (NSError*)lastError;
+
+- (sqlite_int64)lastInsertRowId;
+
+- (sqlite3*)sqliteHandle;
+
+- (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError**)outErr, ...;
+- (BOOL)executeUpdate:(NSString*)sql, ...;
+- (BOOL)executeUpdateWithFormat:(NSString *)format, ...;
+- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
+- (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments;
+
+- (FMResultSet *)executeQuery:(NSString*)sql, ...;
+- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...;
+- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments;
+- (FMResultSet *)executeQuery:(NSString *)sql withParameterDictionary:(NSDictionary *)arguments;
+
+- (BOOL)rollback;
+- (BOOL)commit;
+- (BOOL)beginTransaction;
+- (BOOL)beginDeferredTransaction;
+- (BOOL)inTransaction;
+- (BOOL)shouldCacheStatements;
+- (void)setShouldCacheStatements:(BOOL)value;
+
+#if SQLITE_VERSION_NUMBER >= 3007000
+- (BOOL)startSavePointWithName:(NSString*)name error:(NSError**)outErr;
+- (BOOL)releaseSavePointWithName:(NSString*)name error:(NSError**)outErr;
+- (BOOL)rollbackToSavePointWithName:(NSString*)name error:(NSError**)outErr;
+- (NSError*)inSavePoint:(void (^)(BOOL *rollback))block;
+#endif
+
++ (BOOL)isSQLiteThreadSafe;
++ (NSString*)sqliteLibVersion;
+
+- (int)changes;
+
+- (void)makeFunctionNamed:(NSString*)name maximumArguments:(int)count withBlock:(void (^)(sqlite3_context *context, int argc, sqlite3_value **argv))block;
+
+@end
+
+@interface FMStatement : NSObject {
+ sqlite3_stmt *_statement;
+ NSString *_query;
+ long _useCount;
+}
+
+@property (assign) long useCount;
+@property (retain) NSString *query;
+@property (assign) sqlite3_stmt *statement;
+
+- (void)close;
+- (void)reset;
+
+@end
+
View
1,127 ncMeta App/ncMeta/FMDatabase.m
@@ -0,0 +1,1127 @@
+#import "FMDatabase.h"
+#import "unistd.h"
+#import <objc/runtime.h>
+
+@interface FMDatabase ()
+
+- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args;
+- (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args;
+@end
+
+@implementation FMDatabase
+@synthesize cachedStatements=_cachedStatements;
+@synthesize logsErrors=_logsErrors;
+@synthesize crashOnErrors=_crashOnErrors;
+@synthesize busyRetryTimeout=_busyRetryTimeout;
+@synthesize checkedOut=_checkedOut;
+@synthesize traceExecution=_traceExecution;
+
++ (id)databaseWithPath:(NSString*)aPath {
+ return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]);
+}
+
++ (NSString*)sqliteLibVersion {
+ return [NSString stringWithFormat:@"%s", sqlite3_libversion()];
+}
+
++ (BOOL)isSQLiteThreadSafe {
+ // make sure to read the sqlite headers on this guy!
+ return sqlite3_threadsafe();
+}
+
+- (id)initWithPath:(NSString*)aPath {
+
+ assert(sqlite3_threadsafe()); // whoa there big boy- gotta make sure sqlite it happy with what we're going to do.
+
+ self = [super init];
+
+ if (self) {
+ _databasePath = [aPath copy];
+ _openResultSets = [[NSMutableSet alloc] init];
+ _db = 0x00;
+ _logsErrors = 0x00;
+ _crashOnErrors = 0x00;
+ _busyRetryTimeout = 0x00;
+ }
+
+ return self;
+}
+
+- (void)finalize {
+ [self close];
+ [super finalize];
+}
+
+- (void)dealloc {
+ [self close];
+ FMDBRelease(_openResultSets);
+ FMDBRelease(_cachedStatements);
+ FMDBRelease(_databasePath);
+ FMDBRelease(_openFunctions);
+
+#if ! __has_feature(objc_arc)
+ [super dealloc];
+#endif
+}
+
+- (NSString *)databasePath {
+ return _databasePath;
+}
+
+- (sqlite3*)sqliteHandle {
+ return _db;
+}
+
+- (BOOL)open {
+ if (_db) {
+ return YES;
+ }
+
+ int err = sqlite3_open((_databasePath ? [_databasePath fileSystemRepresentation] : ":memory:"), &_db );
+ if(err != SQLITE_OK) {
+ NSLog(@"error opening!: %d", err);
+ return NO;
+ }
+
+ return YES;
+}
+
+#if SQLITE_VERSION_NUMBER >= 3005000
+- (BOOL)openWithFlags:(int)flags {
+ int err = sqlite3_open_v2((_databasePath ? [_databasePath fileSystemRepresentation] : ":memory:"), &_db, flags, NULL /* Name of VFS module to use */);
+ if(err != SQLITE_OK) {
+ NSLog(@"error opening!: %d", err);
+ return NO;
+ }
+ return YES;
+}
+#endif
+
+
+- (BOOL)close {
+
+ [self clearCachedStatements];
+ [self closeOpenResultSets];
+
+ if (!_db) {
+ return YES;
+ }
+
+ int rc;
+ BOOL retry;
+ int numberOfRetries = 0;
+ BOOL triedFinalizingOpenStatements = NO;
+
+ do {
+ retry = NO;
+ rc = sqlite3_close(_db);
+
+ if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
+
+ retry = YES;
+ usleep(20);
+
+ if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
+ NSLog(@"%s:%d", __FUNCTION__, __LINE__);
+ NSLog(@"Database busy, unable to close");
+ return NO;
+ }
+
+ if (!triedFinalizingOpenStatements) {
+ triedFinalizingOpenStatements = YES;
+ sqlite3_stmt *pStmt;
+ while ((pStmt = sqlite3_next_stmt(_db, 0x00)) !=0) {
+ NSLog(@"Closing leaked statement");
+ sqlite3_finalize(pStmt);
+ }
+ }
+ }
+ else if (SQLITE_OK != rc) {
+ NSLog(@"error closing!: %d", rc);
+ }
+ }
+ while (retry);
+
+ _db = nil;
+ return YES;
+}
+
+- (void)clearCachedStatements {
+
+ for (FMStatement *cachedStmt in [_cachedStatements objectEnumerator]) {
+ [cachedStmt close];
+ }
+
+ [_cachedStatements removeAllObjects];
+}
+
+- (BOOL)hasOpenResultSets {
+ return [_openResultSets count] > 0;
+}
+
+- (void)closeOpenResultSets {
+
+ //Copy the set so we don't get mutation errors
+ NSMutableSet *openSetCopy = FMDBReturnAutoreleased([_openResultSets copy]);
+ for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) {
+ FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue];
+
+ [rs setParentDB:nil];
+ [rs close];
+
+ [_openResultSets removeObject:rsInWrappedInATastyValueMeal];
+ }
+}
+
+- (void)resultSetDidClose:(FMResultSet *)resultSet {
+ NSValue *setValue = [NSValue valueWithNonretainedObject:resultSet];
+
+ [_openResultSets removeObject:setValue];
+}
+
+- (FMStatement*)cachedStatementForQuery:(NSString*)query {
+ return [_cachedStatements objectForKey:query];
+}
+
+- (void)setCachedStatement:(FMStatement*)statement forQuery:(NSString*)query {
+
+ query = [query copy]; // in case we got handed in a mutable string...
+
+ [statement setQuery:query];
+
+ [_cachedStatements setObject:statement forKey:query];
+
+ FMDBRelease(query);
+}
+
+
+- (BOOL)rekey:(NSString*)key {
+#ifdef SQLITE_HAS_CODEC
+ if (!key) {
+ return NO;
+ }
+
+ int rc = sqlite3_rekey(_db, [key UTF8String], (int)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], (int)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)warnInUse {
+ NSLog(@"The FMDatabase %@ is currently in use.", self);
+
+#ifndef NS_BLOCK_ASSERTIONS
+ if (_crashOnErrors) {
+ abort();
+ NSAssert1(false, @"The FMDatabase %@ is currently in use.", self);
+ }
+#endif
+}
+
+- (BOOL)databaseExists {
+
+ if (!_db) {
+
+ NSLog(@"The FMDatabase %@ is not open.", self);
+
+ #ifndef NS_BLOCK_ASSERTIONS
+ if (_crashOnErrors) {
+ abort();
+ NSAssert1(false, @"The FMDatabase %@ is not open.", self);
+ }
+ #endif
+
+ return NO;
+ }
+
+ return YES;
+}
+
+- (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);
+}
+
+- (NSError*)lastError {
+ return [NSError errorWithDomain:@"FMDatabase" code:sqlite3_errcode(_db) userInfo:[NSDictionary dictionaryWithObject:[self lastErrorMessage] forKey:NSLocalizedDescriptionKey]];
+}
+
+- (sqlite_int64)lastInsertRowId {
+
+ if (_isExecutingStatement) {
+ [self warnInUse];
+ return NO;
+ }
+
+ _isExecutingStatement = YES;
+
+ sqlite_int64 ret = sqlite3_last_insert_rowid(_db);
+
+ _isExecutingStatement = NO;
+
+ return ret;
+}
+
+- (int)changes {
+ if (_isExecutingStatement) {
+ [self warnInUse];
+ return 0;
+ }
+
+ _isExecutingStatement = YES;
+
+ int ret = sqlite3_changes(_db);
+
+ _isExecutingStatement = 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], (int)[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(long long)) == 0) {
+ sqlite3_bind_int64(pStmt, idx, [obj longLongValue]);
+ }
+ else if (strcmp([obj objCType], @encode(unsigned long long)) == 0) {
+ sqlite3_bind_int64(pStmt, idx, [obj unsignedLongLongValue]);
+ }
+ 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);
+ }
+}
+
+- (void)extractSQL:(NSString *)sql argumentsList:(va_list)args intoString:(NSMutableString *)cleanedSQL arguments:(NSMutableArray *)arguments {
+
+ NSUInteger length = [sql length];
+ unichar last = '\0';
+ for (NSUInteger i = 0; i < length; ++i) {
+ id arg = nil;
+ unichar current = [sql characterAtIndex:i];
+ unichar add = current;
+ if (last == '%') {
+ switch (current) {
+ case '@':
+ arg = va_arg(args, id); break;
+ case 'c':
+ // warning: second argument to 'va_arg' is of promotable type 'char'; this va_arg has undefined behavior because arguments will be promoted to 'int'
+ arg = [NSString stringWithFormat:@"%c", va_arg(args, int)]; break;
+ case 's':
+ arg = [NSString stringWithUTF8String:va_arg(args, char*)]; break;
+ case 'd':
+ case 'D':
+ case 'i':
+ arg = [NSNumber numberWithInt:va_arg(args, int)]; break;
+ case 'u':
+ case 'U':
+ arg = [NSNumber numberWithUnsignedInt:va_arg(args, unsigned int)]; break;
+ case 'h':
+ i++;
+ if (i < length && [sql characterAtIndex:i] == 'i') {
+ // warning: second argument to 'va_arg' is of promotable type 'short'; this va_arg has undefined behavior because arguments will be promoted to 'int'
+ arg = [NSNumber numberWithShort:va_arg(args, int)];
+ }
+ else if (i < length && [sql characterAtIndex:i] == 'u') {
+ // warning: second argument to 'va_arg' is of promotable type 'unsigned short'; this va_arg has undefined behavior because arguments will be promoted to 'int'
+ arg = [NSNumber numberWithUnsignedShort:va_arg(args, uint)];
+ }
+ else {
+ i--;
+ }
+ break;
+ case 'q':
+ i++;
+ if (i < length && [sql characterAtIndex:i] == 'i') {
+ arg = [NSNumber numberWithLongLong:va_arg(args, long long)];
+ }
+ else if (i < length && [sql characterAtIndex:i] == 'u') {
+ arg = [NSNumber numberWithUnsignedLongLong:va_arg(args, unsigned long long)];
+ }
+ else {
+ i--;
+ }
+ break;
+ case 'f':
+ arg = [NSNumber numberWithDouble:va_arg(args, double)]; break;
+ case 'g':
+ // warning: second argument to 'va_arg' is of promotable type 'float'; this va_arg has undefined behavior because arguments will be promoted to 'double'
+ arg = [NSNumber numberWithFloat:va_arg(args, double)]; break;
+ case 'l':
+ i++;
+ if (i < length) {
+ unichar next = [sql characterAtIndex:i];
+ if (next == 'l') {
+ i++;
+ if (i < length && [sql characterAtIndex:i] == 'd') {
+ //%lld
+ arg = [NSNumber numberWithLongLong:va_arg(args, long long)];
+ }
+ else if (i < length && [sql characterAtIndex:i] == 'u') {
+ //%llu
+ arg = [NSNumber numberWithUnsignedLongLong:va_arg(args, unsigned long long)];
+ }
+ else {
+ i--;
+ }
+ }
+ else if (next == 'd') {
+ //%ld
+ arg = [NSNumber numberWithLong:va_arg(args, long)];
+ }
+ else if (next == 'u') {
+ //%lu
+ arg = [NSNumber numberWithUnsignedLong:va_arg(args, unsigned long)];
+ }
+ else {
+ i--;
+ }
+ }
+ else {
+ i--;
+ }
+ break;
+ default:
+ // something else that we can't interpret. just pass it on through like normal
+ break;
+ }
+ }
+ else if (current == '%') {
+ // percent sign; skip this character
+ add = '\0';
+ }
+
+ if (arg != nil) {
+ [cleanedSQL appendString:@"?"];
+ [arguments addObject:arg];
+ }
+ else if (add != '\0') {
+ [cleanedSQL appendFormat:@"%C", add];
+ }
+ last = current;
+ }
+}
+
+- (FMResultSet *)executeQuery:(NSString *)sql withParameterDictionary:(NSDictionary *)arguments {
+ return [self executeQuery:sql withArgumentsInArray:nil orDictionary:arguments orVAList:nil];
+}
+
+- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
+
+ if (![self databaseExists]) {
+ return 0x00;
+ }
+
+ if (_isExecutingStatement) {
+ [self warnInUse];
+ return 0x00;
+ }
+
+ _isExecutingStatement = YES;
+
+ int rc = 0x00;
+ sqlite3_stmt *pStmt = 0x00;
+ FMStatement *statement = 0x00;
+ FMResultSet *rs = 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 || SQLITE_LOCKED == rc) {
+ retry = YES;
+ usleep(20);
+
+ if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
+ NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
+ NSLog(@"Database busy");
+ sqlite3_finalize(pStmt);
+ _isExecutingStatement = NO;
+ return nil;
+ }
+ }
+ else if (SQLITE_OK != rc) {
+
+ if (_logsErrors) {
+ NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+ NSLog(@"DB Query: %@", sql);
+ NSLog(@"DB Path: %@", _databasePath);
+#ifndef NS_BLOCK_ASSERTIONS
+ if (_crashOnErrors) {
+ abort();
+ NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+ }
+#endif
+ }
+
+ sqlite3_finalize(pStmt);
+ _isExecutingStatement = NO;
+ return nil;
+ }
+ }
+ while (retry);
+ }
+
+ id obj;
+ int idx = 0;
+ int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)
+
+ // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support
+ if (dictionaryArgs) {
+
+ for (NSString *dictionaryKey in [dictionaryArgs allKeys]) {
+
+ // Prefix the key with a colon.
+ NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey];
+
+ // Get the index for the parameter name.
+ int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]);
+
+ FMDBRelease(parameterName);
+
+ if (namedIdx > 0) {
+ // Standard binding from here.
+ [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt];
+ }
+ else {
+ NSLog(@"Could not find index for %@", dictionaryKey);
+ }
+ }
+
+ // we need the count of params to avoid an error below.
+ idx = (int) [[dictionaryArgs allKeys] count];
+ }
+ else {
+
+ while (idx < queryCount) {
+
+ if (arrayArgs) {
+ obj = [arrayArgs objectAtIndex:idx];
+ }
+ else {
+ 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);
+ _isExecutingStatement = NO;
+ return nil;
+ }
+
+ FMDBRetain(statement); // to balance the release below
+
+ if (!statement) {
+ statement = [[FMStatement alloc] init];
+ [statement setStatement:pStmt];
+
+ if (_shouldCacheStatements) {
+ [self setCachedStatement:statement forQuery:sql];
+ }
+ }
+
+ // the statement gets closed in rs's dealloc or [rs close];
+ rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
+ [rs setQuery:sql];
+
+ NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
+ [_openResultSets addObject:openResultSet];
+
+ [statement setUseCount:[statement useCount] + 1];
+
+ FMDBRelease(statement);
+
+ _isExecutingStatement = NO;
+
+ return rs;
+}
+
+- (FMResultSet *)executeQuery:(NSString*)sql, ... {
+ va_list args;
+ va_start(args, sql);
+
+ id result = [self executeQuery:sql withArgumentsInArray:nil orDictionary:nil orVAList:args];
+
+ va_end(args);
+ return result;
+}
+
+- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ... {
+ va_list args;
+ va_start(args, format);
+
+ NSMutableString *sql = [NSMutableString stringWithCapacity:[format length]];
+ NSMutableArray *arguments = [NSMutableArray array];
+ [self extractSQL:format argumentsList:args intoString:sql arguments:arguments];
+
+ va_end(args);
+
+ return [self executeQuery:sql withArgumentsInArray:arguments];
+}
+
+- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments {
+ return [self executeQuery:sql withArgumentsInArray:arguments orDictionary:nil orVAList:nil];
+}
+
+- (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
+
+ if (![self databaseExists]) {
+ return NO;
+ }
+
+ if (_isExecutingStatement) {
+ [self warnInUse];
+ return NO;
+ }
+
+ _isExecutingStatement = 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 || SQLITE_LOCKED == rc) {
+ retry = YES;
+ usleep(20);
+
+ if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
+ NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
+ NSLog(@"Database busy");
+ sqlite3_finalize(pStmt);
+ _isExecutingStatement = NO;
+ return NO;
+ }
+ }
+ else if (SQLITE_OK != rc) {
+
+ if (_logsErrors) {
+ NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+ NSLog(@"DB Query: %@", sql);
+ NSLog(@"DB Path: %@", _databasePath);
+#ifndef NS_BLOCK_ASSERTIONS
+ if (_crashOnErrors) {
+ abort();
+ NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+ }
+#endif
+ }
+
+ sqlite3_finalize(pStmt);
+
+ if (outErr) {
+ *outErr = [NSError errorWithDomain:[NSString stringWithUTF8String:sqlite3_errmsg(_db)] code:rc userInfo:nil];
+ }
+
+ _isExecutingStatement = NO;
+ return NO;
+ }
+ }
+ while (retry);
+ }
+
+ id obj;
+ int idx = 0;
+ int queryCount = sqlite3_bind_parameter_count(pStmt);
+
+ // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support
+ if (dictionaryArgs) {
+
+ for (NSString *dictionaryKey in [dictionaryArgs allKeys]) {
+
+ // Prefix the key with a colon.
+ NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey];
+
+ // Get the index for the parameter name.
+ int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]);
+
+ FMDBRelease(parameterName);
+
+ if (namedIdx > 0) {
+ // Standard binding from here.
+ [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt];
+ }
+ else {
+ NSLog(@"Could not find index for %@", dictionaryKey);
+ }
+ }
+
+ // we need the count of params to avoid an error below.
+ idx = (int) [[dictionaryArgs allKeys] count];
+ }
+ else {
+
+ while (idx < queryCount) {
+
+ if (arrayArgs) {
+ obj = [arrayArgs objectAtIndex:idx];
+ }
+ else {
+ 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);
+ _isExecutingStatement = NO;
+ return NO;
+ }
+