YYKVStorage.m 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042
  1. //
  2. // YYKVStorage.m
  3. // YYCache <https://github.com/ibireme/YYCache>
  4. //
  5. // Created by ibireme on 15/4/22.
  6. // Copyright (c) 2015 ibireme.
  7. //
  8. // This source code is licensed under the MIT-style license found in the
  9. // LICENSE file in the root directory of this source tree.
  10. //
  11. #import "YYKVStorage.h"
  12. #import <UIKit/UIKit.h>
  13. #import <time.h>
  14. #if __has_include(<sqlite3.h>)
  15. #import <sqlite3.h>
  16. #else
  17. #import "sqlite3.h"
  18. #endif
  19. static const int kPathLengthMax = PATH_MAX - 64;
  20. static NSString *const kDBFileName = @"manifest.sqlite";
  21. static NSString *const kDBShmFileName = @"manifest.sqlite-shm";
  22. static NSString *const kDBWalFileName = @"manifest.sqlite-wal";
  23. static NSString *const kDataDirectoryName = @"data";
  24. static NSString *const kTrashDirectoryName = @"trash";
  25. /*
  26. SQL:
  27. create table if not exists manifest (
  28. key text,
  29. filename text,
  30. size integer,
  31. inline_data blob,
  32. modification_time integer,
  33. last_access_time integer,
  34. extended_data blob,
  35. primary key(key)
  36. );
  37. create index if not exists last_access_time_idx on manifest(last_access_time);
  38. */
  39. @implementation YYKVStorageItem
  40. @end
  41. @implementation YYKVStorage {
  42. dispatch_queue_t _trashQueue;
  43. NSString *_path;
  44. NSString *_dbPath;
  45. NSString *_dataPath;
  46. NSString *_trashPath;
  47. sqlite3 *_db;
  48. CFMutableDictionaryRef _dbStmtCache;
  49. BOOL _invalidated; ///< If YES, then the db should not open again, all read/write should be ignored.
  50. BOOL _dbIsClosing; ///< If YES, then the db is during closing.
  51. }
  52. #pragma mark - db
  53. - (BOOL)_dbOpen {
  54. BOOL shouldOpen = YES;
  55. if (_invalidated) {
  56. shouldOpen = NO;
  57. } else if (_dbIsClosing) {
  58. shouldOpen = NO;
  59. } else if (_db){
  60. shouldOpen = NO;
  61. }
  62. if (!shouldOpen) return YES;
  63. int result = sqlite3_open(_dbPath.UTF8String, &_db);
  64. if (result == SQLITE_OK) {
  65. CFDictionaryKeyCallBacks keyCallbacks = kCFCopyStringDictionaryKeyCallBacks;
  66. CFDictionaryValueCallBacks valueCallbacks = {0};
  67. _dbStmtCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &keyCallbacks, &valueCallbacks);
  68. return YES;
  69. } else {
  70. NSLog(@"%s line:%d sqlite open failed (%d).", __FUNCTION__, __LINE__, result);
  71. return NO;
  72. }
  73. }
  74. - (BOOL)_dbClose {
  75. BOOL needClose = YES;
  76. if (!_db) {
  77. needClose = NO;
  78. } else if (_invalidated) {
  79. needClose = NO;
  80. } else if (_dbIsClosing) {
  81. needClose = NO;
  82. } else {
  83. _dbIsClosing = YES;
  84. }
  85. if (!needClose) return YES;
  86. int result = 0;
  87. BOOL retry = NO;
  88. BOOL stmtFinalized = NO;
  89. if (_dbStmtCache) CFRelease(_dbStmtCache);
  90. _dbStmtCache = NULL;
  91. do {
  92. retry = NO;
  93. result = sqlite3_close(_db);
  94. if (result == SQLITE_BUSY || result == SQLITE_LOCKED) {
  95. if (!stmtFinalized) {
  96. stmtFinalized = YES;
  97. sqlite3_stmt *stmt;
  98. while ((stmt = sqlite3_next_stmt(_db, nil)) != 0) {
  99. sqlite3_finalize(stmt);
  100. retry = YES;
  101. }
  102. }
  103. } else if (result != SQLITE_OK) {
  104. NSLog(@"%s line:%d sqlite close failed (%d).", __FUNCTION__, __LINE__, result);
  105. }
  106. } while (retry);
  107. _db = NULL;
  108. _dbIsClosing = NO;
  109. return YES;
  110. }
  111. - (BOOL)_dbIsReady {
  112. return (_db && !_dbIsClosing && !_invalidated);
  113. }
  114. - (BOOL)_dbInitialize {
  115. NSString *sql = @"pragma journal_mode = wal; pragma synchronous = normal; create table if not exists manifest (key text, filename text, size integer, inline_data blob, modification_time integer, last_access_time integer, extended_data blob, primary key(key)); create index if not exists last_access_time_idx on manifest(last_access_time);";
  116. return [self _dbExecute:sql];
  117. }
  118. - (void)_dbCheckpoint {
  119. if (![self _dbIsReady]) return;
  120. // Cause a checkpoint to occur, merge `sqlite-wal` file to `sqlite` file.
  121. sqlite3_wal_checkpoint(_db, NULL);
  122. }
  123. - (BOOL)_dbExecute:(NSString *)sql {
  124. if (sql.length == 0) return NO;
  125. if (![self _dbIsReady]) return NO;
  126. char *error = NULL;
  127. int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &error);
  128. if (error) {
  129. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite exec error (%d): %s", __FUNCTION__, __LINE__, result, error);
  130. sqlite3_free(error);
  131. }
  132. return result == SQLITE_OK;
  133. }
  134. - (sqlite3_stmt *)_dbPrepareStmt:(NSString *)sql {
  135. if (![self _dbIsReady] || sql.length == 0 || !_dbStmtCache) return NULL;
  136. sqlite3_stmt *stmt = (sqlite3_stmt *)CFDictionaryGetValue(_dbStmtCache, (__bridge const void *)(sql));
  137. if (!stmt) {
  138. int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
  139. if (result != SQLITE_OK) {
  140. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  141. return NULL;
  142. }
  143. CFDictionarySetValue(_dbStmtCache, (__bridge const void *)(sql), stmt);
  144. } else {
  145. sqlite3_reset(stmt);
  146. }
  147. return stmt;
  148. }
  149. - (NSString *)_dbJoinedKeys:(NSArray *)keys {
  150. NSMutableString *string = [NSMutableString new];
  151. for (NSUInteger i = 0,max = keys.count; i < max; i++) {
  152. [string appendString:@"?"];
  153. if (i + 1 != max) {
  154. [string appendString:@","];
  155. }
  156. }
  157. return string;
  158. }
  159. - (void)_dbBindJoinedKeys:(NSArray *)keys stmt:(sqlite3_stmt *)stmt fromIndex:(int)index{
  160. for (int i = 0, max = (int)keys.count; i < max; i++) {
  161. NSString *key = keys[i];
  162. sqlite3_bind_text(stmt, index + i, key.UTF8String, -1, NULL);
  163. }
  164. }
  165. - (BOOL)_dbSaveWithKey:(NSString *)key value:(NSData *)value fileName:(NSString *)fileName extendedData:(NSData *)extendedData {
  166. NSString *sql = @"insert or replace into manifest (key, filename, size, inline_data, modification_time, last_access_time, extended_data) values (?1, ?2, ?3, ?4, ?5, ?6, ?7);";
  167. sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
  168. if (!stmt) return NO;
  169. int timestamp = (int)time(NULL);
  170. sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
  171. sqlite3_bind_text(stmt, 2, fileName.UTF8String, -1, NULL);
  172. sqlite3_bind_int(stmt, 3, (int)value.length);
  173. if (fileName.length == 0) {
  174. sqlite3_bind_blob(stmt, 4, value.bytes, (int)value.length, 0);
  175. } else {
  176. sqlite3_bind_blob(stmt, 4, NULL, 0, 0);
  177. }
  178. sqlite3_bind_int(stmt, 5, timestamp);
  179. sqlite3_bind_int(stmt, 6, timestamp);
  180. sqlite3_bind_blob(stmt, 7, extendedData.bytes, (int)extendedData.length, 0);
  181. int result = sqlite3_step(stmt);
  182. if (result != SQLITE_DONE) {
  183. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite insert error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  184. return NO;
  185. }
  186. return YES;
  187. }
  188. - (BOOL)_dbUpdateAccessTimeWithKey:(NSString *)key {
  189. NSString *sql = @"update manifest set last_access_time = ?1 where key = ?2;";
  190. sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
  191. if (!stmt) return NO;
  192. sqlite3_bind_int(stmt, 1, (int)time(NULL));
  193. sqlite3_bind_text(stmt, 2, key.UTF8String, -1, NULL);
  194. int result = sqlite3_step(stmt);
  195. if (result != SQLITE_DONE) {
  196. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite update error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  197. return NO;
  198. }
  199. return YES;
  200. }
  201. - (BOOL)_dbUpdateAccessTimeWithKeys:(NSArray *)keys {
  202. if (![self _dbIsReady]) return NO;
  203. int t = (int)time(NULL);
  204. NSString *sql = [NSString stringWithFormat:@"update manifest set last_access_time = %d where key in (%@);", t, [self _dbJoinedKeys:keys]];
  205. sqlite3_stmt *stmt = NULL;
  206. int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
  207. if (result != SQLITE_OK) {
  208. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  209. return NO;
  210. }
  211. [self _dbBindJoinedKeys:keys stmt:stmt fromIndex:1];
  212. result = sqlite3_step(stmt);
  213. sqlite3_finalize(stmt);
  214. if (result != SQLITE_DONE) {
  215. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite update error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  216. return NO;
  217. }
  218. return YES;
  219. }
  220. - (BOOL)_dbDeleteItemWithKey:(NSString *)key {
  221. NSString *sql = @"delete from manifest where key = ?1;";
  222. sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
  223. if (!stmt) return NO;
  224. sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
  225. int result = sqlite3_step(stmt);
  226. if (result != SQLITE_DONE) {
  227. if (_errorLogsEnabled) NSLog(@"%s line:%d db delete error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  228. return NO;
  229. }
  230. return YES;
  231. }
  232. - (BOOL)_dbDeleteItemWithKeys:(NSArray *)keys {
  233. if (![self _dbIsReady]) return NO;
  234. NSString *sql = [NSString stringWithFormat:@"delete from manifest where key in (%@);", [self _dbJoinedKeys:keys]];
  235. sqlite3_stmt *stmt = NULL;
  236. int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
  237. if (result != SQLITE_OK) {
  238. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  239. return NO;
  240. }
  241. [self _dbBindJoinedKeys:keys stmt:stmt fromIndex:1];
  242. result = sqlite3_step(stmt);
  243. sqlite3_finalize(stmt);
  244. if (result == SQLITE_ERROR) {
  245. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite delete error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  246. return NO;
  247. }
  248. return YES;
  249. }
  250. - (BOOL)_dbDeleteItemsWithSizeLargerThan:(int)size {
  251. NSString *sql = @"delete from manifest where size > ?1;";
  252. sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
  253. if (!stmt) return NO;
  254. sqlite3_bind_int(stmt, 1, size);
  255. int result = sqlite3_step(stmt);
  256. if (result != SQLITE_DONE) {
  257. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite delete error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  258. return NO;
  259. }
  260. return YES;
  261. }
  262. - (BOOL)_dbDeleteItemsWithTimeEarlierThan:(int)time {
  263. NSString *sql = @"delete from manifest where last_access_time < ?1;";
  264. sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
  265. if (!stmt) return NO;
  266. sqlite3_bind_int(stmt, 1, time);
  267. int result = sqlite3_step(stmt);
  268. if (result != SQLITE_DONE) {
  269. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite delete error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  270. return NO;
  271. }
  272. return YES;
  273. }
  274. - (YYKVStorageItem *)_dbGetItemFromStmt:(sqlite3_stmt *)stmt excludeInlineData:(BOOL)excludeInlineData {
  275. int i = 0;
  276. char *key = (char *)sqlite3_column_text(stmt, i++);
  277. char *filename = (char *)sqlite3_column_text(stmt, i++);
  278. int size = sqlite3_column_int(stmt, i++);
  279. const void *inline_data = excludeInlineData ? NULL : sqlite3_column_blob(stmt, i);
  280. int inline_data_bytes = excludeInlineData ? 0 : sqlite3_column_bytes(stmt, i++);
  281. int modification_time = sqlite3_column_int(stmt, i++);
  282. int last_access_time = sqlite3_column_int(stmt, i++);
  283. const void *extended_data = sqlite3_column_blob(stmt, i);
  284. int extended_data_bytes = sqlite3_column_bytes(stmt, i++);
  285. YYKVStorageItem *item = [YYKVStorageItem new];
  286. if (key) item.key = [NSString stringWithUTF8String:key];
  287. if (filename && *filename != 0) item.filename = [NSString stringWithUTF8String:filename];
  288. item.size = size;
  289. if (inline_data_bytes > 0 && inline_data) item.value = [NSData dataWithBytes:inline_data length:inline_data_bytes];
  290. item.modTime = modification_time;
  291. item.accessTime = last_access_time;
  292. if (extended_data_bytes > 0 && extended_data) item.extendedData = [NSData dataWithBytes:extended_data length:extended_data_bytes];
  293. return item;
  294. }
  295. - (YYKVStorageItem *)_dbGetItemWithKey:(NSString *)key excludeInlineData:(BOOL)excludeInlineData {
  296. NSString *sql = excludeInlineData ? @"select key, filename, size, modification_time, last_access_time, extended_data from manifest where key = ?1;" : @"select key, filename, size, inline_data, modification_time, last_access_time, extended_data from manifest where key = ?1;";
  297. sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
  298. if (!stmt) return nil;
  299. sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
  300. YYKVStorageItem *item = nil;
  301. int result = sqlite3_step(stmt);
  302. if (result == SQLITE_ROW) {
  303. item = [self _dbGetItemFromStmt:stmt excludeInlineData:excludeInlineData];
  304. } else {
  305. if (result != SQLITE_DONE) {
  306. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  307. }
  308. }
  309. return item;
  310. }
  311. - (NSMutableArray *)_dbGetItemWithKeys:(NSArray *)keys excludeInlineData:(BOOL)excludeInlineData {
  312. if (![self _dbIsReady]) return nil;
  313. NSString *sql;
  314. if (excludeInlineData) {
  315. sql = [NSString stringWithFormat:@"select key, filename, size, modification_time, last_access_time, extended_data from manifest where key in (%@);", [self _dbJoinedKeys:keys]];
  316. } else {
  317. sql = [NSString stringWithFormat:@"select key, filename, size, inline_data, modification_time, last_access_time, extended_data from manifest where key in (%@)", [self _dbJoinedKeys:keys]];
  318. }
  319. sqlite3_stmt *stmt = NULL;
  320. int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
  321. if (result != SQLITE_OK) {
  322. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  323. return nil;
  324. }
  325. [self _dbBindJoinedKeys:keys stmt:stmt fromIndex:1];
  326. NSMutableArray *items = [NSMutableArray new];
  327. do {
  328. result = sqlite3_step(stmt);
  329. if (result == SQLITE_ROW) {
  330. YYKVStorageItem *item = [self _dbGetItemFromStmt:stmt excludeInlineData:excludeInlineData];
  331. if (item) [items addObject:item];
  332. } else if (result == SQLITE_DONE) {
  333. break;
  334. } else {
  335. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  336. items = nil;
  337. break;
  338. }
  339. } while (1);
  340. sqlite3_finalize(stmt);
  341. return items;
  342. }
  343. - (NSData *)_dbGetValueWithKey:(NSString *)key {
  344. NSString *sql = @"select inline_data from manifest where key = ?1;";
  345. sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
  346. if (!stmt) return nil;
  347. sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
  348. int result = sqlite3_step(stmt);
  349. if (result == SQLITE_ROW) {
  350. const void *inline_data = sqlite3_column_blob(stmt, 0);
  351. int inline_data_bytes = sqlite3_column_bytes(stmt, 0);
  352. if (!inline_data || inline_data_bytes <= 0) return nil;
  353. return [NSData dataWithBytes:inline_data length:inline_data_bytes];
  354. } else {
  355. if (result != SQLITE_DONE) {
  356. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  357. }
  358. return nil;
  359. }
  360. }
  361. - (NSString *)_dbGetFilenameWithKey:(NSString *)key {
  362. NSString *sql = @"select filename from manifest where key = ?1;";
  363. sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
  364. if (!stmt) return nil;
  365. sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
  366. int result = sqlite3_step(stmt);
  367. if (result == SQLITE_ROW) {
  368. char *filename = (char *)sqlite3_column_text(stmt, 0);
  369. if (filename && *filename != 0) {
  370. return [NSString stringWithUTF8String:filename];
  371. }
  372. } else {
  373. if (result != SQLITE_DONE) {
  374. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  375. }
  376. }
  377. return nil;
  378. }
  379. - (NSMutableArray *)_dbGetFilenameWithKeys:(NSArray *)keys {
  380. if (![self _dbIsReady]) return nil;
  381. NSString *sql = [NSString stringWithFormat:@"select filename from manifest where key in (%@);", [self _dbJoinedKeys:keys]];
  382. sqlite3_stmt *stmt = NULL;
  383. int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
  384. if (result != SQLITE_OK) {
  385. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  386. return nil;
  387. }
  388. [self _dbBindJoinedKeys:keys stmt:stmt fromIndex:1];
  389. NSMutableArray *filenames = [NSMutableArray new];
  390. do {
  391. result = sqlite3_step(stmt);
  392. if (result == SQLITE_ROW) {
  393. char *filename = (char *)sqlite3_column_text(stmt, 0);
  394. if (filename && *filename != 0) {
  395. NSString *name = [NSString stringWithUTF8String:filename];
  396. if (name) [filenames addObject:name];
  397. }
  398. } else if (result == SQLITE_DONE) {
  399. break;
  400. } else {
  401. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  402. filenames = nil;
  403. break;
  404. }
  405. } while (1);
  406. sqlite3_finalize(stmt);
  407. return filenames;
  408. }
  409. - (NSMutableArray *)_dbGetFilenamesWithSizeLargerThan:(int)size {
  410. NSString *sql = @"select filename from manifest where size > ?1 and filename is not null;";
  411. sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
  412. if (!stmt) return nil;
  413. sqlite3_bind_int(stmt, 1, size);
  414. NSMutableArray *filenames = [NSMutableArray new];
  415. do {
  416. int result = sqlite3_step(stmt);
  417. if (result == SQLITE_ROW) {
  418. char *filename = (char *)sqlite3_column_text(stmt, 0);
  419. if (filename && *filename != 0) {
  420. NSString *name = [NSString stringWithUTF8String:filename];
  421. if (name) [filenames addObject:name];
  422. }
  423. } else if (result == SQLITE_DONE) {
  424. break;
  425. } else {
  426. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  427. filenames = nil;
  428. break;
  429. }
  430. } while (1);
  431. return filenames;
  432. }
  433. - (NSMutableArray *)_dbGetFilenamesWithTimeEarlierThan:(int)time {
  434. NSString *sql = @"select filename from manifest where last_access_time < ?1 and filename is not null;";
  435. sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
  436. if (!stmt) return nil;
  437. sqlite3_bind_int(stmt, 1, time);
  438. NSMutableArray *filenames = [NSMutableArray new];
  439. do {
  440. int result = sqlite3_step(stmt);
  441. if (result == SQLITE_ROW) {
  442. char *filename = (char *)sqlite3_column_text(stmt, 0);
  443. if (filename && *filename != 0) {
  444. NSString *name = [NSString stringWithUTF8String:filename];
  445. if (name) [filenames addObject:name];
  446. }
  447. } else if (result == SQLITE_DONE) {
  448. break;
  449. } else {
  450. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  451. filenames = nil;
  452. break;
  453. }
  454. } while (1);
  455. return filenames;
  456. }
  457. - (NSMutableArray *)_dbGetItemSizeInfoOrderByTimeAscWithLimit:(int)count {
  458. NSString *sql = @"select key, filename, size from manifest order by last_access_time asc limit ?1;";
  459. sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
  460. if (!stmt) return nil;
  461. sqlite3_bind_int(stmt, 1, count);
  462. NSMutableArray *items = [NSMutableArray new];
  463. do {
  464. int result = sqlite3_step(stmt);
  465. if (result == SQLITE_ROW) {
  466. char *key = (char *)sqlite3_column_text(stmt, 0);
  467. char *filename = (char *)sqlite3_column_text(stmt, 1);
  468. int size = sqlite3_column_int(stmt, 2);
  469. YYKVStorageItem *item = [YYKVStorageItem new];
  470. item.key = key ? [NSString stringWithUTF8String:key] : nil;
  471. item.filename = filename ? [NSString stringWithUTF8String:filename] : nil;
  472. item.size = size;
  473. [items addObject:item];
  474. } else if (result == SQLITE_DONE) {
  475. break;
  476. } else {
  477. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  478. items = nil;
  479. break;
  480. }
  481. } while (1);
  482. return items;
  483. }
  484. - (int)_dbGetItemCountWithKey:(NSString *)key {
  485. NSString *sql = @"select count(key) from manifest where key = ?1;";
  486. sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
  487. if (!stmt) return -1;
  488. sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
  489. int result = sqlite3_step(stmt);
  490. if (result != SQLITE_ROW) {
  491. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  492. return -1;
  493. }
  494. return sqlite3_column_int(stmt, 0);
  495. }
  496. - (int)_dbGetTotalItemSize {
  497. NSString *sql = @"select sum(size) from manifest;";
  498. sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
  499. if (!stmt) return -1;
  500. int result = sqlite3_step(stmt);
  501. if (result != SQLITE_ROW) {
  502. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  503. return -1;
  504. }
  505. return sqlite3_column_int(stmt, 0);
  506. }
  507. - (int)_dbGetTotalItemCount {
  508. NSString *sql = @"select count(*) from manifest;";
  509. sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
  510. if (!stmt) return -1;
  511. int result = sqlite3_step(stmt);
  512. if (result != SQLITE_ROW) {
  513. if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
  514. return -1;
  515. }
  516. return sqlite3_column_int(stmt, 0);
  517. }
  518. #pragma mark - file
  519. - (BOOL)_fileWriteWithName:(NSString *)filename data:(NSData *)data {
  520. if (_invalidated) return NO;
  521. NSString *path = [_dataPath stringByAppendingPathComponent:filename];
  522. return [data writeToFile:path atomically:NO];
  523. }
  524. - (NSData *)_fileReadWithName:(NSString *)filename {
  525. if (_invalidated) return nil;
  526. NSString *path = [_dataPath stringByAppendingPathComponent:filename];
  527. NSData *data = [NSData dataWithContentsOfFile:path];
  528. return data;
  529. }
  530. - (BOOL)_fileDeleteWithName:(NSString *)filename {
  531. if (_invalidated) return NO;
  532. NSString *path = [_dataPath stringByAppendingPathComponent:filename];
  533. return [[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
  534. }
  535. - (BOOL)_fileMoveAllToTrash {
  536. if (_invalidated) return NO;
  537. CFUUIDRef uuidRef = CFUUIDCreate(NULL);
  538. CFStringRef uuid = CFUUIDCreateString(NULL, uuidRef);
  539. CFRelease(uuidRef);
  540. NSString *tmpPath = [_trashPath stringByAppendingPathComponent:(__bridge NSString *)(uuid)];
  541. BOOL suc = [[NSFileManager defaultManager] moveItemAtPath:_dataPath toPath:tmpPath error:nil];
  542. if (suc) {
  543. suc = [[NSFileManager defaultManager] createDirectoryAtPath:_dataPath withIntermediateDirectories:YES attributes:nil error:NULL];
  544. }
  545. CFRelease(uuid);
  546. return suc;
  547. }
  548. - (void)_fileEmptyTrashInBackground {
  549. if (_invalidated) return;
  550. NSString *trashPath = _trashPath;
  551. dispatch_queue_t queue = _trashQueue;
  552. dispatch_async(queue, ^{
  553. NSFileManager *manager = [NSFileManager new];
  554. NSArray *directoryContents = [manager contentsOfDirectoryAtPath:trashPath error:NULL];
  555. for (NSString *path in directoryContents) {
  556. NSString *fullPath = [trashPath stringByAppendingPathComponent:path];
  557. [manager removeItemAtPath:fullPath error:NULL];
  558. }
  559. });
  560. }
  561. #pragma mark - private
  562. /**
  563. Delete all files and empty in background.
  564. Make sure the db is closed.
  565. */
  566. - (void)_reset {
  567. [[NSFileManager defaultManager] removeItemAtPath:[_path stringByAppendingPathComponent:kDBFileName] error:nil];
  568. [[NSFileManager defaultManager] removeItemAtPath:[_path stringByAppendingPathComponent:kDBShmFileName] error:nil];
  569. [[NSFileManager defaultManager] removeItemAtPath:[_path stringByAppendingPathComponent:kDBWalFileName] error:nil];
  570. [self _fileMoveAllToTrash];
  571. [self _fileEmptyTrashInBackground];
  572. }
  573. - (void)_appWillBeTerminated {
  574. _invalidated = YES;
  575. }
  576. #pragma mark - public
  577. - (instancetype)init {
  578. @throw [NSException exceptionWithName:@"YYKVStorage init error" reason:@"Please use the designated initializer and pass the 'path' and 'type'." userInfo:nil];
  579. return [self initWithPath:@"" type:YYKVStorageTypeFile];
  580. }
  581. - (instancetype)initWithPath:(NSString *)path type:(YYKVStorageType)type {
  582. if (path.length == 0 || path.length > kPathLengthMax) {
  583. NSLog(@"YYKVStorage init error: invalid path: [%@].", path);
  584. return nil;
  585. }
  586. if (type > YYKVStorageTypeMixed) {
  587. NSLog(@"YYKVStorage init error: invalid type: %lu.", (unsigned long)type);
  588. return nil;
  589. }
  590. self = [super init];
  591. _path = path.copy;
  592. _type = type;
  593. _dataPath = [path stringByAppendingPathComponent:kDataDirectoryName];
  594. _trashPath = [path stringByAppendingPathComponent:kTrashDirectoryName];
  595. _trashQueue = dispatch_queue_create("com.ibireme.cache.disk.trash", DISPATCH_QUEUE_SERIAL);
  596. _dbPath = [path stringByAppendingPathComponent:kDBFileName];
  597. _errorLogsEnabled = YES;
  598. NSError *error = nil;
  599. if (![[NSFileManager defaultManager] createDirectoryAtPath:path
  600. withIntermediateDirectories:YES
  601. attributes:nil
  602. error:&error] ||
  603. ![[NSFileManager defaultManager] createDirectoryAtPath:[path stringByAppendingPathComponent:kDataDirectoryName]
  604. withIntermediateDirectories:YES
  605. attributes:nil
  606. error:&error] ||
  607. ![[NSFileManager defaultManager] createDirectoryAtPath:[path stringByAppendingPathComponent:kTrashDirectoryName]
  608. withIntermediateDirectories:YES
  609. attributes:nil
  610. error:&error]) {
  611. NSLog(@"YYKVStorage init error:%@", error);
  612. return nil;
  613. }
  614. if (![self _dbOpen] || ![self _dbInitialize]) {
  615. // db file may broken...
  616. [self _dbClose];
  617. [self _reset]; // rebuild
  618. if (![self _dbOpen] || ![self _dbInitialize]) {
  619. [self _dbClose];
  620. NSLog(@"YYKVStorage init error: fail to open sqlite db.");
  621. }
  622. return nil;
  623. }
  624. [self _fileEmptyTrashInBackground]; // empty the trash if failed at last time
  625. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil];
  626. return self;
  627. }
  628. - (void)dealloc {
  629. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
  630. [self _dbClose];
  631. }
  632. - (BOOL)saveItem:(YYKVStorageItem *)item {
  633. return [self saveItemWithKey:item.key value:item.value filename:item.filename extendedData:item.extendedData];
  634. }
  635. - (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value {
  636. return [self saveItemWithKey:key value:value filename:nil extendedData:nil];
  637. }
  638. - (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value filename:(NSString *)filename extendedData:(NSData *)extendedData {
  639. if (key.length == 0 || value.length == 0) return NO;
  640. if (_type == YYKVStorageTypeFile && filename.length == 0) {
  641. return NO;
  642. }
  643. if (filename.length) {
  644. if (![self _fileWriteWithName:filename data:value]) {
  645. return NO;
  646. }
  647. if (![self _dbSaveWithKey:key value:value fileName:filename extendedData:extendedData]) {
  648. [self _fileDeleteWithName:filename];
  649. return NO;
  650. }
  651. return YES;
  652. } else {
  653. if (_type != YYKVStorageTypeSQLite) {
  654. NSString *filename = [self _dbGetFilenameWithKey:key];
  655. if (filename) {
  656. [self _fileDeleteWithName:filename];
  657. }
  658. }
  659. return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData];
  660. }
  661. }
  662. - (BOOL)removeItemForKey:(NSString *)key {
  663. if (key.length == 0) return NO;
  664. switch (_type) {
  665. case YYKVStorageTypeSQLite: {
  666. return [self _dbDeleteItemWithKey:key];
  667. } break;
  668. case YYKVStorageTypeFile:
  669. case YYKVStorageTypeMixed: {
  670. NSString *filename = [self _dbGetFilenameWithKey:key];
  671. if (filename) {
  672. [self _fileDeleteWithName:filename];
  673. }
  674. return [self _dbDeleteItemWithKey:key];
  675. } break;
  676. default: return NO;
  677. }
  678. }
  679. - (BOOL)removeItemForKeys:(NSArray *)keys {
  680. if (keys.count == 0) return NO;
  681. switch (_type) {
  682. case YYKVStorageTypeSQLite: {
  683. return [self _dbDeleteItemWithKeys:keys];
  684. } break;
  685. case YYKVStorageTypeFile:
  686. case YYKVStorageTypeMixed: {
  687. NSArray *filenames = [self _dbGetFilenameWithKeys:keys];
  688. for (NSString *filename in filenames) {
  689. [self _fileDeleteWithName:filename];
  690. }
  691. return [self _dbDeleteItemWithKeys:keys];
  692. } break;
  693. default: return NO;
  694. }
  695. }
  696. - (BOOL)removeItemsLargerThanSize:(int)size {
  697. if (size == INT_MAX) return YES;
  698. if (size <= 0) return [self removeAllItems];
  699. switch (_type) {
  700. case YYKVStorageTypeSQLite: {
  701. if ([self _dbDeleteItemsWithSizeLargerThan:size]) {
  702. [self _dbCheckpoint];
  703. return YES;
  704. }
  705. } break;
  706. case YYKVStorageTypeFile:
  707. case YYKVStorageTypeMixed: {
  708. NSArray *filenames = [self _dbGetFilenamesWithSizeLargerThan:size];
  709. for (NSString *name in filenames) {
  710. [self _fileDeleteWithName:name];
  711. }
  712. if ([self _dbDeleteItemsWithSizeLargerThan:size]) {
  713. [self _dbCheckpoint];
  714. return YES;
  715. }
  716. } break;
  717. }
  718. return NO;
  719. }
  720. - (BOOL)removeItemsEarlierThanTime:(int)time {
  721. if (time <= 0) return YES;
  722. if (time == INT_MAX) return [self removeAllItems];
  723. switch (_type) {
  724. case YYKVStorageTypeSQLite: {
  725. if ([self _dbDeleteItemsWithTimeEarlierThan:time]) {
  726. [self _dbCheckpoint];
  727. return YES;
  728. }
  729. } break;
  730. case YYKVStorageTypeFile:
  731. case YYKVStorageTypeMixed: {
  732. NSArray *filenames = [self _dbGetFilenamesWithTimeEarlierThan:time];
  733. for (NSString *name in filenames) {
  734. [self _fileDeleteWithName:name];
  735. }
  736. if ([self _dbDeleteItemsWithTimeEarlierThan:time]) {
  737. [self _dbCheckpoint];
  738. return NO;
  739. }
  740. } break;
  741. }
  742. return NO;
  743. }
  744. - (BOOL)removeItemsToFitSize:(int)maxSize {
  745. if (maxSize == INT_MAX) return YES;
  746. if (maxSize <= 0) return [self removeAllItems];
  747. int total = [self _dbGetTotalItemSize];
  748. if (total < 0) return NO;
  749. if (total <= maxSize) return YES;
  750. NSArray *items = nil;
  751. BOOL suc = NO;
  752. do {
  753. int perCount = 16;
  754. items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount];
  755. for (YYKVStorageItem *item in items) {
  756. if (total > maxSize) {
  757. if (item.filename) {
  758. [self _fileDeleteWithName:item.filename];
  759. }
  760. suc = [self _dbDeleteItemWithKey:item.key];
  761. total -= item.size;
  762. } else {
  763. break;
  764. }
  765. if (!suc) break;
  766. }
  767. } while (total > maxSize && items.count > 0 && suc);
  768. if (suc) [self _dbCheckpoint];
  769. return suc;
  770. }
  771. - (BOOL)removeItemsToFitCount:(int)maxCount {
  772. if (maxCount == INT_MAX) return YES;
  773. if (maxCount <= 0) return [self removeAllItems];
  774. int total = [self _dbGetTotalItemCount];
  775. if (total < 0) return NO;
  776. if (total <= maxCount) return YES;
  777. NSArray *items = nil;
  778. BOOL suc = NO;
  779. do {
  780. int perCount = 16;
  781. items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount];
  782. for (YYKVStorageItem *item in items) {
  783. if (total > maxCount) {
  784. if (item.filename) {
  785. [self _fileDeleteWithName:item.filename];
  786. }
  787. suc = [self _dbDeleteItemWithKey:item.key];
  788. total--;
  789. } else {
  790. break;
  791. }
  792. if (!suc) break;
  793. }
  794. } while (total > maxCount && items.count > 0 && suc);
  795. if (suc) [self _dbCheckpoint];
  796. return suc;
  797. }
  798. - (BOOL)removeAllItems {
  799. if (![self _dbClose]) return NO;
  800. [self _reset];
  801. if (![self _dbOpen]) return NO;
  802. if (![self _dbInitialize]) return NO;
  803. return YES;
  804. }
  805. - (void)removeAllItemsWithProgressBlock:(void(^)(int removedCount, int totalCount))progress
  806. endBlock:(void(^)(BOOL error))end {
  807. int total = [self _dbGetTotalItemCount];
  808. if (total <= 0) {
  809. if (end) end(total < 0);
  810. } else {
  811. int left = total;
  812. int perCount = 32;
  813. NSArray *items = nil;
  814. BOOL suc = NO;
  815. do {
  816. items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount];
  817. for (YYKVStorageItem *item in items) {
  818. if (left > 0) {
  819. if (item.filename) {
  820. [self _fileDeleteWithName:item.filename];
  821. }
  822. suc = [self _dbDeleteItemWithKey:item.key];
  823. left--;
  824. } else {
  825. break;
  826. }
  827. if (!suc) break;
  828. }
  829. if (progress) progress(total - left, total);
  830. } while (left > 0 && items.count > 0 && suc);
  831. if (suc) [self _dbCheckpoint];
  832. if (end) end(!suc);
  833. }
  834. }
  835. - (YYKVStorageItem *)getItemForKey:(NSString *)key {
  836. if (key.length == 0) return nil;
  837. YYKVStorageItem *item = [self _dbGetItemWithKey:key excludeInlineData:NO];
  838. if (item) {
  839. [self _dbUpdateAccessTimeWithKey:key];
  840. if (item.filename) {
  841. item.value = [self _fileReadWithName:item.filename];
  842. if (!item.value) {
  843. [self _dbDeleteItemWithKey:key];
  844. item = nil;
  845. }
  846. }
  847. }
  848. return item;
  849. }
  850. - (YYKVStorageItem *)getItemInfoForKey:(NSString *)key {
  851. if (key.length == 0) return nil;
  852. YYKVStorageItem *item = [self _dbGetItemWithKey:key excludeInlineData:YES];
  853. return item;
  854. }
  855. - (NSData *)getItemValueForKey:(NSString *)key {
  856. if (key.length == 0) return nil;
  857. NSData *value = nil;
  858. switch (_type) {
  859. case YYKVStorageTypeFile: {
  860. NSString *filename = [self _dbGetFilenameWithKey:key];
  861. if (filename) {
  862. value = [self _fileReadWithName:filename];
  863. if (!value) {
  864. [self _dbDeleteItemWithKey:key];
  865. value = nil;
  866. }
  867. }
  868. } break;
  869. case YYKVStorageTypeSQLite: {
  870. value = [self _dbGetValueWithKey:key];
  871. } break;
  872. case YYKVStorageTypeMixed: {
  873. NSString *filename = [self _dbGetFilenameWithKey:key];
  874. if (filename) {
  875. value = [self _fileReadWithName:filename];
  876. if (!value) {
  877. [self _dbDeleteItemWithKey:key];
  878. value = nil;
  879. }
  880. } else {
  881. value = [self _dbGetValueWithKey:key];
  882. }
  883. } break;
  884. }
  885. if (value) {
  886. [self _dbUpdateAccessTimeWithKey:key];
  887. }
  888. return value;
  889. }
  890. - (NSArray *)getItemForKeys:(NSArray *)keys {
  891. if (keys.count == 0) return nil;
  892. NSMutableArray *items = [self _dbGetItemWithKeys:keys excludeInlineData:NO];
  893. if (_type != YYKVStorageTypeSQLite) {
  894. for (NSInteger i = 0, max = items.count; i < max; i++) {
  895. YYKVStorageItem *item = items[i];
  896. if (item.filename) {
  897. item.value = [self _fileReadWithName:item.filename];
  898. if (!item.value) {
  899. if (item.key) [self _dbDeleteItemWithKey:item.key];
  900. [items removeObjectAtIndex:i];
  901. i--;
  902. max--;
  903. }
  904. }
  905. }
  906. }
  907. if (items.count > 0) {
  908. [self _dbUpdateAccessTimeWithKeys:keys];
  909. }
  910. return items.count ? items : nil;
  911. }
  912. - (NSArray *)getItemInfoForKeys:(NSArray *)keys {
  913. if (keys.count == 0) return nil;
  914. return [self _dbGetItemWithKeys:keys excludeInlineData:YES];
  915. }
  916. - (NSDictionary *)getItemValueForKeys:(NSArray *)keys {
  917. NSMutableArray *items = (NSMutableArray *)[self getItemForKeys:keys];
  918. NSMutableDictionary *kv = [NSMutableDictionary new];
  919. for (YYKVStorageItem *item in items) {
  920. if (item.key && item.value) {
  921. [kv setObject:item.value forKey:item.key];
  922. }
  923. }
  924. return kv.count ? kv : nil;
  925. }
  926. - (BOOL)itemExistsForKey:(NSString *)key {
  927. if (key.length == 0) return NO;
  928. return [self _dbGetItemCountWithKey:key] > 0;
  929. }
  930. - (int)getItemsCount {
  931. return [self _dbGetTotalItemCount];
  932. }
  933. - (int)getItemsSize {
  934. return [self _dbGetTotalItemSize];
  935. }
  936. @end