1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054 |
- //
- // 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 alloc] initWithFormat:@"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 alloc] initWithFormat:@"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 alloc] initWithFormat:@"select key, filename, size, modification_time, last_access_time, extended_data from manifest where key in (%@);", [self _dbJoinedKeys:keys]];
- } else {
- 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]];
- }
-
- 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 alloc] initWithFormat:@"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
|