Authored by Thomas Rasch

o RMDatabaseCache now uses the FMDatabaseQueue

@@ -28,27 +28,15 @@ @@ -28,27 +28,15 @@
28 #import <UIKit/UIKit.h> 28 #import <UIKit/UIKit.h>
29 #import "RMTileCache.h" 29 #import "RMTileCache.h"
30 30
31 -@class RMTileCacheDAO;  
32 -@class FMDatabase;  
33 -  
34 -@interface RMDatabaseCache : NSObject <RMTileCache> {  
35 - // Database  
36 - FMDatabase *db; 31 +@interface RMDatabaseCache : NSObject <RMTileCache>
  32 +{
37 NSString *databasePath; 33 NSString *databasePath;
38 -  
39 - NSUInteger tileCount;  
40 - NSOperationQueue *writeQueue;  
41 - NSRecursiveLock *writeQueueLock;  
42 -  
43 - // Cache  
44 - RMCachePurgeStrategy purgeStrategy;  
45 - NSUInteger capacity;  
46 - NSUInteger minimalPurge;  
47 } 34 }
48 35
49 @property (nonatomic, retain) NSString *databasePath; 36 @property (nonatomic, retain) NSString *databasePath;
50 37
51 + (NSString *)dbPathUsingCacheDir:(BOOL)useCacheDir; 38 + (NSString *)dbPathUsingCacheDir:(BOOL)useCacheDir;
  39 +
52 - (id)initWithDatabase:(NSString *)path; 40 - (id)initWithDatabase:(NSString *)path;
53 - (id)initUsingCacheDir:(BOOL)useCacheDir; 41 - (id)initUsingCacheDir:(BOOL)useCacheDir;
54 42
@@ -27,6 +27,7 @@ @@ -27,6 +27,7 @@
27 27
28 #import "RMDatabaseCache.h" 28 #import "RMDatabaseCache.h"
29 #import "FMDatabase.h" 29 #import "FMDatabase.h"
  30 +#import "FMDatabaseQueue.h"
30 #import "RMTileImage.h" 31 #import "RMTileImage.h"
31 #import "RMTile.h" 32 #import "RMTile.h"
32 33
@@ -44,6 +45,19 @@ @@ -44,6 +45,19 @@
44 #pragma mark - 45 #pragma mark -
45 46
46 @implementation RMDatabaseCache 47 @implementation RMDatabaseCache
  48 +{
  49 + // Database
  50 + FMDatabaseQueue *queue;
  51 +
  52 + NSUInteger tileCount;
  53 + NSOperationQueue *writeQueue;
  54 + NSRecursiveLock *writeQueueLock;
  55 +
  56 + // Cache
  57 + RMCachePurgeStrategy purgeStrategy;
  58 + NSUInteger capacity;
  59 + NSUInteger minimalPurge;
  60 +}
47 61
48 @synthesize databasePath; 62 @synthesize databasePath;
49 63
@@ -51,23 +65,22 @@ @@ -51,23 +65,22 @@
51 { 65 {
52 NSArray *paths; 66 NSArray *paths;
53 67
54 - if (useCacheDir) { 68 + if (useCacheDir)
55 paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 69 paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
56 - } else { 70 + else
57 paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 71 paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
58 - }  
59 72
60 if ([paths count] > 0) // Should only be one... 73 if ([paths count] > 0) // Should only be one...
61 { 74 {
62 NSString *cachePath = [paths objectAtIndex:0]; 75 NSString *cachePath = [paths objectAtIndex:0];
63 - 76 +
64 // check for existence of cache directory 77 // check for existence of cache directory
65 - if ( ![[NSFileManager defaultManager] fileExistsAtPath: cachePath]) 78 + if ( ![[NSFileManager defaultManager] fileExistsAtPath: cachePath])
66 { 79 {
67 // create a new cache directory 80 // create a new cache directory
68 [[NSFileManager defaultManager] createDirectoryAtPath:cachePath withIntermediateDirectories:NO attributes:nil error:nil]; 81 [[NSFileManager defaultManager] createDirectoryAtPath:cachePath withIntermediateDirectories:NO attributes:nil error:nil];
69 } 82 }
70 - 83 +
71 NSString *filename = [NSString stringWithFormat:@"RMTileCache.db"]; 84 NSString *filename = [NSString stringWithFormat:@"RMTileCache.db"];
72 return [cachePath stringByAppendingPathComponent:filename]; 85 return [cachePath stringByAppendingPathComponent:filename];
73 } 86 }
@@ -77,13 +90,13 @@ @@ -77,13 +90,13 @@
77 90
78 - (void)configureDBForFirstUse 91 - (void)configureDBForFirstUse
79 { 92 {
80 - [[db executeQuery:@"PRAGMA synchronous=OFF"] close];  
81 - [[db executeQuery:@"PRAGMA journal_mode=OFF"] close];  
82 - [[db executeQuery:@"PRAGMA cache-size=100"] close];  
83 - [[db executeQuery:@"PRAGMA count_changes=OFF"] close];  
84 - [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)"];  
85 - [db executeUpdate:@"CREATE UNIQUE INDEX IF NOT EXISTS main_index ON ZCACHE(tile_hash, cache_key)"];  
86 - [db executeUpdate:@"CREATE INDEX IF NOT EXISTS last_used_index ON ZCACHE(last_used)"]; 93 + [[[queue database] executeQuery:@"PRAGMA synchronous=OFF"] close];
  94 + [[[queue database] executeQuery:@"PRAGMA journal_mode=OFF"] close];
  95 + [[[queue database] executeQuery:@"PRAGMA cache-size=100"] close];
  96 + [[[queue database] executeQuery:@"PRAGMA count_changes=OFF"] close];
  97 + [[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)"];
  98 + [[queue database] executeUpdate:@"CREATE UNIQUE INDEX IF NOT EXISTS main_index ON ZCACHE(tile_hash, cache_key)"];
  99 + [[queue database] executeUpdate:@"CREATE INDEX IF NOT EXISTS last_used_index ON ZCACHE(last_used)"];
87 } 100 }
88 101
89 - (id)initWithDatabase:(NSString *)path 102 - (id)initWithDatabase:(NSString *)path
@@ -99,19 +112,20 @@ @@ -99,19 +112,20 @@
99 112
100 RMLog(@"Opening database at %@", path); 113 RMLog(@"Opening database at %@", path);
101 114
102 - db = [[FMDatabase alloc] initWithPath:path];  
103 - if (![db open]) 115 + queue = [[FMDatabaseQueue databaseQueueWithPath:path] retain];
  116 +
  117 + if (!queue || ![[queue database] open])
104 { 118 {
105 - RMLog(@"Could not connect to database - %@", [db lastErrorMessage]); 119 + RMLog(@"Could not connect to database - %@", [[queue database] lastErrorMessage]);
  120 +
106 [[NSFileManager defaultManager] removeItemAtPath:path error:NULL]; 121 [[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
107 - if (![db open]) {  
108 - [self release];  
109 - return nil;  
110 - } 122 +
  123 + [self release];
  124 + return nil;
111 } 125 }
112 126
113 - [db setCrashOnErrors:TRUE];  
114 - [db setShouldCacheStatements:TRUE]; 127 + [[queue database] setCrashOnErrors:TRUE];
  128 + [[queue database] setShouldCacheStatements:TRUE];
115 129
116 [self configureDBForFirstUse]; 130 [self configureDBForFirstUse];
117 131
@@ -132,7 +146,7 @@ @@ -132,7 +146,7 @@
132 [writeQueue release]; writeQueue = nil; 146 [writeQueue release]; writeQueue = nil;
133 [writeQueueLock unlock]; 147 [writeQueueLock unlock];
134 [writeQueueLock release]; writeQueueLock = nil; 148 [writeQueueLock release]; writeQueueLock = nil;
135 - [db close]; [db release]; db = nil; 149 + [queue release]; queue = nil;
136 [super dealloc]; 150 [super dealloc];
137 } 151 }
138 152
@@ -155,30 +169,35 @@ @@ -155,30 +169,35 @@
155 { 169 {
156 // RMLog(@"DB cache check for tile %d %d %d", tile.x, tile.y, tile.zoom); 170 // RMLog(@"DB cache check for tile %d %d %d", tile.x, tile.y, tile.zoom);
157 171
  172 + __block UIImage *cachedImage = nil;
  173 +
158 [writeQueueLock lock]; 174 [writeQueueLock lock];
159 175
160 - FMResultSet *results = [db executeQuery:@"SELECT data FROM ZCACHE WHERE tile_hash = ? AND cache_key = ?", [RMTileCache tileHash:tile], aCacheKey]; 176 + [queue inDatabase:^(FMDatabase *db)
  177 + {
  178 + FMResultSet *results = [db executeQuery:@"SELECT data FROM ZCACHE WHERE tile_hash = ? AND cache_key = ?", [RMTileCache tileHash:tile], aCacheKey];
161 179
162 - if ([db hadError]) {  
163 - RMLog(@"DB error while fetching tile data: %@", [db lastErrorMessage]);  
164 - return nil;  
165 - } 180 + if ([db hadError])
  181 + {
  182 + RMLog(@"DB error while fetching tile data: %@", [db lastErrorMessage]);
  183 + return;
  184 + }
166 185
167 - NSData *data = nil;  
168 - UIImage *cachedImage = nil; 186 + NSData *data = nil;
169 187
170 - if ([results next]) {  
171 - data = [results dataForColumnIndex:0];  
172 - if (data) cachedImage = [UIImage imageWithData:data];  
173 - } 188 + if ([results next])
  189 + {
  190 + data = [results dataForColumnIndex:0];
  191 + if (data) cachedImage = [UIImage imageWithData:data];
  192 + }
174 193
175 - [results close]; 194 + [results close];
  195 + }];
176 196
177 [writeQueueLock unlock]; 197 [writeQueueLock unlock];
178 198
179 - if (capacity != 0 && purgeStrategy == RMCachePurgeStrategyLRU) { 199 + if (capacity != 0 && purgeStrategy == RMCachePurgeStrategyLRU)
180 [self touchTile:tile withKey:aCacheKey]; 200 [self touchTile:tile withKey:aCacheKey];
181 - }  
182 201
183 // RMLog(@"DB cache hit tile %d %d %d (%@)", tile.x, tile.y, tile.zoom, [RMTileCache tileHash:tile]); 202 // RMLog(@"DB cache hit tile %d %d %d (%@)", tile.x, tile.y, tile.zoom, [RMTileCache tileHash:tile]);
184 203
@@ -193,32 +212,42 @@ @@ -193,32 +212,42 @@
193 if (capacity != 0) 212 if (capacity != 0)
194 { 213 {
195 NSUInteger tilesInDb = [self count]; 214 NSUInteger tilesInDb = [self count];
196 - if (capacity <= tilesInDb) { 215 +
  216 + if (capacity <= tilesInDb)
197 [self purgeTiles:MAX(minimalPurge, 1+tilesInDb-capacity)]; 217 [self purgeTiles:MAX(minimalPurge, 1+tilesInDb-capacity)];
198 - }  
199 218
200 // RMLog(@"DB cache insert tile %d %d %d (%@)", tile.x, tile.y, tile.zoom, [RMTileCache tileHash:tile]); 219 // RMLog(@"DB cache insert tile %d %d %d (%@)", tile.x, tile.y, tile.zoom, [RMTileCache tileHash:tile]);
201 220
202 // Don't add new images to the database while there are still more than kWriteQueueLimit 221 // Don't add new images to the database while there are still more than kWriteQueueLimit
203 // insert operations pending. This prevents some memory issues. 222 // insert operations pending. This prevents some memory issues.
  223 +
204 BOOL skipThisTile = NO; 224 BOOL skipThisTile = NO;
  225 +
205 [writeQueueLock lock]; 226 [writeQueueLock lock];
206 - if ([writeQueue operationCount] > kWriteQueueLimit) skipThisTile = YES; 227 +
  228 + if ([writeQueue operationCount] > kWriteQueueLimit)
  229 + skipThisTile = YES;
  230 +
207 [writeQueueLock unlock]; 231 [writeQueueLock unlock];
208 232
209 - if (skipThisTile) return; 233 + if (skipThisTile)
  234 + return;
210 235
211 [writeQueue addOperationWithBlock:^{ 236 [writeQueue addOperationWithBlock:^{
212 - // RMLog(@"addData\t%d", tileHash); 237 + __block BOOL result = NO;
213 238
214 [writeQueueLock lock]; 239 [writeQueueLock lock];
215 - BOOL result = [db executeUpdate:@"INSERT OR IGNORE INTO ZCACHE (tile_hash, cache_key, last_used, data) VALUES (?, ?, ?, ?)", [RMTileCache tileHash:tile], aCacheKey, [NSDate date], data]; 240 +
  241 + [queue inDatabase:^(FMDatabase *db)
  242 + {
  243 + result = [db executeUpdate:@"INSERT OR IGNORE INTO ZCACHE (tile_hash, cache_key, last_used, data) VALUES (?, ?, ?, ?)", [RMTileCache tileHash:tile], aCacheKey, [NSDate date], data];
  244 + }];
  245 +
216 [writeQueueLock unlock]; 246 [writeQueueLock unlock];
217 247
218 if (result == NO) 248 if (result == NO)
219 - {  
220 RMLog(@"Error occured adding data"); 249 RMLog(@"Error occured adding data");
221 - } else 250 + else
222 tileCount++; 251 tileCount++;
223 }]; 252 }];
224 } 253 }
@@ -233,15 +262,21 @@ @@ -233,15 +262,21 @@
233 262
234 - (NSUInteger)countTiles 263 - (NSUInteger)countTiles
235 { 264 {
  265 + __block NSUInteger count = 0;
  266 +
236 [writeQueueLock lock]; 267 [writeQueueLock lock];
237 268
238 - NSUInteger count = 0;  
239 - FMResultSet *results = [db executeQuery:@"SELECT COUNT(tile_hash) FROM ZCACHE"];  
240 - if ([results next])  
241 - count = [results intForColumnIndex:0];  
242 - else  
243 - RMLog(@"Unable to count columns");  
244 - [results close]; 269 + [queue inDatabase:^(FMDatabase *db)
  270 + {
  271 + FMResultSet *results = [db executeQuery:@"SELECT COUNT(tile_hash) FROM ZCACHE"];
  272 +
  273 + if ([results next])
  274 + count = [results intForColumnIndex:0];
  275 + else
  276 + RMLog(@"Unable to count columns");
  277 +
  278 + [results close];
  279 + }];
245 280
246 [writeQueueLock unlock]; 281 [writeQueueLock unlock];
247 282
@@ -250,30 +285,43 @@ @@ -250,30 +285,43 @@
250 285
251 - (void)purgeTiles:(NSUInteger)count 286 - (void)purgeTiles:(NSUInteger)count
252 { 287 {
253 - RMLog(@"purging %u old tiles from db cache", count); 288 + RMLog(@"purging %u old tiles from the db cache", count);
254 289
255 [writeQueueLock lock]; 290 [writeQueueLock lock];
256 - BOOL result = [db executeUpdate:@"DELETE FROM ZCACHE WHERE tile_hash IN (SELECT tile_hash FROM ZCACHE ORDER BY last_used LIMIT ?)", [NSNumber numberWithUnsignedInt:count]];  
257 - [db executeQuery:@"VACUUM"];  
258 - tileCount = [self countTiles]; 291 +
  292 + [queue inDatabase:^(FMDatabase *db)
  293 + {
  294 + BOOL result = [db executeUpdate:@"DELETE FROM ZCACHE WHERE tile_hash IN (SELECT tile_hash FROM ZCACHE ORDER BY last_used LIMIT ?)", [NSNumber numberWithUnsignedInt:count]];
  295 +
  296 + if (result == NO)
  297 + RMLog(@"Error purging cache");
  298 +
  299 + [[db executeQuery:@"VACUUM"] close];
  300 + }];
  301 +
259 [writeQueueLock unlock]; 302 [writeQueueLock unlock];
260 303
261 - if (result == NO) {  
262 - RMLog(@"Error purging cache");  
263 - } 304 + tileCount = [self countTiles];
264 } 305 }
265 306
266 - (void)removeAllCachedImages 307 - (void)removeAllCachedImages
267 { 308 {
  309 + RMLog(@"removing all tiles from the db cache");
  310 +
268 [writeQueue addOperationWithBlock:^{ 311 [writeQueue addOperationWithBlock:^{
269 [writeQueueLock lock]; 312 [writeQueueLock lock];
270 - BOOL result = [db executeUpdate:@"DELETE FROM ZCACHE"];  
271 - [db executeQuery:@"VACUUM"];  
272 - [writeQueueLock unlock];  
273 313
274 - if (result == NO) {  
275 - RMLog(@"Error purging cache");  
276 - } 314 + [queue inDatabase:^(FMDatabase *db)
  315 + {
  316 + BOOL result = [db executeUpdate:@"DELETE FROM ZCACHE"];
  317 +
  318 + if (result == NO)
  319 + RMLog(@"Error purging cache");
  320 +
  321 + [[db executeQuery:@"VACUUM"] close];
  322 + }];
  323 +
  324 + [writeQueueLock unlock];
277 325
278 tileCount = [self countTiles]; 326 tileCount = [self countTiles];
279 }]; 327 }];
@@ -283,18 +331,23 @@ @@ -283,18 +331,23 @@
283 { 331 {
284 [writeQueue addOperationWithBlock:^{ 332 [writeQueue addOperationWithBlock:^{
285 [writeQueueLock lock]; 333 [writeQueueLock lock];
286 - BOOL result = [db executeUpdate:@"UPDATE ZCACHE SET last_used = ? WHERE tile_hash = ? AND cache_key = ?", [NSDate date], [RMTileCache tileHash:tile], cacheKey];  
287 - [writeQueueLock unlock];  
288 334
289 - if (result == NO) {  
290 - RMLog(@"Error touching tile");  
291 - } 335 + [queue inDatabase:^(FMDatabase *db)
  336 + {
  337 + BOOL result = [db executeUpdate:@"UPDATE ZCACHE SET last_used = ? WHERE tile_hash = ? AND cache_key = ?", [NSDate date], [RMTileCache tileHash:tile], cacheKey];
  338 +
  339 + if (result == NO)
  340 + RMLog(@"Error touching tile");
  341 + }];
  342 +
  343 + [writeQueueLock unlock];
292 }]; 344 }];
293 } 345 }
294 346
295 - (void)didReceiveMemoryWarning 347 - (void)didReceiveMemoryWarning
296 { 348 {
297 RMLog(@"Low memory in the database tilecache"); 349 RMLog(@"Low memory in the database tilecache");
  350 +
298 [writeQueueLock lock]; 351 [writeQueueLock lock];
299 [writeQueue cancelAllOperations]; 352 [writeQueue cancelAllOperations];
300 [writeQueueLock unlock]; 353 [writeQueueLock unlock];