-
Notifications
You must be signed in to change notification settings - Fork 2.9k
/
DiskImageStore.swift
108 lines (92 loc) · 3.79 KB
/
DiskImageStore.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/
import Shared
import UIKit
import Common
enum DiskImageStoreErrorCase: Error {
case notFound(description: String)
case invalidImageData(description: String)
case cannotWrite(description: String)
}
public protocol DiskImageStore {
/// Gets an image for the given key if it is in the store.
func getImageForKey(_ key: String) async throws -> UIImage
/// Adds an image for the given key.
func saveImageForKey(_ key: String, image: UIImage) async throws
/// Clears all images from the cache, excluding the given set of keys.
func clearAllScreenshotsExcluding(_ keys: Set<String>) async throws
/// Remove image with provided key
func deleteImageForKey(_ key: String) async
}
/// Disk-backed key-value image store.
public actor DefaultDiskImageStore: DiskImageStore {
private let files: FileAccessor
private let filesDir: String
private let quality: CGFloat
private var keys: Set<String>
private var logger: Logger
public init(files: FileAccessor,
namespace: String,
quality: Float,
logger: Logger = DefaultLogger.shared) {
self.files = files
self.quality = CGFloat(quality)
self.logger = logger
do {
self.filesDir = try files.getAndEnsureDirectory(namespace)
} catch {
logger.log("Could not create directory at root path: \(error)",
level: .fatal,
category: .storage)
fatalError("Could not create directory at root path: \(error)")
}
// Build an in-memory set of keys from the existing images on disk.
var keys = [String]()
if let fileEnumerator = FileManager.default.enumerator(atPath: filesDir) {
for file in fileEnumerator {
keys.append(file as! String)
}
}
self.keys = Set(keys)
}
public func getImageForKey(_ key: String) async throws -> UIImage {
if !keys.contains(key) {
throw DiskImageStoreErrorCase.notFound(description: "Image key not found")
}
let imagePath = URL(fileURLWithPath: filesDir).appendingPathComponent(key)
let data = try Data(contentsOf: imagePath)
if let image = UIImage(data: data) {
return image
} else {
throw DiskImageStoreErrorCase.invalidImageData(description: "Invalid image data")
}
}
public func saveImageForKey(_ key: String, image: UIImage) async throws {
let imageURL = URL(fileURLWithPath: filesDir).appendingPathComponent(key)
if let data = image.jpegData(compressionQuality: quality) {
try data.write(to: imageURL, options: .noFileProtection)
keys.insert(key)
} else {
throw DiskImageStoreErrorCase.cannotWrite(description: "Could not write image to file")
}
}
public func clearAllScreenshotsExcluding(_ keys: Set<String>) async throws {
let keysToDelete = self.keys.subtracting(keys)
for key in keysToDelete {
let url = URL(fileURLWithPath: filesDir).appendingPathComponent(key)
try FileManager.default.removeItem(at: url)
}
self.keys = keys
}
public func deleteImageForKey(_ key: String) async {
let url = URL(fileURLWithPath: filesDir).appendingPathComponent(key)
do {
try FileManager.default.removeItem(at: url)
} catch {
logger.log("Failed to remove DiskImageStore item at \(url.absoluteString): \(error)",
level: .debug,
category: .storage)
}
}
}