@@ -3,19 +3,126 @@ 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 localSize : FileSyncSessionEndpointSize
13
+ public let remoteSize : FileSyncSessionEndpointSize
14
+
15
+ public let errors : [ FileSyncError ]
16
+
17
+ init ( state: Synchronization_State ) {
18
+ id = state. session. identifier
19
+ name = state. session. name
20
+
21
+ // If the protocol isn't what we expect for alpha or beta, show unknown
22
+ alphaPath = if state. session. alpha. protocol == Url_Protocol . local, !state. session. alpha. path. isEmpty {
23
+ state. session. alpha. path
24
+ } else {
25
+ " Unknown "
26
+ }
27
+ agentHost = if state. session. beta. protocol == Url_Protocol . ssh, !state. session. beta. host. isEmpty {
28
+ // TOOD: We need to either:
29
+ // - make this compatible with custom suffixes
30
+ // - always strip the tld
31
+ // - always keep the tld
32
+ state. session. beta. host
33
+ } else {
34
+ " Unknown "
35
+ }
36
+ betaPath = if !state. session. beta. path. isEmpty {
37
+ state. session. beta. path
38
+ } else {
39
+ " Unknown "
40
+ }
41
+
42
+ var status : FileSyncStatus = if state. session. paused {
43
+ . paused
44
+ } else {
45
+ convertSessionStatus ( status: state. status)
46
+ }
47
+ if case . error = status { } else {
48
+ if state. conflicts. count > 0 {
49
+ status = . conflicts
50
+ }
51
+ }
52
+ self . status = status
53
+
54
+ localSize = . init(
55
+ sizeBytes: state. alphaState. totalFileSize,
56
+ fileCount: state. alphaState. files,
57
+ dirCount: state. alphaState. directories,
58
+ symLinkCount: state. alphaState. symbolicLinks
59
+ )
60
+ remoteSize = . init(
61
+ sizeBytes: state. betaState. totalFileSize,
62
+ fileCount: state. betaState. files,
63
+ dirCount: state. betaState. directories,
64
+ symLinkCount: state. betaState. symbolicLinks
65
+ )
66
+
67
+ errors = accumulateErrors ( from: state)
68
+ }
69
+
70
+ public var statusAndErrors : String {
71
+ var out = " \( status. type) \n \n \( status. description) "
72
+ errors. forEach { out += " \n \t \( $0) " }
73
+ return out
74
+ }
75
+
76
+ public var sizeDescription : String {
77
+ var out = " "
78
+ out += " Local: \n \( localSize. description ( linePrefix: " " ) ) \n \n "
79
+ out += " Remote: \n \( remoteSize. description ( linePrefix: " " ) ) "
80
+ return out
81
+ }
82
+ }
83
+
84
+ public struct FileSyncSessionEndpointSize : Equatable {
85
+ public let sizeBytes : UInt64
86
+ public let fileCount : UInt64
87
+ public let dirCount : UInt64
88
+ public let symLinkCount : UInt64
89
+
90
+ public init ( sizeBytes: UInt64 , fileCount: UInt64 , dirCount: UInt64 , symLinkCount: UInt64 ) {
91
+ self . sizeBytes = sizeBytes
92
+ self . fileCount = fileCount
93
+ self . dirCount = dirCount
94
+ self . symLinkCount = symLinkCount
95
+ }
96
+
97
+ public var humanSizeBytes : String {
98
+ humanReadableBytes ( sizeBytes)
99
+ }
100
+
101
+ public func description( linePrefix: String = " " ) -> String {
102
+ var result = " "
103
+ result += linePrefix + humanReadableBytes( sizeBytes) + " \n "
104
+ let numberFormatter = NumberFormatter ( )
105
+ numberFormatter. numberStyle = . decimal
106
+ if let formattedFileCount = numberFormatter. string ( from: NSNumber ( value: fileCount) ) {
107
+ result += " \( linePrefix) \( formattedFileCount) file \( fileCount == 1 ? " " : " s " ) \n "
108
+ }
109
+ if let formattedDirCount = numberFormatter. string ( from: NSNumber ( value: dirCount) ) {
110
+ result += " \( linePrefix) \( formattedDirCount) director \( dirCount == 1 ? " y " : " ies " ) "
111
+ }
112
+ if symLinkCount > 0 , let formattedSymLinkCount = numberFormatter. string ( from: NSNumber ( value: symLinkCount) ) {
113
+ result += " \n \( linePrefix) \( formattedSymLinkCount) symlink \( symLinkCount == 1 ? " " : " s " ) "
114
+ }
115
+ return result
116
+ }
10
117
}
11
118
12
119
public enum FileSyncStatus {
13
120
case unknown
14
- case error( String )
121
+ case error( FileSyncErrorStatus )
15
122
case ok
16
123
case paused
17
- case needsAttention ( String )
18
- case working( String )
124
+ case conflicts
125
+ case working( FileSyncWorkingStatus )
19
126
20
127
public var color : Color {
21
128
switch self {
@@ -27,32 +134,164 @@ public enum FileSyncStatus {
27
134
. red
28
135
case . error:
29
136
. red
30
- case . needsAttention :
137
+ case . conflicts :
31
138
. orange
32
139
case . working:
33
- . white
140
+ . purple
34
141
}
35
142
}
36
143
37
- public var description : String {
144
+ public var type : String {
38
145
switch self {
39
146
case . unknown:
40
147
" Unknown "
41
- case let . error( msg ) :
42
- msg
148
+ case let . error( status ) :
149
+ status . name
43
150
case . ok:
44
151
" Watching "
45
152
case . paused:
46
153
" Paused "
47
- case let . needsAttention( msg) :
48
- msg
49
- case let . working( msg) :
50
- msg
154
+ case . conflicts:
155
+ " Conflicts "
156
+ case let . working( status) :
157
+ status. name
158
+ }
159
+ }
160
+
161
+ public var description : String {
162
+ switch self {
163
+ case . unknown:
164
+ " Unknown status message. "
165
+ case let . error( status) :
166
+ status. description
167
+ case . ok:
168
+ " The session is watching for filesystem changes. "
169
+ case . paused:
170
+ " The session is paused. "
171
+ case . conflicts:
172
+ " The session has conflicts that need to be resolved. "
173
+ case let . working( status) :
174
+ status. description
175
+ }
176
+ }
177
+
178
+ public var column : some View {
179
+ Text ( type) . foregroundColor ( color)
180
+ }
181
+ }
182
+
183
+ public enum FileSyncWorkingStatus {
184
+ case connectingAlpha
185
+ case connectingBeta
186
+ case scanning
187
+ case reconciling
188
+ case stagingAlpha
189
+ case stagingBeta
190
+ case transitioning
191
+ case saving
192
+
193
+ var name : String {
194
+ switch self {
195
+ case . connectingAlpha:
196
+ " Connecting (alpha) "
197
+ case . connectingBeta:
198
+ " Connecting (beta) "
199
+ case . scanning:
200
+ " Scanning "
201
+ case . reconciling:
202
+ " Reconciling "
203
+ case . stagingAlpha:
204
+ " Staging (alpha) "
205
+ case . stagingBeta:
206
+ " Staging (beta) "
207
+ case . transitioning:
208
+ " Transitioning "
209
+ case . saving:
210
+ " Saving "
211
+ }
212
+ }
213
+
214
+ var description : String {
215
+ switch self {
216
+ case . connectingAlpha:
217
+ " The session is attempting to connect to the alpha endpoint. "
218
+ case . connectingBeta:
219
+ " The session is attempting to connect to the beta endpoint. "
220
+ case . scanning:
221
+ " The session is scanning the filesystem on each endpoint. "
222
+ case . reconciling:
223
+ " The session is performing reconciliation. "
224
+ case . stagingAlpha:
225
+ " The session is staging files on the alpha endpoint "
226
+ case . stagingBeta:
227
+ " The session is staging files on the beta endpoint "
228
+ case . transitioning:
229
+ " The session is performing transition operations on each endpoint. "
230
+ case . saving:
231
+ " The session is recording synchronization history to disk. "
51
232
}
52
233
}
234
+ }
235
+
236
+ public enum FileSyncErrorStatus {
237
+ case disconnected
238
+ case haltedOnRootEmptied
239
+ case haltedOnRootDeletion
240
+ case haltedOnRootTypeChange
241
+ case waitingForRescan
242
+
243
+ var name : String {
244
+ switch self {
245
+ case . disconnected:
246
+ " Disconnected "
247
+ case . haltedOnRootEmptied:
248
+ " Halted on root emptied "
249
+ case . haltedOnRootDeletion:
250
+ " Halted on root deletion "
251
+ case . haltedOnRootTypeChange:
252
+ " Halted on root type change "
253
+ case . waitingForRescan:
254
+ " Waiting for rescan "
255
+ }
256
+ }
257
+
258
+ var description : String {
259
+ switch self {
260
+ case . disconnected:
261
+ " The session is unpaused but not currently connected or connecting to either endpoint. "
262
+ case . haltedOnRootEmptied:
263
+ " The session is halted due to the root emptying safety check. "
264
+ case . haltedOnRootDeletion:
265
+ " The session is halted due to the root deletion safety check. "
266
+ case . haltedOnRootTypeChange:
267
+ " The session is halted due to the root type change safety check. "
268
+ case . waitingForRescan:
269
+ " The session is waiting to retry scanning after an error during the previous scan. "
270
+ }
271
+ }
272
+ }
53
273
54
- public var body : some View {
55
- Text ( description) . foregroundColor ( color)
274
+ public enum FileSyncEndpoint {
275
+ case local
276
+ case remote
277
+ }
278
+
279
+ public enum FileSyncProblemType {
280
+ case scan
281
+ case transition
282
+ }
283
+
284
+ public enum FileSyncError {
285
+ case generic( String )
286
+ case problem( FileSyncEndpoint , FileSyncProblemType , path: String , error: String )
287
+
288
+ var description : String {
289
+ switch self {
290
+ case let . generic( error) :
291
+ error
292
+ case let . problem( endpoint, type, path, error) :
293
+ " \( endpoint) \( type) error at \( path) : \( error) "
294
+ }
56
295
}
57
296
}
58
297
0 commit comments