+38
-32
README.md
+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
+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
}