mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-01-28 17:59:31 +01:00
228 lines
5.3 KiB
Objective-C
Executable File
228 lines
5.3 KiB
Objective-C
Executable File
//
|
||
// 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
|
||
|