|
@@ -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];
|