From 7b02207537d35bfa1714bf8beafc921f717d100a Mon Sep 17 00:00:00 2001 From: 单军华 Date: Wed, 11 Jul 2018 10:47:42 +0800 Subject: [PATCH] 首次上传 --- screendisplay/Pods/YYCache/YYCache/YYKVStorage.m | 1069 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 1,069 insertions(+), 0 deletions(-) diff --git a/screendisplay/Pods/YYCache/YYCache/YYKVStorage.m b/screendisplay/Pods/YYCache/YYCache/YYKVStorage.m new file mode 100644 index 0000000..501dfdd --- /dev/null +++ b/screendisplay/Pods/YYCache/YYCache/YYKVStorage.m @@ -0,0 +1,1069 @@ +// +// YYKVStorage.m +// YYCache <https://github.com/ibireme/YYCache> +// +// 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 <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); + */ + +/// Returns nil in App Extension. +static UIApplication *_YYSharedApplication() { + static BOOL isAppExtension = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class cls = NSClassFromString(@"UIApplication"); + if(!cls || ![cls respondsToSelector:@selector(sharedApplication)]) isAppExtension = YES; + if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) isAppExtension = YES; + }); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + return isAppExtension ? nil : [UIApplication performSelector:@selector(sharedApplication)]; +#pragma clang diagnostic pop +} + + +@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 = [_YYSharedApplication() beginBackgroundTaskWithExpirationHandler:^{}]; + [self _dbClose]; + if (taskID != UIBackgroundTaskInvalid) { + [_YYSharedApplication() 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 -- Gitblit v1.8.0