1
1
using System ;
2
+ using System . Collections . Generic ;
2
3
using System . Diagnostics ;
4
+ using System . IO ;
3
5
using System . Threading ;
4
6
using System . Threading . Tasks ;
7
+ using Windows . ApplicationModel . Activation ;
5
8
using Coder . Desktop . App . Models ;
6
9
using Coder . Desktop . App . Services ;
7
10
using Coder . Desktop . App . ViewModels ;
12
15
using Microsoft . Extensions . Configuration ;
13
16
using Microsoft . Extensions . DependencyInjection ;
14
17
using Microsoft . Extensions . Hosting ;
18
+ using Microsoft . Extensions . Logging ;
15
19
using Microsoft . UI . Xaml ;
16
20
using Microsoft . Win32 ;
21
+ using Microsoft . Windows . AppLifecycle ;
22
+ using Serilog ;
23
+ using LaunchActivatedEventArgs = Microsoft . UI . Xaml . LaunchActivatedEventArgs ;
17
24
18
25
namespace Coder . Desktop . App ;
19
26
@@ -22,22 +29,39 @@ public partial class App : Application
22
29
private readonly IServiceProvider _services ;
23
30
24
31
private bool _handleWindowClosed = true ;
32
+ private const string MutagenControllerConfigSection = "MutagenController" ;
25
33
26
34
#if ! DEBUG
27
- private const string MutagenControllerConfigSection = "AppMutagenController" ;
35
+ private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\App" ;
36
+ private const string logFilename = "app.log" ;
28
37
#else
29
- private const string MutagenControllerConfigSection = "DebugAppMutagenController" ;
38
+ private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\DebugApp" ;
39
+ private const string logFilename = "debug-app.log" ;
30
40
#endif
31
41
42
+ private readonly ILogger < App > _logger ;
43
+
32
44
public App ( )
33
45
{
34
46
var builder = Host . CreateApplicationBuilder ( ) ;
47
+ var configBuilder = builder . Configuration as IConfigurationBuilder ;
35
48
36
- ( builder . Configuration as IConfigurationBuilder ) . Add (
37
- new RegistryConfigurationSource ( Registry . LocalMachine , @"SOFTWARE\Coder Desktop" ) ) ;
49
+ // Add config in increasing order of precedence: first builtin defaults, then HKLM, finally HKCU
50
+ // so that the user's settings in the registry take precedence.
51
+ AddDefaultConfig ( configBuilder ) ;
52
+ configBuilder . Add (
53
+ new RegistryConfigurationSource ( Registry . LocalMachine , ConfigSubKey ) ) ;
54
+ configBuilder . Add (
55
+ new RegistryConfigurationSource ( Registry . CurrentUser , ConfigSubKey ) ) ;
38
56
39
57
var services = builder . Services ;
40
58
59
+ // Logging
60
+ builder . Services . AddSerilog ( ( _ , loggerConfig ) =>
61
+ {
62
+ loggerConfig . ReadFrom . Configuration ( builder . Configuration ) ;
63
+ } ) ;
64
+
41
65
services . AddSingleton < IAgentApiClientFactory , AgentApiClientFactory > ( ) ;
42
66
43
67
services . AddSingleton < ICredentialManager , CredentialManager > ( ) ;
@@ -71,12 +95,14 @@ public App()
71
95
services . AddTransient < TrayWindow > ( ) ;
72
96
73
97
_services = services . BuildServiceProvider ( ) ;
98
+ _logger = ( ILogger < App > ) _services . GetService ( typeof ( ILogger < App > ) ) ! ;
74
99
75
100
InitializeComponent ( ) ;
76
101
}
77
102
78
103
public async Task ExitApplication ( )
79
104
{
105
+ _logger . LogDebug ( "exiting app" ) ;
80
106
_handleWindowClosed = false ;
81
107
Exit ( ) ;
82
108
var syncController = _services . GetRequiredService < ISyncSessionController > ( ) ;
@@ -89,36 +115,39 @@ public async Task ExitApplication()
89
115
90
116
protected override void OnLaunched ( LaunchActivatedEventArgs args )
91
117
{
118
+ _logger . LogInformation ( "new instance launched" ) ;
92
119
// Start connecting to the manager in the background.
93
120
var rpcController = _services . GetRequiredService < IRpcController > ( ) ;
94
121
if ( rpcController . GetState ( ) . RpcLifecycle == RpcLifecycle . Disconnected )
95
122
// Passing in a CT with no cancellation is desired here, because
96
123
// the named pipe open will block until the pipe comes up.
97
- // TODO: log
98
- _ = rpcController . Reconnect ( CancellationToken . None ) . ContinueWith ( t =>
124
+ _logger . LogDebug ( "reconnecting with VPN service" ) ;
125
+ _ = rpcController . Reconnect ( CancellationToken . None ) . ContinueWith ( t =>
126
+ {
127
+ if ( t . Exception != null )
99
128
{
129
+ _logger . LogError ( t . Exception , "failed to connect to VPN service" ) ;
100
130
#if DEBUG
101
- if ( t . Exception != null )
102
- {
103
- Debug . WriteLine ( t . Exception ) ;
104
- Debugger . Break ( ) ;
105
- }
131
+ Debug . WriteLine ( t . Exception ) ;
132
+ Debugger . Break ( ) ;
106
133
#endif
107
- } ) ;
134
+ }
135
+ } ) ;
108
136
109
137
// Load the credentials in the background.
110
138
var credentialManagerCts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 15 ) ) ;
111
139
var credentialManager = _services . GetRequiredService < ICredentialManager > ( ) ;
112
140
_ = credentialManager . LoadCredentials ( credentialManagerCts . Token ) . ContinueWith ( t =>
113
141
{
114
- // TODO: log
115
- #if DEBUG
116
142
if ( t . Exception != null )
117
143
{
144
+ _logger . LogError ( t . Exception , "failed to load credentials" ) ;
145
+ #if DEBUG
118
146
Debug . WriteLine ( t . Exception ) ;
119
147
Debugger . Break ( ) ;
120
- }
121
148
#endif
149
+ }
150
+
122
151
credentialManagerCts . Dispose ( ) ;
123
152
} , CancellationToken . None ) ;
124
153
@@ -127,10 +156,14 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
127
156
var syncSessionController = _services . GetRequiredService < ISyncSessionController > ( ) ;
128
157
_ = syncSessionController . RefreshState ( syncSessionCts . Token ) . ContinueWith ( t =>
129
158
{
130
- // TODO: log
159
+ if ( t . IsCanceled || t . Exception != null )
160
+ {
161
+ _logger . LogError ( t . Exception , "failed to refresh sync state (canceled = {canceled})" , t . IsCanceled ) ;
131
162
#if DEBUG
132
- if ( t . IsCanceled || t . Exception != null ) Debugger . Break ( ) ;
163
+ Debugger . Break ( ) ;
133
164
#endif
165
+ }
166
+
134
167
syncSessionCts . Dispose ( ) ;
135
168
} , CancellationToken . None ) ;
136
169
@@ -143,4 +176,51 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
143
176
trayWindow . AppWindow . Hide ( ) ;
144
177
} ;
145
178
}
179
+
180
+ public void OnActivated ( object ? sender , AppActivationArguments args )
181
+ {
182
+ switch ( args . Kind )
183
+ {
184
+ case ExtendedActivationKind . Protocol :
185
+ var protoArgs = args . Data as IProtocolActivatedEventArgs ;
186
+ if ( protoArgs == null )
187
+ {
188
+ _logger . LogWarning ( "URI activation with null data" ) ;
189
+ return ;
190
+ }
191
+
192
+ HandleURIActivation ( protoArgs . Uri ) ;
193
+ break ;
194
+
195
+ default :
196
+ _logger . LogWarning ( "activation for {kind}, which is unhandled" , args . Kind ) ;
197
+ break ;
198
+ }
199
+ }
200
+
201
+ public void HandleURIActivation ( Uri uri )
202
+ {
203
+ // don't log the query string as that's where we include some sensitive information like passwords
204
+ _logger . LogInformation ( "handling URI activation for {path}" , uri . AbsolutePath ) ;
205
+ }
206
+
207
+ private static void AddDefaultConfig ( IConfigurationBuilder builder )
208
+ {
209
+ var logPath = Path . Combine (
210
+ Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) ,
211
+ "CoderDesktop" ,
212
+ logFilename ) ;
213
+ builder . AddInMemoryCollection ( new Dictionary < string , string ? >
214
+ {
215
+ [ MutagenControllerConfigSection + ":MutagenExecutablePath" ] = @"C:\mutagen.exe" ,
216
+ [ "Serilog:Using:0" ] = "Serilog.Sinks.File" ,
217
+ [ "Serilog:MinimumLevel" ] = "Information" ,
218
+ [ "Serilog:Enrich:0" ] = "FromLogContext" ,
219
+ [ "Serilog:WriteTo:0:Name" ] = "File" ,
220
+ [ "Serilog:WriteTo:0:Args:path" ] = logPath ,
221
+ [ "Serilog:WriteTo:0:Args:outputTemplate" ] =
222
+ "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}" ,
223
+ [ "Serilog:WriteTo:0:Args:rollingInterval" ] = "Day" ,
224
+ } ) ;
225
+ }
146
226
}
0 commit comments