228 lines
5.3 KiB
Mathematica
228 lines
5.3 KiB
Mathematica
|
//
|
|||
|
// RSDatabaseQueue.m
|
|||
|
// RSDatabase
|
|||
|
//
|
|||
|
// Created by Brent Simmons on 10/19/13.
|
|||
|
// Copyright (c) 2013 Ranchero Software, LLC. All rights reserved.
|
|||
|
//
|
|||
|
|
|||
|
#import "RSDatabaseQueue.h"
|
|||
|
#import <sqlite3.h>
|
|||
|
|
|||
|
// This has been deprecated — use DatabaseQueue instead.
|
|||
|
|
|||
|
@interface RSDatabaseQueue ()
|
|||
|
|
|||
|
@property (nonatomic, strong, readwrite) NSString *databasePath;
|
|||
|
@property (nonatomic, assign) BOOL excludeFromBackup;
|
|||
|
@property (nonatomic, strong, readonly) dispatch_queue_t serialDispatchQueue;
|
|||
|
@property (nonatomic) BOOL closing;
|
|||
|
@property (nonatomic) BOOL closed;
|
|||
|
|
|||
|
@end
|
|||
|
|
|||
|
|
|||
|
@implementation RSDatabaseQueue
|
|||
|
|
|||
|
|
|||
|
#pragma mark - Init
|
|||
|
|
|||
|
- (instancetype)initWithFilepath:(NSString *)filepath excludeFromBackup:(BOOL)excludeFromBackup {
|
|||
|
|
|||
|
self = [super init];
|
|||
|
if (self == nil)
|
|||
|
return self;
|
|||
|
|
|||
|
_databasePath = filepath;
|
|||
|
|
|||
|
_serialDispatchQueue = dispatch_queue_create([[NSString stringWithFormat:@"RSDatabaseQueue serial queue - %@", filepath.lastPathComponent] UTF8String], DISPATCH_QUEUE_SERIAL);
|
|||
|
|
|||
|
_excludeFromBackup = excludeFromBackup;
|
|||
|
|
|||
|
return self;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
#pragma mark - Database
|
|||
|
|
|||
|
- (FMDatabase *)database {
|
|||
|
|
|||
|
/*I've always done it this way -- kept a per-thread database in the threadDictionary -- and I know it's solid. Maybe it's not necessary with a serial queue, but my understanding was that SQLite wanted a different database per thread (and a serial queue may run on different threads).*/
|
|||
|
|
|||
|
if (self.closed) {
|
|||
|
return nil;
|
|||
|
}
|
|||
|
|
|||
|
NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
|
|||
|
FMDatabase *database = threadDictionary[self.databasePath];
|
|||
|
|
|||
|
if (!database || !database.open) {
|
|||
|
|
|||
|
database = [FMDatabase databaseWithPath:self.databasePath];
|
|||
|
[database open];
|
|||
|
[database executeUpdate:@"PRAGMA synchronous = 1;"];
|
|||
|
[database setShouldCacheStatements:YES];
|
|||
|
|
|||
|
if ([self.delegate respondsToSelector:@selector(makeFunctionsForDatabase:queue:)]) {
|
|||
|
[self.delegate makeFunctionsForDatabase:database queue:self];
|
|||
|
}
|
|||
|
|
|||
|
threadDictionary[self.databasePath] = database;
|
|||
|
|
|||
|
if (self.excludeFromBackup) {
|
|||
|
|
|||
|
static dispatch_once_t onceToken;
|
|||
|
dispatch_once(&onceToken, ^{
|
|||
|
NSURL *URL = [NSURL fileURLWithPath:self.databasePath isDirectory:NO];
|
|||
|
NSError *error = nil;
|
|||
|
[URL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:&error];
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return database;
|
|||
|
}
|
|||
|
|
|||
|
#pragma mark - API
|
|||
|
|
|||
|
- (void)createTablesUsingStatements:(NSString *)createStatements {
|
|||
|
|
|||
|
[self runInDatabase:^(FMDatabase *database) {
|
|||
|
[self runCreateStatements:createStatements database:database];
|
|||
|
}];
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
- (void)createTablesUsingStatementsSync:(NSString *)createStatements {
|
|||
|
|
|||
|
[self runInDatabaseSync:^(FMDatabase *database) {
|
|||
|
[self runCreateStatements:createStatements database:database];
|
|||
|
}];
|
|||
|
}
|
|||
|
|
|||
|
- (void)runCreateStatements:(NSString *)createStatements database:(FMDatabase *)database {
|
|||
|
|
|||
|
[createStatements enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
|
|||
|
if ([line.lowercaseString hasPrefix:@"create "]) {
|
|||
|
[database executeUpdate:line];
|
|||
|
}
|
|||
|
*stop = NO;
|
|||
|
}];
|
|||
|
}
|
|||
|
|
|||
|
- (void)update:(RSDatabaseBlock)updateBlock {
|
|||
|
|
|||
|
dispatch_async(self.serialDispatchQueue, ^{
|
|||
|
[self runInTransaction:updateBlock];
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
- (void)updateSync:(RSDatabaseBlock)updateBlock {
|
|||
|
|
|||
|
dispatch_sync(self.serialDispatchQueue, ^{
|
|||
|
[self runInTransaction:updateBlock];
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
- (void)runInTransaction:(RSDatabaseBlock)databaseBlock {
|
|||
|
|
|||
|
@autoreleasepool {
|
|||
|
FMDatabase *database = [self database];
|
|||
|
[database beginTransaction];
|
|||
|
databaseBlock(database);
|
|||
|
[database commit];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
- (void)runInDatabase:(RSDatabaseBlock)databaseBlock {
|
|||
|
|
|||
|
dispatch_async(self.serialDispatchQueue, ^{
|
|||
|
@autoreleasepool {
|
|||
|
databaseBlock([self database]);
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
- (void)runInDatabaseSync:(RSDatabaseBlock)databaseBlock {
|
|||
|
|
|||
|
dispatch_sync(self.serialDispatchQueue, ^{
|
|||
|
@autoreleasepool {
|
|||
|
databaseBlock([self database]);
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
- (void)fetch:(RSDatabaseBlock)fetchBlock {
|
|||
|
|
|||
|
[self runInDatabase:fetchBlock];
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
- (void)fetchSync:(RSDatabaseBlock)fetchBlock {
|
|||
|
|
|||
|
dispatch_sync(self.serialDispatchQueue, ^{
|
|||
|
@autoreleasepool {
|
|||
|
fetchBlock([self database]);
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
- (void)vacuum {
|
|||
|
|
|||
|
dispatch_async(self.serialDispatchQueue, ^{
|
|||
|
@autoreleasepool {
|
|||
|
[[self database] executeUpdate:@"vacuum;"];
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
- (void)vacuumIfNeeded {
|
|||
|
|
|||
|
NSTimeInterval interval = (24 * 60 * 60) * 6; // 6 days
|
|||
|
[self vacuumIfNeeded:@"lastVacuumDate" intervalBetweenVacuums:interval];
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
- (void)vacuumIfNeeded:(NSString *)defaultsKey intervalBetweenVacuums:(NSTimeInterval)intervalBetweenVacuums {
|
|||
|
|
|||
|
NSDate *lastVacuumDate = [[NSUserDefaults standardUserDefaults] objectForKey:defaultsKey];
|
|||
|
if (!lastVacuumDate || ![lastVacuumDate isKindOfClass:[NSDate class]]) {
|
|||
|
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:defaultsKey];
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
NSDate *cutoffDate = [[NSDate date] dateByAddingTimeInterval: -(intervalBetweenVacuums)];
|
|||
|
if ([cutoffDate earlierDate:lastVacuumDate] == lastVacuumDate) {
|
|||
|
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:defaultsKey];
|
|||
|
[self vacuum];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
- (NSArray *)arrayWithSingleColumnResultSet:(FMResultSet *)rs {
|
|||
|
|
|||
|
NSMutableArray *results = [NSMutableArray new];
|
|||
|
while ([rs next]) {
|
|||
|
id oneObject = [rs objectForColumnIndex:0];
|
|||
|
if (oneObject) {
|
|||
|
[results addObject:oneObject];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return [results copy];
|
|||
|
}
|
|||
|
|
|||
|
- (void)close {
|
|||
|
self.closing = YES;
|
|||
|
[self runInDatabaseSync:^(FMDatabase *database) {
|
|||
|
self.closed = YES;
|
|||
|
[database close];
|
|||
|
}];
|
|||
|
}
|
|||
|
|
|||
|
@end
|
|||
|
|