941 lines
37 KiB
C#
941 lines
37 KiB
C#
using Ink_Canvas.Helpers;
|
|
using Ink_Canvas.Windows.SettingsViews.Helpers;
|
|
using iNKORE.UI.WPF.Modern.Common.IconKeys;
|
|
using iNKORE.UI.WPF.Modern.Controls;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using System.Windows.Threading;
|
|
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
|
|
|
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
|
{
|
|
public partial class UpdatePage : iNKORE.UI.WPF.Modern.Controls.Page
|
|
{
|
|
private enum UpdateUiState
|
|
{
|
|
Idle,
|
|
Checking,
|
|
UpdateAvailable,
|
|
Downloading,
|
|
Downloaded,
|
|
NetworkError
|
|
}
|
|
|
|
private class VersionItem
|
|
{
|
|
public string Version { get; set; }
|
|
public string DownloadUrl { get; set; }
|
|
public string ReleaseNotes { get; set; }
|
|
}
|
|
|
|
private bool _isLoaded;
|
|
private bool _isChangingUpdateChannelInternally;
|
|
private bool _isChangingUpdatePackageArchInternally;
|
|
|
|
private UpdateUiState _state = UpdateUiState.Idle;
|
|
private string _remoteVersion;
|
|
private AutoUpdateHelper.UpdateLineGroup _remoteLineGroup;
|
|
private string _remoteReleaseNotes;
|
|
|
|
private List<VersionItem> _versionList = new List<VersionItem>();
|
|
private VersionItem _selectedHistoricalItem;
|
|
private bool _isHistoryLoaded;
|
|
|
|
public UpdatePage()
|
|
{
|
|
InitializeComponent();
|
|
Loaded += UpdatePage_Loaded;
|
|
}
|
|
|
|
private async void UpdatePage_Loaded(object sender, RoutedEventArgs e)
|
|
{
|
|
LoadSettings();
|
|
_isLoaded = true;
|
|
|
|
// 复用启动时自动检查的结果,避免二次检查
|
|
var mainWindow = Application.Current.MainWindow as MainWindow;
|
|
if (mainWindow != null && !string.IsNullOrEmpty(mainWindow.AvailableLatestVersion))
|
|
{
|
|
_remoteVersion = mainWindow.AvailableLatestVersion;
|
|
_remoteLineGroup = mainWindow.AvailableLatestLineGroup;
|
|
_remoteReleaseNotes = mainWindow.AvailableLatestReleaseNotes;
|
|
|
|
try
|
|
{
|
|
var statusFile = AutoUpdateHelper.GetUpdateDownloadStatusFilePath(_remoteVersion);
|
|
if (System.IO.File.Exists(statusFile) &&
|
|
System.IO.File.ReadAllText(statusFile).Trim().ToLower() == "true")
|
|
{
|
|
ApplyState(UpdateUiState.Downloaded);
|
|
}
|
|
else
|
|
{
|
|
ApplyState(UpdateUiState.UpdateAvailable);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
ApplyState(UpdateUiState.UpdateAvailable);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(_remoteReleaseNotes))
|
|
ChangelogViewer.Markdown = _remoteReleaseNotes;
|
|
else
|
|
ChangelogViewer.Markdown = "切换到 *历史版本* 或点击 *检查更新* 查看具体更新日志。";
|
|
return;
|
|
}
|
|
|
|
// 没有缓存的检查结果时,仅展示空白;不主动联网,避免打开页面就触发请求
|
|
ApplyState(UpdateUiState.Idle);
|
|
ChangelogViewer.Markdown = "点击 *检查更新* 来获取最新版本及更新日志。";
|
|
await System.Threading.Tasks.Task.CompletedTask;
|
|
}
|
|
|
|
#region 更新设置加载
|
|
|
|
private void LoadSettings()
|
|
{
|
|
_isLoaded = false;
|
|
|
|
try
|
|
{
|
|
var settings = SettingsManager.Settings;
|
|
if (settings.Startup != null)
|
|
{
|
|
CardAutoUpdate.IsOn = settings.Startup.IsAutoUpdate;
|
|
CardSilentUpdate.IsOn = settings.Startup.IsAutoUpdateWithSilence;
|
|
|
|
AutoUpdateWithSilenceTimeComboBox.InitializeAutoUpdateWithSilenceTimeComboBoxOptions(
|
|
AutoUpdateWithSilenceStartTimeComboBox, AutoUpdateWithSilenceEndTimeComboBox);
|
|
AutoUpdateWithSilenceStartTimeComboBox.SelectedItem = settings.Startup.AutoUpdateWithSilenceStartTime;
|
|
AutoUpdateWithSilenceEndTimeComboBox.SelectedItem = settings.Startup.AutoUpdateWithSilenceEndTime;
|
|
|
|
foreach (var item in UpdateChannelSelector.Items)
|
|
{
|
|
if (item is ComboBoxItem cbi && cbi.Tag != null &&
|
|
string.Equals(cbi.Tag.ToString(), settings.Startup.UpdateChannel.ToString(), StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
UpdateChannelSelector.SelectedItem = cbi;
|
|
break;
|
|
}
|
|
}
|
|
|
|
_isChangingUpdatePackageArchInternally = true;
|
|
try
|
|
{
|
|
string wantTag = settings.Startup.UpdatePackageArchitecture == UpdatePackageArchitecture.X64 ? "X64" : "X86";
|
|
foreach (var item in UpdatePackageArchitectureSelector.Items)
|
|
{
|
|
if (item is ComboBoxItem cbi && cbi.Tag != null &&
|
|
string.Equals(cbi.Tag.ToString(), wantTag, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
UpdatePackageArchitectureSelector.SelectedItem = cbi;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_isChangingUpdatePackageArchInternally = false;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"加载更新设置时出错: {ex.Message}");
|
|
}
|
|
|
|
_isLoaded = true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 自动更新事件处理
|
|
|
|
private void ToggleSwitchIsAutoUpdate_Toggled(object sender, RoutedEventArgs e)
|
|
{
|
|
if (!_isLoaded) return;
|
|
|
|
try
|
|
{
|
|
bool newState = CardAutoUpdate.IsOn;
|
|
SettingsManager.Settings.Startup.IsAutoUpdate = newState;
|
|
|
|
if (!newState)
|
|
{
|
|
SettingsManager.Settings.Startup.IsAutoUpdateWithSilence = false;
|
|
CardSilentUpdate.IsOn = false;
|
|
}
|
|
|
|
SettingsManager.SaveSettingsToFile();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"设置自动更新时出错: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void ToggleSwitchIsAutoUpdateWithSilence_Toggled(object sender, RoutedEventArgs e)
|
|
{
|
|
if (!_isLoaded) return;
|
|
|
|
try
|
|
{
|
|
SettingsManager.Settings.Startup.IsAutoUpdateWithSilence = CardSilentUpdate.IsOn;
|
|
SettingsManager.SaveSettingsToFile();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"设置静默更新时出错: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void AutoUpdateWithSilenceStartTimeComboBox_SelectionChanged(object sender, RoutedEventArgs e)
|
|
{
|
|
if (!_isLoaded) return;
|
|
|
|
try
|
|
{
|
|
SettingsManager.Settings.Startup.AutoUpdateWithSilenceStartTime =
|
|
(string)AutoUpdateWithSilenceStartTimeComboBox.SelectedItem;
|
|
SettingsManager.SaveSettingsToFile();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"设置静默更新开始时间时出错: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void AutoUpdateWithSilenceEndTimeComboBox_SelectionChanged(object sender, RoutedEventArgs e)
|
|
{
|
|
if (!_isLoaded) return;
|
|
|
|
try
|
|
{
|
|
SettingsManager.Settings.Startup.AutoUpdateWithSilenceEndTime =
|
|
(string)AutoUpdateWithSilenceEndTimeComboBox.SelectedItem;
|
|
SettingsManager.SaveSettingsToFile();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"设置静默更新结束时间时出错: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 更新通道和架构事件处理
|
|
|
|
private void UpdatePackageArchitectureSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (!_isLoaded) return;
|
|
if (_isChangingUpdatePackageArchInternally) return;
|
|
if (!(UpdatePackageArchitectureSelector.SelectedItem is ComboBoxItem cbi) || cbi.Tag == null) return;
|
|
|
|
var newArch = string.Equals(cbi.Tag.ToString(), "X64", StringComparison.OrdinalIgnoreCase)
|
|
? UpdatePackageArchitecture.X64
|
|
: UpdatePackageArchitecture.X86;
|
|
|
|
if (SettingsManager.Settings.Startup.UpdatePackageArchitecture == newArch)
|
|
return;
|
|
|
|
SettingsManager.Settings.Startup.UpdatePackageArchitecture = newArch;
|
|
SettingsManager.SaveSettingsToFile();
|
|
LogHelper.WriteLogToFile($"Settings | Update package architecture: {newArch}");
|
|
}
|
|
|
|
private async void UpdateChannelSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (!_isLoaded) return;
|
|
if (_isChangingUpdateChannelInternally) return;
|
|
if (!(UpdateChannelSelector.SelectedItem is ComboBoxItem cbi) || cbi.Tag == null) return;
|
|
|
|
var oldChannel = SettingsManager.Settings.Startup.UpdateChannel;
|
|
string channel = cbi.Tag.ToString();
|
|
UpdateChannel newChannel = channel == "Beta" ? UpdateChannel.Beta
|
|
: channel == "Preview" ? UpdateChannel.Preview
|
|
: UpdateChannel.Release;
|
|
|
|
if (SettingsManager.Settings.Startup.UpdateChannel == newChannel)
|
|
return;
|
|
|
|
bool isTestChannel = newChannel == UpdateChannel.Preview || newChannel == UpdateChannel.Beta;
|
|
|
|
if (isTestChannel && !SettingsManager.Settings.Startup.HasAcceptedTelemetryPrivacy)
|
|
{
|
|
MessageBox.Show(
|
|
"加入预览 / 测试通道前,请先在关于页面勾选“我已阅读并同意 privacy 中的隐私说明”。",
|
|
"需要同意隐私说明",
|
|
MessageBoxButton.OK,
|
|
MessageBoxImage.Warning);
|
|
|
|
SettingsManager.Settings.Startup.UpdateChannel = oldChannel;
|
|
RevertChannelSelection(oldChannel);
|
|
SettingsManager.SaveSettingsToFile();
|
|
LogHelper.WriteLogToFile("Settings | User not accepted privacy, reverted update channel");
|
|
return;
|
|
}
|
|
|
|
if (isTestChannel && SettingsManager.Settings.Startup.TelemetryUploadLevel == TelemetryUploadLevel.None)
|
|
{
|
|
var result = MessageBox.Show(
|
|
"加入预览 / 测试通道需要开启匿名基础数据上传。\n\n是否立即开启匿名基础数据上传?",
|
|
"需要开启匿名使用数据上传",
|
|
MessageBoxButton.YesNo,
|
|
MessageBoxImage.Warning);
|
|
|
|
if (result == MessageBoxResult.Yes)
|
|
{
|
|
SettingsManager.Settings.Startup.TelemetryUploadLevel = TelemetryUploadLevel.Basic;
|
|
SettingsManager.SaveSettingsToFile();
|
|
LogHelper.WriteLogToFile("Settings | Telemetry enabled (Basic) for preview/beta update channel");
|
|
}
|
|
else
|
|
{
|
|
SettingsManager.Settings.Startup.UpdateChannel = oldChannel;
|
|
RevertChannelSelection(oldChannel);
|
|
SettingsManager.SaveSettingsToFile();
|
|
LogHelper.WriteLogToFile("Settings | User declined telemetry, reverted update channel");
|
|
return;
|
|
}
|
|
}
|
|
|
|
SettingsManager.Settings.Startup.UpdateChannel = newChannel;
|
|
DeviceIdentifier.UpdateUsageChannel(newChannel);
|
|
LogHelper.WriteLogToFile($"Settings | Update channel changed to {SettingsManager.Settings.Startup.UpdateChannel}");
|
|
SettingsManager.SaveSettingsToFile();
|
|
|
|
// 通道切换后强制刷新更新日志和历史版本缓存
|
|
_isHistoryLoaded = false;
|
|
_versionList.Clear();
|
|
VersionComboBox.ItemsSource = null;
|
|
ReleaseNotesViewer.Markdown = "";
|
|
await LoadChangelogAsync();
|
|
|
|
if (SettingsManager.Settings.Startup.IsAutoUpdate)
|
|
{
|
|
LogHelper.WriteLogToFile($"AutoUpdate | Channel changed to {newChannel}, performing immediate update check");
|
|
|
|
var mainWindow = Application.Current.MainWindow as MainWindow;
|
|
if (mainWindow != null)
|
|
{
|
|
mainWindow.ResetUpdateCheckRetry();
|
|
await System.Threading.Tasks.Task.Run(() =>
|
|
{
|
|
try
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
mainWindow.AutoUpdate();
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"AutoUpdate | Error during channel switch update check: {ex.Message}", LogHelper.LogType.Error);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RevertChannelSelection(UpdateChannel targetChannel)
|
|
{
|
|
Dispatcher.BeginInvoke(new Action(() =>
|
|
{
|
|
_isChangingUpdateChannelInternally = true;
|
|
try
|
|
{
|
|
string targetTag = targetChannel.ToString();
|
|
foreach (var item in UpdateChannelSelector.Items)
|
|
{
|
|
if (item is ComboBoxItem cbi && cbi.Tag != null &&
|
|
string.Equals(cbi.Tag.ToString(), targetTag, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
UpdateChannelSelector.SelectedItem = cbi;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_isChangingUpdateChannelInternally = false;
|
|
}
|
|
}), DispatcherPriority.Normal);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 更新状态机
|
|
|
|
private void ApplyState(UpdateUiState state, string customSubtitle = null)
|
|
{
|
|
_state = state;
|
|
|
|
CheckUpdateButton.Visibility = Visibility.Collapsed;
|
|
UpdateNowButton.Visibility = Visibility.Collapsed;
|
|
UpdateLaterButton.Visibility = Visibility.Collapsed;
|
|
SkipVersionButton.Visibility = Visibility.Collapsed;
|
|
CancelDownloadButton.Visibility = Visibility.Collapsed;
|
|
ProgressPanel.Visibility = Visibility.Collapsed;
|
|
|
|
CheckUpdateButton.IsEnabled = true;
|
|
|
|
switch (state)
|
|
{
|
|
case UpdateUiState.Idle:
|
|
StatusIcon.Icon = SegoeFluentIcons.Completed;
|
|
StatusTitle.Text = "已是最新版本";
|
|
StatusSubtitle.Text = customSubtitle ?? BuildLastCheckSubtitle();
|
|
CheckUpdateButton.Visibility = Visibility.Visible;
|
|
break;
|
|
|
|
case UpdateUiState.Checking:
|
|
StatusIcon.Icon = SegoeFluentIcons.Sync;
|
|
StatusTitle.Text = "正在检查更新...";
|
|
StatusSubtitle.Text = "";
|
|
CheckUpdateButton.Visibility = Visibility.Visible;
|
|
CheckUpdateButton.IsEnabled = false;
|
|
ProgressPanel.Visibility = Visibility.Visible;
|
|
ProgressText.Text = "正在连接更新服务器...";
|
|
ProgressBar.IsIndeterminate = true;
|
|
break;
|
|
|
|
case UpdateUiState.UpdateAvailable:
|
|
StatusIcon.Icon = SegoeFluentIcons.Upload;
|
|
StatusTitle.Text = $"检测到新版本 {_remoteVersion}";
|
|
StatusSubtitle.Text = customSubtitle ?? $"当前版本 {GetCurrentVersion()} → {_remoteVersion}";
|
|
UpdateNowButton.Visibility = Visibility.Visible;
|
|
UpdateLaterButton.Visibility = Visibility.Visible;
|
|
SkipVersionButton.Visibility = Visibility.Visible;
|
|
break;
|
|
|
|
case UpdateUiState.Downloading:
|
|
StatusIcon.Icon = SegoeFluentIcons.Download;
|
|
StatusTitle.Text = "正在下载更新...";
|
|
StatusSubtitle.Text = customSubtitle ?? $"目标版本 {_remoteVersion}";
|
|
ProgressPanel.Visibility = Visibility.Visible;
|
|
ProgressBar.IsIndeterminate = false;
|
|
break;
|
|
|
|
case UpdateUiState.Downloaded:
|
|
StatusIcon.Icon = SegoeFluentIcons.Download;
|
|
StatusTitle.Text = "更新已下载完成";
|
|
StatusSubtitle.Text = customSubtitle ?? $"将在软件关闭时自动安装 {_remoteVersion}";
|
|
CheckUpdateButton.Visibility = Visibility.Visible;
|
|
break;
|
|
|
|
case UpdateUiState.NetworkError:
|
|
StatusIcon.Icon = SegoeFluentIcons.Error;
|
|
StatusTitle.Text = "网络错误";
|
|
StatusSubtitle.Text = customSubtitle ?? "请检查网络连接后重试。";
|
|
CheckUpdateButton.Visibility = Visibility.Visible;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private string BuildLastCheckSubtitle()
|
|
{
|
|
string current = GetCurrentVersion();
|
|
return $"当前版本 {current}";
|
|
}
|
|
|
|
private static string GetCurrentVersion()
|
|
{
|
|
try
|
|
{
|
|
return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
|
}
|
|
catch
|
|
{
|
|
return "未知";
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 更新日志
|
|
|
|
private async System.Threading.Tasks.Task LoadChangelogAsync()
|
|
{
|
|
try
|
|
{
|
|
ChangelogViewer.Markdown = "正在加载更新日志...";
|
|
|
|
// 优先尝试从 GitHub API 获取最新 Release 的 body(带超时,失败则回退到镜像)
|
|
try
|
|
{
|
|
var apiTask = AutoUpdateHelper.GetAllGithubReleases(SettingsManager.Settings.Startup.UpdateChannel);
|
|
var completed = await System.Threading.Tasks.Task.WhenAny(apiTask, System.Threading.Tasks.Task.Delay(TimeSpan.FromSeconds(8)));
|
|
if (completed == apiTask)
|
|
{
|
|
var releases = await apiTask;
|
|
var latest = releases?
|
|
.OrderByDescending(r => ParseVersionForSort(r.version))
|
|
.Select(r => (Tuple<string, string, string>)Tuple.Create(r.version, r.downloadUrl, r.releaseNotes))
|
|
.FirstOrDefault();
|
|
if (latest != null && !string.IsNullOrWhiteSpace(latest.Item3))
|
|
{
|
|
ChangelogViewer.Markdown = latest.Item3;
|
|
return;
|
|
}
|
|
LogHelper.WriteLogToFile("UpdatePage | GitHub API 未返回可用的更新日志,回退到镜像", LogHelper.LogType.Warning);
|
|
}
|
|
else
|
|
{
|
|
LogHelper.WriteLogToFile("UpdatePage | GitHub API 获取更新日志超时,回退到镜像", LogHelper.LogType.Warning);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"UpdatePage | GitHub API 获取更新日志失败,回退到镜像: {ex.Message}", LogHelper.LogType.Warning);
|
|
}
|
|
|
|
// 回退到镜像源 UpdateLog.md
|
|
string md = await AutoUpdateHelper.GetUpdateLog(SettingsManager.Settings.Startup.UpdateChannel);
|
|
ChangelogViewer.Markdown = string.IsNullOrEmpty(md) ? "暂无更新日志。" : md;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ChangelogViewer.Markdown = $"加载更新日志失败:{ex.Message}";
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 检查 / 下载 / 安装
|
|
|
|
private async void CheckUpdateButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
ApplyState(UpdateUiState.Checking);
|
|
try
|
|
{
|
|
LogHelper.WriteLogToFile("ManualUpdate | Manual update button clicked");
|
|
|
|
string remoteVersion = null;
|
|
string apiReleaseNotes = null;
|
|
AutoUpdateHelper.UpdateLineGroup lineGroup = null;
|
|
|
|
// 优先通过 GitHub Releases API 获取最新版本
|
|
try
|
|
{
|
|
var releases = await AutoUpdateHelper.GetAllGithubReleases(SettingsManager.Settings.Startup.UpdateChannel);
|
|
var latest = releases?
|
|
.OrderByDescending(r => ParseVersionForSort(r.version))
|
|
.Select(r => Tuple.Create(r.version, r.downloadUrl, r.releaseNotes))
|
|
.FirstOrDefault();
|
|
if (latest != null && !string.IsNullOrEmpty(latest.Item1))
|
|
{
|
|
var localVersion = new Version(GetCurrentVersion());
|
|
var remote = ParseVersionForSort(latest.Item1);
|
|
if (remote > localVersion)
|
|
{
|
|
remoteVersion = latest.Item1.TrimStart('v', 'V');
|
|
apiReleaseNotes = latest.Item3;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"UpdatePage | GitHub API 检查更新失败,回退到 CheckForUpdates: {ex.Message}", LogHelper.LogType.Warning);
|
|
}
|
|
|
|
// 回退:调用统一的 CheckForUpdates(包含镜像源 txt 方案)
|
|
if (string.IsNullOrEmpty(remoteVersion))
|
|
{
|
|
var (rv, lg, notes) = await AutoUpdateHelper.CheckForUpdates(
|
|
SettingsManager.Settings.Startup.UpdateChannel, true, false);
|
|
remoteVersion = rv;
|
|
lineGroup = lg;
|
|
if (string.IsNullOrEmpty(apiReleaseNotes)) apiReleaseNotes = notes;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(remoteVersion))
|
|
{
|
|
_remoteVersion = remoteVersion;
|
|
_remoteLineGroup = lineGroup;
|
|
_remoteReleaseNotes = apiReleaseNotes;
|
|
|
|
if (!string.IsNullOrEmpty(apiReleaseNotes))
|
|
ChangelogViewer.Markdown = apiReleaseNotes;
|
|
|
|
ApplyState(UpdateUiState.UpdateAvailable);
|
|
}
|
|
else
|
|
{
|
|
ApplyState(UpdateUiState.Idle);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"Error in CheckUpdateButton_Click: {ex.Message}", LogHelper.LogType.Error);
|
|
ApplyState(UpdateUiState.NetworkError, ex.Message);
|
|
}
|
|
}
|
|
|
|
private bool _downloadCancelled;
|
|
private static List<AutoUpdateHelper.UpdateLineGroup> _cachedOrderedGroups;
|
|
private static UpdateChannel _cachedGroupsChannel;
|
|
|
|
private static async System.Threading.Tasks.Task<List<AutoUpdateHelper.UpdateLineGroup>> GetOrderedGroupsCachedAsync(UpdateChannel channel)
|
|
{
|
|
if (_cachedOrderedGroups != null && _cachedOrderedGroups.Count > 0 && _cachedGroupsChannel == channel)
|
|
return _cachedOrderedGroups;
|
|
var groups = await AutoUpdateHelper.GetAvailableLineGroupsOrdered(channel);
|
|
_cachedOrderedGroups = groups;
|
|
_cachedGroupsChannel = channel;
|
|
return groups;
|
|
}
|
|
|
|
private async System.Threading.Tasks.Task<bool> DownloadWithProgressAsync()
|
|
{
|
|
_downloadCancelled = false;
|
|
var groups = await GetOrderedGroupsCachedAsync(SettingsManager.Settings.Startup.UpdateChannel);
|
|
if (groups == null || groups.Count == 0)
|
|
{
|
|
LogHelper.WriteLogToFile("UpdatePage | 没有可用的下载线路组", LogHelper.LogType.Error);
|
|
return false;
|
|
}
|
|
return await AutoUpdateHelper.DownloadSetupFileWithFallback(_remoteVersion, groups, (percent, text) =>
|
|
{
|
|
if (_downloadCancelled) return;
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
if (_state != UpdateUiState.Downloading) return;
|
|
ProgressBar.IsIndeterminate = false;
|
|
ProgressBar.Value = percent;
|
|
ProgressText.Text = text;
|
|
});
|
|
});
|
|
}
|
|
|
|
private async void UpdateNowButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (string.IsNullOrEmpty(_remoteVersion)) return;
|
|
|
|
ApplyState(UpdateUiState.Downloading);
|
|
CancelDownloadButton.Visibility = Visibility.Visible;
|
|
ProgressBar.Value = 0;
|
|
ProgressText.Text = "正在准备下载...";
|
|
|
|
try
|
|
{
|
|
bool ok = await DownloadWithProgressAsync();
|
|
|
|
if (!ok)
|
|
{
|
|
ApplyState(UpdateUiState.NetworkError, "更新下载失败,请检查网络连接后重试。");
|
|
return;
|
|
}
|
|
|
|
MessageBoxResult result = MessageBox.Show(
|
|
"更新已下载完成,点击确定后将关闭软件并安装新版本!",
|
|
"安装更新",
|
|
MessageBoxButton.OKCancel,
|
|
MessageBoxImage.Information);
|
|
|
|
if (result == MessageBoxResult.OK)
|
|
{
|
|
App.IsAppExitByUser = true;
|
|
AutoUpdateHelper.InstallNewVersionApp(_remoteVersion, true);
|
|
Application.Current.Shutdown();
|
|
}
|
|
else
|
|
{
|
|
ApplyState(UpdateUiState.Downloaded);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"Error in UpdateNowButton_Click: {ex.Message}", LogHelper.LogType.Error);
|
|
ApplyState(UpdateUiState.NetworkError, ex.Message);
|
|
}
|
|
}
|
|
|
|
private async void UpdateLaterButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (string.IsNullOrEmpty(_remoteVersion)) return;
|
|
|
|
var mainWindow = Application.Current.MainWindow as MainWindow;
|
|
if (mainWindow == null) return;
|
|
|
|
ApplyState(UpdateUiState.Downloading);
|
|
CancelDownloadButton.Visibility = Visibility.Visible;
|
|
ProgressBar.Value = 0;
|
|
ProgressText.Text = "正在后台下载...";
|
|
|
|
try
|
|
{
|
|
bool ok = await DownloadWithProgressAsync();
|
|
|
|
if (!ok)
|
|
{
|
|
ApplyState(UpdateUiState.NetworkError, "更新下载失败,请检查网络连接后重试。");
|
|
return;
|
|
}
|
|
|
|
SettingsManager.Settings.Startup.IsAutoUpdate = true;
|
|
SettingsManager.Settings.Startup.IsAutoUpdateWithSilence = true;
|
|
SettingsManager.SaveSettingsToFile();
|
|
CardAutoUpdate.IsOn = true;
|
|
CardSilentUpdate.IsOn = true;
|
|
|
|
mainWindow.StartSilentUpdateTimer();
|
|
ApplyState(UpdateUiState.Downloaded);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"Error in UpdateLaterButton_Click: {ex.Message}", LogHelper.LogType.Error);
|
|
ApplyState(UpdateUiState.NetworkError, ex.Message);
|
|
}
|
|
}
|
|
|
|
private void SkipVersionButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (string.IsNullOrEmpty(_remoteVersion)) return;
|
|
|
|
SettingsManager.Settings.Startup.SkippedVersion = _remoteVersion;
|
|
SettingsManager.SaveSettingsToFile();
|
|
LogHelper.WriteLogToFile($"ManualUpdate | User chose to skip version {_remoteVersion}");
|
|
|
|
ApplyState(UpdateUiState.Idle, $"已跳过版本 {_remoteVersion}");
|
|
}
|
|
|
|
private void CancelDownloadButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
_downloadCancelled = true;
|
|
AutoUpdateHelper.RequestCancelDownload();
|
|
ApplyState(UpdateUiState.UpdateAvailable);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 历史版本回滚
|
|
|
|
private async void UpdateTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (e.OriginalSource != UpdateTabControl) return;
|
|
if (UpdateTabControl.SelectedItem == HistoryTabItem && !_isHistoryLoaded)
|
|
{
|
|
await LoadHistoryAsync();
|
|
}
|
|
}
|
|
|
|
private async void HistoryTabItem_GotFocus(object sender, RoutedEventArgs e)
|
|
{
|
|
// 兼容用户首次切到历史版本 Tab 时再加载
|
|
if (_isHistoryLoaded) return;
|
|
await LoadHistoryAsync();
|
|
}
|
|
|
|
private async System.Threading.Tasks.Task LoadHistoryAsync()
|
|
{
|
|
try
|
|
{
|
|
_isHistoryLoaded = true;
|
|
ReleaseNotesViewer.Markdown = "正在获取历史版本...";
|
|
RollbackButton.IsEnabled = false;
|
|
|
|
var releases = await AutoUpdateHelper.GetAllGithubReleases(SettingsManager.Settings.Startup.UpdateChannel);
|
|
_versionList = releases
|
|
.Select(r => new VersionItem { Version = r.version, DownloadUrl = r.downloadUrl, ReleaseNotes = r.releaseNotes })
|
|
.OrderByDescending(v => ParseVersionForSort(v.Version))
|
|
.ToList();
|
|
VersionComboBox.ItemsSource = _versionList;
|
|
|
|
if (_versionList.Count > 0)
|
|
{
|
|
VersionComboBox.SelectedIndex = 0;
|
|
RollbackButton.IsEnabled = true;
|
|
}
|
|
else
|
|
{
|
|
ReleaseNotesViewer.Markdown = "未获取到历史版本信息。";
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ReleaseNotesViewer.Markdown = $"加载历史版本失败:{ex.Message}";
|
|
}
|
|
}
|
|
|
|
private static Version ParseVersionForSort(string version)
|
|
{
|
|
var v = (version ?? "").TrimStart('v', 'V');
|
|
return Version.TryParse(v, out var result) ? result : new Version(0, 0, 0, 0);
|
|
}
|
|
|
|
private void VersionComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
_selectedHistoricalItem = VersionComboBox.SelectedItem as VersionItem;
|
|
if (_selectedHistoricalItem != null)
|
|
{
|
|
ReleaseNotesViewer.Markdown = _selectedHistoricalItem.ReleaseNotes ?? "无更新日志";
|
|
LogHelper.WriteLogToFile($"HistoryRollback | 用户选择版本: {_selectedHistoricalItem.Version}");
|
|
}
|
|
Keyboard.ClearFocus();
|
|
}
|
|
|
|
private async void RollbackButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_selectedHistoricalItem == null) return;
|
|
|
|
int days = await AskPauseDaysAsync();
|
|
if (days < 0)
|
|
{
|
|
LogHelper.WriteLogToFile("HistoryRollback | 用户取消了回滚操作");
|
|
return;
|
|
}
|
|
|
|
if (days == 0)
|
|
{
|
|
MainWindow.Settings.Startup.AutoUpdatePauseUntilDate = "";
|
|
}
|
|
else
|
|
{
|
|
DateTime pauseUntilDate = DateTime.Now.AddDays(days);
|
|
MainWindow.Settings.Startup.AutoUpdatePauseUntilDate = pauseUntilDate.ToString("yyyy-MM-dd");
|
|
LogHelper.WriteLogToFile($"HistoryRollback | 用户选择暂停自动更新 {days} 天,截止日期: {pauseUntilDate:yyyy-MM-dd}");
|
|
}
|
|
MainWindow.SaveSettingsToFile();
|
|
|
|
LogHelper.WriteLogToFile($"HistoryRollback | 用户确认回滚,目标版本: {_selectedHistoricalItem.Version}");
|
|
RollbackButton.IsEnabled = false;
|
|
VersionComboBox.IsEnabled = false;
|
|
RollbackProgressPanel.Visibility = Visibility.Visible;
|
|
RollbackProgressBar.Value = 0;
|
|
RollbackProgressText.Text = "正在准备下载...";
|
|
|
|
bool downloadSuccess = false;
|
|
try
|
|
{
|
|
downloadSuccess = await AutoUpdateHelper.StartManualDownloadAndInstall(
|
|
_selectedHistoricalItem.Version,
|
|
SettingsManager.Settings.Startup.UpdateChannel,
|
|
(percent, text) =>
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
RollbackProgressBar.Value = percent;
|
|
RollbackProgressText.Text = text;
|
|
});
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
RollbackProgressText.Text = $"下载失败: {ex.Message}";
|
|
LogHelper.WriteLogToFile($"HistoryRollback | 下载异常: {ex.Message}", LogHelper.LogType.Error);
|
|
}
|
|
|
|
if (downloadSuccess)
|
|
{
|
|
RollbackProgressBar.Value = 100;
|
|
RollbackProgressText.Text = "下载完成,准备安装...";
|
|
}
|
|
else
|
|
{
|
|
RollbackProgressText.Text = "下载失败,请检查网络后重试。";
|
|
RollbackButton.IsEnabled = true;
|
|
VersionComboBox.IsEnabled = true;
|
|
}
|
|
}
|
|
|
|
private async System.Threading.Tasks.Task<int> AskPauseDaysAsync()
|
|
{
|
|
var dialog = new ContentDialog
|
|
{
|
|
Title = "暂停自动更新",
|
|
PrimaryButtonText = "确定",
|
|
SecondaryButtonText = "取消"
|
|
};
|
|
|
|
var panel = new iNKORE.UI.WPF.Controls.SimpleStackPanel
|
|
{
|
|
Spacing = 16,
|
|
Margin = new Thickness(0, 10, 0, 0)
|
|
};
|
|
|
|
var textBlock = new TextBlock
|
|
{
|
|
Text = "请选择在回滚后多久不再接收自动更新:",
|
|
FontSize = 14
|
|
};
|
|
|
|
var daysComboBox = new ComboBox
|
|
{
|
|
Width = 200,
|
|
Height = 36,
|
|
HorizontalAlignment = HorizontalAlignment.Left
|
|
};
|
|
for (int i = 0; i <= 7; i++)
|
|
{
|
|
daysComboBox.Items.Add(new ComboBoxItem { Content = $"{i} 天", Tag = i });
|
|
}
|
|
daysComboBox.SelectedIndex = 0;
|
|
|
|
panel.Children.Add(textBlock);
|
|
panel.Children.Add(daysComboBox);
|
|
dialog.Content = panel;
|
|
|
|
var result = await dialog.ShowAsync();
|
|
if (result != ContentDialogResult.Primary) return -1;
|
|
|
|
if (daysComboBox.SelectedItem is ComboBoxItem cbi && cbi.Tag is int days)
|
|
return days;
|
|
|
|
return 1;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 维护
|
|
|
|
private async void FixVersionButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var confirm = MessageBox.Show(
|
|
"此操作将下载当前选择通道的最新版本并安装,软件将自动关闭并更新。\n\n确定要执行版本修复吗?",
|
|
"版本修复确认",
|
|
MessageBoxButton.YesNo,
|
|
MessageBoxImage.Question);
|
|
|
|
if (confirm != MessageBoxResult.Yes) return;
|
|
|
|
FixVersionButton.IsEnabled = false;
|
|
FixVersionButton.Content = "正在修复...";
|
|
|
|
try
|
|
{
|
|
bool result = await AutoUpdateHelper.FixVersion(SettingsManager.Settings.Startup.UpdateChannel);
|
|
if (!result)
|
|
{
|
|
MessageBox.Show(
|
|
"版本修复失败,可能是网络问题或当前已是最新版本。",
|
|
"修复失败",
|
|
MessageBoxButton.OK,
|
|
MessageBoxImage.Error);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"Error in FixVersionButton_Click: {ex.Message}", LogHelper.LogType.Error);
|
|
MessageBox.Show(
|
|
$"版本修复过程中发生错误: {ex.Message}",
|
|
"修复错误",
|
|
MessageBoxButton.OK,
|
|
MessageBoxImage.Error);
|
|
}
|
|
finally
|
|
{
|
|
FixVersionButton.IsEnabled = true;
|
|
FixVersionButton.Content = "版本修复";
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |