@@ -3,19 +3,141 @@ import SwiftUI
3
3
public struct FileSyncSession : Identifiable {
4
4
public let id : String
5
5
public let alphaPath : String
6
+ public let name : String
7
+
6
8
public let agentHost : String
7
9
public let betaPath : String
8
10
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
+ }
10
132
}
11
133
12
134
public enum FileSyncStatus {
13
135
case unknown
14
- case error( String )
136
+ case error( FileSyncErrorStatus )
15
137
case ok
16
138
case paused
17
- case needsAttention ( String )
18
- case working( String )
139
+ case conflicts
140
+ case working( FileSyncWorkingStatus )
19
141
20
142
public var color : Color {
21
143
switch self {
@@ -27,31 +149,163 @@ public enum FileSyncStatus {
27
149
. red
28
150
case . error:
29
151
. red
30
- case . needsAttention :
152
+ case . conflicts :
31
153
. orange
32
154
case . working:
33
- . white
155
+ . purple
34
156
}
35
157
}
36
158
37
- public var description : String {
159
+ public var type : String {
38
160
switch self {
39
161
case . unknown:
40
162
" Unknown "
41
- case let . error( msg ) :
42
- msg
163
+ case let . error( status ) :
164
+ status . name
43
165
case . ok:
44
166
" Watching "
45
167
case . paused:
46
168
" 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
51
190
}
52
191
}
53
192
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
+ }
56
310
}
57
311
}
0 commit comments