CutBox.app

Coverage Report

Created: 2024-03-12 03:40

.../Source/App/Services/History/HistoryRepo.swift
Line
Count
Source (jump to first uncovered line)
1
//
2
//  HistoryRepo.swift
3
//  CutBox
4
//
5
//  Created by Jason Milkins on 29/4/18.
6
//  Copyright © 2018-2023 ocodo. All rights reserved.
7
//
8
9
import Foundation
10
11
class HistoryRepo {
12
13
    /// Clip items store (cached from/to defaults by history service)
14
103
    private var store: [[String: String]] = []
15
16
    /// dict keys of clip items
17
103
    private var historyStoreKey = Constants.kHistoryStoreKey
18
103
    private var stringKey = Constants.kStringKey
19
103
    private var favoriteKey = Constants.kFavoriteKey
20
103
    private var timestampKey = Constants.kTimestampKey
21
22
    /// Optional time filter
23
    var timeFilter: Double?
24
25
    /// preferences defaults key for protect favorites flag
26
    private var kProtectFavorites = "protectFavorites"
27
28
    /// Standalone predicate, false for item older than an earliest timestamp
29
12
    func timeFilterPredicate(item: [String: String], earliest: String) -> Bool {
30
12
        if let timestamp = item[self.timestampKey] {
31
12
            return earliest < timestamp
32
12
        } else {
33
0
            return false
34
0
        }
35
0
    }
36
37
516
    var items: [String] {
38
516
        if let seconds = self.timeFilter {
39
4
            let latest = secondsBeforeTimeNowAsISO8601(seconds: seconds)
40
4
            return self.dict
41
10
                .filter { timeFilterPredicate(item: $0, earliest: latest) }
42
4
                .map { $0[self.stringKey]! }
43
512
        }
44
512
45
512
        return self.dict
46
3.71k
          .map { $0[self.stringKey]! }
47
516
    }
48
49
342
    var favorites: [String] {
50
342
        return self.dict
51
2.86k
            .filter { $0[favoriteKey] == self.favoriteKey }
52
342
            .map { $0[self.stringKey]! }
53
342
    }
54
55
3
    var favoritesDict: [[String: String]] {
56
3
        return self.dict
57
40
            .filter { $0[favoriteKey] == self.favoriteKey }
58
3
    }
59
60
1.22k
    var dict: [[String: String]] {
61
1.22k
        return self.store
62
1.22k
    }
63
64
    private var defaults: UserDefaults
65
    private var prefs: CutBoxPreferencesService
66
67
    init(defaults: UserDefaults = UserDefaults.standard,
68
103
         prefs: CutBoxPreferencesService = CutBoxPreferencesService.shared) {
69
103
        self.defaults = defaults
70
103
        self.prefs = prefs
71
103
    }
72
73
4
    func clear() {
74
4
        self.store.removeAll(protectFavorites: prefs.protectFavorites)
75
4
        self.saveToDefaults()
76
4
    }
77
78
50
    func index(of string: String) -> Int? {
79
50
        return items.firstIndex(of: string)
80
50
    }
81
82
1.45k
    func insert(_ newElement: String, at index: Int = 0, isFavorite: Bool = false, date: Date? = Date()) {
83
1.45k
        var item = [stringKey: newElement]
84
1.45k
85
1.45k
        if let unwrappedDate = date {
86
1.44k
            let timestamp = iso8601Timestamp(fromDate: unwrappedDate)
87
1.44k
            item[self.timestampKey] = timestamp
88
1.45k
        }
89
1.45k
90
1.45k
        if isFavorite {
91
6
            item[self.favoriteKey] = self.favoriteKey
92
1.45k
        }
93
1.45k
94
1.45k
        self.store.insert(item, at: index)
95
1.45k
    }
96
97
4
    func remove(at: Int) {
98
4
        if at >= 0 && self.store.count > at {
99
4
            self.store.remove(at: at)
100
4
            self.saveToDefaults()
101
4
        }
102
4
    }
103
104
3
    func removeAtIndexes(indexes: IndexSet) {
105
3
        self.store.removeAtIndexes(indexes: indexes)
106
3
        self.saveToDefaults()
107
3
    }
108
109
335
    func findIndexSetOf(string: String) -> IndexSet {
110
335
        var indexes = IndexSet()
111
2.84k
        for item in dict where item[stringKey] == string {
112
2.84k
            if let index = dict.firstIndex(of: item) {
113
1
                indexes.insert(index)
114
2.84k
            }
115
2.84k
        }
116
335
        return indexes
117
335
    }
118
119
5
    func toggleFavorite(indexes: IndexSet) {
120
11
        for i in indexes {
121
11
            toggleFavorite(at: i)
122
11
        }
123
5
        self.saveToDefaults()
124
5
    }
125
126
14
    func toggleFavorite(at i: Int) {
127
14
        if self.store[i].keys.contains(favoriteKey) {
128
6
            self.store[i].removeValue(forKey: favoriteKey)
129
14
        } else {
130
8
            self.store[i][favoriteKey] = favoriteKey
131
14
        }
132
14
    }
133
134
4
    func removeSubrange(_ bounds: Range<Int>) {
135
4
        self.store.removeSubrange(bounds)
136
4
        self.saveToDefaults()
137
4
    }
138
139
13
    func migrateLegacyPasteStore(_ newElements: [String], at: Int = 0) {
140
50
        newElements.reversed().forEach {
141
50
            if index(of: $0) == nil {
142
36
                self.insert($0)
143
50
            }
144
50
        }
145
13
    }
146
147
    /// Used to read user defaults to in memory history store
148
93
    func loadFromDefaults() {
149
93
        let key = self.historyStoreKey
150
93
        if let historyStore = self.defaults.array(forKey: key) as? [[String: String]] {
151
55
            self.store = historyStore
152
93
        } else {
153
38
            self.store = []
154
93
        }
155
93
    }
156
157
    /// Used to persist in memory history store to user defaults
158
372
    func saveToDefaults() {
159
372
        self.defaults.set(self.store, forKey: self.historyStoreKey)
160
372
    }
161
162
    /// removes the historyStore completely
163
4
    func clearHistory() {
164
4
        self.clear()
165
4
    }
166
167
    /// Clear the history using a timestampPredicate
168
    ///
169
    /// Favorites are protected when `prefs.protectFavorites` is enabled
170
    ///
171
    /// Items without a timestamp will be ignored.
172
    ///
173
    /// see also: `historyOffsetPredicateFactory(offset: TimeInterval?)`
174
5
    func clearHistory(timestampPredicate: (String) -> Bool) {
175
13
        let keep = self.store.filter {
176
13
            if $0[favoriteKey] != nil {
177
3
                if prefs.protectFavorites {
178
2
                    return true
179
2
                }
180
11
            }
181
11
182
11
            if let timestamp: String = $0[timestampKey] {
183
9
                return !timestampPredicate(timestamp)
184
9
            }
185
2
186
2
            return true
187
11
        }
188
5
189
5
        self.store = keep
190
5
        saveToDefaults()
191
5
    }
192
193
    /// Size of history repo in bytes
194
51
    func bytes() throws -> Int {
195
51
        let data = try PropertyListSerialization.data(
196
51
            fromPropertyList: store,
197
51
            format: .binary,
198
51
            options: .bitWidth)
199
51
200
51
        return data.count
201
51
    }
202
203
    /// Bytes formatter
204
51
    func bytesFormatted() -> String {
205
51
        do {
206
51
            let i: Int = try bytes()
207
51
            let byteFormatter = ByteCountFormatter()
208
51
            byteFormatter.allowedUnits = [.useGB, .useMB, .useKB]
209
51
            return byteFormatter.string(fromByteCount: Int64(i))
210
51
        } catch {
211
0
            return ""
212
0
        }
213
0
    }
214
}