YYKVStorage.m 37 KB

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