@@ -2,21 +2,142 @@ import SwiftUI
22
33public struct FileSyncSession : Identifiable {
44public let id : String
5- public let localPath : URL
6- public let workspace : String
7- // This is a string as to be host-OS agnostic
5+ public let name : String
6+
7+ public let localPath : String
8+ public let agentHost : String
89public let remotePath : String
910public let status : FileSyncStatus
10- 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+ localPath= 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+ remotePath= 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= . needsAttention( name: " Conflicts " , desc: " The session has conflicts that need to be resolved " )
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+ }
11132}
12133
13134public enum FileSyncStatus {
14135case unknown
15- case error( String )
136+ case error( name : String , desc : String )
16137case ok
17138case paused
18- case needsAttention( String )
19- case working( String )
139+ case needsAttention( name : String , desc : String )
140+ case working( name : String , desc : String )
20141
21142public var color : Color {
22143switch self {
@@ -31,28 +152,69 @@ public enum FileSyncStatus {
31152case . needsAttention:
32153. orange
33154case . working:
34- . white
155+ . purple
35156}
36157}
37158
38- public var description : String {
159+ public var type : String {
39160switch self {
40161case . unknown:
41162" Unknown "
42- case let . error( msg ) :
43- msg
163+ case let . error( name , _ ) :
164+ " \( name ) "
44165case . ok:
45166" Watching "
46167case . paused:
47168" Paused "
48- case let . needsAttention( msg ) :
49- msg
50- case let . working( msg ) :
51- msg
169+ case let . needsAttention( name , _ ) :
170+ name
171+ case let . working( name , _ ) :
172+ name
52173}
53174}
54175
55- public var body : some View {
56- Text ( description) . foregroundColor ( color)
176+ public var description : String {
177+ switch self {
178+ case . unknown:
179+ " Unknown status message. "
180+ case let . error( _, desc) :
181+ desc
182+ case . ok:
183+ " The session is watching for filesystem changes. "
184+ case . paused:
185+ " The session is paused. "
186+ case let . needsAttention( _, desc) :
187+ desc
188+ case let . working( _, desc) :
189+ desc
190+ }
191+ }
192+
193+ public var column : some View {
194+ Text ( type) . foregroundColor ( color)
195+ }
196+ }
197+
198+ public enum FileSyncEndpoint {
199+ case local
200+ case remote
201+ }
202+
203+ public enum FileSyncProblemType {
204+ case scan
205+ case transition
206+ }
207+
208+ public enum FileSyncError {
209+ case generic( String )
210+ case problem( FileSyncEndpoint , FileSyncProblemType , path: String , error: String )
211+
212+ var description : String {
213+ switch self {
214+ case let . generic( error) :
215+ error
216+ case let . problem( endpoint, type, path, error) :
217+ " \( endpoint) \( type) error at \( path) : \( error) "
218+ }
57219}
58220}