1+ using System ;
12using System . Collections . Generic ;
3+ using System . Diagnostics ;
4+ using Coder . Desktop . App . Converters ;
25using Coder . Desktop . Vpn . Proto ;
36
47namespace Coder . Desktop . App . Models ;
@@ -19,11 +22,168 @@ public enum VpnLifecycle
1922 Stopping ,
2023}
2124
25+ public enum VpnStartupStage
26+ {
27+ Unknown ,
28+ Initializing ,
29+ Downloading ,
30+ Finalizing ,
31+ }
32+
33+ public class VpnDownloadProgress
34+ {
35+ public ulong BytesWritten { get ; set ; } = 0 ;
36+ public ulong ? BytesTotal { get ; set ; } = null ; // null means unknown total size
37+
38+ public double Progress
39+ {
40+ get
41+ {
42+ if ( BytesTotal is > 0 )
43+ {
44+ return ( double ) BytesWritten / BytesTotal . Value ;
45+ }
46+ return 0.0 ;
47+ }
48+ }
49+
50+ public override string ToString ( )
51+ {
52+ // TODO: it would be nice if the two suffixes could match
53+ var s = FriendlyByteConverter . FriendlyBytes ( BytesWritten ) ;
54+ if ( BytesTotal != null )
55+ s += $ " of { FriendlyByteConverter . FriendlyBytes ( BytesTotal . Value ) } ";
56+ else
57+ s += " of unknown" ;
58+ if ( BytesTotal != null )
59+ s += $ " ({ Progress : 0%} )";
60+ return s ;
61+ }
62+
63+ public VpnDownloadProgress Clone ( )
64+ {
65+ return new VpnDownloadProgress
66+ {
67+ BytesWritten = BytesWritten ,
68+ BytesTotal = BytesTotal ,
69+ } ;
70+ }
71+
72+ public static VpnDownloadProgress FromProto ( StartProgressDownloadProgress proto )
73+ {
74+ return new VpnDownloadProgress
75+ {
76+ BytesWritten = proto . BytesWritten ,
77+ BytesTotal = proto . HasBytesTotal ? proto . BytesTotal : null ,
78+ } ;
79+ }
80+ }
81+
82+ public class VpnStartupProgress
83+ {
84+ public const string DefaultStartProgressMessage = "Starting Coder Connect..." ;
85+
86+ // Scale the download progress to an overall progress value between these
87+ // numbers.
88+ private const double DownloadProgressMin = 0.05 ;
89+ private const double DownloadProgressMax = 0.80 ;
90+
91+ public VpnStartupStage Stage { get ; init ; } = VpnStartupStage . Unknown ;
92+ public VpnDownloadProgress ? DownloadProgress { get ; init ; } = null ;
93+
94+ // 0.0 to 1.0
95+ public double Progress
96+ {
97+ get
98+ {
99+ switch ( Stage )
100+ {
101+ case VpnStartupStage . Unknown :
102+ case VpnStartupStage . Initializing :
103+ return 0.0 ;
104+ case VpnStartupStage . Downloading :
105+ var progress = DownloadProgress ? . Progress ?? 0.0 ;
106+ return DownloadProgressMin + ( DownloadProgressMax - DownloadProgressMin ) * progress ;
107+ case VpnStartupStage . Finalizing :
108+ return DownloadProgressMax ;
109+ default :
110+ throw new ArgumentOutOfRangeException ( ) ;
111+ }
112+ }
113+ }
114+
115+ public override string ToString ( )
116+ {
117+ switch ( Stage )
118+ {
119+ case VpnStartupStage . Unknown :
120+ case VpnStartupStage . Initializing :
121+ return DefaultStartProgressMessage ;
122+ case VpnStartupStage . Downloading :
123+ var s = "Downloading Coder Connect binary..." ;
124+ if ( DownloadProgress is not null )
125+ {
126+ s += "\n " + DownloadProgress ;
127+ }
128+
129+ return s ;
130+ case VpnStartupStage . Finalizing :
131+ return "Finalizing Coder Connect startup..." ;
132+ default :
133+ throw new ArgumentOutOfRangeException ( ) ;
134+ }
135+ }
136+
137+ public VpnStartupProgress Clone ( )
138+ {
139+ return new VpnStartupProgress
140+ {
141+ Stage = Stage ,
142+ DownloadProgress = DownloadProgress ? . Clone ( ) ,
143+ } ;
144+ }
145+
146+ public static VpnStartupProgress FromProto ( StartProgress proto )
147+ {
148+ return new VpnStartupProgress
149+ {
150+ Stage = proto . Stage switch
151+ {
152+ StartProgressStage . Initializing => VpnStartupStage . Initializing ,
153+ StartProgressStage . Downloading => VpnStartupStage . Downloading ,
154+ StartProgressStage . Finalizing => VpnStartupStage . Finalizing ,
155+ _ => VpnStartupStage . Unknown ,
156+ } ,
157+ DownloadProgress = proto . Stage is StartProgressStage . Downloading ?
158+ VpnDownloadProgress . FromProto ( proto . DownloadProgress ) :
159+ null ,
160+ } ;
161+ }
162+ }
163+
22164public class RpcModel
23165{
24166 public RpcLifecycle RpcLifecycle { get ; set ; } = RpcLifecycle . Disconnected ;
25167
26- public VpnLifecycle VpnLifecycle { get ; set ; } = VpnLifecycle . Unknown ;
168+ public VpnLifecycle VpnLifecycle
169+ {
170+ get ;
171+ set
172+ {
173+ if ( VpnLifecycle != value && value == VpnLifecycle . Starting )
174+ // Reset the startup progress when the VPN lifecycle changes to
175+ // Starting.
176+ VpnStartupProgress = null ;
177+ field = value ;
178+ }
179+ }
180+
181+ // Nullable because it is only set when the VpnLifecycle is Starting
182+ public VpnStartupProgress ? VpnStartupProgress
183+ {
184+ get => VpnLifecycle is VpnLifecycle . Starting ? field ?? new VpnStartupProgress ( ) : null ;
185+ set ;
186+ }
27187
28188 public IReadOnlyList < Workspace > Workspaces { get ; set ; } = [ ] ;
29189
@@ -35,6 +195,7 @@ public RpcModel Clone()
35195 {
36196 RpcLifecycle = RpcLifecycle ,
37197 VpnLifecycle = VpnLifecycle ,
198+ VpnStartupProgress = VpnStartupProgress ? . Clone ( ) ,
38199 Workspaces = Workspaces ,
39200 Agents = Agents ,
40201 } ;
0 commit comments