this repo has no description

re-add old logic and update readme

Changed files
+118 -49
Sources
StorageKit
+38 -32
README.md
··· 1 1 # StorageKit 2 2 3 - ### WARNING 4 - This package is subject to change a lot. If you want to use this it probably makes sense to copy and paste the code (it is only 1 file with 82 lines of code). 3 + # StorageKit 5 4 6 - Sometimes Core Data or SQLite are overkill for your app. In those situations, StorageKit is a great solution to save `Codable` objects to disk. 5 + Sometimes Core Data or SQLite are overkill for your app. StorageKit saves `Codable` objects or raw `Data` to disk in the app sandbox. 7 6 8 7 ## Install StorageKit 9 8 10 - StorageKit currently only supports installation via Swift Package Manager. 11 - 12 - ### Add Package Dependency 13 - In Xcode, select `File` > `Add Packages...`. 9 + StorageKit currently only supports installation via Swift Package Manager. 14 10 15 - ### Specify the Repository 16 - Copy and paste the following into the search/input box. 17 - `https://github.com/SparrowTek/StorageKit.git` 18 - 19 - ### Specify Options 20 - In the options for the StorageKit package, we recommend setting the Dependency Rule to Up to Next Major Version, and enter the 21 - current StorageKit version. Then, click `Add Package`. 22 - 23 - ### Select the Package Products 24 - Select `StorageKit`, then click Add Package. 11 + - In Xcode, select `File` > `Add Packages...`. 12 + - Enter `https://github.com/SparrowTek/StorageKit.git`. 13 + - Set the dependency rule (e.g. Up to Next Major Version) and add the `StorageKit` product. 25 14 26 15 ## Getting started 27 - First import StorageKit 28 16 29 17 ```swift 30 18 import StorageKit 31 - ``` 32 - 33 - For any `Codable` object save and retrieve from disk. 34 19 35 - ```swift 36 20 struct User: Codable { 37 21 let name: String 38 22 let id: Int 39 23 } 40 24 ``` 41 25 42 - ### Store 26 + `Storage.Directory` supports `.documents` and `.caches`. All `store` functions overwrite any existing file with the same name. You can customize file protection via the optional `writingOptions` parameter (defaults to `.completeFileProtection`). 27 + 28 + ### Store a Codable value 43 29 ```swift 44 30 do { 45 31 let user = User(name: "Thomas", id: 123) 46 32 try Storage.store(user, to: .documents, as: "user.json") 47 33 } catch { 48 - // TODO: handle error 34 + // handle StorageError.createURL, StorageError.removeObject, or file write errors 49 35 } 50 36 ``` 51 37 52 - ### retrieve 38 + Pass a custom `JSONEncoder` or `writingOptions` if needed: 39 + ```swift 40 + let encoder = JSONEncoder() 41 + encoder.dateEncodingStrategy = .iso8601 42 + try Storage.store(user, 43 + to: .documents, 44 + as: "user.json", 45 + encoder: encoder, 46 + writingOptions: .noFileProtection) 47 + ``` 48 + 49 + ### Store raw Data 50 + ```swift 51 + let data = Data([0x01, 0x02]) 52 + try Storage.store(data, to: .caches, as: "bytes.bin", writingOptions: .completeFileProtectionUnlessOpen) 53 + ``` 54 + 55 + ### Retrieve a Codable value 56 + `retrieve` returns an optional decoded value. Supply a `JSONDecoder` if you need custom settings. 53 57 ```swift 54 58 let user = Storage.retrieve("user.json", from: .documents, as: User.self) 55 59 ``` 56 60 57 - ### delete 61 + ### Delete a file 58 62 ```swift 59 - do { 60 - try Storage.remove("user.json", from: .documents) 61 - } catch { 62 - // TODO: handle error 63 - } 63 + try Storage.remove("user.json", from: .documents) 64 64 ``` 65 65 66 - ### does a file exist? 66 + ### Check existence 67 67 ```swift 68 68 let exists = Storage.fileExists("user.json", in: .documents) 69 69 ``` 70 + 71 + ### Clear a directory 72 + Removes every file in the chosen directory. Throws `StorageError.clearDirectory(failedURLs:)` if any removals fail. 73 + ```swift 74 + try Storage.clear(.caches) 75 + ```
+80 -17
Sources/StorageKit/StorageKit.swift
··· 3 3 public enum StorageError: Error { 4 4 case createURL 5 5 case removeObject 6 - case clearDirectory 7 - case createURLError 6 + case clearDirectory(failedURLs: [URL]) 8 7 } 9 8 10 9 public struct Storage { ··· 18 17 } 19 18 20 19 /// Returns URL constructed from specified directory 21 - private static func getURL(for directory: Directory, fileManager: FileManager) throws -> URL { 20 + private static func getURL(for directory: Directory, 21 + fileManager: FileManager) throws -> URL { 22 22 let searchPathDirectory: FileManager.SearchPathDirectory = switch directory { 23 23 case .documents: .documentDirectory 24 24 case .caches: .cachesDirectory 25 25 } 26 26 27 - guard let url = fileManager.urls(for: searchPathDirectory, in: .userDomainMask).first else { throw StorageError.createURLError } 27 + guard let url = fileManager.urls(for: searchPathDirectory, in: .userDomainMask).first else { throw StorageError.createURL } 28 28 return url 29 29 } 30 30 ··· 34 34 /// - directory: `Directory` to look in 35 35 /// - fileManager: An optional FileManager` argument. Defaults to `FileManager.default` 36 36 /// - Returns: an optional `URL` 37 - public static func url(for fileName: String, in directory: Directory, fileManager: FileManager = .default) -> URL? { 37 + public static func url(for fileName: String, 38 + in directory: Directory, 39 + fileManager: FileManager = .default) -> URL? { 38 40 try? getURL(for: directory, fileManager: fileManager).appendingPathComponent(fileName, isDirectory: false) 39 41 } 40 42 43 + /// Store Data to the specified directory on disk 44 + /// 45 + /// - Paramete\rs: 46 + /// - data: the data to store 47 + /// - directory: where to store the struct 48 + /// - fileName: what to name the file where the struct data will be stored 49 + /// - fileManager: An optional FileManager` argument. Defaults to `FileManager.default` 50 + public static func store(_ data: Data, 51 + to directory: Directory, 52 + as fileName: String, 53 + fileManager: FileManager = .default, 54 + writingOptions: Data.WritingOptions = .completeFileProtection) throws { 55 + guard let url = try? getURL(for: directory, fileManager: fileManager).appendingPathComponent(fileName, isDirectory: false) else { throw StorageError.createURL } 56 + 57 + if fileExists(fileName, in: directory) { 58 + try remove(fileName, from: directory) 59 + } 60 + 61 + try data.write(to: url, options: writingOptions) 62 + } 63 + 41 64 /// Store an encodable struct to the specified directory on disk 42 65 /// 43 66 /// - Parameters: 44 - /// - data: the data to store 67 + /// - object: the encodable struct to store 45 68 /// - directory: where to store the struct 46 69 /// - fileName: what to name the file where the struct data will be stored 47 70 /// - fileManager: An optional FileManager` argument. Defaults to `FileManager.default` 48 - public static func store(_ data: Data, to directory: Directory, as fileName: String, fileManager: FileManager = .default) throws { 49 - guard !fileExists(fileName, in: directory) else { return } 50 - guard let url = try? getURL(for: directory, fileManager: fileManager).appendingPathComponent(fileName, isDirectory: false) else { throw StorageError.createURLError } 51 - try data.write(to: url, options: .completeFileProtection) 71 + public static func store<T: Encodable>(_ object: T, 72 + to directory: Directory, 73 + as fileName: String, 74 + encoder: JSONEncoder = JSONEncoder(), 75 + fileManager: FileManager = .default, 76 + writingOptions: Data.WritingOptions = .completeFileProtection) throws { 77 + guard let url = try? getURL(for: directory, fileManager: fileManager).appendingPathComponent(fileName, isDirectory: false) else { throw StorageError.createURL } 78 + let data = try encoder.encode(object) 79 + try store(data, to: directory, as: fileName, fileManager: fileManager, writingOptions: writingOptions) 80 + } 81 + 82 + /// Retrieve and convert a struct from a file on disk 83 + /// 84 + /// - Parameters: 85 + /// - fileName: name of the file where struct data is stored 86 + /// - directory: directory where struct data is stored 87 + /// - type: struct type (i.e. Message.self) 88 + /// - Returns: decoded struct model(s) of data 89 + public static func retrieve<T: Decodable>(_ fileName: String, 90 + from directory: Directory, 91 + as type: T.Type, 92 + decoder: JSONDecoder = JSONDecoder(), 93 + fileManager: FileManager = .default) -> T? { 94 + guard let url = try? getURL(for: directory, fileManager: fileManager).appendingPathComponent(fileName, isDirectory: false) else { return nil } 95 + guard fileManager.fileExists(atPath: url.path) else { return nil } 96 + guard let data = fileManager.contents(atPath: url.path) else { return nil } 97 + 98 + return try? decoder.decode(type, from: data) 52 99 } 53 100 54 101 /// Remove specified file from specified directory 55 - public static func remove(_ fileName: String, from directory: Directory, fileManager: FileManager = .default) throws { 56 - guard !fileExists(fileName, in: directory) else { return } 57 - guard let url = try? getURL(for: directory, fileManager: fileManager).appendingPathComponent(fileName, isDirectory: false) else { throw StorageError.createURLError } 102 + public static func remove(_ fileName: String, 103 + from directory: Directory, 104 + fileManager: FileManager = .default) throws { 105 + guard fileExists(fileName, in: directory) else { return } 106 + guard let url = try? getURL(for: directory, fileManager: fileManager).appendingPathComponent(fileName, isDirectory: false) else { throw StorageError.createURL } 58 107 59 108 do { 60 109 try fileManager.removeItem(at: url) ··· 64 113 } 65 114 66 115 /// - Returns: `Bool` indicating whether file exists at specified directory with specified file name 67 - public static func fileExists(_ fileName: String, in directory: Directory, fileManager: FileManager = .default) -> Bool { 116 + public static func fileExists(_ fileName: String, 117 + in directory: Directory, 118 + fileManager: FileManager = .default) -> Bool { 68 119 guard let url = try? getURL(for: directory, fileManager: fileManager).appendingPathComponent(fileName, isDirectory: false) else { return false } 69 120 return fileManager.fileExists(atPath: url.path) 70 121 } 71 122 72 123 /// Remove all files at specified directory 73 - public static func clear(_ directory: Directory, fileManager: FileManager = .default) throws { 74 - guard let url = try? getURL(for: directory, fileManager: fileManager) else { return } 124 + public static func clear(_ directory: Directory, 125 + fileManager: FileManager = .default) throws { 126 + let url = try getURL(for: directory, fileManager: fileManager) 75 127 let contents = try fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: []) 128 + var failedURLs: [URL] = [] 76 129 77 130 for fileUrl in contents { 78 - try fileManager.removeItem(at: fileUrl) 131 + do { 132 + try fileManager.removeItem(at: fileUrl) 133 + } catch { 134 + failedURLs.append(fileUrl) 135 + continue 136 + } 137 + } 138 + 139 + 140 + if !failedURLs.isEmpty { 141 + throw StorageError.clearDirectory(failedURLs: failedURLs) 79 142 } 80 143 } 81 144 }