Skip to content

Commit 9bc78d7

Browse files
authored
Code Quality: Introduced AppLifecycleHelper (#13999)
1 parent d4ec2a0 commit 9bc78d7

File tree

7 files changed

+393
-370
lines changed

7 files changed

+393
-370
lines changed

src/Files.App/App.xaml.cs

Lines changed: 55 additions & 366 deletions
Large diffs are not rendered by default.

src/Files.App/Constants.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ public static class AdaptiveLayout
1818
public const float ExtraSmallThreshold = 15.0f;
1919
}
2020

21+
// The following constants will be replaced with actual values by the Files CI workflow
22+
public static class AutomatedWorkflowInjectionKeys
23+
{
24+
public const string AppCenterSecret = "appcenter.secret";
25+
26+
public const string GitHubClientId = "githubclientid.secret";
27+
28+
public const string BingMapsSecret = "bingmapskey.secret";
29+
}
30+
2131
public static class KnownImageFormats
2232
{
2333
public const string BITMAP_IMAGE_FORMAT = "bitmapimage";
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
// Copyright (c) 2023 Files Community
2+
// Licensed under the MIT License. See the LICENSE.
3+
4+
using CommunityToolkit.WinUI.Notifications;
5+
using Files.App.Services.DateTimeFormatter;
6+
using Files.App.Services.Settings;
7+
using Files.App.Storage.FtpStorage;
8+
using Files.App.Storage.NativeStorage;
9+
using Files.App.ViewModels.Settings;
10+
using Files.Core.Services.SizeProvider;
11+
using Files.Core.Storage;
12+
using Microsoft.Extensions.DependencyInjection;
13+
using Microsoft.Extensions.Hosting;
14+
using Microsoft.Extensions.Logging;
15+
using System.IO;
16+
using System.Text;
17+
using Windows.Storage;
18+
using Windows.System;
19+
using Windows.UI.Notifications;
20+
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
21+
22+
namespace Files.App.Helpers
23+
{
24+
/// <summary>
25+
/// Provides static helper to manage app lifecycle.
26+
/// </summary>
27+
public static class AppLifecycleHelper
28+
{
29+
/// <summary>
30+
/// Initializes the app components.
31+
/// </summary>
32+
public static async Task InitializeAppComponentsAsync()
33+
{
34+
var userSettingsService = Ioc.Default.GetRequiredService<IUserSettingsService>();
35+
var addItemService = Ioc.Default.GetRequiredService<IAddItemService>();
36+
var generalSettingsService = userSettingsService.GeneralSettingsService;
37+
38+
// Start off a list of tasks we need to run before we can continue startup
39+
await Task.WhenAll(
40+
OptionalTaskAsync(CloudDrivesManager.UpdateDrivesAsync(), generalSettingsService.ShowCloudDrivesSection),
41+
App.LibraryManager.UpdateLibrariesAsync(),
42+
OptionalTaskAsync(WSLDistroManager.UpdateDrivesAsync(), generalSettingsService.ShowWslSection),
43+
OptionalTaskAsync(App.FileTagsManager.UpdateFileTagsAsync(), generalSettingsService.ShowFileTagsSection),
44+
App.QuickAccessManager.InitializeAsync()
45+
);
46+
47+
await Task.WhenAll(
48+
JumpListHelper.InitializeUpdatesAsync(),
49+
addItemService.InitializeAsync(),
50+
ContextMenu.WarmUpQueryContextMenuAsync()
51+
);
52+
53+
FileTagsHelper.UpdateTagsDb();
54+
55+
await CheckAppUpdate();
56+
57+
static Task OptionalTaskAsync(Task task, bool condition)
58+
{
59+
if (condition)
60+
return task;
61+
62+
return Task.CompletedTask;
63+
}
64+
}
65+
66+
/// <summary>
67+
/// Checks application updates and download if available.
68+
/// </summary>
69+
public static async Task CheckAppUpdate()
70+
{
71+
var updateService = Ioc.Default.GetRequiredService<IUpdateService>();
72+
73+
await updateService.CheckForUpdatesAsync();
74+
await updateService.DownloadMandatoryUpdatesAsync();
75+
await updateService.CheckAndUpdateFilesLauncherAsync();
76+
await updateService.CheckLatestReleaseNotesAsync();
77+
}
78+
79+
/// <summary>
80+
/// Configures AppCenter service, such as Analytics and Crash Report.
81+
/// </summary>
82+
public static void ConfigureAppCenter()
83+
{
84+
try
85+
{
86+
if (!Microsoft.AppCenter.AppCenter.Configured)
87+
{
88+
Microsoft.AppCenter.AppCenter.Start(
89+
Constants.AutomatedWorkflowInjectionKeys.AppCenterSecret,
90+
typeof(Microsoft.AppCenter.Analytics.Analytics),
91+
typeof(Microsoft.AppCenter.Crashes.Crashes));
92+
}
93+
}
94+
catch (Exception ex)
95+
{
96+
App.Logger.LogWarning(ex, "Failed to start AppCenter service.");
97+
}
98+
}
99+
100+
/// <summary>
101+
/// Configures DI (dependency injection) container.
102+
/// </summary>
103+
public static IHost ConfigureHost()
104+
{
105+
return Host.CreateDefaultBuilder()
106+
.UseEnvironment(ApplicationService.AppEnvironment.ToString())
107+
.ConfigureLogging(builder => builder
108+
.AddProvider(new FileLoggerProvider(Path.Combine(ApplicationData.Current.LocalFolder.Path, "debug.log")))
109+
.SetMinimumLevel(LogLevel.Information))
110+
.ConfigureServices(services => services
111+
// Settings services
112+
.AddSingleton<IUserSettingsService, UserSettingsService>()
113+
.AddSingleton<IAppearanceSettingsService, AppearanceSettingsService>(sp => new AppearanceSettingsService(((UserSettingsService)sp.GetRequiredService<IUserSettingsService>()).GetSharingContext()))
114+
.AddSingleton<IGeneralSettingsService, GeneralSettingsService>(sp => new GeneralSettingsService(((UserSettingsService)sp.GetRequiredService<IUserSettingsService>()).GetSharingContext()))
115+
.AddSingleton<IFoldersSettingsService, FoldersSettingsService>(sp => new FoldersSettingsService(((UserSettingsService)sp.GetRequiredService<IUserSettingsService>()).GetSharingContext()))
116+
.AddSingleton<IApplicationSettingsService, ApplicationSettingsService>(sp => new ApplicationSettingsService(((UserSettingsService)sp.GetRequiredService<IUserSettingsService>()).GetSharingContext()))
117+
.AddSingleton<IInfoPaneSettingsService, InfoPaneSettingsService>(sp => new InfoPaneSettingsService(((UserSettingsService)sp.GetRequiredService<IUserSettingsService>()).GetSharingContext()))
118+
.AddSingleton<ILayoutSettingsService, LayoutSettingsService>(sp => new LayoutSettingsService(((UserSettingsService)sp.GetRequiredService<IUserSettingsService>()).GetSharingContext()))
119+
.AddSingleton<IAppSettingsService, AppSettingsService>(sp => new AppSettingsService(((UserSettingsService)sp.GetRequiredService<IUserSettingsService>()).GetSharingContext()))
120+
.AddSingleton<IFileTagsSettingsService, FileTagsSettingsService>()
121+
// Contexts
122+
.AddSingleton<IPageContext, PageContext>()
123+
.AddSingleton<IContentPageContext, ContentPageContext>()
124+
.AddSingleton<IDisplayPageContext, DisplayPageContext>()
125+
.AddSingleton<IWindowContext, WindowContext>()
126+
.AddSingleton<IMultitaskingContext, MultitaskingContext>()
127+
.AddSingleton<ITagsContext, TagsContext>()
128+
// Services
129+
.AddSingleton<IDialogService, DialogService>()
130+
.AddSingleton<IImageService, ImagingService>()
131+
.AddSingleton<IThreadingService, ThreadingService>()
132+
.AddSingleton<ILocalizationService, LocalizationService>()
133+
.AddSingleton<ICloudDetector, CloudDetector>()
134+
.AddSingleton<IFileTagsService, FileTagsService>()
135+
.AddSingleton<ICommandManager, CommandManager>()
136+
.AddSingleton<IModifiableCommandManager, ModifiableCommandManager>()
137+
.AddSingleton<IApplicationService, ApplicationService>()
138+
.AddSingleton<IStorageService, NativeStorageService>()
139+
.AddSingleton<IFtpStorageService, FtpStorageService>()
140+
.AddSingleton<IAddItemService, AddItemService>()
141+
#if STABLE || PREVIEW
142+
.AddSingleton<IUpdateService, SideloadUpdateService>()
143+
#else
144+
.AddSingleton<IUpdateService, UpdateService>()
145+
#endif
146+
.AddSingleton<IPreviewPopupService, PreviewPopupService>()
147+
.AddSingleton<IDateTimeFormatterFactory, DateTimeFormatterFactory>()
148+
.AddSingleton<IDateTimeFormatter, UserDateTimeFormatter>()
149+
.AddSingleton<IVolumeInfoFactory, VolumeInfoFactory>()
150+
.AddSingleton<ISizeProvider, UserSizeProvider>()
151+
.AddSingleton<IQuickAccessService, QuickAccessService>()
152+
.AddSingleton<IResourcesService, ResourcesService>()
153+
.AddSingleton<IJumpListService, JumpListService>()
154+
.AddSingleton<IRemovableDrivesService, RemovableDrivesService>()
155+
.AddSingleton<INetworkDrivesService, NetworkDrivesService>()
156+
.AddSingleton<IStartMenuService, StartMenuService>()
157+
// ViewModels
158+
.AddSingleton<MainPageViewModel>()
159+
.AddSingleton<InfoPaneViewModel>()
160+
.AddSingleton<SidebarViewModel>()
161+
.AddSingleton<SettingsViewModel>()
162+
.AddSingleton<DrivesViewModel>()
163+
.AddSingleton<NetworkDrivesViewModel>()
164+
.AddSingleton<StatusCenterViewModel>()
165+
.AddSingleton<AppearanceViewModel>()
166+
// Utilities
167+
.AddSingleton<QuickAccessManager>()
168+
.AddSingleton<StorageHistoryWrapper>()
169+
.AddSingleton<FileTagsManager>()
170+
.AddSingleton<RecentItems>()
171+
.AddSingleton<LibraryManager>()
172+
.AddSingleton<AppModel>()
173+
).Build();
174+
}
175+
176+
/// <summary>
177+
/// Saves saves all opened tabs to the app cache.
178+
/// </summary>
179+
public static void SaveSessionTabs()
180+
{
181+
var userSettingsService = Ioc.Default.GetRequiredService<IUserSettingsService>();
182+
183+
userSettingsService.GeneralSettingsService.LastSessionTabList = MainPageViewModel.AppInstances.DefaultIfEmpty().Select(tab =>
184+
{
185+
if (tab is not null && tab.NavigationParameter is not null)
186+
{
187+
return tab.NavigationParameter.Serialize();
188+
}
189+
else
190+
{
191+
var defaultArg = new CustomTabViewItemParameter()
192+
{
193+
InitialPageType = typeof(PaneHolderPage),
194+
NavigationParameter = "Home"
195+
};
196+
197+
return defaultArg.Serialize();
198+
}
199+
})
200+
.ToList();
201+
}
202+
203+
/// <summary>
204+
/// Shows exception on the Debug Output and sends Toast Notification to the Windows Notification Center.
205+
/// </summary>
206+
public static void HandleAppUnhandledException(Exception? ex, bool showToastNotification)
207+
{
208+
StringBuilder formattedException = new()
209+
{
210+
Capacity = 200
211+
};
212+
213+
formattedException.AppendLine("--------- UNHANDLED EXCEPTION ---------");
214+
215+
if (ex is not null)
216+
{
217+
formattedException.AppendLine($">>>> HRESULT: {ex.HResult}");
218+
219+
if (ex.Message is not null)
220+
{
221+
formattedException.AppendLine("--- MESSAGE ---");
222+
formattedException.AppendLine(ex.Message);
223+
}
224+
if (ex.StackTrace is not null)
225+
{
226+
formattedException.AppendLine("--- STACKTRACE ---");
227+
formattedException.AppendLine(ex.StackTrace);
228+
}
229+
if (ex.Source is not null)
230+
{
231+
formattedException.AppendLine("--- SOURCE ---");
232+
formattedException.AppendLine(ex.Source);
233+
}
234+
if (ex.InnerException is not null)
235+
{
236+
formattedException.AppendLine("--- INNER ---");
237+
formattedException.AppendLine(ex.InnerException.ToString());
238+
}
239+
}
240+
else
241+
{
242+
formattedException.AppendLine("Exception data is not available.");
243+
}
244+
245+
formattedException.AppendLine("---------------------------------------");
246+
247+
Debug.WriteLine(formattedException.ToString());
248+
249+
// Please check "Output Window" for exception details (View -> Output Window) (CTRL + ALT + O)
250+
Debugger.Break();
251+
252+
SaveSessionTabs();
253+
App.Logger.LogError(ex, ex?.Message ?? "An unhandled error occurred.");
254+
255+
if (!showToastNotification)
256+
return;
257+
258+
var toastContent = new ToastContent()
259+
{
260+
Visual = new()
261+
{
262+
BindingGeneric = new ToastBindingGeneric()
263+
{
264+
Children =
265+
{
266+
new AdaptiveText()
267+
{
268+
Text = "ExceptionNotificationHeader".GetLocalizedResource()
269+
},
270+
new AdaptiveText()
271+
{
272+
Text = "ExceptionNotificationBody".GetLocalizedResource()
273+
}
274+
},
275+
AppLogoOverride = new()
276+
{
277+
Source = "ms-appx:///Assets/error.png"
278+
}
279+
}
280+
},
281+
Actions = new ToastActionsCustom()
282+
{
283+
Buttons =
284+
{
285+
new ToastButton("ExceptionNotificationReportButton".GetLocalizedResource(), Constants.GitHub.BugReportUrl)
286+
{
287+
ActivationType = ToastActivationType.Protocol
288+
}
289+
}
290+
},
291+
ActivationType = ToastActivationType.Protocol
292+
};
293+
294+
// Create the toast notification
295+
var toastNotification = new ToastNotification(toastContent.GetXml());
296+
297+
// And send the notification
298+
ToastNotificationManager.CreateToastNotifier().Show(toastNotification);
299+
300+
// Restart the app
301+
var userSettingsService = Ioc.Default.GetRequiredService<IUserSettingsService>();
302+
var lastSessionTabList = userSettingsService.GeneralSettingsService.LastSessionTabList;
303+
304+
if (userSettingsService.GeneralSettingsService.LastCrashedTabList?.SequenceEqual(lastSessionTabList) ?? false)
305+
{
306+
// Avoid infinite restart loop
307+
userSettingsService.GeneralSettingsService.LastSessionTabList = null;
308+
}
309+
else
310+
{
311+
userSettingsService.AppSettingsService.RestoreTabsOnStartup = true;
312+
userSettingsService.GeneralSettingsService.LastCrashedTabList = lastSessionTabList;
313+
314+
// Try to re-launch and start over
315+
MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () =>
316+
{
317+
await Launcher.LaunchUriAsync(new Uri("files-uwp:"));
318+
})
319+
.Wait(1000);
320+
}
321+
Process.GetCurrentProcess().Kill();
322+
}
323+
}
324+
}

src/Files.App/Helpers/LocationHelpers.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public static async Task<string> GetAddressFromCoordinatesAsync(double? Lat, dou
1717
{
1818
try
1919
{
20-
MapService.ServiceToken = "bingmapskey.secret";
20+
MapService.ServiceToken = Constants.AutomatedWorkflowInjectionKeys.BingMapsSecret;
2121
}
2222
catch (Exception)
2323
{

src/Files.App/Services/UpdateService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public async Task CheckForUpdatesAsync()
109109

110110
private async Task DownloadAndInstallAsync()
111111
{
112-
App.SaveSessionTabs();
112+
AppLifecycleHelper.SaveSessionTabs();
113113
App.AppModel.ForceProcessTermination = true;
114114
var downloadOperation = _storeContext?.RequestDownloadAndInstallStorePackageUpdatesAsync(_updatePackages);
115115
var result = await downloadOperation.AsTask();

src/Files.App/Utils/Git/GitHelpers.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ internal static class GitHelpers
2121

2222
private const string GIT_RESOURCE_USERNAME = "Personal Access Token";
2323

24-
private const string CLIENT_ID_SECRET = "githubclientid.secret";
24+
private const string CLIENT_ID_SECRET = Constants.AutomatedWorkflowInjectionKeys.GitHubClientId;
2525

2626
private const int END_OF_ORIGIN_PREFIX = 7;
2727

src/Files.App/ViewModels/Settings/GeneralViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public GeneralViewModel()
116116
private async void DoRestartAsync()
117117
{
118118
UserSettingsService.AppSettingsService.RestoreTabsOnStartup = true; // Tells the app to restore tabs when it's next launched
119-
App.SaveSessionTabs(); // Saves the open tabs
119+
AppLifecycleHelper.SaveSessionTabs(); // Saves the open tabs
120120
await Launcher.LaunchUriAsync(new Uri("files-uwp:")); // Launches a new instance of Files
121121
Process.GetCurrentProcess().Kill(); // Closes the current instance
122122
}

0 commit comments

Comments
 (0)