/
githubmirror
/
Files
Обзор
Документация
Войти
/
githubmirror
/
Files
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Files.App/Services/App/AppUpdateStoreService.cs
288 строк
7 KB
Yair
Code Quality: Improve store update flow and error handling
05 май 2026, 02:39
05 май 2026, 02:39
779a127
Код
Авторство
О чём код?
// Copyright (c) Files Community // Licensed under the MIT License. using Microsoft.Extensions.Logging; using Microsoft.UI.Xaml.Controls; using System.IO; using System.Net.Http; using System.Text; using Windows.Foundation.Metadata; using Windows.Services.Store; using Windows.Storage; using WinRT.Interop; namespace Files.App.Services { internal sealed partial class StoreUpdateService : ObservableObject, IUpdateService { private StoreContext? _storeContext; private List<StorePackageUpdate>? _updatePackages; private bool IsMandatory => _updatePackages?.Where(e => e.Mandatory).ToList().Count >= 1; private bool _isUpdateAvailable; public bool IsUpdateAvailable { get => _isUpdateAvailable; set => SetProperty(ref _isUpdateAvailable, value); } private bool _isUpdating; public bool IsUpdating { get => _isUpdating; private set => SetProperty(ref _isUpdating, value); } private int _updateProgress; public int UpdateProgress { get => _updateProgress; private set => SetProperty(ref _updateProgress, value); } public bool IsAppUpdated { get => AppLifecycleHelper.IsAppUpdated; } private bool _areReleaseNotesAvailable = false; public bool AreReleaseNotesAvailable { get => _areReleaseNotesAvailable; private set => SetProperty(ref _areReleaseNotesAvailable, value); } public StoreUpdateService() { _updatePackages = []; } public async Task DownloadUpdatesAsync() { if (!HasUpdates()) return; OnUpdateInProgress(); // double check for Mandatory if (IsMandatory) { // Show dialog var dialog = await ShowDialogAsync(); if (!dialog) { // User rejected mandatory update. OnUpdateCancelled(); return; } } await DownloadAndInstallAsync(); OnUpdateCompleted(); } public async Task DownloadMandatoryUpdatesAsync() { // Prompt the user to download if the package list // contains mandatory updates. if (!IsMandatory || !HasUpdates()) return; // CheckAppUpdate runs inside Task.Run, so the consent dialog and the Store // download API must be marshalled to the UI thread to avoid hard crashes. await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () => { try { if (!await ShowDialogAsync()) return; App.Logger.LogInformation("STORE: Downloading updates..."); OnUpdateInProgress(); await DownloadAndInstallAsync(); OnUpdateCompleted(); } catch (Exception ex) { App.Logger.LogWarning(ex, "STORE: Mandatory update flow failed."); OnUpdateCancelled(); } }); } public async Task CheckForUpdatesAsync() { IsUpdateAvailable = false; App.Logger.LogInformation("STORE: Checking for updates..."); await GetUpdatePackagesAsync(); if (_updatePackages is not null && _updatePackages.Count > 0) { App.Logger.LogInformation("STORE: Update found."); MainWindow.Instance.DispatcherQueue.TryEnqueue(() => { IsUpdateAvailable = true; }); } } public async Task CheckForReleaseNotesAsync() { using var client = new HttpClient(); try { var response = await client.GetAsync(Constants.ExternalUrl.ReleaseNotesUrl); AreReleaseNotesAvailable = response.IsSuccessStatusCode; } catch { AreReleaseNotesAvailable = false; } } private async Task DownloadAndInstallAsync() { // Save the updated tab list before installing the update AppLifecycleHelper.SaveSessionTabs(); App.AppModel.ForceProcessTermination = true; StorePackageUpdateResult? result = null; try { var downloadOperation = _storeContext?.RequestDownloadAndInstallStorePackageUpdatesAsync(_updatePackages); if (downloadOperation is null) return; downloadOperation.Progress = (op, status) => { MainWindow.Instance.DispatcherQueue.TryEnqueue(() => { UpdateProgress = (int)(status.TotalDownloadProgress * 100); }); }; result = await downloadOperation.AsTask(); } catch (Exception ex) { App.Logger.LogWarning(ex, "STORE: Update download failed."); } finally { // Only keep ForceProcessTermination set when the Store actually completed // the install — otherwise the app would hard-exit on next close instead of // going to background. if (result is null || result.OverallState != StorePackageUpdateState.Completed) App.AppModel.ForceProcessTermination = false; } } private async Task GetUpdatePackagesAsync() { try { _storeContext ??= await Task.Run(StoreContext.GetDefault); InitializeWithWindow.Initialize(_storeContext, MainWindow.Instance.WindowHandle); var updateList = await _storeContext.GetAppAndOptionalStorePackageUpdatesAsync(); _updatePackages = updateList?.ToList(); } catch (Exception ex) { // GetAppAndOptionalStorePackageUpdatesAsync throws for unknown reasons. App.Logger.LogWarning(ex, ex.Message); } } private static async Task<bool> ShowDialogAsync() { //TODO: Use IDialogService in future. ContentDialog dialog = new() { Title = Strings.ConsentDialogTitle.GetLocalizedResource(), Content = Strings.ConsentDialogContent.GetLocalizedResource(), CloseButtonText = Strings.Close.GetLocalizedResource(), PrimaryButtonText = Strings.ConsentDialogPrimaryButtonText.GetLocalizedResource() }; if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) dialog.XamlRoot = MainWindow.Instance.Content.XamlRoot; ContentDialogResult result = await dialog.TryShowAsync(); return result == ContentDialogResult.Primary; } public async Task CheckAndUpdateFilesLauncherAsync() { var destFolderPath = Path.Combine(UserDataPaths.GetDefault().LocalAppData, "Files"); var destExeFilePath = Path.Combine(destFolderPath, "Files.App.Launcher.exe"); var branchFilePath = Path.Combine(destFolderPath, "Branch.txt"); // If Files.App.Launcher.exe doesn't exist, no need to update it. if (!File.Exists(destExeFilePath)) return; // Check if the launcher file is associated with the current branch of the app. if (File.Exists(branchFilePath)) { try { var branch = await File.ReadAllTextAsync(branchFilePath, Encoding.UTF8); if (!string.Equals(branch.Trim(), "files-dev", StringComparison.OrdinalIgnoreCase)) return; } catch { } } else { try { // Create branch file for users updating from versions earlier than v4.0.20. await File.WriteAllTextAsync(branchFilePath, "files-dev", Encoding.UTF8); } catch { } } var srcExeFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/FilesOpenDialog/Files.App.Launcher.exe")); var destFolder = await StorageFolder.GetFolderFromPathAsync(destFolderPath); await srcExeFile.CopyAsync(destFolder, "Files.App.Launcher.exe", NameCollisionOption.ReplaceExisting); App.Logger.LogInformation("Files.App.Launcher updated."); } private bool HasUpdates() { return _updatePackages is not null && _updatePackages.Count >= 1; } private void OnUpdateInProgress() { IsUpdating = true; } private void OnUpdateCompleted() { IsUpdating = false; IsUpdateAvailable = false; UpdateProgress = 0; _updatePackages?.Clear(); } private void OnUpdateCancelled() { IsUpdating = false; UpdateProgress = 0; } } }