From 1cb39956ae0ad79a0af350a7751a13becd404036 Mon Sep 17 00:00:00 2001 From: huangzheng Date: Sun, 28 Jul 2019 23:28:49 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E5=AE=8C=E6=88=90KVStorage=E7=9A=84dataB?= =?UTF-8?q?ase=E5=92=8Cfile=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ClaretCacheDemo.xcodeproj/project.pbxproj | 8 + Sources/ClaretCache/KVStorage.swift | 719 ++++++++++++++++++ Sources/ClaretCache/UIApplication+CCAdd.swift | 29 + 3 files changed, 756 insertions(+) create mode 100644 Sources/ClaretCache/KVStorage.swift create mode 100644 Sources/ClaretCache/UIApplication+CCAdd.swift diff --git a/ClaretCacheDemo.xcodeproj/project.pbxproj b/ClaretCacheDemo.xcodeproj/project.pbxproj index 774c41f..9e10981 100644 --- a/ClaretCacheDemo.xcodeproj/project.pbxproj +++ b/ClaretCacheDemo.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ 01FA686D22E719DB008E24FC /* MemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FA686B22E719DB008E24FC /* MemoryCache.swift */; }; 01FA686E22E719DB008E24FC /* ClaretCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FA686C22E719DB008E24FC /* ClaretCache.swift */; }; 8E077CCCBC628B6EF406E97A /* Pods_ClaretCacheDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1BF5D3C4BBDD1FACD276DAA1 /* Pods_ClaretCacheDemo.framework */; }; + C30EB06522ED8E8A0064ED79 /* KVStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30EB06422ED8E8A0064ED79 /* KVStorage.swift */; }; + C30EB06722ED9B1D0064ED79 /* UIApplication+CCAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30EB06622ED9B1D0064ED79 /* UIApplication+CCAdd.swift */; }; F8C53E7722EB9EBC00B53664 /* ClaretCacheDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C53E7622EB9EBC00B53664 /* ClaretCacheDemoTests.swift */; }; /* End PBXBuildFile section */ @@ -41,6 +43,8 @@ 1BF5D3C4BBDD1FACD276DAA1 /* Pods_ClaretCacheDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ClaretCacheDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 71DE9F8F64DFC38FD8ABCF96 /* Pods-ClaretCacheDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClaretCacheDemo.release.xcconfig"; path = "Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo.release.xcconfig"; sourceTree = ""; }; 96892E93F01C8D0A4FEC2EC1 /* Pods-ClaretCacheDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClaretCacheDemo.debug.xcconfig"; path = "Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo.debug.xcconfig"; sourceTree = ""; }; + C30EB06422ED8E8A0064ED79 /* KVStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KVStorage.swift; sourceTree = ""; }; + C30EB06622ED9B1D0064ED79 /* UIApplication+CCAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+CCAdd.swift"; sourceTree = ""; }; F8C53E7422EB9EBC00B53664 /* ClaretCacheDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ClaretCacheDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F8C53E7622EB9EBC00B53664 /* ClaretCacheDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClaretCacheDemoTests.swift; sourceTree = ""; }; F8C53E7822EB9EBC00B53664 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -112,6 +116,8 @@ children = ( 01FA686B22E719DB008E24FC /* MemoryCache.swift */, 01FA686C22E719DB008E24FC /* ClaretCache.swift */, + C30EB06422ED8E8A0064ED79 /* KVStorage.swift */, + C30EB06622ED9B1D0064ED79 /* UIApplication+CCAdd.swift */, ); path = ClaretCache; sourceTree = ""; @@ -313,7 +319,9 @@ files = ( 01FA686D22E719DB008E24FC /* MemoryCache.swift in Sources */, 01C75DEE22E0723E00C7D03F /* ViewController.swift in Sources */, + C30EB06522ED8E8A0064ED79 /* KVStorage.swift in Sources */, 01C75DEC22E0723E00C7D03F /* AppDelegate.swift in Sources */, + C30EB06722ED9B1D0064ED79 /* UIApplication+CCAdd.swift in Sources */, 01FA686E22E719DB008E24FC /* ClaretCache.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/ClaretCache/KVStorage.swift b/Sources/ClaretCache/KVStorage.swift new file mode 100644 index 0000000..f53284b --- /dev/null +++ b/Sources/ClaretCache/KVStorage.swift @@ -0,0 +1,719 @@ +// +// KVStorage.swift +// ClaretCacheDemo +// +// Created by HZheng on 2019/7/28. +// Copyright © 2019 com.ClaretCache. All rights reserved. +// + +import UIKit +import SQLite3 + +#if os(iOS) && canImport(UIKit) +import UIKit.UIApplication +#endif + + +enum KVStorageType { + case KVStorageTypeFile + case KVStorageTypeSQLite + case KVStorageTypeMixed +} + +/* + 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); + */ + +class KVStorage: NSObject { + /** + KVStorageItem is used by `KVStorage` to store key-value pair and meta data. + Typically, you should not use this class directly. + */ + class KVStorageItem: NSObject { + var key: String? ///< key + var value: Data? ///< value + var fileName: String? ///< fileName (nil if inline) + var size: Int = 0 ///< value's size in bytes + var modTime: Int = 0 ///< modification unix timestamp + var accessTime: Int = 0 ///< last access unix timestamp + var extendedData: Data? ///< extended data (nil if no extended data) + } + + + + fileprivate let kMaxErrorRetryCount = 8 + fileprivate let kMinRetryTimeInterval = 2.0 + fileprivate let kPathLengthMax = PATH_MAX - 64 + fileprivate let kDBFileName = "manifest.sqlite" + fileprivate let kDBShmFileName = "manifest.sqlite-shm" + fileprivate let kDBWalFileName = "manifest.sqlite-wal" + fileprivate let kDataDirectoryName = "data" + fileprivate let kTrashDirectoryName = "trash" + + fileprivate var trashQueue : DispatchQueue + fileprivate var path: URL + fileprivate var dbPath: URL + fileprivate var dataPath: URL + fileprivate var trashPath: URL + fileprivate var db: OpaquePointer? = nil + fileprivate var dbStmtCache: Dictionary? + fileprivate var dbLastOpenErrorTime: TimeInterval = 0 + fileprivate var dbOpenErrorCount: UInt = 0 + fileprivate(set) var type: KVStorageType + fileprivate var errorLogsEnabled: Bool = true + + init?(path: URL, type: KVStorageType) { + guard path.absoluteString.count > 0, path.absoluteString.count <= kPathLengthMax else { + print("KVStorage init error: invalid path: [\(path)].") + return nil; + } + + self.path = path; + self.type = type; + trashQueue = OS_dispatch_queue_serial(label: "com.iteatime.cache.disk.trash") + dataPath = path.appendingPathComponent(kDataDirectoryName) + trashPath = path.appendingPathComponent(kTrashDirectoryName) + dbPath = path.appendingPathComponent(kDBFileName) + do { + try FileManager.default.createDirectory(at: path, withIntermediateDirectories: true, attributes: nil) + try FileManager.default.createDirectory(at: dataPath, withIntermediateDirectories: true, attributes: nil) + try FileManager.default.createDirectory(at: trashPath, withIntermediateDirectories: true, attributes: nil) + } catch { + return nil; + } + + super.init(); + if !dbOpen() || !dbInitialize() { + // db file may broken... + dbClose() + reset() // rebuild + if !dbOpen() || !dbInitialize() { + dbClose() + print("KVStorage init error: fail to open sqlite db.") + return nil; + } + } + fileEmptyTrashInBackground() + } + + deinit { + #if os(iOS) && canImport(UIKit) + #endif + let taskID = UIApplication.sharedExtensionApplication()?.beginBackgroundTask(expirationHandler: nil); + dbClose() + #if os(iOS) && canImport(UIKit) + if let task = taskID { + UIApplication.sharedExtensionApplication()?.endBackgroundTask(task) + } + #endif + } + + //MARK: private + fileprivate func reset() { + do { + try FileManager.default.removeItem(at: path.appendingPathComponent(kDBFileName)) + try FileManager.default.removeItem(at: path.appendingPathComponent(kDBShmFileName)) + try FileManager.default.removeItem(at: path.appendingPathComponent(kDBWalFileName)) + try fileMoveAllToTrash() + fileEmptyTrashInBackground() + } catch { + print("reset error: \(error)") + } + } + + + //MARK: File + + fileprivate func fileWrite(fileName: String, data: Data) throws { + try data.write(to: dataPath.appendingPathComponent(fileName)) + } + + fileprivate func fileRead(fileName: String) throws -> Data? { + return try Data.init(contentsOf: dataPath.appendingPathComponent(fileName)) + } + + fileprivate func deleteFile(fileName: String) throws { + try FileManager.default.removeItem(at: dataPath.appendingPathComponent(fileName)) + } + + fileprivate func fileMoveAllToTrash() throws { + let uuid = UUID().uuidString + let tmpPath = trashPath.appendingPathComponent(uuid) + try FileManager.default.moveItem(at: dataPath, to: tmpPath) + try FileManager.default.createDirectory(at: dataPath, withIntermediateDirectories: true, attributes: nil) + } + + // empty the trash if failed at last time + fileprivate func fileEmptyTrashInBackground() { + let trashPath = self.trashPath + DispatchQueue.global().async { + let fileMgr = FileManager.default + do { + let directoryContents = try fileMgr.contentsOfDirectory(atPath: trashPath.absoluteString) + for path in directoryContents { + let fullPath = trashPath.appendingPathComponent(path) + try fileMgr.removeItem(at: fullPath) + } + } catch { + print("remove trash error: \(error)") + } + } + } + + //MARK: DataBase + + fileprivate func dbOpen() -> Bool { + guard db == nil else { + return true + } + let result = sqlite3_open(dbPath.absoluteString, &db) + guard result == SQLITE_OK else { + db = nil + dbStmtCache = nil + dbLastOpenErrorTime = CACurrentMediaTime() + dbOpenErrorCount+=1 + if errorLogsEnabled { + print("\(#function) line:\(#line) sqlite open failed (\(result)).") + } + return false + } + dbStmtCache = Dictionary() + dbLastOpenErrorTime = 0 + dbOpenErrorCount = 0; + return true + } + + @discardableResult + fileprivate func dbClose() -> Bool { + guard db != nil else { + return true + } + + var retry = false; + var stmtFinalized = false; + dbStmtCache = nil + repeat { + retry = false + let result = sqlite3_close(db!) + if result == SQLITE_BUSY || result == SQLITE_LOCKED { + if !stmtFinalized { + stmtFinalized = true + var stmt = sqlite3_next_stmt(db!, nil) + while stmt != nil { + sqlite3_finalize(stmt); + stmt = sqlite3_next_stmt(db!, nil) + retry = true + } + } + } else if result != SQLITE_OK { + if (errorLogsEnabled) { + print("\(#function) line:\(#line) sqlite close failed (\(result).") + } + } + } while(retry) + db = nil + return true + } + + fileprivate func dbCheck() -> Bool { + guard db == nil else { + return true + } + if dbOpenErrorCount < kMaxErrorRetryCount && + CACurrentMediaTime() - dbLastOpenErrorTime > kMinRetryTimeInterval { + return dbOpen() && dbInitialize() + } else { + return false + } + } + + fileprivate func dbInitialize() -> Bool { + let 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 dbExecute(sql) + } + + fileprivate func dbCheckpoint() { + guard dbCheck() else { + return + } + // Cause a checkpoint to occur, merge `sqlite-wal` file to `sqlite` file. + sqlite3_wal_checkpoint(db, nil) + } + + fileprivate func dbExecute(_ sql: String) -> Bool { + guard sql.count > 0, dbCheck() else { + return false + } + return sqlite3_exec(db, sql, nil, nil, nil) == SQLITE_OK + } + + fileprivate func dbPrepareStmt(_ sql: String) -> OpaquePointer? { + guard dbCheck(), sql.count > 0, dbStmtCache != nil else { + return nil + } + var stmt = dbStmtCache?[sql] as? OpaquePointer + if stmt == nil { + let result = sqlite3_prepare_v2(db, sql, -1, &stmt, nil) + guard result == SQLITE_OK else { + if (errorLogsEnabled) { + print("\(#function) line:\(#line) sqlite stmt prepare error (\(result)): \(errorMessage)") + } + return nil + } + dbStmtCache?[sql] = stmt + } else { + sqlite3_reset(stmt) + } + return stmt + } + + fileprivate var errorMessage: String { + if let errorPointer = sqlite3_errmsg(db) { + let errorMessage = String(cString: errorPointer) + return errorMessage + } else { + return "No error message provided from sqlite." + } + } + + fileprivate func dbJoinedKeys(_ keys: Array) -> String { + var string = "" + let max = keys.count + for i in 0.., stmt: OpaquePointer, fromIndex index : Int) { + let max = keys.count + for i in 0.. Bool { + let 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);" + guard let stmt = dbPrepareStmt(sql) else { + return false + } + let timestamp = Int32(time(nil)) + sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) + sqlite3_bind_text(stmt, 2, (fileName as NSString).utf8String, -1, nil) + sqlite3_bind_int(stmt, 3, Int32(value.count)) + if fileName.count == 0 { + sqlite3_bind_blob(stmt, 4, (value as NSData).bytes, Int32(value.count), nil) + } else { + sqlite3_bind_blob(stmt, 4, nil, 0, nil) + } + sqlite3_bind_int(stmt, 5, timestamp) + sqlite3_bind_int(stmt, 6, timestamp) + sqlite3_bind_blob(stmt, 7, (extendedData as NSData).bytes, Int32(extendedData.count), nil) + + let result = sqlite3_step(stmt) + if result != SQLITE_DONE { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite insert error (\(result): (\(errorMessage))") + } + return false + } + return true + } + + fileprivate func dbUpdateAccessTime(_ key: String) -> Bool { + let sql = "update manifest set last_access_time = ?1 where key = ?2;" + guard let stmt = dbPrepareStmt(sql) else { + return false + } + sqlite3_bind_int(stmt, 1, Int32(time(nil))) + sqlite3_bind_text(stmt, 2, (key as NSString).utf8String, -1, nil) + let result = sqlite3_step(stmt) + if (result != SQLITE_DONE) { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite update error (\(result): (\(errorMessage))") + } + return false + } + return true + } + + fileprivate func dbUpdateAccessTimes(_ keys: Array) -> Bool { + guard dbCheck() else { + return false + } + let sql = "update manifest set last_access_time = \(Int32(time(nil))) where key in (\(dbJoinedKeys(keys)));" + var stmtPointer: OpaquePointer? + var result = sqlite3_prepare_v2(db, sql, -1, &stmtPointer, nil) + guard result == SQLITE_OK, let stmt = stmtPointer else { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") + } + return false + } + dbBindJoinedKeys(keys: keys, stmt: stmt, fromIndex: 1) + result = sqlite3_step(stmt) + sqlite3_finalize(stmt) + if (result != SQLITE_DONE) { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite update error (\(result): (\(errorMessage))") + } + return false + } + return true + } + + fileprivate func dbDeleteItem(_ key: String) -> Bool { + let sql = "delete from manifest where key = ?1;" + guard let stmt = dbPrepareStmt(sql) else { + return false + } + sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) + let result = sqlite3_step(stmt) + if (result != SQLITE_DONE) { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite delete error (\(result): (\(errorMessage))") + } + return false + } + return true + } + + fileprivate func dbDeleteItems(_ keys: Array) -> Bool { + guard dbCheck() else { + return false + } + let sql = "delete from manifest where key in (\(dbJoinedKeys(keys));" + var stmtPointer: OpaquePointer? + var result = sqlite3_prepare_v2(db, sql, -1, &stmtPointer, nil) + guard result == SQLITE_OK, let stmt = stmtPointer else { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") + } + return false + } + dbBindJoinedKeys(keys: keys, stmt: stmt, fromIndex: 1) + result = sqlite3_step(stmt) + sqlite3_finalize(stmt) + if (result == SQLITE_ERROR) { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite delete error (\(result): (\(errorMessage))") + } + return false + } + return true + } + + fileprivate func dbDeleteItem(sql: String, param: Int32) -> Bool { + guard let stmt = dbPrepareStmt(sql) else { + return false + } + sqlite3_bind_int(stmt, 1, param); + let result = sqlite3_step(stmt) + if (result != SQLITE_DONE) { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite delete error (\(result): (\(errorMessage))") + } + return false + } + return true + } + + fileprivate func dbDeleteItemsWithSizeLargerThan(size: Int) -> Bool { + return dbDeleteItem(sql: "delete from manifest where size > ?1;", param: Int32(size)) + } + + fileprivate func dbDeleteItemsWithSizeEarlierThan(time: Int) -> Bool { + return dbDeleteItem(sql: "delete from manifest where last_access_time < ?1;", param: Int32(time)) + } + + fileprivate func dbGetItemFromStmt(stmt: OpaquePointer, excludeInlineData: Bool) -> KVStorageItem { + let item = KVStorageItem() + var i: Int32 = 0 + item.key = String(cString: UnsafePointer(sqlite3_column_text(stmt, i))) + i += 1 + item.fileName = String(cString: UnsafePointer(sqlite3_column_text(stmt, i))) + i += 1 + item.size = Int(sqlite3_column_int(stmt, Int32(i))) + i += 1 + let inline_data: UnsafeRawPointer? = excludeInlineData ? nil : sqlite3_column_blob(stmt, i); + let inline_data_length = excludeInlineData ? 0 : sqlite3_column_bytes(stmt, i) + i += 1 + if inline_data_length > 0 && (inline_data != nil) { + item.value = NSData(bytes:inline_data, length:Int(inline_data_length)) as Data + } + item.modTime = Int(sqlite3_column_int(stmt, i)) + i += 1 + item.accessTime = Int(sqlite3_column_int(stmt, i)) + i += 1 + let extended_data: UnsafeRawPointer? = sqlite3_column_blob(stmt, i) + let extended_data_length = sqlite3_column_bytes(stmt, i) + if extended_data_length > 0 && (extended_data != nil) { + item.extendedData = NSData(bytes:extended_data, length:Int(extended_data_length)) as Data + } + return item + } + + fileprivate func dbGetItem(key: String, excludeInlineData: Bool) -> KVStorageItem? { + let 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;" + guard let stmt = dbPrepareStmt(sql) else { + return nil + } + sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) + var item: KVStorageItem? + let result = sqlite3_step(stmt) + if (result == SQLITE_ROW) { + item = dbGetItemFromStmt(stmt: stmt, excludeInlineData: excludeInlineData) + } else { + if (result != SQLITE_DONE) { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") + } + } + } + return item + } + + fileprivate func dbGetItems(keys: Array, excludeInlineData: Bool) -> Array? { + guard dbCheck() else { + return nil + } + let sql: String + if (excludeInlineData) { + sql = "select key, filename, size, modification_time, last_access_time, extended_data from manifest where key in (\(dbJoinedKeys(keys)));" + } else { + sql = "select key, filename, size, inline_data, modification_time, last_access_time, extended_data from manifest where key in (\(dbJoinedKeys(keys))" + } + var stmtPointer: OpaquePointer? + var result = sqlite3_prepare_v2(db, sql, -1, &stmtPointer, nil) + guard result == SQLITE_OK, let stmt = stmtPointer else { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") + } + return nil + } + dbBindJoinedKeys(keys: keys, stmt: stmt, fromIndex: 1) + var items: Array? + items = Array() + repeat { + result = sqlite3_step(stmt) + if (result == SQLITE_ROW) { + items?.append(dbGetItemFromStmt(stmt: stmt, excludeInlineData: excludeInlineData)) + } else if (result == SQLITE_DONE) { + break; + } else { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") + } + items = nil + break + } + } while(true) + sqlite3_finalize(stmt) + return items + } + + fileprivate func dbGetValue(key: String) -> Data? { + let sql = "select inline_data from manifest where key = ?1;" + guard let stmt = dbPrepareStmt(sql) else { + return nil + } + sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) + let result = sqlite3_step(stmt) + if (result == SQLITE_ROW) { + let inline_data: UnsafeRawPointer? = sqlite3_column_blob(stmt, 0) + let inline_data_length = sqlite3_column_bytes(stmt, 0) + guard inline_data_length > 0 && (inline_data != nil) else { + return nil + } + return NSData(bytes:inline_data, length:Int(inline_data_length)) as Data + } else { + if (result != SQLITE_DONE) { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") + } + } + return nil + } + } + + fileprivate func dbGetFilename(key: String) -> String? { + let sql = "select filename from manifest where key = ?1;" + guard let stmt = dbPrepareStmt(sql) else { + return nil + } + sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) + let result = sqlite3_step(stmt); + if (result == SQLITE_ROW) { + return String(cString: UnsafePointer(sqlite3_column_text(stmt, 0))) + } else { + if (result != SQLITE_DONE) { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") + } + } + return nil + } + } + + fileprivate func dbGetFileNames(keys: Array) -> Array? { + guard dbCheck() else { + return nil + } + let sql = "select filename from manifest where key in (\(dbJoinedKeys(keys)));" + var stmtPointer: OpaquePointer? + var result = sqlite3_prepare_v2(db, sql, -1, &stmtPointer, nil) + guard result == SQLITE_OK, let stmt = stmtPointer else { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") + } + return nil + } + dbBindJoinedKeys(keys: keys, stmt: stmt, fromIndex: 1) + var fileNames: Array? + fileNames = Array() + repeat { + result = sqlite3_step(stmt) + if (result == SQLITE_ROW) { + fileNames?.append(String(cString: UnsafePointer(sqlite3_column_text(stmt, 0)))) + } else if (result == SQLITE_DONE) { + break; + } else { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") + } + fileNames = nil + break + } + } while(true) + sqlite3_finalize(stmt) + return fileNames + } + + fileprivate func dbGetFilenames(sql: String, param: Int32) -> Array? { + guard let stmt = dbPrepareStmt(sql) else { + return nil + } + sqlite3_bind_int(stmt, 1, Int32(param)); + var fileNames: Array? + fileNames = Array() + repeat { + let result = sqlite3_step(stmt) + if (result == SQLITE_ROW) { + fileNames?.append(String(cString: UnsafePointer(sqlite3_column_text(stmt, 0)))) + } else if (result == SQLITE_DONE) { + break; + } else { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") + } + fileNames = nil + break + } + } while(true) + sqlite3_finalize(stmt) + return fileNames + } + + fileprivate func dbGetFilenamesWithSizeLargerThan(size: Int) -> Array? { + let sql = "select filename from manifest where size > ?1 and filename is not null;" + return dbGetFilenames(sql: sql, param: Int32(size)) + } + + fileprivate func dbGetFilenamesWithSizeEarlierThan(time: Int) -> Array? { + let sql = "select filename from manifest where last_access_time < ?1 and filename is not null;" + return dbGetFilenames(sql: sql, param: Int32(time)) + } + + fileprivate func dbGetItemSizeInfoOrderByTimeAscWithLimit(count: Int) -> Array? { + let sql = "select key, filename, size from manifest order by last_access_time asc limit ?1;" + guard let stmt = dbPrepareStmt(sql) else { + return nil + } + var items: Array? + items = Array() + repeat { + let result = sqlite3_step(stmt) + if (result == SQLITE_ROW) { + let item = KVStorageItem() + item.key = String(cString: UnsafePointer(sqlite3_column_text(stmt, 0))) + item.fileName = String(cString: UnsafePointer(sqlite3_column_text(stmt, 1))) + item.size = Int(sqlite3_column_int(stmt, 2)) + items?.append(item) + } else if (result == SQLITE_DONE) { + break; + } else { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") + } + items = nil + break + } + } while(true) + sqlite3_finalize(stmt) + return items + } + + fileprivate func dbGetItemCount(key: String) -> Int { + let sql = "select count(key) from manifest where key = ?1;" + guard let stmt = dbPrepareStmt(sql) else { + return -1 + } + sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) + let result = sqlite3_step(stmt) + if result != SQLITE_ROW { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") + } + return -1 + } + return Int(sqlite3_column_int(stmt, 0)) + } + + fileprivate func dbGetInt(_ sql: String) -> Int { + guard let stmt = dbPrepareStmt(sql) else { + return -1 + } + let result = sqlite3_step(stmt) + if result != SQLITE_ROW { + if errorLogsEnabled { + print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") + } + return -1 + } + return Int(sqlite3_column_int(stmt, 0)) + } + + fileprivate func dbGetTotalItemSize() -> Int { + return dbGetInt("select sum(size) from manifest;") + } + + fileprivate func dbGetTotalItemCount() -> Int { + return dbGetInt("select count(*) from manifest;") + } +} diff --git a/Sources/ClaretCache/UIApplication+CCAdd.swift b/Sources/ClaretCache/UIApplication+CCAdd.swift new file mode 100644 index 0000000..61d93d9 --- /dev/null +++ b/Sources/ClaretCache/UIApplication+CCAdd.swift @@ -0,0 +1,29 @@ +// +// UIApplication+CCAdd.swift +// ClaretCacheDemo +// +// Created by HZheng on 2019/7/28. +// Copyright © 2019 com.ClaretCache. All rights reserved. +// + +import UIKit.UIApplication + +var IsAppExtension: Bool { + guard Bundle.main.bundleURL.pathExtension != "appex" else { + return false + } + guard let app = NSClassFromString("UIApplication"), app.value(forKey: "shared") != nil else { + return true + } + return false +} + +extension UIApplication { + static func isAppExtension() -> Bool { + return IsAppExtension; + } + + static func sharedExtensionApplication() -> UIApplication? { + return IsAppExtension ? nil : UIApplication.shared + } +} From 000bea84b062f386c3c9b244901b364604fce30c Mon Sep 17 00:00:00 2001 From: Huang Zheng Date: Mon, 29 Jul 2019 19:47:38 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E7=94=A8swiftlint=E5=B0=86=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E6=A3=80=E6=9F=A5=E4=BA=86=E4=B8=80=E9=81=8D=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E6=A0=B9=E6=8D=AE=E5=A4=A7=E4=BD=AC=E4=BB=AC=E6=8F=90?= =?UTF-8?q?=E5=87=BA=E7=9A=84=E6=84=8F=E8=A7=81=EF=BC=8C=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E3=80=82=20swiftlint=20=E6=A3=80=E6=9F=A5?= =?UTF-8?q?=E5=8E=BB=E9=99=A4=E4=BA=86body=20length=E5=92=8Cfile=20length?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ClaretCacheDemo.xcodeproj/project.pbxproj | 12 +- ClaretCacheDemo/AppDelegate.swift | 3 - ClaretCacheDemo/ViewController.swift | 2 - Package.swift | 4 +- Sources/ClaretCache/KVStorage.swift | 442 ++++++++---------- Sources/ClaretCache/MemoryCache.swift | 8 +- Sources/ClaretCache/UIApplication+CCAdd.swift | 29 -- Tests/ClaretCacheTests/ClaretCacheTests.swift | 2 +- Tests/ClaretCacheTests/XCTestManifests.swift | 2 +- configs/.swiftlint.yml | 2 + 10 files changed, 221 insertions(+), 285 deletions(-) delete mode 100644 Sources/ClaretCache/UIApplication+CCAdd.swift diff --git a/ClaretCacheDemo.xcodeproj/project.pbxproj b/ClaretCacheDemo.xcodeproj/project.pbxproj index 9e10981..f25dea9 100644 --- a/ClaretCacheDemo.xcodeproj/project.pbxproj +++ b/ClaretCacheDemo.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ 01FA686E22E719DB008E24FC /* ClaretCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FA686C22E719DB008E24FC /* ClaretCache.swift */; }; 8E077CCCBC628B6EF406E97A /* Pods_ClaretCacheDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1BF5D3C4BBDD1FACD276DAA1 /* Pods_ClaretCacheDemo.framework */; }; C30EB06522ED8E8A0064ED79 /* KVStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30EB06422ED8E8A0064ED79 /* KVStorage.swift */; }; - C30EB06722ED9B1D0064ED79 /* UIApplication+CCAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30EB06622ED9B1D0064ED79 /* UIApplication+CCAdd.swift */; }; F8C53E7722EB9EBC00B53664 /* ClaretCacheDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C53E7622EB9EBC00B53664 /* ClaretCacheDemoTests.swift */; }; /* End PBXBuildFile section */ @@ -44,7 +43,6 @@ 71DE9F8F64DFC38FD8ABCF96 /* Pods-ClaretCacheDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClaretCacheDemo.release.xcconfig"; path = "Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo.release.xcconfig"; sourceTree = ""; }; 96892E93F01C8D0A4FEC2EC1 /* Pods-ClaretCacheDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClaretCacheDemo.debug.xcconfig"; path = "Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo.debug.xcconfig"; sourceTree = ""; }; C30EB06422ED8E8A0064ED79 /* KVStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KVStorage.swift; sourceTree = ""; }; - C30EB06622ED9B1D0064ED79 /* UIApplication+CCAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+CCAdd.swift"; sourceTree = ""; }; F8C53E7422EB9EBC00B53664 /* ClaretCacheDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ClaretCacheDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F8C53E7622EB9EBC00B53664 /* ClaretCacheDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClaretCacheDemoTests.swift; sourceTree = ""; }; F8C53E7822EB9EBC00B53664 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -117,7 +115,6 @@ 01FA686B22E719DB008E24FC /* MemoryCache.swift */, 01FA686C22E719DB008E24FC /* ClaretCache.swift */, C30EB06422ED8E8A0064ED79 /* KVStorage.swift */, - C30EB06622ED9B1D0064ED79 /* UIApplication+CCAdd.swift */, ); path = ClaretCache; sourceTree = ""; @@ -321,7 +318,6 @@ 01C75DEE22E0723E00C7D03F /* ViewController.swift in Sources */, C30EB06522ED8E8A0064ED79 /* KVStorage.swift in Sources */, 01C75DEC22E0723E00C7D03F /* AppDelegate.swift in Sources */, - C30EB06722ED9B1D0064ED79 /* UIApplication+CCAdd.swift in Sources */, 01FA686E22E719DB008E24FC /* ClaretCache.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -486,14 +482,14 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 7GZF8W7RUY; + DEVELOPMENT_TEAM = 3289Y6BF27; INFOPLIST_FILE = ClaretCacheDemo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.ClaretCache.-"; + PRODUCT_BUNDLE_IDENTIFIER = "com.ClaretCache.-222"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -506,14 +502,14 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 7GZF8W7RUY; + DEVELOPMENT_TEAM = 3289Y6BF27; INFOPLIST_FILE = ClaretCacheDemo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.ClaretCache.-"; + PRODUCT_BUNDLE_IDENTIFIER = "com.ClaretCache.-222"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/ClaretCacheDemo/AppDelegate.swift b/ClaretCacheDemo/AppDelegate.swift index 2af27cb..a3c3aa9 100644 --- a/ClaretCacheDemo/AppDelegate.swift +++ b/ClaretCacheDemo/AppDelegate.swift @@ -13,7 +13,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true @@ -41,6 +40,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - } - diff --git a/ClaretCacheDemo/ViewController.swift b/ClaretCacheDemo/ViewController.swift index 4b3b0ed..049956c 100644 --- a/ClaretCacheDemo/ViewController.swift +++ b/ClaretCacheDemo/ViewController.swift @@ -15,6 +15,4 @@ class ViewController: UIViewController { // Do any additional setup after loading the view. } - } - diff --git a/Package.swift b/Package.swift index dff7d99..57a1026 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let package = Package( // Products define the executables and libraries produced by a package, and make them visible to other packages. .library( name: "ClaretCache", - targets: ["ClaretCache"]), + targets: ["ClaretCache"]) ], dependencies: [ // Dependencies declare other packages that this package depends on. @@ -23,6 +23,6 @@ let package = Package( dependencies: []), .testTarget( name: "ClaretCacheTests", - dependencies: ["ClaretCache"]), + dependencies: ["ClaretCache"]) ] ) diff --git a/Sources/ClaretCache/KVStorage.swift b/Sources/ClaretCache/KVStorage.swift index f53284b..35f3f77 100644 --- a/Sources/ClaretCache/KVStorage.swift +++ b/Sources/ClaretCache/KVStorage.swift @@ -13,11 +13,20 @@ import SQLite3 import UIKit.UIApplication #endif +var isAppExtension: Bool = { + guard Bundle.main.bundleURL.pathExtension != "appex" else { + return false + } + guard let app = NSClassFromString("UIApplication"), app.value(forKey: "shared") != nil else { + return true + } + return false +}() -enum KVStorageType { - case KVStorageTypeFile - case KVStorageTypeSQLite - case KVStorageTypeMixed +public enum KVStorageType { + case file + case sqlite + case mixed } /* @@ -46,12 +55,12 @@ enum KVStorageType { create index if not exists last_access_time_idx on manifest(last_access_time); */ -class KVStorage: NSObject { +public class KVStorage { /** KVStorageItem is used by `KVStorage` to store key-value pair and meta data. Typically, you should not use this class directly. */ - class KVStorageItem: NSObject { + fileprivate class KVStorageItem { var key: String? ///< key var value: Data? ///< value var fileName: String? ///< fileName (nil if inline) @@ -60,9 +69,7 @@ class KVStorage: NSObject { var accessTime: Int = 0 ///< last access unix timestamp var extendedData: Data? ///< extended data (nil if no extended data) } - - - + fileprivate let kMaxErrorRetryCount = 8 fileprivate let kMinRetryTimeInterval = 2.0 fileprivate let kPathLengthMax = PATH_MAX - 64 @@ -71,174 +78,179 @@ class KVStorage: NSObject { fileprivate let kDBWalFileName = "manifest.sqlite-wal" fileprivate let kDataDirectoryName = "data" fileprivate let kTrashDirectoryName = "trash" - - fileprivate var trashQueue : DispatchQueue + + fileprivate var trashQueue: DispatchQueue fileprivate var path: URL fileprivate var dbPath: URL fileprivate var dataPath: URL fileprivate var trashPath: URL - fileprivate var db: OpaquePointer? = nil - fileprivate var dbStmtCache: Dictionary? + fileprivate var database: OpaquePointer? + fileprivate var dbStmtCache: [String: Any]? fileprivate var dbLastOpenErrorTime: TimeInterval = 0 fileprivate var dbOpenErrorCount: UInt = 0 fileprivate(set) var type: KVStorageType fileprivate var errorLogsEnabled: Bool = true - + + fileprivate let fileManger = FileManager.default + init?(path: URL, type: KVStorageType) { - guard path.absoluteString.count > 0, path.absoluteString.count <= kPathLengthMax else { + guard !path.absoluteString.isEmpty, path.absoluteString.count <= kPathLengthMax else { print("KVStorage init error: invalid path: [\(path)].") - return nil; + return nil } - - self.path = path; - self.type = type; + + self.path = path + self.type = type trashQueue = OS_dispatch_queue_serial(label: "com.iteatime.cache.disk.trash") dataPath = path.appendingPathComponent(kDataDirectoryName) trashPath = path.appendingPathComponent(kTrashDirectoryName) dbPath = path.appendingPathComponent(kDBFileName) do { - try FileManager.default.createDirectory(at: path, withIntermediateDirectories: true, attributes: nil) - try FileManager.default.createDirectory(at: dataPath, withIntermediateDirectories: true, attributes: nil) - try FileManager.default.createDirectory(at: trashPath, withIntermediateDirectories: true, attributes: nil) + try fileManger.createDirectory(at: path, withIntermediateDirectories: true, attributes: nil) + try fileManger.createDirectory(at: dataPath, withIntermediateDirectories: true, attributes: nil) + try fileManger.createDirectory(at: trashPath, withIntermediateDirectories: true, attributes: nil) } catch { - return nil; + return nil } - - super.init(); + if !dbOpen() || !dbInitialize() { // db file may broken... dbClose() reset() // rebuild if !dbOpen() || !dbInitialize() { dbClose() - print("KVStorage init error: fail to open sqlite db.") - return nil; + log("KVStorage init error: fail to open sqlite db.") + return nil } } fileEmptyTrashInBackground() } - + + func sharedExtensionApplication() -> UIApplication? { + return isAppExtension ? nil : UIApplication.shared + } + deinit { #if os(iOS) && canImport(UIKit) + let taskID = sharedExtensionApplication()?.beginBackgroundTask(expirationHandler: nil) #endif - let taskID = UIApplication.sharedExtensionApplication()?.beginBackgroundTask(expirationHandler: nil); dbClose() #if os(iOS) && canImport(UIKit) if let task = taskID { - UIApplication.sharedExtensionApplication()?.endBackgroundTask(task) + sharedExtensionApplication()?.endBackgroundTask(task) } #endif } - - //MARK: private + + // MARK: private fileprivate func reset() { do { - try FileManager.default.removeItem(at: path.appendingPathComponent(kDBFileName)) - try FileManager.default.removeItem(at: path.appendingPathComponent(kDBShmFileName)) - try FileManager.default.removeItem(at: path.appendingPathComponent(kDBWalFileName)) + try fileManger.removeItem(at: path.appendingPathComponent(kDBFileName)) + try fileManger.removeItem(at: path.appendingPathComponent(kDBShmFileName)) + try fileManger.removeItem(at: path.appendingPathComponent(kDBWalFileName)) try fileMoveAllToTrash() fileEmptyTrashInBackground() } catch { - print("reset error: \(error)") + log("reset error: \(error)") } } - - - //MARK: File - + + fileprivate func log(_ items: Any..., separator: String = " ", terminator: String = "\n") { + if errorLogsEnabled { + print(items, separator, terminator) + } + } + + // MARK: File + fileprivate func fileWrite(fileName: String, data: Data) throws { try data.write(to: dataPath.appendingPathComponent(fileName)) } fileprivate func fileRead(fileName: String) throws -> Data? { - return try Data.init(contentsOf: dataPath.appendingPathComponent(fileName)) + return try Data(contentsOf: dataPath.appendingPathComponent(fileName)) } - + fileprivate func deleteFile(fileName: String) throws { - try FileManager.default.removeItem(at: dataPath.appendingPathComponent(fileName)) + try fileManger.removeItem(at: dataPath.appendingPathComponent(fileName)) } - + fileprivate func fileMoveAllToTrash() throws { let uuid = UUID().uuidString let tmpPath = trashPath.appendingPathComponent(uuid) - try FileManager.default.moveItem(at: dataPath, to: tmpPath) - try FileManager.default.createDirectory(at: dataPath, withIntermediateDirectories: true, attributes: nil) + try fileManger.moveItem(at: dataPath, to: tmpPath) + try fileManger.createDirectory(at: dataPath, withIntermediateDirectories: true, attributes: nil) } - + // empty the trash if failed at last time fileprivate func fileEmptyTrashInBackground() { let trashPath = self.trashPath DispatchQueue.global().async { - let fileMgr = FileManager.default do { - let directoryContents = try fileMgr.contentsOfDirectory(atPath: trashPath.absoluteString) + let directoryContents = try self.fileManger.contentsOfDirectory(atPath: trashPath.absoluteString) for path in directoryContents { let fullPath = trashPath.appendingPathComponent(path) - try fileMgr.removeItem(at: fullPath) + try self.fileManger.removeItem(at: fullPath) } } catch { - print("remove trash error: \(error)") + self.log("remove trash error: \(error)") } } } - - //MARK: DataBase - + + // MARK: DataBase + fileprivate func dbOpen() -> Bool { - guard db == nil else { + guard database == nil else { return true } - let result = sqlite3_open(dbPath.absoluteString, &db) + let result = sqlite3_open(dbPath.absoluteString, &database) guard result == SQLITE_OK else { - db = nil + database = nil dbStmtCache = nil dbLastOpenErrorTime = CACurrentMediaTime() dbOpenErrorCount+=1 - if errorLogsEnabled { - print("\(#function) line:\(#line) sqlite open failed (\(result)).") - } + log("\(#function) line:\(#line) sqlite open failed (\(result)).") return false } dbStmtCache = Dictionary() dbLastOpenErrorTime = 0 - dbOpenErrorCount = 0; + dbOpenErrorCount = 0 return true } - + @discardableResult fileprivate func dbClose() -> Bool { - guard db != nil else { + guard let database = database else { return true } - - var retry = false; - var stmtFinalized = false; + + var retry = false + var stmtFinalized = false dbStmtCache = nil repeat { retry = false - let result = sqlite3_close(db!) + let result = sqlite3_close(database) if result == SQLITE_BUSY || result == SQLITE_LOCKED { if !stmtFinalized { stmtFinalized = true - var stmt = sqlite3_next_stmt(db!, nil) + var stmt = sqlite3_next_stmt(database, nil) while stmt != nil { - sqlite3_finalize(stmt); - stmt = sqlite3_next_stmt(db!, nil) + sqlite3_finalize(stmt) + stmt = sqlite3_next_stmt(database, nil) retry = true } } } else if result != SQLITE_OK { - if (errorLogsEnabled) { - print("\(#function) line:\(#line) sqlite close failed (\(result).") - } + log("\(#function) line:\(#line) sqlite close failed (\(result).") } } while(retry) - db = nil + self.database = nil return true } - + fileprivate func dbCheck() -> Bool { - guard db == nil else { + guard database == nil else { return true } if dbOpenErrorCount < kMaxErrorRetryCount && @@ -248,38 +260,38 @@ class KVStorage: NSObject { return false } } - + fileprivate func dbInitialize() -> Bool { - let 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);" + let 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 dbExecute(sql) } - + fileprivate func dbCheckpoint() { guard dbCheck() else { return } // Cause a checkpoint to occur, merge `sqlite-wal` file to `sqlite` file. - sqlite3_wal_checkpoint(db, nil) + sqlite3_wal_checkpoint(database, nil) } - + fileprivate func dbExecute(_ sql: String) -> Bool { - guard sql.count > 0, dbCheck() else { + guard !sql.isEmpty, dbCheck() else { return false } - return sqlite3_exec(db, sql, nil, nil, nil) == SQLITE_OK + return sqlite3_exec(database, sql, nil, nil, nil) == SQLITE_OK } - + fileprivate func dbPrepareStmt(_ sql: String) -> OpaquePointer? { - guard dbCheck(), sql.count > 0, dbStmtCache != nil else { + guard dbCheck(), !sql.isEmpty, dbStmtCache != nil else { return nil } var stmt = dbStmtCache?[sql] as? OpaquePointer if stmt == nil { - let result = sqlite3_prepare_v2(db, sql, -1, &stmt, nil) + let result = sqlite3_prepare_v2(database, sql, -1, &stmt, nil) guard result == SQLITE_OK else { - if (errorLogsEnabled) { - print("\(#function) line:\(#line) sqlite stmt prepare error (\(result)): \(errorMessage)") - } + log("\(#function) line:\(#line) sqlite stmt prepare error (\(result)): \(errorMessage)") return nil } dbStmtCache?[sql] = stmt @@ -288,36 +300,36 @@ class KVStorage: NSObject { } return stmt } - + fileprivate var errorMessage: String { - if let errorPointer = sqlite3_errmsg(db) { + if let errorPointer = sqlite3_errmsg(database) { let errorMessage = String(cString: errorPointer) return errorMessage } else { return "No error message provided from sqlite." } } - - fileprivate func dbJoinedKeys(_ keys: Array) -> String { + + fileprivate func dbJoinedKeys(_ keys: [Any]) -> String { var string = "" let max = keys.count - for i in 0.., stmt: OpaquePointer, fromIndex index : Int) { + + fileprivate func dbBindJoinedKeys(keys: [String], stmt: OpaquePointer, fromIndex index: Int) { let max = keys.count - for i in 0.. Bool { let 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);" guard let stmt = dbPrepareStmt(sql) else { @@ -327,25 +339,23 @@ class KVStorage: NSObject { sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) sqlite3_bind_text(stmt, 2, (fileName as NSString).utf8String, -1, nil) sqlite3_bind_int(stmt, 3, Int32(value.count)) - if fileName.count == 0 { + if fileName.isEmpty { sqlite3_bind_blob(stmt, 4, (value as NSData).bytes, Int32(value.count), nil) } else { sqlite3_bind_blob(stmt, 4, nil, 0, nil) } sqlite3_bind_int(stmt, 5, timestamp) sqlite3_bind_int(stmt, 6, timestamp) - sqlite3_bind_blob(stmt, 7, (extendedData as NSData).bytes, Int32(extendedData.count), nil) - + sqlite3_bind_blob(stmt, 7, (extendedData as NSData).bytes, Int32(extendedData.count), nil) + let result = sqlite3_step(stmt) if result != SQLITE_DONE { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite insert error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite insert error (\(result): (\(errorMessage))") return false } return true } - + fileprivate func dbUpdateAccessTime(_ key: String) -> Bool { let sql = "update manifest set last_access_time = ?1 where key = ?2;" guard let stmt = dbPrepareStmt(sql) else { @@ -355,39 +365,33 @@ class KVStorage: NSObject { sqlite3_bind_text(stmt, 2, (key as NSString).utf8String, -1, nil) let result = sqlite3_step(stmt) if (result != SQLITE_DONE) { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite update error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite update error (\(result): (\(errorMessage))") return false } return true } - - fileprivate func dbUpdateAccessTimes(_ keys: Array) -> Bool { + + fileprivate func dbUpdateAccessTimes(_ keys: [String]) -> Bool { guard dbCheck() else { return false } let sql = "update manifest set last_access_time = \(Int32(time(nil))) where key in (\(dbJoinedKeys(keys)));" var stmtPointer: OpaquePointer? - var result = sqlite3_prepare_v2(db, sql, -1, &stmtPointer, nil) + var result = sqlite3_prepare_v2(database, sql, -1, &stmtPointer, nil) guard result == SQLITE_OK, let stmt = stmtPointer else { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") return false } dbBindJoinedKeys(keys: keys, stmt: stmt, fromIndex: 1) result = sqlite3_step(stmt) sqlite3_finalize(stmt) if (result != SQLITE_DONE) { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite update error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite update error (\(result): (\(errorMessage))") return false } return true } - + fileprivate func dbDeleteItem(_ key: String) -> Bool { let sql = "delete from manifest where key = ?1;" guard let stmt = dbPrepareStmt(sql) else { @@ -396,91 +400,84 @@ class KVStorage: NSObject { sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) let result = sqlite3_step(stmt) if (result != SQLITE_DONE) { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite delete error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite delete error (\(result): (\(errorMessage))") return false } return true } - - fileprivate func dbDeleteItems(_ keys: Array) -> Bool { + + fileprivate func dbDeleteItems(_ keys: [String]) -> Bool { guard dbCheck() else { return false } let sql = "delete from manifest where key in (\(dbJoinedKeys(keys));" var stmtPointer: OpaquePointer? - var result = sqlite3_prepare_v2(db, sql, -1, &stmtPointer, nil) + var result = sqlite3_prepare_v2(database, sql, -1, &stmtPointer, nil) guard result == SQLITE_OK, let stmt = stmtPointer else { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") return false } dbBindJoinedKeys(keys: keys, stmt: stmt, fromIndex: 1) result = sqlite3_step(stmt) sqlite3_finalize(stmt) if (result == SQLITE_ERROR) { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite delete error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite delete error (\(result): (\(errorMessage))") return false } return true } - + fileprivate func dbDeleteItem(sql: String, param: Int32) -> Bool { guard let stmt = dbPrepareStmt(sql) else { return false } - sqlite3_bind_int(stmt, 1, param); + sqlite3_bind_int(stmt, 1, param) let result = sqlite3_step(stmt) if (result != SQLITE_DONE) { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite delete error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite delete error (\(result): (\(errorMessage))") return false } return true } - + fileprivate func dbDeleteItemsWithSizeLargerThan(size: Int) -> Bool { return dbDeleteItem(sql: "delete from manifest where size > ?1;", param: Int32(size)) } - + fileprivate func dbDeleteItemsWithSizeEarlierThan(time: Int) -> Bool { return dbDeleteItem(sql: "delete from manifest where last_access_time < ?1;", param: Int32(time)) } - + fileprivate func dbGetItemFromStmt(stmt: OpaquePointer, excludeInlineData: Bool) -> KVStorageItem { let item = KVStorageItem() - var i: Int32 = 0 - item.key = String(cString: UnsafePointer(sqlite3_column_text(stmt, i))) - i += 1 - item.fileName = String(cString: UnsafePointer(sqlite3_column_text(stmt, i))) - i += 1 - item.size = Int(sqlite3_column_int(stmt, Int32(i))) - i += 1 - let inline_data: UnsafeRawPointer? = excludeInlineData ? nil : sqlite3_column_blob(stmt, i); - let inline_data_length = excludeInlineData ? 0 : sqlite3_column_bytes(stmt, i) - i += 1 - if inline_data_length > 0 && (inline_data != nil) { - item.value = NSData(bytes:inline_data, length:Int(inline_data_length)) as Data - } - item.modTime = Int(sqlite3_column_int(stmt, i)) - i += 1 - item.accessTime = Int(sqlite3_column_int(stmt, i)) - i += 1 - let extended_data: UnsafeRawPointer? = sqlite3_column_blob(stmt, i) - let extended_data_length = sqlite3_column_bytes(stmt, i) - if extended_data_length > 0 && (extended_data != nil) { - item.extendedData = NSData(bytes:extended_data, length:Int(extended_data_length)) as Data + var index: Int32 = 0 + item.key = String(cString: UnsafePointer(sqlite3_column_text(stmt, index))) + index += 1 + item.fileName = String(cString: UnsafePointer(sqlite3_column_text(stmt, index))) + index += 1 + item.size = Int(sqlite3_column_int(stmt, index)) + index += 1 + let inlineData: UnsafeRawPointer? = excludeInlineData ? nil : sqlite3_column_blob(stmt, index) + let inlineDataLength = excludeInlineData ? 0 : sqlite3_column_bytes(stmt, index) + index += 1 + if inlineDataLength > 0 && (inlineData != nil) { + item.value = NSData(bytes: inlineData, length: Int(inlineDataLength)) as Data + } + item.modTime = Int(sqlite3_column_int(stmt, index)) + index += 1 + item.accessTime = Int(sqlite3_column_int(stmt, index)) + index += 1 + let extendedData: UnsafeRawPointer? = sqlite3_column_blob(stmt, index) + let extendedDataLength = sqlite3_column_bytes(stmt, index) + if extendedDataLength > 0 && (extendedData != nil) { + item.extendedData = NSData(bytes: extendedData, length: Int(extendedDataLength)) as Data } return item } - + fileprivate func dbGetItem(key: String, excludeInlineData: Bool) -> KVStorageItem? { - let 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;" + let 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;" guard let stmt = dbPrepareStmt(sql) else { return nil } @@ -491,15 +488,13 @@ class KVStorage: NSObject { item = dbGetItemFromStmt(stmt: stmt, excludeInlineData: excludeInlineData) } else { if (result != SQLITE_DONE) { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") } } return item } - - fileprivate func dbGetItems(keys: Array, excludeInlineData: Bool) -> Array? { + + fileprivate func dbGetItems(keys: [String], excludeInlineData: Bool) -> [KVStorageItem]? { guard dbCheck() else { return nil } @@ -510,26 +505,22 @@ class KVStorage: NSObject { sql = "select key, filename, size, inline_data, modification_time, last_access_time, extended_data from manifest where key in (\(dbJoinedKeys(keys))" } var stmtPointer: OpaquePointer? - var result = sqlite3_prepare_v2(db, sql, -1, &stmtPointer, nil) + var result = sqlite3_prepare_v2(database, sql, -1, &stmtPointer, nil) guard result == SQLITE_OK, let stmt = stmtPointer else { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") return nil } dbBindJoinedKeys(keys: keys, stmt: stmt, fromIndex: 1) - var items: Array? - items = Array() + var items: [KVStorageItem]? = [KVStorageItem]() +// items = Array() repeat { result = sqlite3_step(stmt) if (result == SQLITE_ROW) { items?.append(dbGetItemFromStmt(stmt: stmt, excludeInlineData: excludeInlineData)) } else if (result == SQLITE_DONE) { - break; + break } else { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") items = nil break } @@ -537,7 +528,7 @@ class KVStorage: NSObject { sqlite3_finalize(stmt) return items } - + fileprivate func dbGetValue(key: String) -> Data? { let sql = "select inline_data from manifest where key = ?1;" guard let stmt = dbPrepareStmt(sql) else { @@ -546,67 +537,58 @@ class KVStorage: NSObject { sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) let result = sqlite3_step(stmt) if (result == SQLITE_ROW) { - let inline_data: UnsafeRawPointer? = sqlite3_column_blob(stmt, 0) - let inline_data_length = sqlite3_column_bytes(stmt, 0) - guard inline_data_length > 0 && (inline_data != nil) else { + let inlineData: UnsafeRawPointer? = sqlite3_column_blob(stmt, 0) + let inlineDataLength = sqlite3_column_bytes(stmt, 0) + guard inlineDataLength > 0 && (inlineData != nil) else { return nil } - return NSData(bytes:inline_data, length:Int(inline_data_length)) as Data + return NSData(bytes: inlineData, length: Int(inlineDataLength)) as Data } else { if (result != SQLITE_DONE) { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") } return nil } } - + fileprivate func dbGetFilename(key: String) -> String? { let sql = "select filename from manifest where key = ?1;" guard let stmt = dbPrepareStmt(sql) else { return nil } sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) - let result = sqlite3_step(stmt); + let result = sqlite3_step(stmt) if (result == SQLITE_ROW) { return String(cString: UnsafePointer(sqlite3_column_text(stmt, 0))) } else { if (result != SQLITE_DONE) { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") } return nil } } - - fileprivate func dbGetFileNames(keys: Array) -> Array? { + + fileprivate func dbGetFileNames(keys: [String]) -> [String]? { guard dbCheck() else { return nil } let sql = "select filename from manifest where key in (\(dbJoinedKeys(keys)));" var stmtPointer: OpaquePointer? - var result = sqlite3_prepare_v2(db, sql, -1, &stmtPointer, nil) + var result = sqlite3_prepare_v2(database, sql, -1, &stmtPointer, nil) guard result == SQLITE_OK, let stmt = stmtPointer else { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") return nil } dbBindJoinedKeys(keys: keys, stmt: stmt, fromIndex: 1) - var fileNames: Array? - fileNames = Array() + var fileNames: [String]? = [String]() repeat { result = sqlite3_step(stmt) if (result == SQLITE_ROW) { fileNames?.append(String(cString: UnsafePointer(sqlite3_column_text(stmt, 0)))) } else if (result == SQLITE_DONE) { - break; + break } else { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") fileNames = nil break } @@ -614,24 +596,21 @@ class KVStorage: NSObject { sqlite3_finalize(stmt) return fileNames } - - fileprivate func dbGetFilenames(sql: String, param: Int32) -> Array? { + + fileprivate func dbGetFilenames(sql: String, param: Int32) -> [String]? { guard let stmt = dbPrepareStmt(sql) else { return nil } - sqlite3_bind_int(stmt, 1, Int32(param)); - var fileNames: Array? - fileNames = Array() + sqlite3_bind_int(stmt, 1, Int32(param)) + var fileNames: [String]? = [String]() repeat { let result = sqlite3_step(stmt) if (result == SQLITE_ROW) { fileNames?.append(String(cString: UnsafePointer(sqlite3_column_text(stmt, 0)))) } else if (result == SQLITE_DONE) { - break; + break } else { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") fileNames = nil break } @@ -639,24 +618,23 @@ class KVStorage: NSObject { sqlite3_finalize(stmt) return fileNames } - - fileprivate func dbGetFilenamesWithSizeLargerThan(size: Int) -> Array? { + + fileprivate func dbGetFilenamesWithSizeLargerThan(size: Int) -> [String]? { let sql = "select filename from manifest where size > ?1 and filename is not null;" return dbGetFilenames(sql: sql, param: Int32(size)) } - - fileprivate func dbGetFilenamesWithSizeEarlierThan(time: Int) -> Array? { + + fileprivate func dbGetFilenamesWithSizeEarlierThan(time: Int) -> [String]? { let sql = "select filename from manifest where last_access_time < ?1 and filename is not null;" return dbGetFilenames(sql: sql, param: Int32(time)) } - - fileprivate func dbGetItemSizeInfoOrderByTimeAscWithLimit(count: Int) -> Array? { + + fileprivate func dbGetItemSizeInfoOrderByTimeAscWithLimit(count: Int) -> [KVStorageItem]? { let sql = "select key, filename, size from manifest order by last_access_time asc limit ?1;" guard let stmt = dbPrepareStmt(sql) else { return nil } - var items: Array? - items = Array() + var items: [KVStorageItem]? = [KVStorageItem]() repeat { let result = sqlite3_step(stmt) if (result == SQLITE_ROW) { @@ -666,11 +644,9 @@ class KVStorage: NSObject { item.size = Int(sqlite3_column_int(stmt, 2)) items?.append(item) } else if (result == SQLITE_DONE) { - break; + break } else { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") items = nil break } @@ -678,7 +654,7 @@ class KVStorage: NSObject { sqlite3_finalize(stmt) return items } - + fileprivate func dbGetItemCount(key: String) -> Int { let sql = "select count(key) from manifest where key = ?1;" guard let stmt = dbPrepareStmt(sql) else { @@ -687,32 +663,28 @@ class KVStorage: NSObject { sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) let result = sqlite3_step(stmt) if result != SQLITE_ROW { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") return -1 } return Int(sqlite3_column_int(stmt, 0)) } - + fileprivate func dbGetInt(_ sql: String) -> Int { guard let stmt = dbPrepareStmt(sql) else { return -1 } let result = sqlite3_step(stmt) if result != SQLITE_ROW { - if errorLogsEnabled { - print("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") - } + log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") return -1 } return Int(sqlite3_column_int(stmt, 0)) } - + fileprivate func dbGetTotalItemSize() -> Int { return dbGetInt("select sum(size) from manifest;") } - + fileprivate func dbGetTotalItemCount() -> Int { return dbGetInt("select count(*) from manifest;") } diff --git a/Sources/ClaretCache/MemoryCache.swift b/Sources/ClaretCache/MemoryCache.swift index 47ab54e..1326f4c 100644 --- a/Sources/ClaretCache/MemoryCache.swift +++ b/Sources/ClaretCache/MemoryCache.swift @@ -179,7 +179,7 @@ private extension MemoryCache { #if os(iOS) && canImport(UIKit) func addNotification() { - let memoryWarningObserver = NotificationCenter.default.addObserver(forName: UIApplication.didReceiveMemoryWarningNotification ,object: nil, queue: nil) { [weak self] _ in + let memoryWarningObserver = NotificationCenter.default.addObserver(forName: UIApplication.didReceiveMemoryWarningNotification, object: nil, queue: nil) { [weak self] _ in guard let self = self else { return } self.didReceiveMemoryWarning?(self) if self.removeAllObjectsOnMemoryWarning { @@ -224,8 +224,8 @@ private extension MemoryCache { Typically, you should not use this class directly. */ -fileprivate final class LinkedMap where Key : Hashable { - var dic: [Key:Value] = [:] +fileprivate final class LinkedMap where Key: Hashable { + var dic: [Key: Value] = [:] var totalCost: UInt = 0 var totalCount: UInt = 0 var head: Node? // MRU, do not change it directly @@ -233,7 +233,7 @@ fileprivate final class LinkedMap where Key : Hashable { var releaseOnMainThread: Bool = false var releaseAsynchronously: Bool = true - final class Node where Key : Hashable { + final class Node where Key: Hashable { var prev: Node? var next: Node? var key: Key? diff --git a/Sources/ClaretCache/UIApplication+CCAdd.swift b/Sources/ClaretCache/UIApplication+CCAdd.swift deleted file mode 100644 index 61d93d9..0000000 --- a/Sources/ClaretCache/UIApplication+CCAdd.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// UIApplication+CCAdd.swift -// ClaretCacheDemo -// -// Created by HZheng on 2019/7/28. -// Copyright © 2019 com.ClaretCache. All rights reserved. -// - -import UIKit.UIApplication - -var IsAppExtension: Bool { - guard Bundle.main.bundleURL.pathExtension != "appex" else { - return false - } - guard let app = NSClassFromString("UIApplication"), app.value(forKey: "shared") != nil else { - return true - } - return false -} - -extension UIApplication { - static func isAppExtension() -> Bool { - return IsAppExtension; - } - - static func sharedExtensionApplication() -> UIApplication? { - return IsAppExtension ? nil : UIApplication.shared - } -} diff --git a/Tests/ClaretCacheTests/ClaretCacheTests.swift b/Tests/ClaretCacheTests/ClaretCacheTests.swift index 95ab497..bc5def5 100644 --- a/Tests/ClaretCacheTests/ClaretCacheTests.swift +++ b/Tests/ClaretCacheTests/ClaretCacheTests.swift @@ -10,6 +10,6 @@ final class ClaretCacheTests: XCTestCase { } static var allTests = [ - ("testExample", testExample), + ("testExample", testExample) ] } diff --git a/Tests/ClaretCacheTests/XCTestManifests.swift b/Tests/ClaretCacheTests/XCTestManifests.swift index 5539661..0c27b39 100644 --- a/Tests/ClaretCacheTests/XCTestManifests.swift +++ b/Tests/ClaretCacheTests/XCTestManifests.swift @@ -3,7 +3,7 @@ import XCTest #if !canImport(ObjectiveC) public func allTests() -> [XCTestCaseEntry] { return [ - testCase(ClaretCacheTests.allTests), + testCase(ClaretCacheTests.allTests) ] } #endif diff --git a/configs/.swiftlint.yml b/configs/.swiftlint.yml index 383fb2c..5a55cf2 100644 --- a/configs/.swiftlint.yml +++ b/configs/.swiftlint.yml @@ -2,6 +2,8 @@ disabled_rules: # rule identifiers to exclude from running - colon - comma - control_statement + - file_length + - type_body_length opt_in_rules: # some rules are only opt-in - empty_count # Find all the available rules by running: From b7083c5d812fec81f9da921f1135a90cb0b054a3 Mon Sep 17 00:00:00 2001 From: huangzheng Date: Tue, 30 Jul 2019 00:37:07 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E5=AE=8C=E6=88=90KVStorage=E7=BC=96?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E9=9C=80=E8=A6=81=E8=BF=9B=E8=A1=8C=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ClaretCacheDemo.xcodeproj/project.pbxproj | 4 + Sources/ClaretCache/KVStorage.swift | 481 ++++++++++++++++++++-- 2 files changed, 454 insertions(+), 31 deletions(-) diff --git a/ClaretCacheDemo.xcodeproj/project.pbxproj b/ClaretCacheDemo.xcodeproj/project.pbxproj index f25dea9..1c90c10 100644 --- a/ClaretCacheDemo.xcodeproj/project.pbxproj +++ b/ClaretCacheDemo.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 01FA686E22E719DB008E24FC /* ClaretCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FA686C22E719DB008E24FC /* ClaretCache.swift */; }; 8E077CCCBC628B6EF406E97A /* Pods_ClaretCacheDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1BF5D3C4BBDD1FACD276DAA1 /* Pods_ClaretCacheDemo.framework */; }; C30EB06522ED8E8A0064ED79 /* KVStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30EB06422ED8E8A0064ED79 /* KVStorage.swift */; }; + C3BC07DC22EF369600345659 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = C3BC07DB22EF369600345659 /* libsqlite3.tbd */; }; F8C53E7722EB9EBC00B53664 /* ClaretCacheDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C53E7622EB9EBC00B53664 /* ClaretCacheDemoTests.swift */; }; /* End PBXBuildFile section */ @@ -43,6 +44,7 @@ 71DE9F8F64DFC38FD8ABCF96 /* Pods-ClaretCacheDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClaretCacheDemo.release.xcconfig"; path = "Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo.release.xcconfig"; sourceTree = ""; }; 96892E93F01C8D0A4FEC2EC1 /* Pods-ClaretCacheDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClaretCacheDemo.debug.xcconfig"; path = "Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo.debug.xcconfig"; sourceTree = ""; }; C30EB06422ED8E8A0064ED79 /* KVStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KVStorage.swift; sourceTree = ""; }; + C3BC07DB22EF369600345659 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; F8C53E7422EB9EBC00B53664 /* ClaretCacheDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ClaretCacheDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F8C53E7622EB9EBC00B53664 /* ClaretCacheDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClaretCacheDemoTests.swift; sourceTree = ""; }; F8C53E7822EB9EBC00B53664 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -53,6 +55,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C3BC07DC22EF369600345659 /* libsqlite3.tbd in Frameworks */, 8E077CCCBC628B6EF406E97A /* Pods_ClaretCacheDemo.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -122,6 +125,7 @@ 53C8A8FFD2D1D8A4F837C496 /* Frameworks */ = { isa = PBXGroup; children = ( + C3BC07DB22EF369600345659 /* libsqlite3.tbd */, 1BF5D3C4BBDD1FACD276DAA1 /* Pods_ClaretCacheDemo.framework */, ); name = Frameworks; diff --git a/Sources/ClaretCache/KVStorage.swift b/Sources/ClaretCache/KVStorage.swift index 35f3f77..6b00f10 100644 --- a/Sources/ClaretCache/KVStorage.swift +++ b/Sources/ClaretCache/KVStorage.swift @@ -29,6 +29,20 @@ public enum KVStorageType { case mixed } +/** + KVStorageItem is used by `KVStorage` to store key-value pair and meta data. + Typically, you should not use this class directly. + */ +public class KVStorageItem { + var key: String? ///< key + var value: Data? ///< value + var fileName: String? ///< fileName (nil if inline) + var size: Int = 0 ///< value's size in bytes + var modTime: Int = 0 ///< modification unix timestamp + var accessTime: Int = 0 ///< last access unix timestamp + var extendedData: Data? ///< extended data (nil if no extended data) +} + /* File: /path/ @@ -56,20 +70,6 @@ public enum KVStorageType { */ public class KVStorage { - /** - KVStorageItem is used by `KVStorage` to store key-value pair and meta data. - Typically, you should not use this class directly. - */ - fileprivate class KVStorageItem { - var key: String? ///< key - var value: Data? ///< value - var fileName: String? ///< fileName (nil if inline) - var size: Int = 0 ///< value's size in bytes - var modTime: Int = 0 ///< modification unix timestamp - var accessTime: Int = 0 ///< last access unix timestamp - var extendedData: Data? ///< extended data (nil if no extended data) - } - fileprivate let kMaxErrorRetryCount = 8 fileprivate let kMinRetryTimeInterval = 2.0 fileprivate let kPathLengthMax = PATH_MAX - 64 @@ -163,23 +163,48 @@ public class KVStorage { // MARK: File - fileprivate func fileWrite(fileName: String, data: Data) throws { - try data.write(to: dataPath.appendingPathComponent(fileName)) + fileprivate func fileWrite(fileName: String, data: Data) -> Bool { + do { + try data.write(to: dataPath.appendingPathComponent(fileName)) + } catch { + log("\(#function) line:(\(#line) file write error. fileName: (\(fileName)") + return false + } + return true } - fileprivate func fileRead(fileName: String) throws -> Data? { - return try Data(contentsOf: dataPath.appendingPathComponent(fileName)) + fileprivate func fileRead(fileName: String) -> Data? { + do { + return try Data(contentsOf: dataPath.appendingPathComponent(fileName)) + } catch { + log("\(#function) line:(\(#line) file read error. fileName: (\(fileName)") + return nil + } } - fileprivate func deleteFile(fileName: String) throws { - try fileManger.removeItem(at: dataPath.appendingPathComponent(fileName)) + @discardableResult + fileprivate func fileDelete(fileName: String) -> Bool { + do { + try fileManger.removeItem(at: dataPath.appendingPathComponent(fileName)) + } catch { + log("\(#function) line:(\(#line) file delete error. fileName: (\(fileName)") + return false + } + return true } - fileprivate func fileMoveAllToTrash() throws { + @discardableResult + fileprivate func fileMoveAllToTrash() -> Bool { let uuid = UUID().uuidString let tmpPath = trashPath.appendingPathComponent(uuid) - try fileManger.moveItem(at: dataPath, to: tmpPath) - try fileManger.createDirectory(at: dataPath, withIntermediateDirectories: true, attributes: nil) + do { + try fileManger.moveItem(at: dataPath, to: tmpPath) + try fileManger.createDirectory(at: dataPath, withIntermediateDirectories: true, attributes: nil) + } catch { + log("\(#function) line:(\(#line) file move all to trash error.") + return false + } + return true } // empty the trash if failed at last time @@ -330,23 +355,31 @@ public class KVStorage { } } - fileprivate func dbSave(key: String, value: Data, fileName: String, extendedData: Data) -> Bool { + fileprivate func dbSave(key: String, value: Data, fileName: String?, extendedData: Data?) -> Bool { let 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);" guard let stmt = dbPrepareStmt(sql) else { return false } let timestamp = Int32(time(nil)) sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) - sqlite3_bind_text(stmt, 2, (fileName as NSString).utf8String, -1, nil) + if let file = fileName { + sqlite3_bind_text(stmt, 2, (file as NSString).utf8String, -1, nil) + } else { + sqlite3_bind_text(stmt, 2, nil, -1, nil) + } sqlite3_bind_int(stmt, 3, Int32(value.count)) - if fileName.isEmpty { + if fileName?.isEmpty ?? false { sqlite3_bind_blob(stmt, 4, (value as NSData).bytes, Int32(value.count), nil) } else { sqlite3_bind_blob(stmt, 4, nil, 0, nil) } sqlite3_bind_int(stmt, 5, timestamp) sqlite3_bind_int(stmt, 6, timestamp) - sqlite3_bind_blob(stmt, 7, (extendedData as NSData).bytes, Int32(extendedData.count), nil) + if let exData = extendedData { + sqlite3_bind_blob(stmt, 7, (exData as NSData).bytes, Int32(exData.count), nil) + } else { + sqlite3_bind_blob(stmt, 7, nil, 0, nil) + } let result = sqlite3_step(stmt) if result != SQLITE_DONE { @@ -356,6 +389,7 @@ public class KVStorage { return true } + @discardableResult fileprivate func dbUpdateAccessTime(_ key: String) -> Bool { let sql = "update manifest set last_access_time = ?1 where key = ?2;" guard let stmt = dbPrepareStmt(sql) else { @@ -371,6 +405,7 @@ public class KVStorage { return true } + @discardableResult fileprivate func dbUpdateAccessTimes(_ keys: [String]) -> Bool { guard dbCheck() else { return false @@ -392,6 +427,7 @@ public class KVStorage { return true } + @discardableResult fileprivate func dbDeleteItem(_ key: String) -> Bool { let sql = "delete from manifest where key = ?1;" guard let stmt = dbPrepareStmt(sql) else { @@ -440,11 +476,11 @@ public class KVStorage { return true } - fileprivate func dbDeleteItemsWithSizeLargerThan(size: Int) -> Bool { + fileprivate func dbDeleteItemsWithSizeLargerThan(_ size: Int) -> Bool { return dbDeleteItem(sql: "delete from manifest where size > ?1;", param: Int32(size)) } - fileprivate func dbDeleteItemsWithSizeEarlierThan(time: Int) -> Bool { + fileprivate func dbDeleteItemsWithTimeEarlierThan(_ time: Int) -> Bool { return dbDeleteItem(sql: "delete from manifest where last_access_time < ?1;", param: Int32(time)) } @@ -619,12 +655,12 @@ public class KVStorage { return fileNames } - fileprivate func dbGetFilenamesWithSizeLargerThan(size: Int) -> [String]? { + fileprivate func dbGetFilenamesWithSizeLargerThan(_ size: Int) -> [String]? { let sql = "select filename from manifest where size > ?1 and filename is not null;" return dbGetFilenames(sql: sql, param: Int32(size)) } - fileprivate func dbGetFilenamesWithSizeEarlierThan(time: Int) -> [String]? { + fileprivate func dbGetFilenamesWithTimeEarlierThan(_ time: Int) -> [String]? { let sql = "select filename from manifest where last_access_time < ?1 and filename is not null;" return dbGetFilenames(sql: sql, param: Int32(time)) } @@ -688,4 +724,387 @@ public class KVStorage { fileprivate func dbGetTotalItemCount() -> Int { return dbGetInt("select count(*) from manifest;") } + + // MARK: Public + public func saveItem(key: String, value: Data, fileName: String?, extendedData: Data?) -> Bool { + guard !key.isEmpty, !value.isEmpty else { + return false + } + if type == .file && fileName?.isEmpty ?? true { + return false + } + if let file = fileName, !file.isEmpty { + if !fileWrite(fileName: file, data: value) { + return false + } + if !dbSave(key: key, value: value, fileName: file, extendedData: extendedData) { + fileDelete(fileName: file) + return false + } + return true + } else { + if type != .sqlite { + if let file = dbGetFilename(key: key) { + fileDelete(fileName: file) + } + } + return dbSave(key: key, value: value, fileName: nil, extendedData: extendedData) + } + } + + public func removeItem(key: String) -> Bool { + guard !key.isEmpty else { + return false + } + switch type { + case .sqlite: + return dbDeleteItem(key) + case .file, .mixed: + if let fileName = dbGetFilename(key: key) { + fileDelete(fileName: fileName) + } + return dbDeleteItem(key) + } + } + + public func removeItems(keys: [String]) -> Bool { + guard !keys.isEmpty else { + return false + } + switch type { + case .sqlite: + return dbDeleteItems(keys) + case .file, .mixed: + if let fileNames = dbGetFileNames(keys: keys), !fileNames.isEmpty { + for file in fileNames { + fileDelete(fileName: file) + } + } + return dbDeleteItems(keys) + } + } + + public func removeAllItems() -> Bool { + guard dbClose() else { + return false + } + reset() + guard dbOpen() else { + return false + } + guard dbInitialize() else { + return false + } + return true + } + + public func removeItemsLargerThanSize(_ size: Int) -> Bool { + guard size != Int.max else { + return true + } + guard size > 0 else { + return removeAllItems() + } + switch type { + case .sqlite: + if dbDeleteItemsWithSizeLargerThan(size) { + dbCheckpoint() + return true + } + case .file, .mixed: + if let fileNames = dbGetFilenamesWithSizeLargerThan(size) { + for file in fileNames { + fileDelete(fileName: file) + } + } + if dbDeleteItemsWithSizeLargerThan(size) { + dbCheckpoint() + return true + } + } + return false + } + + public func removeItemsEarlierThanTime(_ time: Int) -> Bool { + guard time > 0 else { + return true + } + guard time != Int.max else { + return removeAllItems() + } + switch type { + case .sqlite: + if dbDeleteItemsWithTimeEarlierThan(time) { + dbCheckpoint() + return true + } + case .file, .mixed: + if let fileNames = dbGetFilenamesWithTimeEarlierThan(time) { + for file in fileNames { + fileDelete(fileName: file) + } + } + if dbDeleteItemsWithTimeEarlierThan(time) { + dbCheckpoint() + return true + } + } + return false + } + + public func removeItemsToFitSize(_ maxSize: Int) -> Bool { + guard maxSize != Int.max else { + return true + } + guard maxSize > 0 else { + return removeAllItems() + } + var total = dbGetTotalItemSize() + guard total >= 0 else { + return false + } + guard total > maxSize else { + return true + } + var suc = false + let perCount = 16 + repeat { + if let items = dbGetItemSizeInfoOrderByTimeAscWithLimit(count: perCount), !items.isEmpty { + for item in items { + if total > maxSize { + if let fileName = item.fileName { + fileDelete(fileName: fileName) + } + if let key = item.key { + suc = dbDeleteItem(key) + } else { + suc = true + } + total -= item.size + } else { + break + } + if !suc { + break + } + } + } else { + break + } + } while (total > maxSize && suc) + if suc { + dbCheckpoint() + } + return suc + } + + public func removeItemsToFitCount(_ maxCount: Int) -> Bool { + guard maxCount != Int.max else { + return true + } + guard maxCount > 0 else { + return removeAllItems() + } + var total = dbGetTotalItemCount() + guard total >= 0 else { + return false + } + guard total > maxCount else { + return true + } + var suc = false + let perCount = 16 + repeat { + if let items = dbGetItemSizeInfoOrderByTimeAscWithLimit(count: perCount), !items.isEmpty { + for item in items { + if total > maxCount { + if let fileName = item.fileName { + fileDelete(fileName: fileName) + } + if let key = item.key { + suc = dbDeleteItem(key) + } else { + suc = true + } + total -= 1 + } else { + break + } + if !suc { + break + } + } + } else { + break + } + } while (total > maxCount && suc) + if suc { + dbCheckpoint() + } + return suc + } + + public func removeAllItemsWithProgressBlock(progress: ((_ removedCount: Int, _ totalCount: Int) -> Void)?, + end: ((_ error: Bool) -> Void)?) { + let total = dbGetTotalItemCount() + if total <= 0 { + end?(total < 0) + } else { + var left = total + let perCount = 32 + var suc = false + repeat { + if let items = dbGetItemSizeInfoOrderByTimeAscWithLimit(count: perCount), !items.isEmpty { + for item in items { + if left > 0 { + if let fileName = item.fileName { + fileDelete(fileName: fileName) + } + if let key = item.key { + suc = dbDeleteItem(key) + } else { + suc = true + } + left -= 1 + } else { + break + } + if !suc { + break + } + } + } else { + break + } + progress?(total - left, total) + } while (left > 0 && suc) + if suc { + dbCheckpoint() + } + end?(!suc) + } + } + + public func getItemForKey(_ key: String) -> KVStorageItem? { + guard !key.isEmpty else { + return nil + } + guard let item = dbGetItem(key: key, excludeInlineData: false) else { + return nil + } + dbUpdateAccessTime(key) + if let fileName = item.fileName { + if let value = fileRead(fileName: fileName) { + item.value = value + } else { + dbDeleteItem(key) + return nil + } + } + return item + } + + public func getItemInfoForKey(_ key: String) -> KVStorageItem? { + guard !key.isEmpty else { + return nil + } + return dbGetItem(key: key, excludeInlineData: true) + } + + public func getItemValueForKey(_ key: String) -> Data? { + guard !key.isEmpty else { + return nil + } + var value: Data? + switch type { + case .file: + if let fileName = dbGetFilename(key: key) { + value = fileRead(fileName: fileName) + if value == nil { + dbDeleteItem(key) + } + } + case .sqlite: + value = dbGetValue(key: key) + case .mixed: + if let fileName = dbGetFilename(key: key) { + value = fileRead(fileName: fileName) + if value == nil { + dbDeleteItem(key) + } + } else { + value = dbGetValue(key: key) + } + } + if value != nil { + dbUpdateAccessTime(key) + } + return value + } + + public func getItemForKeys(_ keys: [String]) -> [KVStorageItem]? { + guard !keys.isEmpty else { + return nil + } + if var items = dbGetItems(keys: keys, excludeInlineData: false), !items.isEmpty { + if type == .sqlite { + var index = 0 + var max = items.count + repeat { + let item = items[index] + if let fileName = item.fileName { + if let value = fileRead(fileName: fileName) { + item.value = value + } else { + if let key = item.key { + dbDeleteItem(key) + } + items.remove(at: index) + index -= 1 + max -= 1 + } + } + index += 1 + } while(index < max) + } + return items.isEmpty ? nil : items + } else { + return nil + } + } + + public func getItemInfoForKeys(_ keys: [String]) -> [KVStorageItem]? { + guard !keys.isEmpty else { + return nil + } + return dbGetItems(keys: keys, excludeInlineData: true) + } + + public func getItemValueForKeys(_ keys: [String]) -> [String: Any]? { + guard let items = getItemForKeys(keys) else { + return nil + } + var keyAndValue = [String: Any]() + for item in items { + if let key = item.key, let value = item.value { + keyAndValue[key] = value + } + } + return keyAndValue.isEmpty ? nil : keyAndValue + } + + public func itemExistsForKey(_ key: String) -> Bool { + guard !key.isEmpty else { + return false + } + return dbGetItemCount(key: key) > 0 + } + + public func getItemsCount() -> Int { + return dbGetTotalItemCount() + } + + public func getItemsSize() -> Int { + return dbGetTotalItemSize() + } } From fb79c42ba152e3fffefdd8cca88b4d08faae7c59 Mon Sep 17 00:00:00 2001 From: Huang Zheng Date: Tue, 30 Jul 2019 11:00:52 +0800 Subject: [PATCH 04/10] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=8C=E8=A7=A3=E5=86=B3=E6=8E=89=E5=9C=88=E6=95=B0=E5=A4=AA?= =?UTF-8?q?=E5=A4=9A=E7=9A=84=E8=AD=A6=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/ClaretCache/KVStorage.swift | 299 ++++++++-------------------- 1 file changed, 84 insertions(+), 215 deletions(-) diff --git a/Sources/ClaretCache/KVStorage.swift b/Sources/ClaretCache/KVStorage.swift index 6b00f10..9d4d31f 100644 --- a/Sources/ClaretCache/KVStorage.swift +++ b/Sources/ClaretCache/KVStorage.swift @@ -226,9 +226,7 @@ public class KVStorage { // MARK: DataBase fileprivate func dbOpen() -> Bool { - guard database == nil else { - return true - } + guard database == nil else { return true } let result = sqlite3_open(dbPath.absoluteString, &database) guard result == SQLITE_OK else { database = nil @@ -246,10 +244,7 @@ public class KVStorage { @discardableResult fileprivate func dbClose() -> Bool { - guard let database = database else { - return true - } - + guard let database = database else { return true } var retry = false var stmtFinalized = false dbStmtCache = nil @@ -275,9 +270,7 @@ public class KVStorage { } fileprivate func dbCheck() -> Bool { - guard database == nil else { - return true - } + guard database == nil else { return true } if dbOpenErrorCount < kMaxErrorRetryCount && CACurrentMediaTime() - dbLastOpenErrorTime > kMinRetryTimeInterval { return dbOpen() && dbInitialize() @@ -294,24 +287,18 @@ public class KVStorage { } fileprivate func dbCheckpoint() { - guard dbCheck() else { - return - } + guard dbCheck() else { return } // Cause a checkpoint to occur, merge `sqlite-wal` file to `sqlite` file. sqlite3_wal_checkpoint(database, nil) } fileprivate func dbExecute(_ sql: String) -> Bool { - guard !sql.isEmpty, dbCheck() else { - return false - } + guard !sql.isEmpty, dbCheck() else { return false } return sqlite3_exec(database, sql, nil, nil, nil) == SQLITE_OK } fileprivate func dbPrepareStmt(_ sql: String) -> OpaquePointer? { - guard dbCheck(), !sql.isEmpty, dbStmtCache != nil else { - return nil - } + guard dbCheck(), !sql.isEmpty, dbStmtCache != nil else { return nil } var stmt = dbStmtCache?[sql] as? OpaquePointer if stmt == nil { let result = sqlite3_prepare_v2(database, sql, -1, &stmt, nil) @@ -357,9 +344,7 @@ public class KVStorage { fileprivate func dbSave(key: String, value: Data, fileName: String?, extendedData: Data?) -> Bool { let 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);" - guard let stmt = dbPrepareStmt(sql) else { - return false - } + guard let stmt = dbPrepareStmt(sql) else { return false } let timestamp = Int32(time(nil)) sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) if let file = fileName { @@ -392,9 +377,7 @@ public class KVStorage { @discardableResult fileprivate func dbUpdateAccessTime(_ key: String) -> Bool { let sql = "update manifest set last_access_time = ?1 where key = ?2;" - guard let stmt = dbPrepareStmt(sql) else { - return false - } + guard let stmt = dbPrepareStmt(sql) else { return false } sqlite3_bind_int(stmt, 1, Int32(time(nil))) sqlite3_bind_text(stmt, 2, (key as NSString).utf8String, -1, nil) let result = sqlite3_step(stmt) @@ -407,9 +390,7 @@ public class KVStorage { @discardableResult fileprivate func dbUpdateAccessTimes(_ keys: [String]) -> Bool { - guard dbCheck() else { - return false - } + guard dbCheck() else { return false } let sql = "update manifest set last_access_time = \(Int32(time(nil))) where key in (\(dbJoinedKeys(keys)));" var stmtPointer: OpaquePointer? var result = sqlite3_prepare_v2(database, sql, -1, &stmtPointer, nil) @@ -430,9 +411,7 @@ public class KVStorage { @discardableResult fileprivate func dbDeleteItem(_ key: String) -> Bool { let sql = "delete from manifest where key = ?1;" - guard let stmt = dbPrepareStmt(sql) else { - return false - } + guard let stmt = dbPrepareStmt(sql) else { return false } sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) let result = sqlite3_step(stmt) if (result != SQLITE_DONE) { @@ -443,9 +422,7 @@ public class KVStorage { } fileprivate func dbDeleteItems(_ keys: [String]) -> Bool { - guard dbCheck() else { - return false - } + guard dbCheck() else { return false } let sql = "delete from manifest where key in (\(dbJoinedKeys(keys));" var stmtPointer: OpaquePointer? var result = sqlite3_prepare_v2(database, sql, -1, &stmtPointer, nil) @@ -464,9 +441,7 @@ public class KVStorage { } fileprivate func dbDeleteItem(sql: String, param: Int32) -> Bool { - guard let stmt = dbPrepareStmt(sql) else { - return false - } + guard let stmt = dbPrepareStmt(sql) else { return false } sqlite3_bind_int(stmt, 1, param) let result = sqlite3_step(stmt) if (result != SQLITE_DONE) { @@ -514,9 +489,7 @@ public class KVStorage { fileprivate func dbGetItem(key: String, excludeInlineData: Bool) -> KVStorageItem? { let 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;" - guard let stmt = dbPrepareStmt(sql) else { - return nil - } + guard let stmt = dbPrepareStmt(sql) else { return nil } sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) var item: KVStorageItem? let result = sqlite3_step(stmt) @@ -531,9 +504,7 @@ public class KVStorage { } fileprivate func dbGetItems(keys: [String], excludeInlineData: Bool) -> [KVStorageItem]? { - guard dbCheck() else { - return nil - } + guard dbCheck() else { return nil } let sql: String if (excludeInlineData) { sql = "select key, filename, size, modification_time, last_access_time, extended_data from manifest where key in (\(dbJoinedKeys(keys)));" @@ -548,7 +519,6 @@ public class KVStorage { } dbBindJoinedKeys(keys: keys, stmt: stmt, fromIndex: 1) var items: [KVStorageItem]? = [KVStorageItem]() -// items = Array() repeat { result = sqlite3_step(stmt) if (result == SQLITE_ROW) { @@ -567,9 +537,7 @@ public class KVStorage { fileprivate func dbGetValue(key: String) -> Data? { let sql = "select inline_data from manifest where key = ?1;" - guard let stmt = dbPrepareStmt(sql) else { - return nil - } + guard let stmt = dbPrepareStmt(sql) else { return nil } sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) let result = sqlite3_step(stmt) if (result == SQLITE_ROW) { @@ -589,9 +557,7 @@ public class KVStorage { fileprivate func dbGetFilename(key: String) -> String? { let sql = "select filename from manifest where key = ?1;" - guard let stmt = dbPrepareStmt(sql) else { - return nil - } + guard let stmt = dbPrepareStmt(sql) else { return nil } sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) let result = sqlite3_step(stmt) if (result == SQLITE_ROW) { @@ -605,9 +571,7 @@ public class KVStorage { } fileprivate func dbGetFileNames(keys: [String]) -> [String]? { - guard dbCheck() else { - return nil - } + guard dbCheck() else { return nil } let sql = "select filename from manifest where key in (\(dbJoinedKeys(keys)));" var stmtPointer: OpaquePointer? var result = sqlite3_prepare_v2(database, sql, -1, &stmtPointer, nil) @@ -634,9 +598,7 @@ public class KVStorage { } fileprivate func dbGetFilenames(sql: String, param: Int32) -> [String]? { - guard let stmt = dbPrepareStmt(sql) else { - return nil - } + guard let stmt = dbPrepareStmt(sql) else { return nil } sqlite3_bind_int(stmt, 1, Int32(param)) var fileNames: [String]? = [String]() repeat { @@ -667,9 +629,7 @@ public class KVStorage { fileprivate func dbGetItemSizeInfoOrderByTimeAscWithLimit(count: Int) -> [KVStorageItem]? { let sql = "select key, filename, size from manifest order by last_access_time asc limit ?1;" - guard let stmt = dbPrepareStmt(sql) else { - return nil - } + guard let stmt = dbPrepareStmt(sql) else { return nil } var items: [KVStorageItem]? = [KVStorageItem]() repeat { let result = sqlite3_step(stmt) @@ -693,9 +653,7 @@ public class KVStorage { fileprivate func dbGetItemCount(key: String) -> Int { let sql = "select count(key) from manifest where key = ?1;" - guard let stmt = dbPrepareStmt(sql) else { - return -1 - } + guard let stmt = dbPrepareStmt(sql) else { return -1 } sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) let result = sqlite3_step(stmt) if result != SQLITE_ROW { @@ -706,9 +664,7 @@ public class KVStorage { } fileprivate func dbGetInt(_ sql: String) -> Int { - guard let stmt = dbPrepareStmt(sql) else { - return -1 - } + guard let stmt = dbPrepareStmt(sql) else { return -1 } let result = sqlite3_step(stmt) if result != SQLITE_ROW { log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") @@ -727,12 +683,8 @@ public class KVStorage { // MARK: Public public func saveItem(key: String, value: Data, fileName: String?, extendedData: Data?) -> Bool { - guard !key.isEmpty, !value.isEmpty else { - return false - } - if type == .file && fileName?.isEmpty ?? true { - return false - } + guard !key.isEmpty, !value.isEmpty else { return false } + if type == .file && fileName?.isEmpty ?? true { return false } if let file = fileName, !file.isEmpty { if !fileWrite(fileName: file, data: value) { return false @@ -753,9 +705,7 @@ public class KVStorage { } public func removeItem(key: String) -> Bool { - guard !key.isEmpty else { - return false - } + guard !key.isEmpty else { return false } switch type { case .sqlite: return dbDeleteItem(key) @@ -768,9 +718,7 @@ public class KVStorage { } public func removeItems(keys: [String]) -> Bool { - guard !keys.isEmpty else { - return false - } + guard !keys.isEmpty else { return false } switch type { case .sqlite: return dbDeleteItems(keys) @@ -785,26 +733,16 @@ public class KVStorage { } public func removeAllItems() -> Bool { - guard dbClose() else { - return false - } + guard dbClose() else { return false } reset() - guard dbOpen() else { - return false - } - guard dbInitialize() else { - return false - } + guard dbOpen() else { return false } + guard dbInitialize() else { return false } return true } public func removeItemsLargerThanSize(_ size: Int) -> Bool { - guard size != Int.max else { - return true - } - guard size > 0 else { - return removeAllItems() - } + guard size != Int.max else { return true } + guard size > 0 else { return removeAllItems() } switch type { case .sqlite: if dbDeleteItemsWithSizeLargerThan(size) { @@ -826,12 +764,8 @@ public class KVStorage { } public func removeItemsEarlierThanTime(_ time: Int) -> Bool { - guard time > 0 else { - return true - } - guard time != Int.max else { - return removeAllItems() - } + guard time > 0 else { return true } + guard time != Int.max else { return removeAllItems() } switch type { case .sqlite: if dbDeleteItemsWithTimeEarlierThan(time) { @@ -853,45 +787,26 @@ public class KVStorage { } public func removeItemsToFitSize(_ maxSize: Int) -> Bool { - guard maxSize != Int.max else { - return true - } - guard maxSize > 0 else { - return removeAllItems() - } + guard maxSize != Int.max else { return true } + guard maxSize > 0 else { return removeAllItems() } var total = dbGetTotalItemSize() - guard total >= 0 else { - return false - } - guard total > maxSize else { - return true - } + guard total >= 0 else { return false } + guard total > maxSize else { return true } var suc = false - let perCount = 16 - repeat { - if let items = dbGetItemSizeInfoOrderByTimeAscWithLimit(count: perCount), !items.isEmpty { - for item in items { - if total > maxSize { - if let fileName = item.fileName { - fileDelete(fileName: fileName) - } - if let key = item.key { - suc = dbDeleteItem(key) - } else { - suc = true - } - total -= item.size - } else { - break - } - if !suc { - break - } - } + dbGetItemSizeInfoOrderByTimeAscWithLimit(count: 16)?.forEach({ (item) in + if let fileName = item.fileName { + fileDelete(fileName: fileName) + } + if let key = item.key { + suc = dbDeleteItem(key) } else { - break + suc = true + } + total -= item.size + if total <= maxSize || !suc { + return } - } while (total > maxSize && suc) + }) if suc { dbCheckpoint() } @@ -899,45 +814,26 @@ public class KVStorage { } public func removeItemsToFitCount(_ maxCount: Int) -> Bool { - guard maxCount != Int.max else { - return true - } - guard maxCount > 0 else { - return removeAllItems() - } + guard maxCount != Int.max else { return true } + guard maxCount > 0 else { return removeAllItems() } var total = dbGetTotalItemCount() - guard total >= 0 else { - return false - } - guard total > maxCount else { - return true - } + guard total >= 0 else { return false } + guard total > maxCount else { return true } var suc = false - let perCount = 16 - repeat { - if let items = dbGetItemSizeInfoOrderByTimeAscWithLimit(count: perCount), !items.isEmpty { - for item in items { - if total > maxCount { - if let fileName = item.fileName { - fileDelete(fileName: fileName) - } - if let key = item.key { - suc = dbDeleteItem(key) - } else { - suc = true - } - total -= 1 - } else { - break - } - if !suc { - break - } - } + dbGetItemSizeInfoOrderByTimeAscWithLimit(count: 16)?.forEach({ (item) in + if let fileName = item.fileName { + fileDelete(fileName: fileName) + } + if let key = item.key { + suc = dbDeleteItem(key) } else { - break + suc = true } - } while (total > maxCount && suc) + total -= 1 + if total <= maxCount || !suc { + return + } + }) if suc { dbCheckpoint() } @@ -951,33 +847,22 @@ public class KVStorage { end?(total < 0) } else { var left = total - let perCount = 32 var suc = false - repeat { - if let items = dbGetItemSizeInfoOrderByTimeAscWithLimit(count: perCount), !items.isEmpty { - for item in items { - if left > 0 { - if let fileName = item.fileName { - fileDelete(fileName: fileName) - } - if let key = item.key { - suc = dbDeleteItem(key) - } else { - suc = true - } - left -= 1 - } else { - break - } - if !suc { - break - } - } + dbGetItemSizeInfoOrderByTimeAscWithLimit(count: 32)?.forEach({ (item) in + if let fileName = item.fileName { + fileDelete(fileName: fileName) + } + if let key = item.key { + suc = dbDeleteItem(key) } else { - break + suc = true + } + left -= 1 + if left <= 0 || !suc { + return } progress?(total - left, total) - } while (left > 0 && suc) + }) if suc { dbCheckpoint() } @@ -986,12 +871,8 @@ public class KVStorage { } public func getItemForKey(_ key: String) -> KVStorageItem? { - guard !key.isEmpty else { - return nil - } - guard let item = dbGetItem(key: key, excludeInlineData: false) else { - return nil - } + guard !key.isEmpty else { return nil } + guard let item = dbGetItem(key: key, excludeInlineData: false) else { return nil } dbUpdateAccessTime(key) if let fileName = item.fileName { if let value = fileRead(fileName: fileName) { @@ -1005,16 +886,12 @@ public class KVStorage { } public func getItemInfoForKey(_ key: String) -> KVStorageItem? { - guard !key.isEmpty else { - return nil - } + guard !key.isEmpty else { return nil } return dbGetItem(key: key, excludeInlineData: true) } public func getItemValueForKey(_ key: String) -> Data? { - guard !key.isEmpty else { - return nil - } + guard !key.isEmpty else { return nil } var value: Data? switch type { case .file: @@ -1043,9 +920,7 @@ public class KVStorage { } public func getItemForKeys(_ keys: [String]) -> [KVStorageItem]? { - guard !keys.isEmpty else { - return nil - } + guard !keys.isEmpty else { return nil } if var items = dbGetItems(keys: keys, excludeInlineData: false), !items.isEmpty { if type == .sqlite { var index = 0 @@ -1074,16 +949,12 @@ public class KVStorage { } public func getItemInfoForKeys(_ keys: [String]) -> [KVStorageItem]? { - guard !keys.isEmpty else { - return nil - } + guard !keys.isEmpty else { return nil } return dbGetItems(keys: keys, excludeInlineData: true) } public func getItemValueForKeys(_ keys: [String]) -> [String: Any]? { - guard let items = getItemForKeys(keys) else { - return nil - } + guard let items = getItemForKeys(keys) else { return nil } var keyAndValue = [String: Any]() for item in items { if let key = item.key, let value = item.value { @@ -1094,9 +965,7 @@ public class KVStorage { } public func itemExistsForKey(_ key: String) -> Bool { - guard !key.isEmpty else { - return false - } + guard !key.isEmpty else { return false } return dbGetItemCount(key: key) > 0 } From db997629ee67950aa2c610c5945d1f4517422a4b Mon Sep 17 00:00:00 2001 From: huangzheng Date: Tue, 30 Jul 2019 22:56:50 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20CACurrentMediaTime?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/ClaretCache/KVStorage.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Sources/ClaretCache/KVStorage.swift b/Sources/ClaretCache/KVStorage.swift index 9d4d31f..c088303 100644 --- a/Sources/ClaretCache/KVStorage.swift +++ b/Sources/ClaretCache/KVStorage.swift @@ -154,6 +154,14 @@ public class KVStorage { log("reset error: \(error)") } } + + fileprivate final func currentTime() -> TimeInterval { + #if canImport(QuartzCore) + return CACurrentMediaTime() + #else + return Date().timeIntervalSince1970 + #endif + } fileprivate func log(_ items: Any..., separator: String = " ", terminator: String = "\n") { if errorLogsEnabled { @@ -231,7 +239,7 @@ public class KVStorage { guard result == SQLITE_OK else { database = nil dbStmtCache = nil - dbLastOpenErrorTime = CACurrentMediaTime() + dbLastOpenErrorTime = currentTime() dbOpenErrorCount+=1 log("\(#function) line:\(#line) sqlite open failed (\(result)).") return false @@ -272,7 +280,7 @@ public class KVStorage { fileprivate func dbCheck() -> Bool { guard database == nil else { return true } if dbOpenErrorCount < kMaxErrorRetryCount && - CACurrentMediaTime() - dbLastOpenErrorTime > kMinRetryTimeInterval { + currentTime() - dbLastOpenErrorTime > kMinRetryTimeInterval { return dbOpen() && dbInitialize() } else { return false From 5927fc8e2e4b0f978b1c7060bc4fd68e16979dc8 Mon Sep 17 00:00:00 2001 From: huangzheng Date: Tue, 30 Jul 2019 23:13:04 +0800 Subject: [PATCH 06/10] fix warning --- Sources/ClaretCache/KVStorage.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ClaretCache/KVStorage.swift b/Sources/ClaretCache/KVStorage.swift index c088303..2545984 100644 --- a/Sources/ClaretCache/KVStorage.swift +++ b/Sources/ClaretCache/KVStorage.swift @@ -148,13 +148,13 @@ public class KVStorage { try fileManger.removeItem(at: path.appendingPathComponent(kDBFileName)) try fileManger.removeItem(at: path.appendingPathComponent(kDBShmFileName)) try fileManger.removeItem(at: path.appendingPathComponent(kDBWalFileName)) - try fileMoveAllToTrash() + fileMoveAllToTrash() fileEmptyTrashInBackground() } catch { log("reset error: \(error)") } } - + fileprivate final func currentTime() -> TimeInterval { #if canImport(QuartzCore) return CACurrentMediaTime() From 5ae0a278d4d67ba5790097cd3e47f831fff3c731 Mon Sep 17 00:00:00 2001 From: Huang Zheng Date: Wed, 31 Jul 2019 09:50:19 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E4=BF=AE=E6=94=B9appExtension=E7=9A=84?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/ClaretCache/KVStorage.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Sources/ClaretCache/KVStorage.swift b/Sources/ClaretCache/KVStorage.swift index 2545984..2b21d85 100644 --- a/Sources/ClaretCache/KVStorage.swift +++ b/Sources/ClaretCache/KVStorage.swift @@ -14,13 +14,7 @@ import UIKit.UIApplication #endif var isAppExtension: Bool = { - guard Bundle.main.bundleURL.pathExtension != "appex" else { - return false - } - guard let app = NSClassFromString("UIApplication"), app.value(forKey: "shared") != nil else { - return true - } - return false + return Bundle.main.bundleURL.pathExtension == "appex" }() public enum KVStorageType { From db69d11cd4e408ce459e7e2ccec54950e02723d6 Mon Sep 17 00:00:00 2001 From: Huang Zheng Date: Wed, 31 Jul 2019 09:57:29 +0800 Subject: [PATCH 08/10] =?UTF-8?q?fix=20Import=20UIKit=20=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/ClaretCache/KVStorage.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/ClaretCache/KVStorage.swift b/Sources/ClaretCache/KVStorage.swift index 2b21d85..75dbdf0 100644 --- a/Sources/ClaretCache/KVStorage.swift +++ b/Sources/ClaretCache/KVStorage.swift @@ -6,15 +6,14 @@ // Copyright © 2019 com.ClaretCache. All rights reserved. // -import UIKit import SQLite3 -#if os(iOS) && canImport(UIKit) +#if canImport(UIKit) import UIKit.UIApplication #endif var isAppExtension: Bool = { - return Bundle.main.bundleURL.pathExtension == "appex" + return Bundle.main.bundleURL.pathExtension == "appex" }() public enum KVStorageType { From e7d31de984af7cd933d9a49ce266ed3aa0badbea Mon Sep 17 00:00:00 2001 From: Huang Zheng Date: Wed, 31 Jul 2019 10:09:09 +0800 Subject: [PATCH 09/10] import Foundation --- Sources/ClaretCache/KVStorage.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/ClaretCache/KVStorage.swift b/Sources/ClaretCache/KVStorage.swift index 75dbdf0..7345589 100644 --- a/Sources/ClaretCache/KVStorage.swift +++ b/Sources/ClaretCache/KVStorage.swift @@ -6,6 +6,7 @@ // Copyright © 2019 com.ClaretCache. All rights reserved. // +import Foundation import SQLite3 #if canImport(UIKit) From 67e2e96bead1f43f0200b839634fb2ad1247b9ca Mon Sep 17 00:00:00 2001 From: Huang Zheng Date: Wed, 31 Jul 2019 10:18:44 +0800 Subject: [PATCH 10/10] =?UTF-8?q?fix=20UIApplication=E5=AE=8F=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E9=97=AE=E9=A2=98=EF=BC=8CCACurrentTime=E6=B2=A1?= =?UTF-8?q?=E5=BC=95=E5=A4=B4=E6=96=87=E4=BB=B6=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/ClaretCache/KVStorage.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/ClaretCache/KVStorage.swift b/Sources/ClaretCache/KVStorage.swift index 7345589..0de8580 100644 --- a/Sources/ClaretCache/KVStorage.swift +++ b/Sources/ClaretCache/KVStorage.swift @@ -13,6 +13,10 @@ import SQLite3 import UIKit.UIApplication #endif +#if canImport(QuartzCore) +import QuartzCore.CABase +#endif + var isAppExtension: Bool = { return Bundle.main.bundleURL.pathExtension == "appex" }() @@ -120,16 +124,18 @@ public class KVStorage { fileEmptyTrashInBackground() } + #if canImport(UIKit) func sharedExtensionApplication() -> UIApplication? { return isAppExtension ? nil : UIApplication.shared } + #endif deinit { - #if os(iOS) && canImport(UIKit) + #if canImport(UIKit) let taskID = sharedExtensionApplication()?.beginBackgroundTask(expirationHandler: nil) #endif dbClose() - #if os(iOS) && canImport(UIKit) + #if canImport(UIKit) if let task = taskID { sharedExtensionApplication()?.endBackgroundTask(task) }