forked from github/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGeneral.swift
More file actions
147 lines (135 loc) · 6.17 KB
/
General.swift
File metadata and controls
147 lines (135 loc) · 6.17 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import Client
import ComposableArchitecture
import Foundation
import LaunchAgentManager
import Status
import SwiftUI
import XPCShared
import Logger
@Reducer
public struct General {
@ObservableState
public struct State: Equatable {
var xpcServiceVersion: String?
var xpcCLSVersion: String?
var isAccessibilityPermissionGranted: ObservedAXStatus = .unknown
var isExtensionPermissionGranted: ExtensionPermissionStatus = .unknown
var xpcServiceAuthStatus: AuthStatus = .init(status: .unknown)
var isReloading = false
}
public enum Action: Equatable {
case appear
case setupLaunchAgentIfNeeded
case openExtensionManager
case reloadStatus
case finishReloading(
xpcServiceVersion: String,
xpcCLSVersion: String?,
axStatus: ObservedAXStatus,
extensionStatus: ExtensionPermissionStatus,
authStatus: AuthStatus
)
case failedReloading
case retryReloading
}
@Dependency(\.toast) var toast
struct ReloadStatusCancellableId: Hashable {}
public var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .appear:
return .run { send in
await send(.setupLaunchAgentIfNeeded)
for await _ in DistributedNotificationCenter.default().notifications(named: .serviceStatusDidChange) {
await send(.reloadStatus)
}
}
case .setupLaunchAgentIfNeeded:
return .run { send in
#if DEBUG
// do not auto install on debug build
await send(.reloadStatus)
#else
Task {
do {
try await LaunchAgentManager()
.setupLaunchAgentForTheFirstTimeIfNeeded()
} catch {
Logger.ui.error("Failed to setup launch agent. \(error.localizedDescription)")
toast("Operation failed: permission denied. This may be due to missing background permissions.", .error)
}
await send(.reloadStatus)
}
#endif
}
case .openExtensionManager:
return .run { send in
let service = try getService()
do {
_ = try await service
.send(requestBody: ExtensionServiceRequests.OpenExtensionManager())
} catch {
Logger.ui.error("Failed to open extension manager. \(error.localizedDescription)")
toast(error.localizedDescription, .error)
await send(.failedReloading)
}
}
case .reloadStatus:
guard !state.isReloading else { return .none }
state.isReloading = true
return .run { send in
let service = try getService()
do {
let isCommunicationReady = try await service.launchIfNeeded()
if isCommunicationReady {
let xpcServiceVersion = try await service.getXPCServiceVersion().version
let isAccessibilityPermissionGranted = try await service
.getXPCServiceAccessibilityPermission()
let isExtensionPermissionGranted = try await service.getXPCServiceExtensionPermission()
let xpcServiceAuthStatus = try await service.getXPCServiceAuthStatus() ?? .init(status: .unknown)
let xpcCLSVersion = try await service.getXPCCLSVersion()
await send(.finishReloading(
xpcServiceVersion: xpcServiceVersion,
xpcCLSVersion: xpcCLSVersion,
axStatus: isAccessibilityPermissionGranted,
extensionStatus: isExtensionPermissionGranted,
authStatus: xpcServiceAuthStatus
))
} else {
toast("Launching service app.", .info)
try await Task.sleep(nanoseconds: 5_000_000_000)
await send(.retryReloading)
}
} catch let error as XPCCommunicationBridgeError {
Logger.ui.error("Failed to reach communication bridge. \(error.localizedDescription)")
toast(
"Unable to connect to the communication bridge. The helper application didn't respond. This may be due to missing background permissions.",
.error
)
await send(.failedReloading)
} catch {
Logger.ui.error("Failed to reload status. \(error.localizedDescription)")
toast(error.localizedDescription, .error)
await send(.failedReloading)
}
}.cancellable(id: ReloadStatusCancellableId(), cancelInFlight: true)
case let .finishReloading(version, clsVersion, axStatus, extensionStatus, authStatus):
state.xpcServiceVersion = version
state.isAccessibilityPermissionGranted = axStatus
state.isExtensionPermissionGranted = extensionStatus
state.xpcServiceAuthStatus = authStatus
state.xpcCLSVersion = clsVersion
state.isReloading = false
return .none
case .failedReloading:
state.isReloading = false
return .none
case .retryReloading:
state.isReloading = false
return .run { send in
await send(.reloadStatus)
}
}
}
}
}