-
Notifications
You must be signed in to change notification settings - Fork 38
Expand file tree
/
Copy pathSignalCache.swift
More file actions
104 lines (88 loc) · 3.75 KB
/
SignalCache.swift
File metadata and controls
104 lines (88 loc) · 3.75 KB
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
import Foundation
/// A local cache for signals to be sent to the TelemetryDeck ingestion service
///
/// There is no guarantee that Signals come out in the same order you put them in. This shouldn't matter though,
/// since all Signals automatically get a `receivedAt` property with a date, allowing the server to reorder them
/// correctly.
///
/// Currently the cache is only in-memory. This will probably change in the near future.
internal class SignalCache<T> where T: Codable {
internal var logHandler: LogHandler?
private var cachedSignals: [T] = []
private let maximumNumberOfSignalsToPopAtOnce = 100
let queue = DispatchQueue(label: "telemetrydeck-signal-cache", attributes: .concurrent)
/// How many Signals are cached
func count() -> Int {
queue.sync(flags: .barrier) {
self.cachedSignals.count
}
}
/// Insert a Signal into the cache
func push(_ signal: T) {
queue.sync(flags: .barrier) {
self.cachedSignals.append(signal)
}
}
/// Insert a number of Signals into the cache
func push(_ signals: [T]) {
queue.sync(flags: .barrier) {
self.cachedSignals.append(contentsOf: signals)
}
}
/// Remove a number of Signals from the cache and return them
///
/// You should hold on to the signals returned by this function. If the action you are trying to do with them fails
/// (e.g. sending them to a server) you should reinsert them into the cache with the `push` function.
func pop() -> [T] {
var poppedSignals: [T]!
queue.sync {
let sliceSize = min(maximumNumberOfSignalsToPopAtOnce, cachedSignals.count)
poppedSignals = Array(cachedSignals[..<sliceSize])
cachedSignals.removeFirst(sliceSize)
}
return poppedSignals
}
private func fileURL() -> URL {
let cacheFolderURL = try! FileManager.default.url(
for: .cachesDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false
)
return cacheFolderURL.appendingPathComponent("telemetrysignalcache")
}
/// Save the entire signal cache to disk asynchronously
func backupCache() {
queue.async { [self] in
if let data = try? JSONEncoder().encode(self.cachedSignals) {
do {
try data.write(to: fileURL())
logHandler?.log(message: "Saved Telemetry cache \(data) of \(self.cachedSignals.count) signals")
// After saving the cache, we need to clear our local cache otherwise
// it could get merged with the cache read back from disk later if
// it's still in memory
self.cachedSignals = []
} catch {
logHandler?.log(.error, message: "Error saving Telemetry cache")
}
}
}
}
/// Loads any previous signal cache from disk asynchronously
init(logHandler: LogHandler?) {
self.logHandler = logHandler
queue.async { [weak self] in
guard let self else { return }
self.logHandler?.log(message: "Loading Telemetry cache from: \(self.fileURL())")
if let data = try? Data(contentsOf: self.fileURL()) {
// Loaded cache file, now delete it to stop it being loaded multiple times
try? FileManager.default.removeItem(at: self.fileURL())
// Decode the data into a new cache
if let signals = try? JSONDecoder().decode([T].self, from: data) {
logHandler?.log(message: "Loaded \(signals.count) signals")
self.cachedSignals = signals
}
}
}
}
}