Skip to content

Commit 397007d

Browse files
committed
chore: add mutagen session state conversions
1 parent ae1e3b0 commit 397007d

File tree

4 files changed

+332
-21
lines changed

4 files changed

+332
-21
lines changed

Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,12 @@ struct FileSyncConfig<VPN: VPNService, FS: FileSyncDaemon>: View {
2020
}.width(min: 200, ideal: 240)
2121
TableColumn("Workspace", value: \.agentHost)
2222
.width(min: 100, ideal: 120)
23-
TableColumn("Remote Path", value: \.betaPath)
23+
TableColumn("Remote Path") { Text($0.betaPath).help($0.betaPath) }
2424
.width(min: 100, ideal: 120)
25-
TableColumn("Status") { $0.status.body }
25+
TableColumn("Status") { $0.status.column.help($0.statusAndErrors) }
2626
.width(min: 80, ideal: 100)
27-
TableColumn("Size") { item in
28-
Text(item.size)
29-
}
30-
.width(min: 60, ideal: 80)
27+
TableColumn("Size") { Text($0.maxSize.humanSizeBytes).help($0.sizeDescription) }
28+
.width(min: 60, ideal: 80)
3129
}
3230
.frame(minWidth: 400, minHeight: 200)
3331
.padding(.bottom, 25)

Coder-Desktop/VPNLib/FileSync/FileSyncSession.swift

Lines changed: 269 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,141 @@ import SwiftUI
33
public struct FileSyncSession: Identifiable {
44
public let id: String
55
public let alphaPath: String
6+
public let name: String
7+
68
public let agentHost: String
79
public let betaPath: String
810
public let status: FileSyncStatus
9-
public let size: String
11+
12+
public let maxSize: FileSyncSessionEndpointSize
13+
public let localSize: FileSyncSessionEndpointSize
14+
public let remoteSize: FileSyncSessionEndpointSize
15+
16+
public let errors: [FileSyncError]
17+
18+
init(state: Synchronization_State) {
19+
id = state.session.identifier
20+
name = state.session.name
21+
22+
// If the protocol isn't what we expect for alpha or beta, show unknown
23+
alphaPath = if state.session.alpha.protocol == Url_Protocol.local, !state.session.alpha.path.isEmpty {
24+
state.session.alpha.path
25+
} else {
26+
"Unknown"
27+
}
28+
if state.session.beta.protocol == Url_Protocol.ssh, !state.session.beta.host.isEmpty {
29+
let host = state.session.beta.host
30+
// TOOD: We need to either:
31+
// - make this compatible with custom suffixes
32+
// - always strip the tld
33+
// - always keep the tld
34+
agentHost = host.hasSuffix(".coder") ? String(host.dropLast(6)) : host
35+
} else {
36+
agentHost = "Unknown"
37+
}
38+
betaPath = if !state.session.beta.path.isEmpty {
39+
state.session.beta.path
40+
} else {
41+
"Unknown"
42+
}
43+
44+
var status: FileSyncStatus = if state.session.paused {
45+
.paused
46+
} else {
47+
convertSessionStatus(status: state.status)
48+
}
49+
if case .error = status {} else {
50+
if state.conflicts.count > 0 {
51+
status = .conflicts
52+
}
53+
}
54+
self.status = status
55+
56+
localSize = .init(
57+
sizeBytes: state.alphaState.totalFileSize,
58+
fileCount: state.alphaState.files,
59+
dirCount: state.alphaState.directories,
60+
symLinkCount: state.alphaState.symbolicLinks
61+
)
62+
remoteSize = .init(
63+
sizeBytes: state.betaState.totalFileSize,
64+
fileCount: state.betaState.files,
65+
dirCount: state.betaState.directories,
66+
symLinkCount: state.betaState.symbolicLinks
67+
)
68+
maxSize = localSize.maxOf(other: remoteSize)
69+
70+
errors = accumulateErrors(from: state)
71+
}
72+
73+
public var statusAndErrors: String {
74+
var out = "\(status.type)\n\n\(status.description)"
75+
errors.forEach { out += "\n\t\($0)" }
76+
return out
77+
}
78+
79+
public var sizeDescription: String {
80+
var out = ""
81+
if localSize != remoteSize {
82+
out += "Maximum:\n\(maxSize.description(linePrefix: " "))\n\n"
83+
}
84+
out += "Local:\n\(localSize.description(linePrefix: " "))\n\n"
85+
out += "Remote:\n\(remoteSize.description(linePrefix: " "))"
86+
return out
87+
}
88+
}
89+
90+
public struct FileSyncSessionEndpointSize: Equatable {
91+
public let sizeBytes: UInt64
92+
public let fileCount: UInt64
93+
public let dirCount: UInt64
94+
public let symLinkCount: UInt64
95+
96+
public init(sizeBytes: UInt64, fileCount: UInt64, dirCount: UInt64, symLinkCount: UInt64) {
97+
self.sizeBytes = sizeBytes
98+
self.fileCount = fileCount
99+
self.dirCount = dirCount
100+
self.symLinkCount = symLinkCount
101+
}
102+
103+
func maxOf(other: FileSyncSessionEndpointSize) -> FileSyncSessionEndpointSize {
104+
FileSyncSessionEndpointSize(
105+
sizeBytes: max(sizeBytes, other.sizeBytes),
106+
fileCount: max(fileCount, other.fileCount),
107+
dirCount: max(dirCount, other.dirCount),
108+
symLinkCount: max(symLinkCount, other.symLinkCount)
109+
)
110+
}
111+
112+
public var humanSizeBytes: String {
113+
humanReadableBytes(sizeBytes)
114+
}
115+
116+
public func description(linePrefix: String = "") -> String {
117+
var result = ""
118+
result += linePrefix + humanReadableBytes(sizeBytes) + "\n"
119+
let numberFormatter = NumberFormatter()
120+
numberFormatter.numberStyle = .decimal
121+
if let formattedFileCount = numberFormatter.string(from: NSNumber(value: fileCount)) {
122+
result += "\(linePrefix)\(formattedFileCount) file\(fileCount == 1 ? "" : "s")\n"
123+
}
124+
if let formattedDirCount = numberFormatter.string(from: NSNumber(value: dirCount)) {
125+
result += "\(linePrefix)\(formattedDirCount) director\(dirCount == 1 ? "y" : "ies")"
126+
}
127+
if symLinkCount > 0, let formattedSymLinkCount = numberFormatter.string(from: NSNumber(value: symLinkCount)) {
128+
result += "\n\(linePrefix)\(formattedSymLinkCount) symlink\(symLinkCount == 1 ? "" : "s")"
129+
}
130+
return result
131+
}
10132
}
11133

12134
public enum FileSyncStatus {
13135
case unknown
14-
case error(String)
136+
case error(FileSyncErrorStatus)
15137
case ok
16138
case paused
17-
case needsAttention(String)
18-
case working(String)
139+
case conflicts
140+
case working(FileSyncWorkingStatus)
19141

20142
public var color: Color {
21143
switch self {
@@ -27,31 +149,163 @@ public enum FileSyncStatus {
27149
.red
28150
case .error:
29151
.red
30-
case .needsAttention:
152+
case .conflicts:
31153
.orange
32154
case .working:
33-
.white
155+
.purple
34156
}
35157
}
36158

37-
public var description: String {
159+
public var type: String {
38160
switch self {
39161
case .unknown:
40162
"Unknown"
41-
case let .error(msg):
42-
msg
163+
case let .error(status):
164+
status.name
43165
case .ok:
44166
"Watching"
45167
case .paused:
46168
"Paused"
47-
case let .needsAttention(msg):
48-
msg
49-
case let .working(msg):
50-
msg
169+
case .conflicts:
170+
"Conflicts"
171+
case let .working(status):
172+
status.name
173+
}
174+
}
175+
176+
public var description: String {
177+
switch self {
178+
case .unknown:
179+
"Unknown status message."
180+
case let .error(status):
181+
status.description
182+
case .ok:
183+
"The session is watching for filesystem changes."
184+
case .paused:
185+
"The session is paused."
186+
case .conflicts:
187+
"The session has conflicts that need to be resolved."
188+
case let .working(status):
189+
status.description
51190
}
52191
}
53192

54-
public var body: some View {
55-
Text(description).foregroundColor(color)
193+
public var column: some View {
194+
Text(type).foregroundColor(color)
195+
}
196+
}
197+
198+
public enum FileSyncWorkingStatus {
199+
case connectingAlpha
200+
case connectingBeta
201+
case scanning
202+
case reconciling
203+
case stagingAlpha
204+
case stagingBeta
205+
case transitioning
206+
case saving
207+
208+
var name: String {
209+
switch self {
210+
case .connectingAlpha:
211+
"Connecting (alpha)"
212+
case .connectingBeta:
213+
"Connecting (beta)"
214+
case .scanning:
215+
"Scanning"
216+
case .reconciling:
217+
"Reconciling"
218+
case .stagingAlpha:
219+
"Staging (alpha)"
220+
case .stagingBeta:
221+
"Staging (beta)"
222+
case .transitioning:
223+
"Transitioning"
224+
case .saving:
225+
"Saving"
226+
}
227+
}
228+
229+
var description: String {
230+
switch self {
231+
case .connectingAlpha:
232+
"The session is attempting to connect to the alpha endpoint."
233+
case .connectingBeta:
234+
"The session is attempting to connect to the beta endpoint."
235+
case .scanning:
236+
"The session is scanning the filesystem on each endpoint."
237+
case .reconciling:
238+
"The session is performing reconciliation."
239+
case .stagingAlpha:
240+
"The session is staging files on the alpha endpoint"
241+
case .stagingBeta:
242+
"The session is staging files on the beta endpoint"
243+
case .transitioning:
244+
"The session is performing transition operations on each endpoint."
245+
case .saving:
246+
"The session is recording synchronization history to disk."
247+
}
248+
}
249+
}
250+
251+
public enum FileSyncErrorStatus {
252+
case disconnected
253+
case haltedOnRootEmptied
254+
case haltedOnRootDeletion
255+
case haltedOnRootTypeChange
256+
case waitingForRescan
257+
258+
var name: String {
259+
switch self {
260+
case .disconnected:
261+
"Disconnected"
262+
case .haltedOnRootEmptied:
263+
"Halted on root emptied"
264+
case .haltedOnRootDeletion:
265+
"Halted on root deletion"
266+
case .haltedOnRootTypeChange:
267+
"Halted on root type change"
268+
case .waitingForRescan:
269+
"Waiting for rescan"
270+
}
271+
}
272+
273+
var description: String {
274+
switch self {
275+
case .disconnected:
276+
"The session is unpaused but not currently connected or connecting to either endpoint."
277+
case .haltedOnRootEmptied:
278+
"The session is halted due to the root emptying safety check."
279+
case .haltedOnRootDeletion:
280+
"The session is halted due to the root deletion safety check."
281+
case .haltedOnRootTypeChange:
282+
"The session is halted due to the root type change safety check."
283+
case .waitingForRescan:
284+
"The session is waiting to retry scanning after an error during the previous scan."
285+
}
286+
}
287+
}
288+
289+
public enum FileSyncEndpoint {
290+
case local
291+
case remote
292+
}
293+
294+
public enum FileSyncProblemType {
295+
case scan
296+
case transition
297+
}
298+
299+
public enum FileSyncError {
300+
case generic(String)
301+
case problem(FileSyncEndpoint, FileSyncProblemType, path: String, error: String)
302+
303+
var description: String {
304+
switch self {
305+
case let .generic(error):
306+
error
307+
case let .problem(endpoint, type, path, error):
308+
"\(endpoint) \(type) error at \(path): \(error)"
309+
}
56310
}
57311
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// swiftlint:disable:next cyclomatic_complexity
2+
func convertSessionStatus(status: Synchronization_Status) -> FileSyncStatus {
3+
switch status {
4+
case .disconnected:
5+
.error(.disconnected)
6+
case .haltedOnRootEmptied:
7+
.error(.haltedOnRootEmptied)
8+
case .haltedOnRootDeletion:
9+
.error(.haltedOnRootDeletion)
10+
case .haltedOnRootTypeChange:
11+
.error(.haltedOnRootTypeChange)
12+
case .waitingForRescan:
13+
.error(.waitingForRescan)
14+
case .connectingAlpha:
15+
.working(.connectingAlpha)
16+
case .connectingBeta:
17+
.working(.connectingBeta)
18+
case .scanning:
19+
.working(.scanning)
20+
case .reconciling:
21+
.working(.reconciling)
22+
case .stagingAlpha:
23+
.working(.stagingAlpha)
24+
case .stagingBeta:
25+
.working(.stagingBeta)
26+
case .transitioning:
27+
.working(.transitioning)
28+
case .saving:
29+
.working(.saving)
30+
case .watching:
31+
.ok
32+
case .UNRECOGNIZED:
33+
.unknown
34+
}
35+
}
36+
37+
func accumulateErrors(from state: Synchronization_State) -> [FileSyncError] {
38+
var errors: [FileSyncError] = []
39+
if !state.lastError.isEmpty {
40+
errors.append(.generic(state.lastError))
41+
}
42+
for problem in state.alphaState.scanProblems {
43+
errors.append(.problem(.local, .scan, path: problem.path, error: problem.error))
44+
}
45+
for problem in state.alphaState.transitionProblems {
46+
errors.append(.problem(.local, .transition, path: problem.path, error: problem.error))
47+
}
48+
for problem in state.betaState.scanProblems {
49+
errors.append(.problem(.remote, .scan, path: problem.path, error: problem.error))
50+
}
51+
for problem in state.betaState.transitionProblems {
52+
errors.append(.problem(.remote, .transition, path: problem.path, error: problem.error))
53+
}
54+
return errors
55+
}
56+
57+
func humanReadableBytes(_ bytes: UInt64) -> String {
58+
ByteCountFormatter().string(fromByteCount: Int64(bytes))
59+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy