-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathApp.xaml.cs
211 lines (184 loc) · 7.93 KB
/
App.xaml.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Coder.Desktop.App.Models;
using Coder.Desktop.App.Services;
using Coder.Desktop.App.ViewModels;
using Coder.Desktop.App.Views;
using Coder.Desktop.App.Views.Pages;
using Coder.Desktop.Vpn;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.UI.Xaml;
using Microsoft.Win32;
using Microsoft.Windows.AppLifecycle;
using Windows.ApplicationModel.Activation;
using Microsoft.Extensions.Logging;
using Serilog;
namespace Coder.Desktop.App;
public partial class App : Application
{
private readonly IServiceProvider _services;
private bool _handleWindowClosed = true;
private const string MutagenControllerConfigSection = "MutagenController";
private const string logTemplate =
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}";
#if !DEBUG
private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\App";
private const string logFilename = "app.log";
#else
private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\DebugApp";
private const string logFilename = "debug-app.log";
#endif
private readonly ILogger<App> _logger;
public App()
{
var builder = Host.CreateApplicationBuilder();
(builder.Configuration as IConfigurationBuilder).Add(
new RegistryConfigurationSource(Registry.LocalMachine, ConfigSubKey));
var services = builder.Services;
// Logging
builder.Services.AddSerilog((_, loggerConfig) =>
{
loggerConfig.ReadFrom.Configuration(builder.Configuration);
var sinkConfig = builder.Configuration.GetSection("Serilog").GetSection("WriteTo");
if (!sinkConfig.GetChildren().Any())
{
// no log sink defined in the registry, so we'll add one here.
// We can't generally define these in the registry because we don't
// know, a priori, what user will execute Coder Desktop, and therefore
// what directories are writable by them. But, it's nice to be able to
// directly customize Serilog via the registry if you know what you are
// doing.
var logPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"CoderDesktop",
logFilename);
loggerConfig.WriteTo.File(logPath, outputTemplate: logTemplate, rollingInterval: RollingInterval.Day);
}
});
services.AddSingleton<ICredentialManager, CredentialManager>();
services.AddSingleton<IRpcController, RpcController>();
services.AddOptions<MutagenControllerConfig>()
.Bind(builder.Configuration.GetSection(MutagenControllerConfigSection));
services.AddSingleton<ISyncSessionController, MutagenController>();
// SignInWindow views and view models
services.AddTransient<SignInViewModel>();
services.AddTransient<SignInWindow>();
// FileSyncListWindow views and view models
services.AddTransient<FileSyncListViewModel>();
// FileSyncListMainPage is created by FileSyncListWindow.
services.AddTransient<FileSyncListWindow>();
// TrayWindow views and view models
services.AddTransient<TrayWindowLoadingPage>();
services.AddTransient<TrayWindowDisconnectedViewModel>();
services.AddTransient<TrayWindowDisconnectedPage>();
services.AddTransient<TrayWindowLoginRequiredViewModel>();
services.AddTransient<TrayWindowLoginRequiredPage>();
services.AddTransient<TrayWindowLoginRequiredViewModel>();
services.AddTransient<TrayWindowLoginRequiredPage>();
services.AddTransient<TrayWindowViewModel>();
services.AddTransient<TrayWindowMainPage>();
services.AddTransient<TrayWindow>();
_services = services.BuildServiceProvider();
_logger = (ILogger<App>)(_services.GetService(typeof(ILogger<App>))!);
InitializeComponent();
}
public async Task ExitApplication()
{
_handleWindowClosed = false;
Exit();
var syncController = _services.GetRequiredService<ISyncSessionController>();
await syncController.DisposeAsync();
var rpcController = _services.GetRequiredService<IRpcController>();
// TODO: send a StopRequest if we're connected???
await rpcController.DisposeAsync();
Environment.Exit(0);
}
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
_logger.LogInformation("new instance launched");
// Start connecting to the manager in the background.
var rpcController = _services.GetRequiredService<IRpcController>();
if (rpcController.GetState().RpcLifecycle == RpcLifecycle.Disconnected)
// Passing in a CT with no cancellation is desired here, because
// the named pipe open will block until the pipe comes up.
// TODO: log
_ = rpcController.Reconnect(CancellationToken.None).ContinueWith(t =>
{
#if DEBUG
if (t.Exception != null)
{
Debug.WriteLine(t.Exception);
Debugger.Break();
}
#endif
});
// Load the credentials in the background.
var credentialManagerCts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
var credentialManager = _services.GetRequiredService<ICredentialManager>();
_ = credentialManager.LoadCredentials(credentialManagerCts.Token).ContinueWith(t =>
{
// TODO: log
if (t.Exception != null)
{
_logger.LogError(t.Exception, "failed to load credentials");
#if DEBUG
Debug.WriteLine(t.Exception);
Debugger.Break();
#endif
}
credentialManagerCts.Dispose();
}, CancellationToken.None);
// Initialize file sync.
var syncSessionCts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var syncSessionController = _services.GetRequiredService<ISyncSessionController>();
_ = syncSessionController.RefreshState(syncSessionCts.Token).ContinueWith(t =>
{
// TODO: log
if (t.IsCanceled || t.Exception != null)
{
_logger.LogError(t.Exception, "failed to refresh sync state (canceled = {canceled})", t.IsCanceled);
#if DEBUG
Debugger.Break();
#endif
}
syncSessionCts.Dispose();
}, CancellationToken.None);
// Prevent the TrayWindow from closing, just hide it.
var trayWindow = _services.GetRequiredService<TrayWindow>();
trayWindow.Closed += (_, closedArgs) =>
{
if (!_handleWindowClosed) return;
closedArgs.Handled = true;
trayWindow.AppWindow.Hide();
};
}
public void OnActivated(object? sender, AppActivationArguments args)
{
switch (args.Kind)
{
case ExtendedActivationKind.Protocol:
var protoArgs = args.Data as IProtocolActivatedEventArgs;
if (protoArgs == null)
{
_logger.LogWarning("URI activation with null data");
return;
}
HandleURIActivation(protoArgs.Uri);
break;
default:
_logger.LogWarning("activation for {kind}, which is unhandled", args.Kind);
break;
}
}
public void HandleURIActivation(Uri uri)
{
// don't log the query string as that's where we include some sensitive information like passwords
_logger.LogInformation("handling URI activation for {path}", uri.AbsolutePath);
}
}