From e8357bab1e9d6036caea868789fa2008015eb2cf Mon Sep 17 00:00:00 2001 From: Exest Date: Thu, 13 Aug 2026 10:10:13 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=202.0.4:?= =?UTF-8?q?=20=D0=B0=D0=B2=D1=82=D0=BE-=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=20Avalonia-=D0=BA=D0=BB?= =?UTF-8?q?=D0=B8=D0=B5=D0=BD=D1=82=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UpdateService (Core): проверка Gitea releases/latest + скачивание установщика с CDN - UpdateWindow/VM: уведомление «доступно обновление», тихая установка поверх с перезапуском - ShellView: проверка при старте (один раз, офлайн/ошибки игнорируются) Закрывает пробел: Avalonia-клиент раньше не умел обновляться (апдейтер был только в WinForms). Co-Authored-By: Claude Opus 4.8 --- installer/ElfisaPharmacy.iss | 2 +- src/Elfisa.Avalonia/Elfisa.Avalonia.csproj | 6 +- .../ViewModels/UpdateViewModel.cs | 95 ++++++++++++++++++ src/Elfisa.Avalonia/Views/ShellView.axaml.cs | 36 ++++++- src/Elfisa.Avalonia/Views/UpdateWindow.axaml | 28 ++++++ .../Views/UpdateWindow.axaml.cs | 9 ++ src/Elfisa.Core/UpdateService.cs | 96 +++++++++++++++++++ 7 files changed, 267 insertions(+), 5 deletions(-) create mode 100644 src/Elfisa.Avalonia/ViewModels/UpdateViewModel.cs create mode 100644 src/Elfisa.Avalonia/Views/UpdateWindow.axaml create mode 100644 src/Elfisa.Avalonia/Views/UpdateWindow.axaml.cs create mode 100644 src/Elfisa.Core/UpdateService.cs diff --git a/installer/ElfisaPharmacy.iss b/installer/ElfisaPharmacy.iss index 6bdad70..ba49fbd 100644 --- a/installer/ElfisaPharmacy.iss +++ b/installer/ElfisaPharmacy.iss @@ -3,7 +3,7 @@ #define MyAppName "Электронная Фармация" #define MyAppBrand "ЭльФиСА" -#define MyAppVersion "2.0.3" +#define MyAppVersion "2.0.4" #define MyAppPublisher "PharmData" #define MyAppURL "https://cdn.24pharmdata.ru" #define MyAppSupportURL "https://24pharmdata.ru" diff --git a/src/Elfisa.Avalonia/Elfisa.Avalonia.csproj b/src/Elfisa.Avalonia/Elfisa.Avalonia.csproj index bb8e0fe..c55b8ac 100644 --- a/src/Elfisa.Avalonia/Elfisa.Avalonia.csproj +++ b/src/Elfisa.Avalonia/Elfisa.Avalonia.csproj @@ -5,9 +5,9 @@ enable app.manifest true - 2.0.3 - 2.0.3.0 - 2.0.3.0 + 2.0.4 + 2.0.4.0 + 2.0.4.0 ЭльФиСА — Электронная Фармация PharmData diff --git a/src/Elfisa.Avalonia/ViewModels/UpdateViewModel.cs b/src/Elfisa.Avalonia/ViewModels/UpdateViewModel.cs new file mode 100644 index 0000000..bc18047 --- /dev/null +++ b/src/Elfisa.Avalonia/ViewModels/UpdateViewModel.cs @@ -0,0 +1,95 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Avalonia.Controls.ApplicationLifetimes; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Elfisa.Core; + +namespace Elfisa.Avalonia.ViewModels; + +/// Окно «Доступно обновление»: скачивает установщик и запускает его. +public partial class UpdateViewModel : ViewModelBase +{ + private readonly UpdateInfo _info; + private readonly Action _close; + + public UpdateViewModel(UpdateInfo info, Action close) + { + _info = info; + _close = close; + } + + public string Message => + $"Установлена версия {_info.CurrentVersion}.\nДоступна новая версия {_info.LatestVersion}."; + + [ObservableProperty] private bool _busy; + [ObservableProperty] private double _progress; + [ObservableProperty] private string _status = ""; + + [RelayCommand] + private void Later() => _close(); + + [RelayCommand] + private async Task UpdateAsync() + { + // Не-Windows: установщика нет — открываем страницу релиза в браузере. + if (!OperatingSystem.IsWindows()) + { + OpenUrl(string.IsNullOrWhiteSpace(_info.ReleaseUrl) ? "https://cdn.24pharmdata.ru" : _info.ReleaseUrl); + _close(); + return; + } + + try + { + Busy = true; + Status = "Скачивание обновления…"; + var progress = new Progress(p => + { + Progress = p; + Status = $"Скачивание обновления… {p:0}%"; + }); + var setupPath = await UpdateService.DownloadSetupAsync(progress); + + Status = "Запуск установки…"; + LaunchInstallerAndExit(setupPath); + } + catch (Exception ex) + { + Busy = false; + Status = "Не удалось обновить: " + ex.Message; + } + } + + /// Тихая установка поверх + перезапуск приложения, затем закрываем себя. + private void LaunchInstallerAndExit(string setupPath) + { + var exe = Process.GetCurrentProcess().MainModule?.FileName; + var inner = $"\"{setupPath}\" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART"; + if (!string.IsNullOrEmpty(exe)) + inner += $" && start \"\" \"{exe}\""; + + Process.Start(new ProcessStartInfo("cmd.exe") + { + Arguments = "/c \"" + inner + "\"", + UseShellExecute = false, + CreateNoWindow = true, + }); + + // Закрываем приложение, чтобы установщик заменил файлы (CloseApplications=yes). + if (global::Avalonia.Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime d) + d.Shutdown(); + else + Environment.Exit(0); + } + + private static void OpenUrl(string url) + { + try + { + Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); + } + catch { /* игнорируем */ } + } +} diff --git a/src/Elfisa.Avalonia/Views/ShellView.axaml.cs b/src/Elfisa.Avalonia/Views/ShellView.axaml.cs index 5aa2e3f..cd91df6 100644 --- a/src/Elfisa.Avalonia/Views/ShellView.axaml.cs +++ b/src/Elfisa.Avalonia/Views/ShellView.axaml.cs @@ -1,15 +1,25 @@ +using System; +using System.Reflection; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; +using Avalonia.Threading; using Elfisa.Avalonia.ViewModels; +using Elfisa.Core; namespace Elfisa.Avalonia.Views; public partial class ShellView : UserControl { - public ShellView() => AvaloniaXamlLoader.Load(this); + private static bool _updateChecked; // один раз за запуск + + public ShellView() + { + AvaloniaXamlLoader.Load(this); + Loaded += OnLoaded; + } private async void OnSettingsClick(object? sender, RoutedEventArgs e) { @@ -18,4 +28,28 @@ public partial class ShellView : UserControl if (owner is not null) await win.ShowDialog(owner); else win.Show(); } + + // Проверка обновлений при первом появлении шелла. Тихо игнорируем офлайн/ошибки. + private async void OnLoaded(object? sender, RoutedEventArgs e) + { + if (_updateChecked) return; + _updateChecked = true; + + try + { + var current = Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(0, 0, 0, 0); + var info = await UpdateService.CheckAsync(current); + if (info is not { Available: true }) return; + + await Dispatcher.UIThread.InvokeAsync(async () => + { + var win = new UpdateWindow(); + win.DataContext = new UpdateViewModel(info, win.Close); + var owner = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow; + if (owner is not null) await win.ShowDialog(owner); + else win.Show(); + }); + } + catch { /* обновление не должно мешать работе */ } + } } diff --git a/src/Elfisa.Avalonia/Views/UpdateWindow.axaml b/src/Elfisa.Avalonia/Views/UpdateWindow.axaml new file mode 100644 index 0000000..787c72f --- /dev/null +++ b/src/Elfisa.Avalonia/Views/UpdateWindow.axaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + +