-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathAppState.swift
More file actions
126 lines (111 loc) · 4.34 KB
/
AppState.swift
File metadata and controls
126 lines (111 loc) · 4.34 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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import CryptoKit
import Foundation
import JSONRPC
import Logger
import Status
public extension JSONValue {
subscript(key: String) -> JSONValue? {
if case .hash(let dict) = self {
return dict[key]
}
return nil
}
var stringValue: String? {
if case .string(let value) = self {
return value
}
return nil
}
static func convertToJSONValue<T: Codable>(_ object: T) -> JSONValue? {
do {
let data = try JSONEncoder().encode(object)
let jsonValue = try JSONDecoder().decode(JSONValue.self, from: data)
return jsonValue
} catch {
Logger.client.info("Error converting to JSONValue: \(error)")
return nil
}
}
}
public class AppState {
public static let shared = AppState()
private var cache: [String: [String: JSONValue]] = [:]
private let cacheFileName = "appstate.json"
private let queue = DispatchQueue(label: "com.github.AppStateCacheQueue")
private var loadStatus: [String: Bool] = [:]
private init() {
cache[""] = [:] // initialize a default cache if no user exists
initCacheForUserIfNeeded()
}
func toHash(contents: String, _ length: Int = 16) -> String {
let data = Data(contents.utf8)
let hashData = SHA256.hash(data: data)
let hashValue = hashData.compactMap { String(format: "%02x", $0 ) }.joined()
let index = hashValue.index(hashValue.startIndex, offsetBy: length)
return String(hashValue[..<index])
}
public func update<T: Codable>(key: String, value: T) {
queue.async {
let userName = Status.currentUser() ?? ""
self.initCacheForUserIfNeeded(userName)
self.cache[userName]![key] = JSONValue.convertToJSONValue(value)
self.saveCacheForUser(userName)
}
}
public func get(key: String) -> JSONValue? {
return queue.sync {
let userName = Status.currentUser() ?? ""
initCacheForUserIfNeeded(userName)
return (self.cache[userName] ?? [:])[key]
}
}
private func configFilePath(userName: String) -> URL {
let baseURL: URL = getXdgConfigHome()
.appendingPathComponent("github-copilot/xcode")
.appendingPathComponent(toHash(contents: userName))
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: baseURL.path) {
do {
try fileManager.createDirectory(at: baseURL, withIntermediateDirectories: true, attributes: nil)
} catch {
Logger.client.info("Failed to create directory: \(error)")
}
}
return baseURL.appendingPathComponent(cacheFileName)
}
private func getXdgConfigHome() -> URL {
if let xdgConfigHome = ProcessInfo.processInfo.environment["XDG_CONFIG_HOME"],
xdgConfigHome.hasPrefix("/") {
return URL(fileURLWithPath: xdgConfigHome)
}
return FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".config")
}
private func saveCacheForUser(_ userName: String? = nil) {
if let user = userName ?? Status.currentUser(), !user.isEmpty { // save cache for non-empty user
let cacheFilePath = configFilePath(userName: user)
do {
let data = try JSONEncoder().encode(self.cache[user] ?? [:])
try data.write(to: cacheFilePath)
} catch {
Logger.client.info("Failed to save AppState cache: \(error)")
}
}
}
private func initCacheForUserIfNeeded(_ userName: String? = nil) {
if let user = userName ?? Status.currentUser(), !user.isEmpty,
loadStatus[user] != true { // load cache for non-empty user
self.loadStatus[user] = true
self.cache[user] = [:]
let cacheFilePath = configFilePath(userName: user)
guard FileManager.default.fileExists(atPath: cacheFilePath.path) else {
return
}
do {
let data = try Data(contentsOf: cacheFilePath)
self.cache[user] = try JSONDecoder().decode([String: JSONValue].self, from: data)
} catch {
Logger.client.info("Failed to load AppState cache: \(error)")
}
}
}
}