Authored by Thomas Rasch

o RMDatabaseCache now uses the FMDatabaseQueue

... ... @@ -28,27 +28,15 @@
#import <UIKit/UIKit.h>
#import "RMTileCache.h"
@class RMTileCacheDAO;
@class FMDatabase;
@interface RMDatabaseCache : NSObject <RMTileCache> {
// Database
FMDatabase *db;
@interface RMDatabaseCache : NSObject <RMTileCache>
{
NSString *databasePath;
NSUInteger tileCount;
NSOperationQueue *writeQueue;
NSRecursiveLock *writeQueueLock;
// Cache
RMCachePurgeStrategy purgeStrategy;
NSUInteger capacity;
NSUInteger minimalPurge;
}
@property (nonatomic, retain) NSString *databasePath;
+ (NSString *)dbPathUsingCacheDir:(BOOL)useCacheDir;
- (id)initWithDatabase:(NSString *)path;
- (id)initUsingCacheDir:(BOOL)useCacheDir;
... ...
... ... @@ -27,6 +27,7 @@
#import "RMDatabaseCache.h"
#import "FMDatabase.h"
#import "FMDatabaseQueue.h"
#import "RMTileImage.h"
#import "RMTile.h"
... ... @@ -44,6 +45,19 @@
#pragma mark -
@implementation RMDatabaseCache
{
// Database
FMDatabaseQueue *queue;
NSUInteger tileCount;
NSOperationQueue *writeQueue;
NSRecursiveLock *writeQueueLock;
// Cache
RMCachePurgeStrategy purgeStrategy;
NSUInteger capacity;
NSUInteger minimalPurge;
}
@synthesize databasePath;
... ... @@ -51,23 +65,22 @@
{
NSArray *paths;
if (useCacheDir) {
if (useCacheDir)
paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
} else {
else
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
}
if ([paths count] > 0) // Should only be one...
{
NSString *cachePath = [paths objectAtIndex:0];
// check for existence of cache directory
if ( ![[NSFileManager defaultManager] fileExistsAtPath: cachePath])
if ( ![[NSFileManager defaultManager] fileExistsAtPath: cachePath])
{
// create a new cache directory
[[NSFileManager defaultManager] createDirectoryAtPath:cachePath withIntermediateDirectories:NO attributes:nil error:nil];
}
NSString *filename = [NSString stringWithFormat:@"RMTileCache.db"];
return [cachePath stringByAppendingPathComponent:filename];
}
... ... @@ -77,13 +90,13 @@
- (void)configureDBForFirstUse
{
[[db executeQuery:@"PRAGMA synchronous=OFF"] close];
[[db executeQuery:@"PRAGMA journal_mode=OFF"] close];
[[db executeQuery:@"PRAGMA cache-size=100"] close];
[[db executeQuery:@"PRAGMA count_changes=OFF"] close];
[db executeUpdate:@"CREATE TABLE IF NOT EXISTS ZCACHE (tile_hash INTEGER NOT NULL, cache_key VARCHAR(25) NOT NULL, last_used DOUBLE NOT NULL, data BLOB NOT NULL)"];
[db executeUpdate:@"CREATE UNIQUE INDEX IF NOT EXISTS main_index ON ZCACHE(tile_hash, cache_key)"];
[db executeUpdate:@"CREATE INDEX IF NOT EXISTS last_used_index ON ZCACHE(last_used)"];
[[[queue database] executeQuery:@"PRAGMA synchronous=OFF"] close];
[[[queue database] executeQuery:@"PRAGMA journal_mode=OFF"] close];
[[[queue database] executeQuery:@"PRAGMA cache-size=100"] close];
[[[queue database] executeQuery:@"PRAGMA count_changes=OFF"] close];
[[queue database] executeUpdate:@"CREATE TABLE IF NOT EXISTS ZCACHE (tile_hash INTEGER NOT NULL, cache_key VARCHAR(25) NOT NULL, last_used DOUBLE NOT NULL, data BLOB NOT NULL)"];
[[queue database] executeUpdate:@"CREATE UNIQUE INDEX IF NOT EXISTS main_index ON ZCACHE(tile_hash, cache_key)"];
[[queue database] executeUpdate:@"CREATE INDEX IF NOT EXISTS last_used_index ON ZCACHE(last_used)"];
}
- (id)initWithDatabase:(NSString *)path
... ... @@ -99,19 +112,20 @@
RMLog(@"Opening database at %@", path);
db = [[FMDatabase alloc] initWithPath:path];
if (![db open])
queue = [[FMDatabaseQueue databaseQueueWithPath:path] retain];
if (!queue || ![[queue database] open])
{
RMLog(@"Could not connect to database - %@", [db lastErrorMessage]);
RMLog(@"Could not connect to database - %@", [[queue database] lastErrorMessage]);
[[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
if (![db open]) {
[self release];
return nil;
}
[self release];
return nil;
}
[db setCrashOnErrors:TRUE];
[db setShouldCacheStatements:TRUE];
[[queue database] setCrashOnErrors:TRUE];
[[queue database] setShouldCacheStatements:TRUE];
[self configureDBForFirstUse];
... ... @@ -132,7 +146,7 @@
[writeQueue release]; writeQueue = nil;
[writeQueueLock unlock];
[writeQueueLock release]; writeQueueLock = nil;
[db close]; [db release]; db = nil;
[queue release]; queue = nil;
[super dealloc];
}
... ... @@ -155,30 +169,35 @@
{
// RMLog(@"DB cache check for tile %d %d %d", tile.x, tile.y, tile.zoom);
__block UIImage *cachedImage = nil;
[writeQueueLock lock];
FMResultSet *results = [db executeQuery:@"SELECT data FROM ZCACHE WHERE tile_hash = ? AND cache_key = ?", [RMTileCache tileHash:tile], aCacheKey];
[queue inDatabase:^(FMDatabase *db)
{
FMResultSet *results = [db executeQuery:@"SELECT data FROM ZCACHE WHERE tile_hash = ? AND cache_key = ?", [RMTileCache tileHash:tile], aCacheKey];
if ([db hadError]) {
RMLog(@"DB error while fetching tile data: %@", [db lastErrorMessage]);
return nil;
}
if ([db hadError])
{
RMLog(@"DB error while fetching tile data: %@", [db lastErrorMessage]);
return;
}
NSData *data = nil;
UIImage *cachedImage = nil;
NSData *data = nil;
if ([results next]) {
data = [results dataForColumnIndex:0];
if (data) cachedImage = [UIImage imageWithData:data];
}
if ([results next])
{
data = [results dataForColumnIndex:0];
if (data) cachedImage = [UIImage imageWithData:data];
}
[results close];
[results close];
}];
[writeQueueLock unlock];
if (capacity != 0 && purgeStrategy == RMCachePurgeStrategyLRU) {
if (capacity != 0 && purgeStrategy == RMCachePurgeStrategyLRU)
[self touchTile:tile withKey:aCacheKey];
}
// RMLog(@"DB cache hit tile %d %d %d (%@)", tile.x, tile.y, tile.zoom, [RMTileCache tileHash:tile]);
... ... @@ -193,32 +212,42 @@
if (capacity != 0)
{
NSUInteger tilesInDb = [self count];
if (capacity <= tilesInDb) {
if (capacity <= tilesInDb)
[self purgeTiles:MAX(minimalPurge, 1+tilesInDb-capacity)];
}
// RMLog(@"DB cache insert tile %d %d %d (%@)", tile.x, tile.y, tile.zoom, [RMTileCache tileHash:tile]);
// Don't add new images to the database while there are still more than kWriteQueueLimit
// insert operations pending. This prevents some memory issues.
BOOL skipThisTile = NO;
[writeQueueLock lock];
if ([writeQueue operationCount] > kWriteQueueLimit) skipThisTile = YES;
if ([writeQueue operationCount] > kWriteQueueLimit)
skipThisTile = YES;
[writeQueueLock unlock];
if (skipThisTile) return;
if (skipThisTile)
return;
[writeQueue addOperationWithBlock:^{
// RMLog(@"addData\t%d", tileHash);
__block BOOL result = NO;
[writeQueueLock lock];
BOOL result = [db executeUpdate:@"INSERT OR IGNORE INTO ZCACHE (tile_hash, cache_key, last_used, data) VALUES (?, ?, ?, ?)", [RMTileCache tileHash:tile], aCacheKey, [NSDate date], data];
[queue inDatabase:^(FMDatabase *db)
{
result = [db executeUpdate:@"INSERT OR IGNORE INTO ZCACHE (tile_hash, cache_key, last_used, data) VALUES (?, ?, ?, ?)", [RMTileCache tileHash:tile], aCacheKey, [NSDate date], data];
}];
[writeQueueLock unlock];
if (result == NO)
{
RMLog(@"Error occured adding data");
} else
else
tileCount++;
}];
}
... ... @@ -233,15 +262,21 @@
- (NSUInteger)countTiles
{
__block NSUInteger count = 0;
[writeQueueLock lock];
NSUInteger count = 0;
FMResultSet *results = [db executeQuery:@"SELECT COUNT(tile_hash) FROM ZCACHE"];
if ([results next])
count = [results intForColumnIndex:0];
else
RMLog(@"Unable to count columns");
[results close];
[queue inDatabase:^(FMDatabase *db)
{
FMResultSet *results = [db executeQuery:@"SELECT COUNT(tile_hash) FROM ZCACHE"];
if ([results next])
count = [results intForColumnIndex:0];
else
RMLog(@"Unable to count columns");
[results close];
}];
[writeQueueLock unlock];
... ... @@ -250,30 +285,43 @@
- (void)purgeTiles:(NSUInteger)count
{
RMLog(@"purging %u old tiles from db cache", count);
RMLog(@"purging %u old tiles from the db cache", count);
[writeQueueLock lock];
BOOL result = [db executeUpdate:@"DELETE FROM ZCACHE WHERE tile_hash IN (SELECT tile_hash FROM ZCACHE ORDER BY last_used LIMIT ?)", [NSNumber numberWithUnsignedInt:count]];
[db executeQuery:@"VACUUM"];
tileCount = [self countTiles];
[queue inDatabase:^(FMDatabase *db)
{
BOOL result = [db executeUpdate:@"DELETE FROM ZCACHE WHERE tile_hash IN (SELECT tile_hash FROM ZCACHE ORDER BY last_used LIMIT ?)", [NSNumber numberWithUnsignedInt:count]];
if (result == NO)
RMLog(@"Error purging cache");
[[db executeQuery:@"VACUUM"] close];
}];
[writeQueueLock unlock];
if (result == NO) {
RMLog(@"Error purging cache");
}
tileCount = [self countTiles];
}
- (void)removeAllCachedImages
{
RMLog(@"removing all tiles from the db cache");
[writeQueue addOperationWithBlock:^{
[writeQueueLock lock];
BOOL result = [db executeUpdate:@"DELETE FROM ZCACHE"];
[db executeQuery:@"VACUUM"];
[writeQueueLock unlock];
if (result == NO) {
RMLog(@"Error purging cache");
}
[queue inDatabase:^(FMDatabase *db)
{
BOOL result = [db executeUpdate:@"DELETE FROM ZCACHE"];
if (result == NO)
RMLog(@"Error purging cache");
[[db executeQuery:@"VACUUM"] close];
}];
[writeQueueLock unlock];
tileCount = [self countTiles];
}];
... ... @@ -283,18 +331,23 @@
{
[writeQueue addOperationWithBlock:^{
[writeQueueLock lock];
BOOL result = [db executeUpdate:@"UPDATE ZCACHE SET last_used = ? WHERE tile_hash = ? AND cache_key = ?", [NSDate date], [RMTileCache tileHash:tile], cacheKey];
[writeQueueLock unlock];
if (result == NO) {
RMLog(@"Error touching tile");
}
[queue inDatabase:^(FMDatabase *db)
{
BOOL result = [db executeUpdate:@"UPDATE ZCACHE SET last_used = ? WHERE tile_hash = ? AND cache_key = ?", [NSDate date], [RMTileCache tileHash:tile], cacheKey];
if (result == NO)
RMLog(@"Error touching tile");
}];
[writeQueueLock unlock];
}];
}
- (void)didReceiveMemoryWarning
{
RMLog(@"Low memory in the database tilecache");
[writeQueueLock lock];
[writeQueue cancelAllOperations];
[writeQueueLock unlock];
... ...