123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053 |
- //
- // YYKVStorage.m
- // YYKit <https://github.com/ibireme/YYKit>
- //
- // Created by ibireme on 15/4/22.
- // Copyright (c) 2015 ibireme.
- //
- // This source code is licensed under the MIT-style license found in the
- // LICENSE file in the root directory of this source tree.
- //
- #import "YYKVStorage.h"
- #import "UIApplication+YYAdd.h"
- #import <UIKit/UIKit.h>
- #import <time.h>
- #if __has_include(<sqlite3.h>)
- #import <sqlite3.h>
- #else
- #import "sqlite3.h"
- #endif
- static const NSUInteger kMaxErrorRetryCount = 8;
- static const NSTimeInterval kMinRetryTimeInterval = 2.0;
- static const int kPathLengthMax = PATH_MAX - 64;
- static NSString *const kDBFileName = @"manifest.sqlite";
- static NSString *const kDBShmFileName = @"manifest.sqlite-shm";
- static NSString *const kDBWalFileName = @"manifest.sqlite-wal";
- static NSString *const kDataDirectoryName = @"data";
- static NSString *const kTrashDirectoryName = @"trash";
- /*
- File:
- /path/
- /manifest.sqlite
- /manifest.sqlite-shm
- /manifest.sqlite-wal
- /data/
- /e10adc3949ba59abbe56e057f20f883e
- /e10adc3949ba59abbe56e057f20f883e
- /trash/
- /unused_file_or_folder
-
- SQL:
- 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);
- */
- @implementation YYKVStorageItem
- @end
- @implementation YYKVStorage {
- dispatch_queue_t _trashQueue;
-
- NSString *_path;
- NSString *_dbPath;
- NSString *_dataPath;
- NSString *_trashPath;
-
- sqlite3 *_db;
- CFMutableDictionaryRef _dbStmtCache;
- NSTimeInterval _dbLastOpenErrorTime;
- NSUInteger _dbOpenErrorCount;
- }
- #pragma mark - db
- - (BOOL)_dbOpen {
- if (_db) return YES;
-
- int result = sqlite3_open(_dbPath.UTF8String, &_db);
- if (result == SQLITE_OK) {
- CFDictionaryKeyCallBacks keyCallbacks = kCFCopyStringDictionaryKeyCallBacks;
- CFDictionaryValueCallBacks valueCallbacks = {0};
- _dbStmtCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &keyCallbacks, &valueCallbacks);
- _dbLastOpenErrorTime = 0;
- _dbOpenErrorCount = 0;
- return YES;
- } else {
- _db = NULL;
- if (_dbStmtCache) CFRelease(_dbStmtCache);
- _dbStmtCache = NULL;
- _dbLastOpenErrorTime = CACurrentMediaTime();
- _dbOpenErrorCount++;
-
- if (_errorLogsEnabled) {
- NSLog(@"%s line:%d sqlite open failed (%d).", __FUNCTION__, __LINE__, result);
- }
- return NO;
- }
- }
- - (BOOL)_dbClose {
- if (!_db) return YES;
-
- int result = 0;
- BOOL retry = NO;
- BOOL stmtFinalized = NO;
-
- if (_dbStmtCache) CFRelease(_dbStmtCache);
- _dbStmtCache = NULL;
-
- do {
- retry = NO;
- result = sqlite3_close(_db);
- if (result == SQLITE_BUSY || result == SQLITE_LOCKED) {
- if (!stmtFinalized) {
- stmtFinalized = YES;
- sqlite3_stmt *stmt;
- while ((stmt = sqlite3_next_stmt(_db, nil)) != 0) {
- sqlite3_finalize(stmt);
- retry = YES;
- }
- }
- } else if (result != SQLITE_OK) {
- if (_errorLogsEnabled) {
- NSLog(@"%s line:%d sqlite close failed (%d).", __FUNCTION__, __LINE__, result);
- }
- }
- } while (retry);
- _db = NULL;
- return YES;
- }
- - (BOOL)_dbCheck {
- if (!_db) {
- if (_dbOpenErrorCount < kMaxErrorRetryCount &&
- CACurrentMediaTime() - _dbLastOpenErrorTime > kMinRetryTimeInterval) {
- return [self _dbOpen] && [self _dbInitialize];
- } else {
- return NO;
- }
- }
- return YES;
- }
- - (BOOL)_dbInitialize {
- 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);";
- return [self _dbExecute:sql];
- }
- - (void)_dbCheckpoint {
- if (![self _dbCheck]) return;
- // Cause a checkpoint to occur, merge `sqlite-wal` file to `sqlite` file.
- sqlite3_wal_checkpoint(_db, NULL);
- }
- - (BOOL)_dbExecute:(NSString *)sql {
- if (sql.length == 0) return NO;
- if (![self _dbCheck]) return NO;
-
- char *error = NULL;
- int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &error);
- if (error) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite exec error (%d): %s", __FUNCTION__, __LINE__, result, error);
- sqlite3_free(error);
- }
-
- return result == SQLITE_OK;
- }
- - (sqlite3_stmt *)_dbPrepareStmt:(NSString *)sql {
- if (![self _dbCheck] || sql.length == 0 || !_dbStmtCache) return NULL;
- sqlite3_stmt *stmt = (sqlite3_stmt *)CFDictionaryGetValue(_dbStmtCache, (__bridge const void *)(sql));
- if (!stmt) {
- int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
- if (result != SQLITE_OK) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- return NULL;
- }
- CFDictionarySetValue(_dbStmtCache, (__bridge const void *)(sql), stmt);
- } else {
- sqlite3_reset(stmt);
- }
- return stmt;
- }
- - (NSString *)_dbJoinedKeys:(NSArray *)keys {
- NSMutableString *string = [NSMutableString new];
- for (NSUInteger i = 0,max = keys.count; i < max; i++) {
- [string appendString:@"?"];
- if (i + 1 != max) {
- [string appendString:@","];
- }
- }
- return string;
- }
- - (void)_dbBindJoinedKeys:(NSArray *)keys stmt:(sqlite3_stmt *)stmt fromIndex:(int)index{
- for (int i = 0, max = (int)keys.count; i < max; i++) {
- NSString *key = keys[i];
- sqlite3_bind_text(stmt, index + i, key.UTF8String, -1, NULL);
- }
- }
- - (BOOL)_dbSaveWithKey:(NSString *)key value:(NSData *)value fileName:(NSString *)fileName extendedData:(NSData *)extendedData {
- 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);";
- sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
- if (!stmt) return NO;
-
- int timestamp = (int)time(NULL);
- sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
- sqlite3_bind_text(stmt, 2, fileName.UTF8String, -1, NULL);
- sqlite3_bind_int(stmt, 3, (int)value.length);
- if (fileName.length == 0) {
- sqlite3_bind_blob(stmt, 4, value.bytes, (int)value.length, 0);
- } else {
- sqlite3_bind_blob(stmt, 4, NULL, 0, 0);
- }
- sqlite3_bind_int(stmt, 5, timestamp);
- sqlite3_bind_int(stmt, 6, timestamp);
- sqlite3_bind_blob(stmt, 7, extendedData.bytes, (int)extendedData.length, 0);
-
- int result = sqlite3_step(stmt);
- if (result != SQLITE_DONE) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite insert error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- return NO;
- }
- return YES;
- }
- - (BOOL)_dbUpdateAccessTimeWithKey:(NSString *)key {
- NSString *sql = @"update manifest set last_access_time = ?1 where key = ?2;";
- sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
- if (!stmt) return NO;
- sqlite3_bind_int(stmt, 1, (int)time(NULL));
- sqlite3_bind_text(stmt, 2, key.UTF8String, -1, NULL);
- int result = sqlite3_step(stmt);
- if (result != SQLITE_DONE) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite update error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- return NO;
- }
- return YES;
- }
- - (BOOL)_dbUpdateAccessTimeWithKeys:(NSArray *)keys {
- if (![self _dbCheck]) return NO;
- int t = (int)time(NULL);
- NSString *sql = [NSString stringWithFormat:@"update manifest set last_access_time = %d where key in (%@);", t, [self _dbJoinedKeys:keys]];
-
- sqlite3_stmt *stmt = NULL;
- int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
- if (result != SQLITE_OK) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- return NO;
- }
-
- [self _dbBindJoinedKeys:keys stmt:stmt fromIndex:1];
- result = sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- if (result != SQLITE_DONE) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite update error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- return NO;
- }
- return YES;
- }
- - (BOOL)_dbDeleteItemWithKey:(NSString *)key {
- NSString *sql = @"delete from manifest where key = ?1;";
- sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
- if (!stmt) return NO;
- sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
-
- int result = sqlite3_step(stmt);
- if (result != SQLITE_DONE) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d db delete error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- return NO;
- }
- return YES;
- }
- - (BOOL)_dbDeleteItemWithKeys:(NSArray *)keys {
- if (![self _dbCheck]) return NO;
- NSString *sql = [NSString stringWithFormat:@"delete from manifest where key in (%@);", [self _dbJoinedKeys:keys]];
- sqlite3_stmt *stmt = NULL;
- int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
- if (result != SQLITE_OK) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- return NO;
- }
-
- [self _dbBindJoinedKeys:keys stmt:stmt fromIndex:1];
- result = sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- if (result == SQLITE_ERROR) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite delete error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- return NO;
- }
- return YES;
- }
- - (BOOL)_dbDeleteItemsWithSizeLargerThan:(int)size {
- NSString *sql = @"delete from manifest where size > ?1;";
- sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
- if (!stmt) return NO;
- sqlite3_bind_int(stmt, 1, size);
- int result = sqlite3_step(stmt);
- if (result != SQLITE_DONE) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite delete error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- return NO;
- }
- return YES;
- }
- - (BOOL)_dbDeleteItemsWithTimeEarlierThan:(int)time {
- NSString *sql = @"delete from manifest where last_access_time < ?1;";
- sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
- if (!stmt) return NO;
- sqlite3_bind_int(stmt, 1, time);
- int result = sqlite3_step(stmt);
- if (result != SQLITE_DONE) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite delete error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- return NO;
- }
- return YES;
- }
- - (YYKVStorageItem *)_dbGetItemFromStmt:(sqlite3_stmt *)stmt excludeInlineData:(BOOL)excludeInlineData {
- int i = 0;
- char *key = (char *)sqlite3_column_text(stmt, i++);
- char *filename = (char *)sqlite3_column_text(stmt, i++);
- int size = sqlite3_column_int(stmt, i++);
- const void *inline_data = excludeInlineData ? NULL : sqlite3_column_blob(stmt, i);
- int inline_data_bytes = excludeInlineData ? 0 : sqlite3_column_bytes(stmt, i++);
- int modification_time = sqlite3_column_int(stmt, i++);
- int last_access_time = sqlite3_column_int(stmt, i++);
- const void *extended_data = sqlite3_column_blob(stmt, i);
- int extended_data_bytes = sqlite3_column_bytes(stmt, i++);
-
- YYKVStorageItem *item = [YYKVStorageItem new];
- if (key) item.key = [NSString stringWithUTF8String:key];
- if (filename && *filename != 0) item.filename = [NSString stringWithUTF8String:filename];
- item.size = size;
- if (inline_data_bytes > 0 && inline_data) item.value = [NSData dataWithBytes:inline_data length:inline_data_bytes];
- item.modTime = modification_time;
- item.accessTime = last_access_time;
- if (extended_data_bytes > 0 && extended_data) item.extendedData = [NSData dataWithBytes:extended_data length:extended_data_bytes];
- return item;
- }
- - (YYKVStorageItem *)_dbGetItemWithKey:(NSString *)key excludeInlineData:(BOOL)excludeInlineData {
- 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;";
- sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
- if (!stmt) return nil;
- sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
-
- YYKVStorageItem *item = nil;
- int result = sqlite3_step(stmt);
- if (result == SQLITE_ROW) {
- item = [self _dbGetItemFromStmt:stmt excludeInlineData:excludeInlineData];
- } else {
- if (result != SQLITE_DONE) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- }
- }
- return item;
- }
- - (NSMutableArray *)_dbGetItemWithKeys:(NSArray *)keys excludeInlineData:(BOOL)excludeInlineData {
- if (![self _dbCheck]) return nil;
- NSString *sql;
- if (excludeInlineData) {
- sql = [NSString stringWithFormat:@"select key, filename, size, modification_time, last_access_time, extended_data from manifest where key in (%@);", [self _dbJoinedKeys:keys]];
- } else {
- sql = [NSString stringWithFormat:@"select key, filename, size, inline_data, modification_time, last_access_time, extended_data from manifest where key in (%@)", [self _dbJoinedKeys:keys]];
- }
-
- sqlite3_stmt *stmt = NULL;
- int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
- if (result != SQLITE_OK) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- return nil;
- }
-
- [self _dbBindJoinedKeys:keys stmt:stmt fromIndex:1];
- NSMutableArray *items = [NSMutableArray new];
- do {
- result = sqlite3_step(stmt);
- if (result == SQLITE_ROW) {
- YYKVStorageItem *item = [self _dbGetItemFromStmt:stmt excludeInlineData:excludeInlineData];
- if (item) [items addObject:item];
- } else if (result == SQLITE_DONE) {
- break;
- } else {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- items = nil;
- break;
- }
- } while (1);
- sqlite3_finalize(stmt);
- return items;
- }
- - (NSData *)_dbGetValueWithKey:(NSString *)key {
- NSString *sql = @"select inline_data from manifest where key = ?1;";
- sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
- if (!stmt) return nil;
- sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
-
- int result = sqlite3_step(stmt);
- if (result == SQLITE_ROW) {
- const void *inline_data = sqlite3_column_blob(stmt, 0);
- int inline_data_bytes = sqlite3_column_bytes(stmt, 0);
- if (!inline_data || inline_data_bytes <= 0) return nil;
- return [NSData dataWithBytes:inline_data length:inline_data_bytes];
- } else {
- if (result != SQLITE_DONE) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- }
- return nil;
- }
- }
- - (NSString *)_dbGetFilenameWithKey:(NSString *)key {
- NSString *sql = @"select filename from manifest where key = ?1;";
- sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
- if (!stmt) return nil;
- sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
- int result = sqlite3_step(stmt);
- if (result == SQLITE_ROW) {
- char *filename = (char *)sqlite3_column_text(stmt, 0);
- if (filename && *filename != 0) {
- return [NSString stringWithUTF8String:filename];
- }
- } else {
- if (result != SQLITE_DONE) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- }
- }
- return nil;
- }
- - (NSMutableArray *)_dbGetFilenameWithKeys:(NSArray *)keys {
- if (![self _dbCheck]) return nil;
- NSString *sql = [NSString stringWithFormat:@"select filename from manifest where key in (%@);", [self _dbJoinedKeys:keys]];
- sqlite3_stmt *stmt = NULL;
- int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
- if (result != SQLITE_OK) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- return nil;
- }
-
- [self _dbBindJoinedKeys:keys stmt:stmt fromIndex:1];
- NSMutableArray *filenames = [NSMutableArray new];
- do {
- result = sqlite3_step(stmt);
- if (result == SQLITE_ROW) {
- char *filename = (char *)sqlite3_column_text(stmt, 0);
- if (filename && *filename != 0) {
- NSString *name = [NSString stringWithUTF8String:filename];
- if (name) [filenames addObject:name];
- }
- } else if (result == SQLITE_DONE) {
- break;
- } else {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- filenames = nil;
- break;
- }
- } while (1);
- sqlite3_finalize(stmt);
- return filenames;
- }
- - (NSMutableArray *)_dbGetFilenamesWithSizeLargerThan:(int)size {
- NSString *sql = @"select filename from manifest where size > ?1 and filename is not null;";
- sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
- if (!stmt) return nil;
- sqlite3_bind_int(stmt, 1, size);
-
- NSMutableArray *filenames = [NSMutableArray new];
- do {
- int result = sqlite3_step(stmt);
- if (result == SQLITE_ROW) {
- char *filename = (char *)sqlite3_column_text(stmt, 0);
- if (filename && *filename != 0) {
- NSString *name = [NSString stringWithUTF8String:filename];
- if (name) [filenames addObject:name];
- }
- } else if (result == SQLITE_DONE) {
- break;
- } else {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- filenames = nil;
- break;
- }
- } while (1);
- return filenames;
- }
- - (NSMutableArray *)_dbGetFilenamesWithTimeEarlierThan:(int)time {
- NSString *sql = @"select filename from manifest where last_access_time < ?1 and filename is not null;";
- sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
- if (!stmt) return nil;
- sqlite3_bind_int(stmt, 1, time);
-
- NSMutableArray *filenames = [NSMutableArray new];
- do {
- int result = sqlite3_step(stmt);
- if (result == SQLITE_ROW) {
- char *filename = (char *)sqlite3_column_text(stmt, 0);
- if (filename && *filename != 0) {
- NSString *name = [NSString stringWithUTF8String:filename];
- if (name) [filenames addObject:name];
- }
- } else if (result == SQLITE_DONE) {
- break;
- } else {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- filenames = nil;
- break;
- }
- } while (1);
- return filenames;
- }
- - (NSMutableArray *)_dbGetItemSizeInfoOrderByTimeAscWithLimit:(int)count {
- NSString *sql = @"select key, filename, size from manifest order by last_access_time asc limit ?1;";
- sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
- if (!stmt) return nil;
- sqlite3_bind_int(stmt, 1, count);
-
- NSMutableArray *items = [NSMutableArray new];
- do {
- int result = sqlite3_step(stmt);
- if (result == SQLITE_ROW) {
- char *key = (char *)sqlite3_column_text(stmt, 0);
- char *filename = (char *)sqlite3_column_text(stmt, 1);
- int size = sqlite3_column_int(stmt, 2);
- NSString *keyStr = key ? [NSString stringWithUTF8String:key] : nil;
- if (keyStr) {
- YYKVStorageItem *item = [YYKVStorageItem new];
- item.key = key ? [NSString stringWithUTF8String:key] : nil;
- item.filename = filename ? [NSString stringWithUTF8String:filename] : nil;
- item.size = size;
- [items addObject:item];
- }
- } else if (result == SQLITE_DONE) {
- break;
- } else {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- items = nil;
- break;
- }
- } while (1);
- return items;
- }
- - (int)_dbGetItemCountWithKey:(NSString *)key {
- NSString *sql = @"select count(key) from manifest where key = ?1;";
- sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
- if (!stmt) return -1;
- sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
- int result = sqlite3_step(stmt);
- if (result != SQLITE_ROW) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- return -1;
- }
- return sqlite3_column_int(stmt, 0);
- }
- - (int)_dbGetTotalItemSize {
- NSString *sql = @"select sum(size) from manifest;";
- sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
- if (!stmt) return -1;
- int result = sqlite3_step(stmt);
- if (result != SQLITE_ROW) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- return -1;
- }
- return sqlite3_column_int(stmt, 0);
- }
- - (int)_dbGetTotalItemCount {
- NSString *sql = @"select count(*) from manifest;";
- sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
- if (!stmt) return -1;
- int result = sqlite3_step(stmt);
- if (result != SQLITE_ROW) {
- if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
- return -1;
- }
- return sqlite3_column_int(stmt, 0);
- }
- #pragma mark - file
- - (BOOL)_fileWriteWithName:(NSString *)filename data:(NSData *)data {
- NSString *path = [_dataPath stringByAppendingPathComponent:filename];
- return [data writeToFile:path atomically:NO];
- }
- - (NSData *)_fileReadWithName:(NSString *)filename {
- NSString *path = [_dataPath stringByAppendingPathComponent:filename];
- NSData *data = [NSData dataWithContentsOfFile:path];
- return data;
- }
- - (BOOL)_fileDeleteWithName:(NSString *)filename {
- NSString *path = [_dataPath stringByAppendingPathComponent:filename];
- return [[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
- }
- - (BOOL)_fileMoveAllToTrash {
- CFUUIDRef uuidRef = CFUUIDCreate(NULL);
- CFStringRef uuid = CFUUIDCreateString(NULL, uuidRef);
- CFRelease(uuidRef);
- NSString *tmpPath = [_trashPath stringByAppendingPathComponent:(__bridge NSString *)(uuid)];
- BOOL suc = [[NSFileManager defaultManager] moveItemAtPath:_dataPath toPath:tmpPath error:nil];
- if (suc) {
- suc = [[NSFileManager defaultManager] createDirectoryAtPath:_dataPath withIntermediateDirectories:YES attributes:nil error:NULL];
- }
- CFRelease(uuid);
- return suc;
- }
- - (void)_fileEmptyTrashInBackground {
- NSString *trashPath = _trashPath;
- dispatch_queue_t queue = _trashQueue;
- dispatch_async(queue, ^{
- NSFileManager *manager = [NSFileManager new];
- NSArray *directoryContents = [manager contentsOfDirectoryAtPath:trashPath error:NULL];
- for (NSString *path in directoryContents) {
- NSString *fullPath = [trashPath stringByAppendingPathComponent:path];
- [manager removeItemAtPath:fullPath error:NULL];
- }
- });
- }
- #pragma mark - private
- /**
- Delete all files and empty in background.
- Make sure the db is closed.
- */
- - (void)_reset {
- [[NSFileManager defaultManager] removeItemAtPath:[_path stringByAppendingPathComponent:kDBFileName] error:nil];
- [[NSFileManager defaultManager] removeItemAtPath:[_path stringByAppendingPathComponent:kDBShmFileName] error:nil];
- [[NSFileManager defaultManager] removeItemAtPath:[_path stringByAppendingPathComponent:kDBWalFileName] error:nil];
- [self _fileMoveAllToTrash];
- [self _fileEmptyTrashInBackground];
- }
- #pragma mark - public
- - (instancetype)init {
- @throw [NSException exceptionWithName:@"YYKVStorage init error" reason:@"Please use the designated initializer and pass the 'path' and 'type'." userInfo:nil];
- return [self initWithPath:@"" type:YYKVStorageTypeFile];
- }
- - (instancetype)initWithPath:(NSString *)path type:(YYKVStorageType)type {
- if (path.length == 0 || path.length > kPathLengthMax) {
- NSLog(@"YYKVStorage init error: invalid path: [%@].", path);
- return nil;
- }
- if (type > YYKVStorageTypeMixed) {
- NSLog(@"YYKVStorage init error: invalid type: %lu.", (unsigned long)type);
- return nil;
- }
-
- self = [super init];
- _path = path.copy;
- _type = type;
- _dataPath = [path stringByAppendingPathComponent:kDataDirectoryName];
- _trashPath = [path stringByAppendingPathComponent:kTrashDirectoryName];
- _trashQueue = dispatch_queue_create("com.ibireme.cache.disk.trash", DISPATCH_QUEUE_SERIAL);
- _dbPath = [path stringByAppendingPathComponent:kDBFileName];
- _errorLogsEnabled = YES;
- NSError *error = nil;
- if (![[NSFileManager defaultManager] createDirectoryAtPath:path
- withIntermediateDirectories:YES
- attributes:nil
- error:&error] ||
- ![[NSFileManager defaultManager] createDirectoryAtPath:[path stringByAppendingPathComponent:kDataDirectoryName]
- withIntermediateDirectories:YES
- attributes:nil
- error:&error] ||
- ![[NSFileManager defaultManager] createDirectoryAtPath:[path stringByAppendingPathComponent:kTrashDirectoryName]
- withIntermediateDirectories:YES
- attributes:nil
- error:&error]) {
- NSLog(@"YYKVStorage init error:%@", error);
- return nil;
- }
-
- if (![self _dbOpen] || ![self _dbInitialize]) {
- // db file may broken...
- [self _dbClose];
- [self _reset]; // rebuild
- if (![self _dbOpen] || ![self _dbInitialize]) {
- [self _dbClose];
- NSLog(@"YYKVStorage init error: fail to open sqlite db.");
- return nil;
- }
- }
- [self _fileEmptyTrashInBackground]; // empty the trash if failed at last time
- return self;
- }
- - (void)dealloc {
- UIBackgroundTaskIdentifier taskID = [[UIApplication sharedExtensionApplication] beginBackgroundTaskWithExpirationHandler:^{}];
- [self _dbClose];
- if (taskID != UIBackgroundTaskInvalid) {
- [[UIApplication sharedExtensionApplication] endBackgroundTask:taskID];
- }
- }
- - (BOOL)saveItem:(YYKVStorageItem *)item {
- return [self saveItemWithKey:item.key value:item.value filename:item.filename extendedData:item.extendedData];
- }
- - (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value {
- return [self saveItemWithKey:key value:value filename:nil extendedData:nil];
- }
- - (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value filename:(NSString *)filename extendedData:(NSData *)extendedData {
- if (key.length == 0 || value.length == 0) return NO;
- if (_type == YYKVStorageTypeFile && filename.length == 0) {
- return NO;
- }
-
- if (filename.length) {
- if (![self _fileWriteWithName:filename data:value]) {
- return NO;
- }
- if (![self _dbSaveWithKey:key value:value fileName:filename extendedData:extendedData]) {
- [self _fileDeleteWithName:filename];
- return NO;
- }
- return YES;
- } else {
- if (_type != YYKVStorageTypeSQLite) {
- NSString *filename = [self _dbGetFilenameWithKey:key];
- if (filename) {
- [self _fileDeleteWithName:filename];
- }
- }
- return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData];
- }
- }
- - (BOOL)removeItemForKey:(NSString *)key {
- if (key.length == 0) return NO;
- switch (_type) {
- case YYKVStorageTypeSQLite: {
- return [self _dbDeleteItemWithKey:key];
- } break;
- case YYKVStorageTypeFile:
- case YYKVStorageTypeMixed: {
- NSString *filename = [self _dbGetFilenameWithKey:key];
- if (filename) {
- [self _fileDeleteWithName:filename];
- }
- return [self _dbDeleteItemWithKey:key];
- } break;
- default: return NO;
- }
- }
- - (BOOL)removeItemForKeys:(NSArray *)keys {
- if (keys.count == 0) return NO;
- switch (_type) {
- case YYKVStorageTypeSQLite: {
- return [self _dbDeleteItemWithKeys:keys];
- } break;
- case YYKVStorageTypeFile:
- case YYKVStorageTypeMixed: {
- NSArray *filenames = [self _dbGetFilenameWithKeys:keys];
- for (NSString *filename in filenames) {
- [self _fileDeleteWithName:filename];
- }
- return [self _dbDeleteItemWithKeys:keys];
- } break;
- default: return NO;
- }
- }
- - (BOOL)removeItemsLargerThanSize:(int)size {
- if (size == INT_MAX) return YES;
- if (size <= 0) return [self removeAllItems];
-
- switch (_type) {
- case YYKVStorageTypeSQLite: {
- if ([self _dbDeleteItemsWithSizeLargerThan:size]) {
- [self _dbCheckpoint];
- return YES;
- }
- } break;
- case YYKVStorageTypeFile:
- case YYKVStorageTypeMixed: {
- NSArray *filenames = [self _dbGetFilenamesWithSizeLargerThan:size];
- for (NSString *name in filenames) {
- [self _fileDeleteWithName:name];
- }
- if ([self _dbDeleteItemsWithSizeLargerThan:size]) {
- [self _dbCheckpoint];
- return YES;
- }
- } break;
- }
- return NO;
- }
- - (BOOL)removeItemsEarlierThanTime:(int)time {
- if (time <= 0) return YES;
- if (time == INT_MAX) return [self removeAllItems];
-
- switch (_type) {
- case YYKVStorageTypeSQLite: {
- if ([self _dbDeleteItemsWithTimeEarlierThan:time]) {
- [self _dbCheckpoint];
- return YES;
- }
- } break;
- case YYKVStorageTypeFile:
- case YYKVStorageTypeMixed: {
- NSArray *filenames = [self _dbGetFilenamesWithTimeEarlierThan:time];
- for (NSString *name in filenames) {
- [self _fileDeleteWithName:name];
- }
- if ([self _dbDeleteItemsWithTimeEarlierThan:time]) {
- [self _dbCheckpoint];
- return YES;
- }
- } break;
- }
- return NO;
- }
- - (BOOL)removeItemsToFitSize:(int)maxSize {
- if (maxSize == INT_MAX) return YES;
- if (maxSize <= 0) return [self removeAllItems];
-
- int total = [self _dbGetTotalItemSize];
- if (total < 0) return NO;
- if (total <= maxSize) return YES;
-
- NSArray *items = nil;
- BOOL suc = NO;
- do {
- int perCount = 16;
- items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount];
- for (YYKVStorageItem *item in items) {
- if (total > maxSize) {
- if (item.filename) {
- [self _fileDeleteWithName:item.filename];
- }
- suc = [self _dbDeleteItemWithKey:item.key];
- total -= item.size;
- } else {
- break;
- }
- if (!suc) break;
- }
- } while (total > maxSize && items.count > 0 && suc);
- if (suc) [self _dbCheckpoint];
- return suc;
- }
- - (BOOL)removeItemsToFitCount:(int)maxCount {
- if (maxCount == INT_MAX) return YES;
- if (maxCount <= 0) return [self removeAllItems];
-
- int total = [self _dbGetTotalItemCount];
- if (total < 0) return NO;
- if (total <= maxCount) return YES;
-
- NSArray *items = nil;
- BOOL suc = NO;
- do {
- int perCount = 16;
- items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount];
- for (YYKVStorageItem *item in items) {
- if (total > maxCount) {
- if (item.filename) {
- [self _fileDeleteWithName:item.filename];
- }
- suc = [self _dbDeleteItemWithKey:item.key];
- total--;
- } else {
- break;
- }
- if (!suc) break;
- }
- } while (total > maxCount && items.count > 0 && suc);
- if (suc) [self _dbCheckpoint];
- return suc;
- }
- - (BOOL)removeAllItems {
- if (![self _dbClose]) return NO;
- [self _reset];
- if (![self _dbOpen]) return NO;
- if (![self _dbInitialize]) return NO;
- return YES;
- }
- - (void)removeAllItemsWithProgressBlock:(void(^)(int removedCount, int totalCount))progress
- endBlock:(void(^)(BOOL error))end {
-
- int total = [self _dbGetTotalItemCount];
- if (total <= 0) {
- if (end) end(total < 0);
- } else {
- int left = total;
- int perCount = 32;
- NSArray *items = nil;
- BOOL suc = NO;
- do {
- items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount];
- for (YYKVStorageItem *item in items) {
- if (left > 0) {
- if (item.filename) {
- [self _fileDeleteWithName:item.filename];
- }
- suc = [self _dbDeleteItemWithKey:item.key];
- left--;
- } else {
- break;
- }
- if (!suc) break;
- }
- if (progress) progress(total - left, total);
- } while (left > 0 && items.count > 0 && suc);
- if (suc) [self _dbCheckpoint];
- if (end) end(!suc);
- }
- }
- - (YYKVStorageItem *)getItemForKey:(NSString *)key {
- if (key.length == 0) return nil;
- YYKVStorageItem *item = [self _dbGetItemWithKey:key excludeInlineData:NO];
- if (item) {
- [self _dbUpdateAccessTimeWithKey:key];
- if (item.filename) {
- item.value = [self _fileReadWithName:item.filename];
- if (!item.value) {
- [self _dbDeleteItemWithKey:key];
- item = nil;
- }
- }
- }
- return item;
- }
- - (YYKVStorageItem *)getItemInfoForKey:(NSString *)key {
- if (key.length == 0) return nil;
- YYKVStorageItem *item = [self _dbGetItemWithKey:key excludeInlineData:YES];
- return item;
- }
- - (NSData *)getItemValueForKey:(NSString *)key {
- if (key.length == 0) return nil;
- NSData *value = nil;
- switch (_type) {
- case YYKVStorageTypeFile: {
- NSString *filename = [self _dbGetFilenameWithKey:key];
- if (filename) {
- value = [self _fileReadWithName:filename];
- if (!value) {
- [self _dbDeleteItemWithKey:key];
- value = nil;
- }
- }
- } break;
- case YYKVStorageTypeSQLite: {
- value = [self _dbGetValueWithKey:key];
- } break;
- case YYKVStorageTypeMixed: {
- NSString *filename = [self _dbGetFilenameWithKey:key];
- if (filename) {
- value = [self _fileReadWithName:filename];
- if (!value) {
- [self _dbDeleteItemWithKey:key];
- value = nil;
- }
- } else {
- value = [self _dbGetValueWithKey:key];
- }
- } break;
- }
- if (value) {
- [self _dbUpdateAccessTimeWithKey:key];
- }
- return value;
- }
- - (NSArray *)getItemForKeys:(NSArray *)keys {
- if (keys.count == 0) return nil;
- NSMutableArray *items = [self _dbGetItemWithKeys:keys excludeInlineData:NO];
- if (_type != YYKVStorageTypeSQLite) {
- for (NSInteger i = 0, max = items.count; i < max; i++) {
- YYKVStorageItem *item = items[i];
- if (item.filename) {
- item.value = [self _fileReadWithName:item.filename];
- if (!item.value) {
- if (item.key) [self _dbDeleteItemWithKey:item.key];
- [items removeObjectAtIndex:i];
- i--;
- max--;
- }
- }
- }
- }
- if (items.count > 0) {
- [self _dbUpdateAccessTimeWithKeys:keys];
- }
- return items.count ? items : nil;
- }
- - (NSArray *)getItemInfoForKeys:(NSArray *)keys {
- if (keys.count == 0) return nil;
- return [self _dbGetItemWithKeys:keys excludeInlineData:YES];
- }
- - (NSDictionary *)getItemValueForKeys:(NSArray *)keys {
- NSMutableArray *items = (NSMutableArray *)[self getItemForKeys:keys];
- NSMutableDictionary *kv = [NSMutableDictionary new];
- for (YYKVStorageItem *item in items) {
- if (item.key && item.value) {
- [kv setObject:item.value forKey:item.key];
- }
- }
- return kv.count ? kv : nil;
- }
- - (BOOL)itemExistsForKey:(NSString *)key {
- if (key.length == 0) return NO;
- return [self _dbGetItemCountWithKey:key] > 0;
- }
- - (int)getItemsCount {
- return [self _dbGetTotalItemCount];
- }
- - (int)getItemsSize {
- return [self _dbGetTotalItemSize];
- }
- @end
|