@@ -3,19 +3,141 @@ import SwiftUI
33public 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
12134public 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,32 +149,164 @@ 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}
58312
0 commit comments