2024-11-08 21:52:53 -08:00

228 lines
5.3 KiB
Objective-C
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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