...
|
...
|
@@ -26,10 +26,23 @@ |
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
#import "RMDatabaseCache.h"
|
|
|
#import "RMTileCacheDAO.h"
|
|
|
#import "FMDatabase.h"
|
|
|
#import "RMTileImage.h"
|
|
|
#import "RMTile.h"
|
|
|
|
|
|
#define kWriteQueueLimit 25
|
|
|
|
|
|
@interface RMDatabaseCache ()
|
|
|
|
|
|
- (NSUInteger)count;
|
|
|
- (NSUInteger)countTiles;
|
|
|
- (void)touchTile:(RMTile)tile withKey:(NSString *)cacheKey;
|
|
|
- (void)purgeTiles:(NSUInteger)count;
|
|
|
|
|
|
@end
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
@implementation RMDatabaseCache
|
|
|
|
|
|
@synthesize databasePath;
|
...
|
...
|
@@ -62,20 +75,45 @@ |
|
|
return nil;
|
|
|
}
|
|
|
|
|
|
- (void)configureDBForFirstUse
|
|
|
{
|
|
|
[db executeQuery:@"PRAGMA synchronous=OFF"];
|
|
|
[db executeQuery:@"PRAGMA journal_mode=OFF"];
|
|
|
[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)"];
|
|
|
}
|
|
|
|
|
|
- (id)initWithDatabase:(NSString *)path
|
|
|
{
|
|
|
if (!(self = [super init]))
|
|
|
return nil;
|
|
|
|
|
|
self.databasePath = path;
|
|
|
dao = [[RMTileCacheDAO alloc] initWithDatabase:path];
|
|
|
if (!dao) {
|
|
|
|
|
|
writeQueue = [NSOperationQueue new];
|
|
|
[writeQueue setMaxConcurrentOperationCount:1];
|
|
|
writeQueueLock = [NSRecursiveLock new];
|
|
|
|
|
|
RMLog(@"Opening database at %@", path);
|
|
|
|
|
|
db = [[FMDatabase alloc] initWithPath:path];
|
|
|
if (![db open])
|
|
|
{
|
|
|
RMLog(@"Could not connect to database - %@", [db lastErrorMessage]);
|
|
|
[[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
|
|
|
dao = [[RMTileCacheDAO alloc] initWithDatabase:path];
|
|
|
}
|
|
|
if (![db open]) {
|
|
|
[self release];
|
|
|
return nil;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (dao == nil)
|
|
|
return nil;
|
|
|
[db setCrashOnErrors:TRUE];
|
|
|
[db setShouldCacheStatements:TRUE];
|
|
|
|
|
|
[self configureDBForFirstUse];
|
|
|
|
|
|
tileCount = [self countTiles];
|
|
|
|
|
|
return self;
|
|
|
}
|
...
|
...
|
@@ -88,7 +126,11 @@ |
|
|
- (void)dealloc
|
|
|
{
|
|
|
self.databasePath = nil;
|
|
|
[dao release]; dao = nil;
|
|
|
[writeQueueLock lock];
|
|
|
[writeQueue release]; writeQueue = nil;
|
|
|
[writeQueueLock unlock];
|
|
|
[writeQueueLock release]; writeQueueLock = nil;
|
|
|
[db close]; [db release]; db = nil;
|
|
|
[super dealloc];
|
|
|
}
|
|
|
|
...
|
...
|
@@ -111,44 +153,142 @@ |
|
|
{
|
|
|
// RMLog(@"DB cache check for tile %d %d %d", tile.x, tile.y, tile.zoom);
|
|
|
|
|
|
NSData *data = [dao dataForTile:RMTileKey(tile) withKey:aCacheKey];
|
|
|
if (data == nil)
|
|
|
return nil;
|
|
|
[writeQueueLock lock];
|
|
|
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
NSData *data = nil;
|
|
|
UIImage *cachedImage = nil;
|
|
|
|
|
|
if ([results next]) {
|
|
|
data = [results dataForColumnIndex:0];
|
|
|
if (data) cachedImage = [UIImage imageWithData:data];
|
|
|
}
|
|
|
|
|
|
[results close];
|
|
|
|
|
|
[writeQueueLock unlock];
|
|
|
|
|
|
if (capacity != 0 && purgeStrategy == RMCachePurgeStrategyLRU) {
|
|
|
[dao touchTile:RMTileKey(tile) withKey:aCacheKey];
|
|
|
[self touchTile:tile withKey:aCacheKey];
|
|
|
}
|
|
|
|
|
|
// RMLog(@"DB cache hit tile %d %d %d (%@)", tile.x, tile.y, tile.zoom, [RMTileCache tileHash:tile]);
|
|
|
|
|
|
return [UIImage imageWithData:data];
|
|
|
return cachedImage;
|
|
|
}
|
|
|
|
|
|
- (void)addImage:(UIImage *)image forTile:(RMTile)tile withCacheKey:(NSString *)aCacheKey
|
|
|
{
|
|
|
// TODO: Converting the image here (again) is not so good...
|
|
|
NSData *data = UIImagePNGRepresentation(image);
|
|
|
|
|
|
if (capacity != 0) {
|
|
|
NSUInteger tilesInDb = [dao count];
|
|
|
|
|
|
if (capacity != 0)
|
|
|
{
|
|
|
NSUInteger tilesInDb = [self count];
|
|
|
if (capacity <= tilesInDb) {
|
|
|
[dao purgeTiles: MAX(minimalPurge, 1+tilesInDb-capacity)];
|
|
|
[self purgeTiles: MAX(minimalPurge, 1+tilesInDb-capacity)];
|
|
|
}
|
|
|
|
|
|
|
|
|
// RMLog(@"DB cache insert tile %d %d %d (%@)", tile.x, tile.y, tile.zoom, [RMTileCache tileHash:tile]);
|
|
|
|
|
|
[dao addData:data forTile:RMTileKey(tile) withKey:aCacheKey];
|
|
|
|
|
|
// Don't add new images to the database while there are still more than kWriteQueueLimit
|
|
|
// insert operations pending. This prevents some memory issues.
|
|
|
if ([writeQueue operationCount] > kWriteQueueLimit) return;
|
|
|
|
|
|
[writeQueue addOperationWithBlock:^{
|
|
|
// RMLog(@"addData\t%d", tileHash);
|
|
|
|
|
|
[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];
|
|
|
[writeQueueLock unlock];
|
|
|
|
|
|
if (result == NO)
|
|
|
{
|
|
|
RMLog(@"Error occured adding data");
|
|
|
} else
|
|
|
tileCount++;
|
|
|
}];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)didReceiveMemoryWarning
|
|
|
#pragma mark -
|
|
|
|
|
|
- (NSUInteger)count
|
|
|
{
|
|
|
return tileCount;
|
|
|
}
|
|
|
|
|
|
- (NSUInteger)countTiles
|
|
|
{
|
|
|
[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];
|
|
|
|
|
|
[writeQueueLock unlock];
|
|
|
|
|
|
return count;
|
|
|
}
|
|
|
|
|
|
- (void)purgeTiles:(NSUInteger)count
|
|
|
{
|
|
|
[dao didReceiveMemoryWarning];
|
|
|
RMLog(@"purging %u old tiles from 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];
|
|
|
[writeQueueLock unlock];
|
|
|
|
|
|
if (result == NO) {
|
|
|
RMLog(@"Error purging cache");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)removeAllCachedImages
|
|
|
{
|
|
|
[dao removeAllCachedImages];
|
|
|
[writeQueue addOperationWithBlock:^{
|
|
|
[writeQueueLock lock];
|
|
|
BOOL result = [db executeUpdate: @"DELETE FROM ZCACHE"];
|
|
|
[db executeQuery:@"VACUUM"];
|
|
|
[writeQueueLock unlock];
|
|
|
|
|
|
if (result == NO) {
|
|
|
RMLog(@"Error purging cache");
|
|
|
}
|
|
|
|
|
|
tileCount = [self countTiles];
|
|
|
}];
|
|
|
}
|
|
|
|
|
|
- (void)touchTile:(RMTile)tile withKey:(NSString *)cacheKey
|
|
|
{
|
|
|
[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");
|
|
|
}
|
|
|
}];
|
|
|
}
|
|
|
|
|
|
- (void)didReceiveMemoryWarning
|
|
|
{
|
|
|
RMLog(@"Low memory in the tilecache");
|
|
|
[writeQueue cancelAllOperations];
|
|
|
}
|
|
|
|
|
|
@end |
...
|
...
|
|