+38
-32
README.md
+38
-32
README.md
···
1
# StorageKit
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).
5
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.
7
8
## Install StorageKit
9
10
-
StorageKit currently only supports installation via Swift Package Manager.
11
-
12
-
### Add Package Dependency
13
-
In Xcode, select `File` > `Add Packages...`.
14
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.
25
26
## Getting started
27
-
First import StorageKit
28
29
```swift
30
import StorageKit
31
-
```
32
-
33
-
For any `Codable` object save and retrieve from disk.
34
35
-
```swift
36
struct User: Codable {
37
let name: String
38
let id: Int
39
}
40
```
41
42
-
### Store
43
```swift
44
do {
45
let user = User(name: "Thomas", id: 123)
46
try Storage.store(user, to: .documents, as: "user.json")
47
} catch {
48
-
// TODO: handle error
49
}
50
```
51
52
-
### retrieve
53
```swift
54
let user = Storage.retrieve("user.json", from: .documents, as: User.self)
55
```
56
57
-
### delete
58
```swift
59
-
do {
60
-
try Storage.remove("user.json", from: .documents)
61
-
} catch {
62
-
// TODO: handle error
63
-
}
64
```
65
66
-
### does a file exist?
67
```swift
68
let exists = Storage.fileExists("user.json", in: .documents)
69
```
···
1
# StorageKit
2
3
+
# StorageKit
4
5
+
Sometimes Core Data or SQLite are overkill for your app. StorageKit saves `Codable` objects or raw `Data` to disk in the app sandbox.
6
7
## Install StorageKit
8
9
+
StorageKit currently only supports installation via Swift Package Manager.
10
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.
14
15
## Getting started
16
17
```swift
18
import StorageKit
19
20
struct User: Codable {
21
let name: String
22
let id: Int
23
}
24
```
25
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
29
```swift
30
do {
31
let user = User(name: "Thomas", id: 123)
32
try Storage.store(user, to: .documents, as: "user.json")
33
} catch {
34
+
// handle StorageError.createURL, StorageError.removeObject, or file write errors
35
}
36
```
37
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.
57
```swift
58
let user = Storage.retrieve("user.json", from: .documents, as: User.self)
59
```
60
61
+
### Delete a file
62
```swift
63
+
try Storage.remove("user.json", from: .documents)
64
```
65
66
+
### Check existence
67
```swift
68
let exists = Storage.fileExists("user.json", in: .documents)
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
public enum StorageError: Error {
4
case createURL
5
case removeObject
6
-
case clearDirectory
7
-
case createURLError
8
}
9
10
public struct Storage {
···
18
}
19
20
/// Returns URL constructed from specified directory
21
-
private static func getURL(for directory: Directory, fileManager: FileManager) throws -> URL {
22
let searchPathDirectory: FileManager.SearchPathDirectory = switch directory {
23
case .documents: .documentDirectory
24
case .caches: .cachesDirectory
25
}
26
27
-
guard let url = fileManager.urls(for: searchPathDirectory, in: .userDomainMask).first else { throw StorageError.createURLError }
28
return url
29
}
30
···
34
/// - directory: `Directory` to look in
35
/// - fileManager: An optional FileManager` argument. Defaults to `FileManager.default`
36
/// - Returns: an optional `URL`
37
-
public static func url(for fileName: String, in directory: Directory, fileManager: FileManager = .default) -> URL? {
38
try? getURL(for: directory, fileManager: fileManager).appendingPathComponent(fileName, isDirectory: false)
39
}
40
41
/// Store an encodable struct to the specified directory on disk
42
///
43
/// - Parameters:
44
-
/// - data: the data to store
45
/// - directory: where to store the struct
46
/// - fileName: what to name the file where the struct data will be stored
47
/// - 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)
52
}
53
54
/// 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 }
58
59
do {
60
try fileManager.removeItem(at: url)
···
64
}
65
66
/// - 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 {
68
guard let url = try? getURL(for: directory, fileManager: fileManager).appendingPathComponent(fileName, isDirectory: false) else { return false }
69
return fileManager.fileExists(atPath: url.path)
70
}
71
72
/// 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 }
75
let contents = try fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [])
76
77
for fileUrl in contents {
78
-
try fileManager.removeItem(at: fileUrl)
79
}
80
}
81
}
···
3
public enum StorageError: Error {
4
case createURL
5
case removeObject
6
+
case clearDirectory(failedURLs: [URL])
7
}
8
9
public struct Storage {
···
17
}
18
19
/// Returns URL constructed from specified directory
20
+
private static func getURL(for directory: Directory,
21
+
fileManager: FileManager) throws -> URL {
22
let searchPathDirectory: FileManager.SearchPathDirectory = switch directory {
23
case .documents: .documentDirectory
24
case .caches: .cachesDirectory
25
}
26
27
+
guard let url = fileManager.urls(for: searchPathDirectory, in: .userDomainMask).first else { throw StorageError.createURL }
28
return url
29
}
30
···
34
/// - directory: `Directory` to look in
35
/// - fileManager: An optional FileManager` argument. Defaults to `FileManager.default`
36
/// - Returns: an optional `URL`
37
+
public static func url(for fileName: String,
38
+
in directory: Directory,
39
+
fileManager: FileManager = .default) -> URL? {
40
try? getURL(for: directory, fileManager: fileManager).appendingPathComponent(fileName, isDirectory: false)
41
}
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
+
64
/// Store an encodable struct to the specified directory on disk
65
///
66
/// - Parameters:
67
+
/// - object: the encodable struct to store
68
/// - directory: where to store the struct
69
/// - fileName: what to name the file where the struct data will be stored
70
/// - fileManager: An optional FileManager` argument. Defaults to `FileManager.default`
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)
99
}
100
101
/// Remove specified file from specified directory
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 }
107
108
do {
109
try fileManager.removeItem(at: url)
···
113
}
114
115
/// - Returns: `Bool` indicating whether file exists at specified directory with specified file name
116
+
public static func fileExists(_ fileName: String,
117
+
in directory: Directory,
118
+
fileManager: FileManager = .default) -> Bool {
119
guard let url = try? getURL(for: directory, fileManager: fileManager).appendingPathComponent(fileName, isDirectory: false) else { return false }
120
return fileManager.fileExists(atPath: url.path)
121
}
122
123
/// Remove all files at specified directory
124
+
public static func clear(_ directory: Directory,
125
+
fileManager: FileManager = .default) throws {
126
+
let url = try getURL(for: directory, fileManager: fileManager)
127
let contents = try fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [])
128
+
var failedURLs: [URL] = []
129
130
for fileUrl in contents {
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)
142
}
143
}
144
}