2025-08-25 09:00:45 +08:00
|
|
|
|
using Newtonsoft.Json;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
|
using System;
|
2025-07-28 14:40:44 +08:00
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Collections.ObjectModel;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
using System.IO;
|
2025-07-28 14:40:44 +08:00
|
|
|
|
using System.IO.Compression;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Net;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
using System.Net.Http;
|
2025-07-28 14:40:44 +08:00
|
|
|
|
using System.Net.Http.Headers;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
using System.Reflection;
|
2025-06-18 18:16:48 +08:00
|
|
|
|
using System.Text;
|
2025-07-28 14:40:44 +08:00
|
|
|
|
using System.Text.RegularExpressions;
|
2025-07-22 18:25:04 +08:00
|
|
|
|
using System.Threading;
|
2025-07-28 14:40:44 +08:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using System.Windows;
|
|
|
|
|
|
using System.Windows.Controls;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
|
|
|
|
|
namespace Ink_Canvas.Helpers
|
|
|
|
|
|
{
|
|
|
|
|
|
internal class AutoUpdateHelper
|
|
|
|
|
|
{
|
2025-06-29 11:56:38 +08:00
|
|
|
|
// 定义超时时间为10秒
|
|
|
|
|
|
private static readonly TimeSpan RequestTimeout = TimeSpan.FromSeconds(10);
|
2025-07-26 16:22:55 +08:00
|
|
|
|
private static readonly string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate");
|
2025-07-28 14:40:44 +08:00
|
|
|
|
private static string statusFilePath;
|
2025-07-19 15:36:05 +08:00
|
|
|
|
|
|
|
|
|
|
// 线路组结构体(包含版本、下载、日志地址)
|
|
|
|
|
|
public class UpdateLineGroup
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-07-19 15:36:05 +08:00
|
|
|
|
public string GroupName { get; set; } // 组名
|
|
|
|
|
|
public string VersionUrl { get; set; } // 版本检测地址
|
|
|
|
|
|
public string DownloadUrlFormat { get; set; } // 下载地址格式(带{0}占位符)
|
|
|
|
|
|
public string LogUrl { get; set; } // 更新日志地址
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 通道-线路组映射
|
2025-07-22 18:02:29 +08:00
|
|
|
|
public static readonly Dictionary<UpdateChannel, List<UpdateLineGroup>> ChannelLineGroups = new Dictionary<UpdateChannel, List<UpdateLineGroup>>
|
2025-07-19 15:36:05 +08:00
|
|
|
|
{
|
|
|
|
|
|
{ UpdateChannel.Release, new List<UpdateLineGroup>
|
2025-06-29 11:56:38 +08:00
|
|
|
|
{
|
2025-07-19 15:36:05 +08:00
|
|
|
|
new UpdateLineGroup
|
|
|
|
|
|
{
|
|
|
|
|
|
GroupName = "GitHub主线",
|
2025-08-25 09:00:45 +08:00
|
|
|
|
VersionUrl = "https://github.com/InkCanvasForClass/community/raw/refs/heads/main/AutomaticUpdateVersionControl.txt",
|
|
|
|
|
|
DownloadUrlFormat = "https://github.com/InkCanvasForClass/community/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
|
|
|
|
|
|
LogUrl = "https://github.com/InkCanvasForClass/community/raw/refs/heads/main/UpdateLog.md"
|
2025-07-19 15:36:05 +08:00
|
|
|
|
},
|
|
|
|
|
|
new UpdateLineGroup
|
|
|
|
|
|
{
|
|
|
|
|
|
GroupName = "bgithub备用",
|
|
|
|
|
|
VersionUrl = "https://bgithub.xyz/InkCanvasForClass/community/raw/refs/heads/main/AutomaticUpdateVersionControl.txt",
|
|
|
|
|
|
DownloadUrlFormat = "https://bgithub.xyz/InkCanvasForClass/community/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
|
|
|
|
|
|
LogUrl = "https://bgithub.xyz/InkCanvasForClass/community/raw/refs/heads/main/UpdateLog.md"
|
|
|
|
|
|
},
|
|
|
|
|
|
new UpdateLineGroup
|
|
|
|
|
|
{
|
|
|
|
|
|
GroupName = "kkgithub线路",
|
|
|
|
|
|
VersionUrl = "https://kkgithub.com/InkCanvasForClass/community/raw/refs/heads/main/AutomaticUpdateVersionControl.txt",
|
|
|
|
|
|
DownloadUrlFormat = "https://kkgithub.com/InkCanvasForClass/community/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
|
|
|
|
|
|
LogUrl = "https://kkgithub.com/InkCanvasForClass/community/raw/refs/heads/main/UpdateLog.md"
|
2025-07-24 22:16:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
new UpdateLineGroup
|
|
|
|
|
|
{
|
|
|
|
|
|
GroupName = "智教联盟",
|
|
|
|
|
|
DownloadUrlFormat = "https://get.smart-teach.cn/d/Ningbo-S3/shared/jiangling/community/InkCanvasForClass.CE.{0}.zip",
|
2025-08-25 09:53:01 +08:00
|
|
|
|
LogUrl = "https://bgithub.xyz/InkCanvasForClass/community/raw/refs/heads/main/UpdateLog.md"
|
2025-07-26 12:02:58 +08:00
|
|
|
|
},
|
|
|
|
|
|
new UpdateLineGroup
|
|
|
|
|
|
{
|
|
|
|
|
|
GroupName = "inkeys",
|
|
|
|
|
|
DownloadUrlFormat = "https://iccce.inkeys.top/Release/InkCanvasForClass.CE.{0}.zip",
|
2025-08-25 09:53:01 +08:00
|
|
|
|
LogUrl = "https://bgithub.xyz/InkCanvasForClass/community/raw/refs/heads/main/UpdateLog.md"
|
2025-07-19 15:36:05 +08:00
|
|
|
|
}
|
2025-06-29 11:56:38 +08:00
|
|
|
|
}
|
2025-07-19 15:36:05 +08:00
|
|
|
|
},
|
|
|
|
|
|
{ UpdateChannel.Beta, new List<UpdateLineGroup>
|
2025-06-29 11:56:38 +08:00
|
|
|
|
{
|
2025-07-19 15:36:05 +08:00
|
|
|
|
new UpdateLineGroup
|
|
|
|
|
|
{
|
|
|
|
|
|
GroupName = "GitHub主线",
|
2025-08-25 09:00:45 +08:00
|
|
|
|
VersionUrl = "https://github.com/InkCanvasForClass/community-beta/raw/refs/heads/main/AutomaticUpdateVersionControl.txt",
|
|
|
|
|
|
DownloadUrlFormat = "https://github.com/InkCanvasForClass/community-beta/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
|
|
|
|
|
|
LogUrl = "https://github.com/InkCanvasForClass/community-beta/raw/refs/heads/main/UpdateLog.md"
|
2025-07-19 15:36:05 +08:00
|
|
|
|
},
|
|
|
|
|
|
new UpdateLineGroup
|
|
|
|
|
|
{
|
|
|
|
|
|
GroupName = "bgithub备用",
|
|
|
|
|
|
VersionUrl = "https://bgithub.xyz/InkCanvasForClass/community-beta/raw/refs/heads/main/AutomaticUpdateVersionControl.txt",
|
|
|
|
|
|
DownloadUrlFormat = "https://bgithub.xyz/InkCanvasForClass/community-beta/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
|
|
|
|
|
|
LogUrl = "https://bgithub.xyz/InkCanvasForClass/community-beta/raw/refs/heads/main/UpdateLog.md"
|
|
|
|
|
|
},
|
|
|
|
|
|
new UpdateLineGroup
|
|
|
|
|
|
{
|
|
|
|
|
|
GroupName = "kkgithub线路",
|
|
|
|
|
|
VersionUrl = "https://kkgithub.com/InkCanvasForClass/community-beta/raw/refs/heads/main/AutomaticUpdateVersionControl.txt",
|
|
|
|
|
|
DownloadUrlFormat = "https://kkgithub.com/InkCanvasForClass/community-beta/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
|
|
|
|
|
|
LogUrl = "https://kkgithub.com/InkCanvasForClass/community-beta/raw/refs/heads/main/UpdateLog.md"
|
2025-07-24 22:16:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
new UpdateLineGroup
|
|
|
|
|
|
{
|
|
|
|
|
|
GroupName = "智教联盟",
|
|
|
|
|
|
DownloadUrlFormat = "https://get.smart-teach.cn/d/Ningbo-S3/shared/jiangling/community-beta/InkCanvasForClass.CE.{0}.zip",
|
2025-08-25 09:53:01 +08:00
|
|
|
|
LogUrl = "https://bgithub.xyz/InkCanvasForClass/community-beta/raw/refs/heads/main/UpdateLog.md"
|
2025-07-26 12:02:58 +08:00
|
|
|
|
},
|
|
|
|
|
|
new UpdateLineGroup
|
|
|
|
|
|
{
|
|
|
|
|
|
GroupName = "inkeys",
|
|
|
|
|
|
DownloadUrlFormat = "https://iccce.inkeys.top/Beta/InkCanvasForClass.CE.{0}.zip",
|
2025-08-25 09:53:01 +08:00
|
|
|
|
LogUrl = "https://bgithub.xyz/InkCanvasForClass/community-beta/raw/refs/heads/main/UpdateLog.md"
|
2025-07-19 15:36:05 +08:00
|
|
|
|
}
|
2025-06-29 11:56:38 +08:00
|
|
|
|
}
|
2025-07-19 15:36:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
2025-07-22 18:45:43 +08:00
|
|
|
|
// 区块任务结构体(移到类体内)
|
|
|
|
|
|
private class BlockTask
|
|
|
|
|
|
{
|
|
|
|
|
|
public int Index;
|
|
|
|
|
|
public long Start;
|
|
|
|
|
|
public long End;
|
|
|
|
|
|
public int RetryCount;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
// 检测URL延迟
|
|
|
|
|
|
private static async Task<long> GetUrlDelay(string url)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-07-19 16:51:03 +08:00
|
|
|
|
// 检测是否为Windows 7
|
|
|
|
|
|
var osVersion = Environment.OSVersion;
|
|
|
|
|
|
bool isWindows7 = osVersion.Version.Major == 6 && osVersion.Version.Minor == 1;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
if (isWindows7)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Windows 7使用特殊配置
|
|
|
|
|
|
using (var handler = new HttpClientHandler())
|
|
|
|
|
|
{
|
|
|
|
|
|
// 配置HttpClientHandler以支持Windows 7
|
|
|
|
|
|
handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
using (var client = new HttpClient(handler))
|
|
|
|
|
|
{
|
|
|
|
|
|
client.Timeout = TimeSpan.FromSeconds(5);
|
|
|
|
|
|
var sw = Stopwatch.StartNew();
|
|
|
|
|
|
var resp = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, url));
|
|
|
|
|
|
sw.Stop();
|
|
|
|
|
|
if (resp.IsSuccessStatusCode)
|
|
|
|
|
|
return sw.ElapsedMilliseconds;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2025-06-18 22:40:48 +08:00
|
|
|
|
{
|
2025-07-19 16:51:03 +08:00
|
|
|
|
// 其他Windows版本使用标准配置
|
|
|
|
|
|
using (var client = new HttpClient())
|
|
|
|
|
|
{
|
|
|
|
|
|
client.Timeout = TimeSpan.FromSeconds(5);
|
|
|
|
|
|
var sw = Stopwatch.StartNew();
|
|
|
|
|
|
var resp = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, url));
|
|
|
|
|
|
sw.Stop();
|
|
|
|
|
|
if (resp.IsSuccessStatusCode)
|
|
|
|
|
|
return sw.ElapsedMilliseconds;
|
|
|
|
|
|
}
|
2025-06-18 22:40:48 +08:00
|
|
|
|
}
|
2025-07-19 15:36:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch { }
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
2025-06-18 22:40:48 +08:00
|
|
|
|
|
2025-07-19 16:03:45 +08:00
|
|
|
|
// 检测线路组延迟,返回最快组(保持向后兼容)
|
2025-07-19 15:36:05 +08:00
|
|
|
|
private static async Task<UpdateLineGroup> GetFastestLineGroup(UpdateChannel channel)
|
2025-07-19 16:03:45 +08:00
|
|
|
|
{
|
|
|
|
|
|
var availableGroups = await GetAvailableLineGroupsOrdered(channel);
|
|
|
|
|
|
return availableGroups.Count > 0 ? availableGroups[0] : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有可用线路组,按延迟排序
|
|
|
|
|
|
public static async Task<List<UpdateLineGroup>> GetAvailableLineGroupsOrdered(UpdateChannel channel)
|
2025-07-19 15:36:05 +08:00
|
|
|
|
{
|
|
|
|
|
|
var groups = ChannelLineGroups[channel];
|
2025-07-19 16:03:45 +08:00
|
|
|
|
var availableGroups = new List<(UpdateLineGroup group, long delay)>();
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 开始检测通道 {channel} 下所有线路组延迟...");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
foreach (var group in groups)
|
|
|
|
|
|
{
|
2025-07-26 12:02:58 +08:00
|
|
|
|
// 跳过"智教联盟"和"inkeys"线路组,不参与延迟检测和排序
|
|
|
|
|
|
if (group.GroupName == "智教联盟" || group.GroupName == "inkeys")
|
2025-07-24 22:16:38 +08:00
|
|
|
|
{
|
2025-07-26 12:02:58 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 跳过{group.GroupName}线路组延迟检测");
|
2025-07-24 22:16:38 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 检测线路组: {group.GroupName} ({group.VersionUrl})");
|
|
|
|
|
|
var delay = await GetUrlDelay(group.VersionUrl);
|
|
|
|
|
|
if (delay >= 0)
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 线路组 {group.GroupName} 延迟: {delay}ms");
|
2025-07-19 16:03:45 +08:00
|
|
|
|
availableGroups.Add((group, delay));
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 线路组 {group.GroupName} 不可用", LogHelper.LogType.Warning);
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:03:45 +08:00
|
|
|
|
// 按延迟排序,延迟最小的排在前面
|
|
|
|
|
|
var orderedGroups = availableGroups
|
|
|
|
|
|
.OrderBy(x => x.delay)
|
|
|
|
|
|
.Select(x => x.group)
|
|
|
|
|
|
.ToList();
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 12:02:58 +08:00
|
|
|
|
// 将"智教联盟"线路组插入到最前面(如果存在)
|
2025-07-24 22:16:38 +08:00
|
|
|
|
var zhiJiaoGroup = groups.FirstOrDefault(g => g.GroupName == "智教联盟");
|
|
|
|
|
|
if (zhiJiaoGroup != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
orderedGroups.Insert(0, zhiJiaoGroup);
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 智教联盟线路组已插入到首位");
|
2025-07-24 22:16:38 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 12:02:58 +08:00
|
|
|
|
// 将"inkeys"线路组插入到第二位(如果存在)
|
|
|
|
|
|
var inkeysGroup = groups.FirstOrDefault(g => g.GroupName == "inkeys");
|
|
|
|
|
|
if (inkeysGroup != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
orderedGroups.Insert(1, inkeysGroup);
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | inkeys线路组已插入到第二位");
|
2025-07-26 12:02:58 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:03:45 +08:00
|
|
|
|
if (orderedGroups.Count > 0)
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-07-19 16:03:45 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 找到 {orderedGroups.Count} 个可用线路组,按延迟排序:");
|
|
|
|
|
|
for (int i = 0; i < orderedGroups.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | {i + 1}. {orderedGroups[i].GroupName}");
|
|
|
|
|
|
}
|
2025-07-19 15:36:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 所有线路组均不可用", LogHelper.LogType.Error);
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:03:45 +08:00
|
|
|
|
return orderedGroups;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
// 获取远程版本号
|
|
|
|
|
|
private static async Task<string> GetRemoteVersion(string fileUrl)
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-07-19 16:51:03 +08:00
|
|
|
|
// 检测是否为Windows 7
|
|
|
|
|
|
var osVersion = Environment.OSVersion;
|
|
|
|
|
|
bool isWindows7 = osVersion.Version.Major == 6 && osVersion.Version.Minor == 1;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
if (isWindows7)
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-07-19 16:51:03 +08:00
|
|
|
|
// Windows 7使用特殊配置
|
|
|
|
|
|
using (var handler = new HttpClientHandler())
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-07-19 16:51:03 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 配置HttpClientHandler以支持Windows 7
|
|
|
|
|
|
handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
using (HttpClient client = new HttpClient(handler))
|
|
|
|
|
|
{
|
|
|
|
|
|
client.Timeout = RequestTimeout;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 发送HTTP请求到: {fileUrl}");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
var downloadTask = client.GetAsync(fileUrl);
|
|
|
|
|
|
var timeoutTask = Task.Delay(RequestTimeout);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
var completedTask = await Task.WhenAny(downloadTask, timeoutTask);
|
|
|
|
|
|
if (completedTask == timeoutTask)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 请求超时 ({RequestTimeout.TotalSeconds}秒)", LogHelper.LogType.Error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
HttpResponseMessage response = await downloadTask;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | HTTP响应状态: {response.StatusCode}");
|
|
|
|
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
|
|
|
|
|
|
|
|
string content = await response.Content.ReadAsStringAsync();
|
|
|
|
|
|
content = content.Trim();
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
// 如果内容包含HTML(可能是GitHub页面而不是原始内容),尝试提取版本号
|
|
|
|
|
|
if (content.Contains("<html") || content.Contains("<!DOCTYPE"))
|
|
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 收到HTML内容而不是原始版本号 - 尝试提取版本");
|
2025-07-19 16:51:03 +08:00
|
|
|
|
int startPos = content.IndexOf("<table");
|
|
|
|
|
|
if (startPos > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
int endPos = content.IndexOf("</table>", startPos);
|
|
|
|
|
|
if (endPos > startPos)
|
|
|
|
|
|
{
|
|
|
|
|
|
string tableContent = content.Substring(startPos, endPos - startPos);
|
2025-07-28 14:40:44 +08:00
|
|
|
|
var match = Regex.Match(tableContent, @"(\d+\.\d+\.\d+(\.\d+)?)");
|
2025-07-19 16:51:03 +08:00
|
|
|
|
if (match.Success)
|
|
|
|
|
|
{
|
|
|
|
|
|
content = match.Groups[1].Value;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 从HTML提取版本: {content}");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 无法从HTML内容提取版本");
|
2025-07-19 16:51:03 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 响应内容: {content}");
|
|
|
|
|
|
return content;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (HttpRequestException ex)
|
2025-06-18 22:40:48 +08:00
|
|
|
|
{
|
2025-07-19 16:51:03 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | HTTP请求错误: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (TaskCanceledException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 请求超时: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 错误: {ex.Message}", LogHelper.LogType.Error);
|
2025-06-18 22:40:48 +08:00
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-28 14:40:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 其他Windows版本使用标准配置
|
|
|
|
|
|
using (HttpClient client = new HttpClient())
|
2025-07-19 16:51:03 +08:00
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
try
|
2025-07-19 16:51:03 +08:00
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
client.Timeout = RequestTimeout;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 发送HTTP请求到: {fileUrl}");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-28 14:40:44 +08:00
|
|
|
|
var downloadTask = client.GetAsync(fileUrl);
|
|
|
|
|
|
var timeoutTask = Task.Delay(RequestTimeout);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-28 14:40:44 +08:00
|
|
|
|
var completedTask = await Task.WhenAny(downloadTask, timeoutTask);
|
|
|
|
|
|
if (completedTask == timeoutTask)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 请求超时 ({RequestTimeout.TotalSeconds}秒)", LogHelper.LogType.Error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-28 14:40:44 +08:00
|
|
|
|
HttpResponseMessage response = await downloadTask;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | HTTP响应状态: {response.StatusCode}");
|
|
|
|
|
|
response.EnsureSuccessStatusCode();
|
2025-07-19 16:51:03 +08:00
|
|
|
|
|
2025-07-28 14:40:44 +08:00
|
|
|
|
string content = await response.Content.ReadAsStringAsync();
|
|
|
|
|
|
content = content.Trim();
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-28 14:40:44 +08:00
|
|
|
|
// 如果内容包含HTML(可能是GitHub页面而不是原始内容),尝试提取版本号
|
|
|
|
|
|
if (content.Contains("<html") || content.Contains("<!DOCTYPE"))
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 收到HTML内容而不是原始版本号 - 尝试提取版本");
|
|
|
|
|
|
int startPos = content.IndexOf("<table");
|
|
|
|
|
|
if (startPos > 0)
|
2025-06-18 16:08:26 +08:00
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
int endPos = content.IndexOf("</table>", startPos);
|
|
|
|
|
|
if (endPos > startPos)
|
2025-06-18 16:08:26 +08:00
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
string tableContent = content.Substring(startPos, endPos - startPos);
|
|
|
|
|
|
var match = Regex.Match(tableContent, @"(\d+\.\d+\.\d+(\.\d+)?)");
|
|
|
|
|
|
if (match.Success)
|
2025-06-18 16:08:26 +08:00
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
content = match.Groups[1].Value;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 从HTML提取版本: {content}");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 无法从HTML内容提取版本");
|
|
|
|
|
|
return null;
|
2025-06-18 16:08:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-19 16:51:03 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 响应内容: {content}");
|
|
|
|
|
|
return content;
|
2025-07-19 16:51:03 +08:00
|
|
|
|
}
|
2025-07-28 14:40:44 +08:00
|
|
|
|
catch (HttpRequestException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | HTTP请求错误: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (TaskCanceledException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 请求超时: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 错误: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-26 16:22:55 +08:00
|
|
|
|
// 通过GitHub API获取指定版本的Release信息
|
|
|
|
|
|
private static async Task<(string version, string downloadUrl, string releaseNotes, DateTime? releaseTime)> GetGithubReleaseByVersion(string targetVersion, UpdateChannel channel)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
string apiUrl = channel == UpdateChannel.Beta
|
|
|
|
|
|
? "https://api.github.com/repos/InkCanvasForClass/community-beta/releases"
|
|
|
|
|
|
: "https://api.github.com/repos/InkCanvasForClass/community/releases";
|
|
|
|
|
|
using (var client = new HttpClient())
|
|
|
|
|
|
{
|
|
|
|
|
|
client.DefaultRequestHeaders.Add("User-Agent", "ICC-CE Auto Updater");
|
|
|
|
|
|
var response = await client.GetStringAsync(apiUrl);
|
|
|
|
|
|
var releases = JArray.Parse(response);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var release in releases)
|
|
|
|
|
|
{
|
|
|
|
|
|
string version = release["tag_name"]?.ToString();
|
|
|
|
|
|
if (version == targetVersion || version == $"v{targetVersion}" || version == $"V{targetVersion}")
|
|
|
|
|
|
{
|
|
|
|
|
|
string releaseNotes = release["body"]?.ToString();
|
|
|
|
|
|
string downloadUrl = release["assets"]?.First?["browser_download_url"]?.ToString();
|
|
|
|
|
|
|
|
|
|
|
|
// 解析发布时间
|
|
|
|
|
|
DateTime? releaseTime = null;
|
|
|
|
|
|
if (release["published_at"] != null && DateTime.TryParse(release["published_at"].ToString(), out DateTime parsedTime))
|
|
|
|
|
|
{
|
|
|
|
|
|
releaseTime = parsedTime;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (version, downloadUrl, releaseNotes, releaseTime);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | GitHub Releases API 获取版本 {targetVersion} 失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
return (null, null, null, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-22 17:07:27 +08:00
|
|
|
|
// 通过GitHub API获取最新Release信息
|
2025-07-26 14:29:24 +08:00
|
|
|
|
private static async Task<(string version, string downloadUrl, string releaseNotes, DateTime? releaseTime)> GetLatestGithubRelease(UpdateChannel channel)
|
2025-07-22 17:07:27 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
string apiUrl = channel == UpdateChannel.Beta
|
|
|
|
|
|
? "https://api.github.com/repos/InkCanvasForClass/community-beta/releases/latest"
|
|
|
|
|
|
: "https://api.github.com/repos/InkCanvasForClass/community/releases/latest";
|
|
|
|
|
|
using (var client = new HttpClient())
|
|
|
|
|
|
{
|
|
|
|
|
|
client.DefaultRequestHeaders.Add("User-Agent", "ICC-CE Auto Updater");
|
|
|
|
|
|
var response = await client.GetStringAsync(apiUrl);
|
|
|
|
|
|
var json = JObject.Parse(response);
|
|
|
|
|
|
string version = json["tag_name"]?.ToString();
|
|
|
|
|
|
string releaseNotes = json["body"]?.ToString();
|
|
|
|
|
|
string downloadUrl = json["assets"]?.First?["browser_download_url"]?.ToString();
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 14:29:24 +08:00
|
|
|
|
// 解析发布时间
|
|
|
|
|
|
DateTime? releaseTime = null;
|
|
|
|
|
|
if (json["published_at"] != null && DateTime.TryParse(json["published_at"].ToString(), out DateTime parsedTime))
|
|
|
|
|
|
{
|
|
|
|
|
|
releaseTime = parsedTime;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-22 17:07:27 +08:00
|
|
|
|
if (!string.IsNullOrEmpty(version) && !string.IsNullOrEmpty(downloadUrl))
|
2025-07-26 14:29:24 +08:00
|
|
|
|
return (version, downloadUrl, releaseNotes, releaseTime);
|
2025-07-22 17:07:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | GitHub Releases API 获取失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
2025-07-26 14:29:24 +08:00
|
|
|
|
return (null, null, null, null);
|
2025-07-22 17:07:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-19 16:03:45 +08:00
|
|
|
|
// 主要的更新检测方法(优先检测延迟,失败时自动切换线路组)
|
2025-07-22 18:02:29 +08:00
|
|
|
|
// 仅检测新版本时用GitHub API,实际下载时只用线路组
|
2025-07-26 16:08:22 +08:00
|
|
|
|
public static async Task<(string remoteVersion, UpdateLineGroup lineGroup, string releaseNotes)> CheckForUpdates(UpdateChannel channel = UpdateChannel.Release, bool alwaysGetRemote = false, bool isVersionFix = false)
|
2025-07-19 15:36:05 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-07-26 14:29:24 +08:00
|
|
|
|
// 记录更新检查时间
|
|
|
|
|
|
DeviceIdentifier.RecordUpdateCheck();
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
string localVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 本地版本: {localVersion}");
|
2025-07-26 14:29:24 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 设备ID: {DeviceIdentifier.GetDeviceId()}");
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 更新优先级: {DeviceIdentifier.GetUpdatePriority()}");
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 优先通过GitHub Releases API检测...");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-22 17:07:27 +08:00
|
|
|
|
// 1. 优先通过GitHub Releases API获取
|
2025-07-26 14:29:24 +08:00
|
|
|
|
var (apiVersion, _, apiReleaseNotes, apiReleaseTime) = await GetLatestGithubRelease(channel);
|
2025-07-22 18:02:29 +08:00
|
|
|
|
if (!string.IsNullOrEmpty(apiVersion))
|
2025-07-22 17:07:27 +08:00
|
|
|
|
{
|
|
|
|
|
|
Version local = new Version(localVersion);
|
|
|
|
|
|
Version remote = new Version(apiVersion.TrimStart('v', 'V'));
|
|
|
|
|
|
if (remote > local || alwaysGetRemote)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 通过GitHub Releases API发现新版本: {apiVersion}");
|
2025-07-26 16:22:55 +08:00
|
|
|
|
|
2025-07-26 16:08:22 +08:00
|
|
|
|
// 检查是否应该根据用户优先级推送更新(版本修复功能不受限制)
|
|
|
|
|
|
if (!isVersionFix)
|
2025-07-26 14:29:24 +08:00
|
|
|
|
{
|
2025-07-26 16:08:22 +08:00
|
|
|
|
DateTime releaseTime = apiReleaseTime ?? DateTime.Now;
|
2025-07-26 16:22:55 +08:00
|
|
|
|
|
|
|
|
|
|
// 尝试获取当前版本的发布时间
|
|
|
|
|
|
DateTime? currentVersionReleaseTime = await GetVersionReleaseTime(localVersion, channel);
|
|
|
|
|
|
|
|
|
|
|
|
bool shouldPush = DeviceIdentifier.ShouldPushUpdate(apiVersion, releaseTime, true, currentVersionReleaseTime); // 明确标记为自动更新
|
2025-07-26 16:08:22 +08:00
|
|
|
|
if (!shouldPush)
|
|
|
|
|
|
{
|
|
|
|
|
|
var priority = DeviceIdentifier.GetUpdatePriority();
|
2025-07-26 16:22:55 +08:00
|
|
|
|
var daysBetweenVersions = currentVersionReleaseTime.HasValue
|
|
|
|
|
|
? (releaseTime - currentVersionReleaseTime.Value).TotalDays
|
|
|
|
|
|
: (DateTime.Now - releaseTime).TotalDays;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级({priority}),暂不推送更新 {apiVersion},版本间隔: {daysBetweenVersions:F1} 天");
|
2025-07-26 16:08:22 +08:00
|
|
|
|
var group = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault();
|
|
|
|
|
|
return (null, group, apiReleaseNotes); // 返回null表示不推送
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 版本修复模式,跳过分级策略检查");
|
2025-07-26 14:29:24 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 14:29:24 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级,推送更新 {apiVersion}");
|
2025-07-22 18:02:29 +08:00
|
|
|
|
// 只返回版本号和日志,不返回直链
|
2025-07-26 14:29:24 +08:00
|
|
|
|
var availableGroup = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault();
|
|
|
|
|
|
return (apiVersion, availableGroup, apiReleaseNotes);
|
2025-07-22 17:07:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 当前版本已是最新 (GitHub Releases API)");
|
2025-07-26 14:29:24 +08:00
|
|
|
|
var availableGroup = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault();
|
|
|
|
|
|
return (null, availableGroup, apiReleaseNotes);
|
2025-07-22 17:07:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 2. 回退到原有txt方案
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | GitHub Releases API获取失败,回退到txt方案...");
|
2025-07-19 16:03:45 +08:00
|
|
|
|
var availableGroups = await GetAvailableLineGroupsOrdered(channel);
|
|
|
|
|
|
if (availableGroups.Count == 0)
|
2025-07-19 15:36:05 +08:00
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 所有线路组均不可用", LogHelper.LogType.Error);
|
2025-07-22 18:02:29 +08:00
|
|
|
|
return (null, null, null);
|
2025-07-19 15:36:05 +08:00
|
|
|
|
}
|
2025-07-19 16:03:45 +08:00
|
|
|
|
foreach (var group in availableGroups)
|
2025-07-19 15:36:05 +08:00
|
|
|
|
{
|
2025-07-19 16:03:45 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 尝试使用线路组获取版本信息: {group.GroupName}");
|
|
|
|
|
|
string remoteVersion = await GetRemoteVersion(group.VersionUrl);
|
|
|
|
|
|
if (remoteVersion != null)
|
2025-07-19 15:36:05 +08:00
|
|
|
|
{
|
2025-07-19 16:03:45 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 成功从线路组 {group.GroupName} 获取远程版本: {remoteVersion}");
|
|
|
|
|
|
Version local = new Version(localVersion);
|
|
|
|
|
|
Version remote = new Version(remoteVersion);
|
|
|
|
|
|
if (remote > local || alwaysGetRemote)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 发现新版本或强制获取: {remoteVersion}");
|
2025-07-26 16:22:55 +08:00
|
|
|
|
|
2025-07-26 16:08:22 +08:00
|
|
|
|
// 检查是否应该根据用户优先级推送更新(版本修复功能不受限制)
|
|
|
|
|
|
if (!isVersionFix)
|
2025-07-26 14:29:24 +08:00
|
|
|
|
{
|
2025-07-26 16:22:55 +08:00
|
|
|
|
// 尝试获取当前版本的发布时间
|
|
|
|
|
|
DateTime? currentVersionReleaseTime = await GetVersionReleaseTime(localVersion, channel);
|
|
|
|
|
|
|
|
|
|
|
|
bool shouldPush = DeviceIdentifier.ShouldPushUpdate(remoteVersion, DateTime.Now, true, currentVersionReleaseTime); // 明确标记为自动更新
|
2025-07-26 16:08:22 +08:00
|
|
|
|
if (!shouldPush)
|
|
|
|
|
|
{
|
|
|
|
|
|
var priority = DeviceIdentifier.GetUpdatePriority();
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级({priority}),暂不推送更新 {remoteVersion}");
|
|
|
|
|
|
return (null, group, null); // 返回null表示不推送
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 版本修复模式,跳过分级策略检查");
|
2025-07-26 14:29:24 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 14:29:24 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级,推送更新 {remoteVersion}");
|
2025-07-22 18:02:29 +08:00
|
|
|
|
return (remoteVersion, group, null);
|
2025-07-19 16:03:45 +08:00
|
|
|
|
}
|
2025-07-28 14:40:44 +08:00
|
|
|
|
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 当前版本已是最新");
|
|
|
|
|
|
return (null, group, null);
|
2025-07-19 15:36:05 +08:00
|
|
|
|
}
|
2025-07-28 14:40:44 +08:00
|
|
|
|
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 线路组 {group.GroupName} 获取版本失败,尝试下一个线路组", LogHelper.LogType.Warning);
|
2025-07-19 15:36:05 +08:00
|
|
|
|
}
|
2025-07-19 16:03:45 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 所有线路组均无法获取版本信息", LogHelper.LogType.Error);
|
2025-07-22 18:02:29 +08:00
|
|
|
|
return (null, null, null);
|
2025-07-19 15:36:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | CheckForUpdates错误: {ex.Message}", LogHelper.LogType.Error);
|
2025-07-22 18:02:29 +08:00
|
|
|
|
return (null, null, null);
|
2025-07-19 15:36:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
// 使用指定线路组下载新版
|
|
|
|
|
|
public static async Task<bool> DownloadSetupFile(string version, UpdateLineGroup group)
|
2025-07-19 16:03:45 +08:00
|
|
|
|
{
|
|
|
|
|
|
return await DownloadSetupFileWithFallback(version, new List<UpdateLineGroup> { group });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-24 22:16:38 +08:00
|
|
|
|
// 获取智教联盟真实下载地址
|
|
|
|
|
|
private static async Task<string> GetZhijiaoRealDownloadUrl(string url)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var handler = new HttpClientHandler { AllowAutoRedirect = false })
|
|
|
|
|
|
using (var client = new HttpClient(handler))
|
|
|
|
|
|
{
|
|
|
|
|
|
client.Timeout = RequestTimeout;
|
|
|
|
|
|
var resp = await client.GetAsync(url);
|
|
|
|
|
|
// 优先取Location头
|
2025-07-28 14:40:44 +08:00
|
|
|
|
if (resp.StatusCode == HttpStatusCode.Found || resp.StatusCode == HttpStatusCode.Redirect || resp.StatusCode == HttpStatusCode.MovedPermanently)
|
2025-07-24 22:16:38 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (resp.Headers.Location != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var realUrl = resp.Headers.Location.ToString();
|
|
|
|
|
|
if (realUrl.Contains(" ")) realUrl = realUrl.Replace(" ", "%20");
|
|
|
|
|
|
return realUrl;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 有些服务器直接返回真实地址在内容里
|
|
|
|
|
|
var content = await resp.Content.ReadAsStringAsync();
|
|
|
|
|
|
if (Uri.IsWellFormedUriString(content.Trim(), UriKind.Absolute))
|
|
|
|
|
|
{
|
|
|
|
|
|
var realUrl = content.Trim();
|
|
|
|
|
|
if (realUrl.Contains(" ")) realUrl = realUrl.Replace(" ", "%20");
|
|
|
|
|
|
return realUrl;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 获取智教联盟真实下载地址失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-19 16:03:45 +08:00
|
|
|
|
// 使用多线路组下载新版(支持自动切换)
|
2025-07-22 18:02:29 +08:00
|
|
|
|
public static async Task<bool> DownloadSetupFileWithFallback(string version, List<UpdateLineGroup> groups, Action<double, string> progressCallback = null)
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
statusFilePath = Path.Combine(updatesFolderPath, $"DownloadV{version}Status.txt");
|
|
|
|
|
|
|
|
|
|
|
|
if (File.Exists(statusFilePath) && File.ReadAllText(statusFilePath).Trim().ToLower() == "true")
|
|
|
|
|
|
{
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 安装包已下载");
|
2025-07-22 18:02:29 +08:00
|
|
|
|
progressCallback?.Invoke(100, "已下载完成");
|
2025-05-25 09:29:48 +08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
// 确保更新目录存在
|
2025-06-18 18:16:48 +08:00
|
|
|
|
if (!Directory.Exists(updatesFolderPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
Directory.CreateDirectory(updatesFolderPath);
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 创建更新目录: {updatesFolderPath}");
|
2025-06-18 18:16:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
string zipFilePath = Path.Combine(updatesFolderPath, $"InkCanvasForClass.CE.{version}.zip");
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 目标文件路径: {zipFilePath}");
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
|
|
|
|
|
SaveDownloadStatus(false);
|
2025-07-19 16:03:45 +08:00
|
|
|
|
|
2025-07-24 22:16:38 +08:00
|
|
|
|
// 优先尝试“智教联盟”线路组
|
|
|
|
|
|
var zhiJiaoGroup = groups.FirstOrDefault(g => g.GroupName == "智教联盟");
|
2025-07-26 12:02:58 +08:00
|
|
|
|
var inkeysGroup = groups.FirstOrDefault(g => g.GroupName == "inkeys");
|
|
|
|
|
|
if (zhiJiaoGroup != null || inkeysGroup != null)
|
2025-07-24 22:16:38 +08:00
|
|
|
|
{
|
2025-07-26 12:02:58 +08:00
|
|
|
|
var priorityGroups = new List<UpdateLineGroup>();
|
|
|
|
|
|
if (zhiJiaoGroup != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
priorityGroups.Add(zhiJiaoGroup);
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 下载时优先尝试智教联盟线路组");
|
2025-07-26 12:02:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (inkeysGroup != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
priorityGroups.Add(inkeysGroup);
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 下载时优先尝试inkeys线路组");
|
2025-07-26 12:02:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
groups = priorityGroups.Concat(groups.Where(g => g.GroupName != "智教联盟" && g.GroupName != "inkeys")).ToList();
|
2025-07-24 22:16:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-19 16:03:45 +08:00
|
|
|
|
// 依次尝试每个线路组
|
|
|
|
|
|
foreach (var group in groups)
|
2025-06-18 18:16:48 +08:00
|
|
|
|
{
|
2025-07-19 16:03:45 +08:00
|
|
|
|
string url = string.Format(group.DownloadUrlFormat, version);
|
2025-07-24 22:16:38 +08:00
|
|
|
|
// 智教联盟需要先获取真实下载地址
|
|
|
|
|
|
if (group.GroupName == "智教联盟")
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 获取智教联盟真实下载地址: {url}");
|
|
|
|
|
|
var realUrl = await GetZhijiaoRealDownloadUrl(url);
|
|
|
|
|
|
if (string.IsNullOrEmpty(realUrl))
|
|
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 智教联盟真实下载地址获取失败,跳过", LogHelper.LogType.Warning);
|
2025-07-24 22:16:38 +08:00
|
|
|
|
progressCallback?.Invoke(0, "智教联盟真实下载地址获取失败,跳过");
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
url = realUrl;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 智教联盟真实下载地址: {url}");
|
|
|
|
|
|
}
|
2025-07-26 12:02:58 +08:00
|
|
|
|
// inkeys线路组直接使用下载地址,无需特殊处理
|
|
|
|
|
|
else if (group.GroupName == "inkeys")
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 使用inkeys线路组下载地址: {url}");
|
|
|
|
|
|
}
|
2025-07-19 16:03:45 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 尝试从线路组 {group.GroupName} 下载: {url}");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-22 18:02:29 +08:00
|
|
|
|
bool downloadSuccess = await DownloadFile(url, zipFilePath, progressCallback);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:03:45 +08:00
|
|
|
|
if (downloadSuccess)
|
|
|
|
|
|
{
|
|
|
|
|
|
SaveDownloadStatus(true);
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 从线路组 {group.GroupName} 下载成功");
|
2025-07-22 18:02:29 +08:00
|
|
|
|
progressCallback?.Invoke(100, "下载完成");
|
2025-07-19 16:03:45 +08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-07-28 14:40:44 +08:00
|
|
|
|
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 线路组 {group.GroupName} 下载失败,尝试下一个线路组", LogHelper.LogType.Warning);
|
2025-06-18 18:16:48 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:03:45 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 所有线路组下载均失败", LogHelper.LogType.Error);
|
2025-07-22 18:02:29 +08:00
|
|
|
|
progressCallback?.Invoke(0, "所有线路组下载均失败");
|
2025-07-19 16:03:45 +08:00
|
|
|
|
return false;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 下载更新时出错: {ex.Message}", LogHelper.LogType.Error);
|
2025-06-18 18:16:48 +08:00
|
|
|
|
if (ex.InnerException != null)
|
|
|
|
|
|
{
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 内部异常: {ex.InnerException.Message}", LogHelper.LogType.Error);
|
2025-06-18 18:16:48 +08:00
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
|
|
|
|
|
SaveDownloadStatus(false);
|
2025-07-22 18:02:29 +08:00
|
|
|
|
progressCallback?.Invoke(0, $"下载异常: {ex.Message}");
|
2025-05-25 09:29:48 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
// 下载文件的具体实现
|
2025-07-22 18:25:04 +08:00
|
|
|
|
public static async Task<bool> DownloadFile(string fileUrl, string destinationPath, Action<double, string> progressCallback = null)
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-07-22 18:25:04 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 正在尝试多线程下载: {fileUrl}");
|
2025-07-22 18:08:22 +08:00
|
|
|
|
int maxRetry = 3;
|
2025-07-26 12:20:23 +08:00
|
|
|
|
// 降低并发数,减少网络压力
|
2025-07-28 14:40:44 +08:00
|
|
|
|
int[] threadOptions = { 32, 16, 8, 4, 1 };
|
2025-07-24 22:16:38 +08:00
|
|
|
|
|
|
|
|
|
|
// 检查服务器是否支持Range分块下载
|
|
|
|
|
|
bool supportRange = false;
|
|
|
|
|
|
long totalSize = -1;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var client = new HttpClient())
|
|
|
|
|
|
{
|
|
|
|
|
|
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
|
|
|
|
|
var req = new HttpRequestMessage(HttpMethod.Head, fileUrl);
|
2025-07-28 14:40:44 +08:00
|
|
|
|
req.Headers.Range = new RangeHeaderValue(0, 0);
|
2025-07-24 22:16:38 +08:00
|
|
|
|
var resp = await client.SendAsync(req);
|
2025-07-28 14:40:44 +08:00
|
|
|
|
if (resp.StatusCode == HttpStatusCode.PartialContent)
|
2025-07-24 22:16:38 +08:00
|
|
|
|
{
|
|
|
|
|
|
supportRange = true;
|
|
|
|
|
|
if (resp.Content.Headers.ContentRange != null && resp.Content.Headers.ContentRange.Length.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
totalSize = resp.Content.Headers.ContentRange.Length.Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (resp.Content.Headers.ContentLength.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
totalSize = resp.Content.Headers.ContentLength.Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-28 14:40:44 +08:00
|
|
|
|
else if (resp.StatusCode == HttpStatusCode.OK)
|
2025-07-24 22:16:38 +08:00
|
|
|
|
{
|
|
|
|
|
|
supportRange = false;
|
|
|
|
|
|
if (resp.Content.Headers.ContentLength.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
totalSize = resp.Content.Headers.ContentLength.Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 检查Range支持时异常: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!supportRange)
|
|
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 服务器不支持分块下载,自动降级为单线程下载");
|
2025-07-24 22:16:38 +08:00
|
|
|
|
progressCallback?.Invoke(0, "服务器不支持分块下载,自动降级为单线程下载");
|
2025-07-26 12:20:23 +08:00
|
|
|
|
return await DownloadSingleThread(fileUrl, destinationPath, totalSize, progressCallback);
|
2025-07-24 22:16:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-22 21:01:25 +08:00
|
|
|
|
foreach (int threadCount in threadOptions)
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-07-26 12:20:23 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 尝试使用 {threadCount} 线程下载");
|
|
|
|
|
|
progressCallback?.Invoke(0, $"尝试使用 {threadCount} 线程下载");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-24 22:16:38 +08:00
|
|
|
|
if (totalSize <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
totalSize = await GetContentLength(fileUrl);
|
|
|
|
|
|
}
|
2025-07-22 21:01:25 +08:00
|
|
|
|
if (totalSize <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
progressCallback?.Invoke(0, "无法获取文件大小,取消下载");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 12:20:23 +08:00
|
|
|
|
// 根据文件大小动态调整分块大小,避免分块过小
|
2025-07-26 12:37:39 +08:00
|
|
|
|
int minBlockSize = 32 * 1024; // 最小32KB
|
2025-07-26 12:20:23 +08:00
|
|
|
|
int blockSize = Math.Max(minBlockSize, (int)Math.Ceiling((double)totalSize / threadCount));
|
2025-07-22 21:01:25 +08:00
|
|
|
|
int blockCount = (int)Math.Ceiling((double)totalSize / blockSize);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 12:20:23 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 文件大小: {totalSize}, 分块数: {blockCount}, 分块大小: {blockSize}");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-28 14:40:44 +08:00
|
|
|
|
var blockQueue = new ConcurrentQueue<BlockTask>();
|
|
|
|
|
|
var finishedBlocks = new ConcurrentDictionary<int, bool>();
|
2025-07-22 21:01:25 +08:00
|
|
|
|
long[] blockDownloaded = new long[blockCount];
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-22 21:01:25 +08:00
|
|
|
|
for (int i = 0; i < blockCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
long start = i * blockSize;
|
|
|
|
|
|
long end = Math.Min(start + blockSize - 1, totalSize - 1);
|
|
|
|
|
|
blockQueue.Enqueue(new BlockTask { Index = i, Start = start, End = end, RetryCount = 0 });
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-22 21:01:25 +08:00
|
|
|
|
CancellationTokenSource cts = new CancellationTokenSource();
|
|
|
|
|
|
var tasks = new List<Task>();
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-22 21:01:25 +08:00
|
|
|
|
for (int t = 0; t < threadCount; t++)
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-07-22 21:01:25 +08:00
|
|
|
|
tasks.Add(Task.Run(async () =>
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-07-22 21:01:25 +08:00
|
|
|
|
while (blockQueue.TryDequeue(out var block))
|
2025-06-18 18:16:48 +08:00
|
|
|
|
{
|
2025-07-22 21:01:25 +08:00
|
|
|
|
bool success = false;
|
2025-07-26 12:20:23 +08:00
|
|
|
|
string tempPath = destinationPath + $".part{block.Index}";
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-22 21:01:25 +08:00
|
|
|
|
for (int retry = block.RetryCount; retry < maxRetry && !success; retry++)
|
2025-07-19 16:51:03 +08:00
|
|
|
|
{
|
2025-07-22 21:01:25 +08:00
|
|
|
|
try
|
2025-06-18 18:16:48 +08:00
|
|
|
|
{
|
2025-07-22 21:01:25 +08:00
|
|
|
|
using (var client = new HttpClient())
|
2025-07-19 16:51:03 +08:00
|
|
|
|
{
|
2025-07-24 22:16:38 +08:00
|
|
|
|
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
2025-07-22 21:01:25 +08:00
|
|
|
|
var req = new HttpRequestMessage(HttpMethod.Get, fileUrl);
|
2025-07-28 14:40:44 +08:00
|
|
|
|
req.Headers.Range = new RangeHeaderValue(block.Start, block.End);
|
2025-07-23 19:29:44 +08:00
|
|
|
|
|
2025-07-26 12:20:23 +08:00
|
|
|
|
// 增加连接超时设置
|
|
|
|
|
|
client.Timeout = TimeSpan.FromSeconds(30);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-23 19:29:44 +08:00
|
|
|
|
var downloadCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token);
|
|
|
|
|
|
var lastReadTime = DateTime.UtcNow;
|
|
|
|
|
|
bool dataReceived = false;
|
|
|
|
|
|
|
|
|
|
|
|
using (var resp = await client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, downloadCts.Token))
|
2025-07-22 18:08:22 +08:00
|
|
|
|
{
|
2025-07-24 22:16:38 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index} 响应状态: {resp.StatusCode}");
|
2025-07-22 21:01:25 +08:00
|
|
|
|
resp.EnsureSuccessStatusCode();
|
|
|
|
|
|
using (var fs = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
2025-07-22 18:45:43 +08:00
|
|
|
|
{
|
2025-07-22 21:01:25 +08:00
|
|
|
|
var stream = await resp.Content.ReadAsStreamAsync();
|
|
|
|
|
|
byte[] buffer = new byte[8192];
|
|
|
|
|
|
int read;
|
2025-07-26 12:20:23 +08:00
|
|
|
|
long blockDownloadedBytes = 0;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-23 19:29:44 +08:00
|
|
|
|
while (true)
|
2025-07-22 21:01:25 +08:00
|
|
|
|
{
|
2025-07-23 19:29:44 +08:00
|
|
|
|
var readTask = stream.ReadAsync(buffer, 0, buffer.Length, downloadCts.Token);
|
2025-07-26 12:20:23 +08:00
|
|
|
|
var timeoutTask = Task.Delay(20000, downloadCts.Token); // 增加到20秒超时
|
2025-07-23 19:29:44 +08:00
|
|
|
|
var completed = await Task.WhenAny(readTask, timeoutTask);
|
|
|
|
|
|
if (completed == timeoutTask)
|
|
|
|
|
|
{
|
2025-07-26 12:20:23 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index} 20秒无数据,线程超时重试", LogHelper.LogType.Warning);
|
|
|
|
|
|
progressCallback?.Invoke(0, $"分块{block.Index} 20秒无数据,线程超时重试");
|
2025-07-23 19:29:44 +08:00
|
|
|
|
downloadCts.Cancel();
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
read = await readTask;
|
|
|
|
|
|
if (read <= 0) break;
|
|
|
|
|
|
await fs.WriteAsync(buffer, 0, read, downloadCts.Token);
|
2025-07-26 12:20:23 +08:00
|
|
|
|
blockDownloadedBytes += read;
|
|
|
|
|
|
blockDownloaded[block.Index] = blockDownloadedBytes;
|
2025-07-23 19:29:44 +08:00
|
|
|
|
lastReadTime = DateTime.UtcNow;
|
|
|
|
|
|
dataReceived = true;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-22 21:01:25 +08:00
|
|
|
|
// 合并所有块进度
|
|
|
|
|
|
long totalDownloaded = blockDownloaded.Sum();
|
|
|
|
|
|
double percent = (double)totalDownloaded / totalSize * 100;
|
|
|
|
|
|
progressCallback?.Invoke(percent, $"多线程下载中({threadCount}线程): {percent:F1}%");
|
|
|
|
|
|
}
|
2025-07-22 18:45:43 +08:00
|
|
|
|
}
|
2025-07-22 18:08:22 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-23 19:29:44 +08:00
|
|
|
|
if (!dataReceived)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new IOException("分块下载超时无数据");
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 12:20:23 +08:00
|
|
|
|
// 验证分块大小是否正确
|
|
|
|
|
|
var fileInfo = new FileInfo(tempPath);
|
|
|
|
|
|
long expectedSize = block.End - block.Start + 1;
|
|
|
|
|
|
if (fileInfo.Length != expectedSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}大小不匹配,期望:{expectedSize},实际:{fileInfo.Length}", LogHelper.LogType.Warning);
|
|
|
|
|
|
throw new IOException($"分块{block.Index}大小不匹配");
|
|
|
|
|
|
}
|
2025-06-18 22:40:48 +08:00
|
|
|
|
}
|
2025-07-22 21:01:25 +08:00
|
|
|
|
success = true;
|
2025-07-26 12:20:23 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载成功");
|
2025-07-22 21:01:25 +08:00
|
|
|
|
}
|
2025-07-23 19:29:44 +08:00
|
|
|
|
catch (Exception ex) when (ex is HttpRequestException || ex is IOException || ex is TaskCanceledException)
|
2025-07-22 21:01:25 +08:00
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载失败,第{retry + 1}次: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
progressCallback?.Invoke(0, $"分块{block.Index}下载失败,第{retry + 1}次: {ex.Message}");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 12:20:23 +08:00
|
|
|
|
// 清理可能损坏的分块文件
|
|
|
|
|
|
if (File.Exists(tempPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
try { File.Delete(tempPath); } catch { }
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 12:20:23 +08:00
|
|
|
|
// 增加重试间隔,避免频繁重试
|
|
|
|
|
|
await Task.Delay(2000 * (retry + 1));
|
2025-06-18 18:16:48 +08:00
|
|
|
|
}
|
2025-06-18 22:40:48 +08:00
|
|
|
|
}
|
2025-07-22 21:01:25 +08:00
|
|
|
|
if (success)
|
2025-07-19 16:51:03 +08:00
|
|
|
|
{
|
2025-07-22 21:01:25 +08:00
|
|
|
|
finishedBlocks[block.Index] = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (block.RetryCount + 1 < maxRetry)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 失败但未超最大重试,重新入队
|
|
|
|
|
|
block.RetryCount++;
|
|
|
|
|
|
blockQueue.Enqueue(block);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 超过最大重试,取消所有任务
|
2025-07-26 12:20:23 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}超过最大重试次数,取消下载", LogHelper.LogType.Error);
|
2025-07-22 21:01:25 +08:00
|
|
|
|
cts.Cancel();
|
|
|
|
|
|
break;
|
2025-07-19 16:51:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-22 21:01:25 +08:00
|
|
|
|
}));
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-22 21:01:25 +08:00
|
|
|
|
await Task.WhenAll(tasks);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-22 21:01:25 +08:00
|
|
|
|
if (cts.IsCancellationRequested || finishedBlocks.Count != blockCount)
|
2025-07-22 18:25:04 +08:00
|
|
|
|
{
|
2025-07-26 12:20:23 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | {threadCount}线程下载失败,完成分块数: {finishedBlocks.Count}/{blockCount}", LogHelper.LogType.Warning);
|
|
|
|
|
|
progressCallback?.Invoke(0, $"{threadCount}线程下载失败,完成分块数: {finishedBlocks.Count}/{blockCount}");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-22 21:01:25 +08:00
|
|
|
|
// 清理分块文件
|
|
|
|
|
|
for (int i = 0; i < blockCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
string tempPath = destinationPath + $".part{i}";
|
|
|
|
|
|
if (File.Exists(tempPath)) File.Delete(tempPath);
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-22 21:01:25 +08:00
|
|
|
|
if (threadCount == threadOptions.Last())
|
|
|
|
|
|
{
|
2025-07-26 12:20:23 +08:00
|
|
|
|
// 已经是最后一次尝试,降级为单线程
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 所有多线程尝试失败,降级为单线程下载");
|
2025-07-26 12:20:23 +08:00
|
|
|
|
progressCallback?.Invoke(0, "所有多线程尝试失败,降级为单线程下载");
|
|
|
|
|
|
return await DownloadSingleThread(fileUrl, destinationPath, totalSize, progressCallback);
|
2025-07-22 21:01:25 +08:00
|
|
|
|
}
|
2025-07-28 14:40:44 +08:00
|
|
|
|
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | {threadCount}线程下载失败,尝试降级为{threadOptions[Array.IndexOf(threadOptions, threadCount) + 1]}线程");
|
|
|
|
|
|
progressCallback?.Invoke(0, $"{threadCount}线程下载失败,尝试降级为{threadOptions[Array.IndexOf(threadOptions, threadCount) + 1]}线程");
|
|
|
|
|
|
continue;
|
2025-07-22 18:08:22 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-22 21:01:25 +08:00
|
|
|
|
// 合并所有块
|
2025-07-26 12:20:23 +08:00
|
|
|
|
try
|
2025-07-22 18:08:22 +08:00
|
|
|
|
{
|
2025-07-26 12:20:23 +08:00
|
|
|
|
using (var output = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
2025-06-18 18:16:48 +08:00
|
|
|
|
{
|
2025-07-26 12:20:23 +08:00
|
|
|
|
for (int i = 0; i < blockCount; i++)
|
2025-07-22 21:01:25 +08:00
|
|
|
|
{
|
2025-07-26 12:20:23 +08:00
|
|
|
|
string tempPath = destinationPath + $".part{i}";
|
|
|
|
|
|
if (!File.Exists(tempPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new FileNotFoundException($"分块文件不存在: {tempPath}");
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 12:20:23 +08:00
|
|
|
|
using (var input = new FileStream(tempPath, FileMode.Open, FileAccess.Read))
|
|
|
|
|
|
{
|
|
|
|
|
|
await input.CopyToAsync(output);
|
|
|
|
|
|
}
|
|
|
|
|
|
File.Delete(tempPath);
|
2025-07-22 21:01:25 +08:00
|
|
|
|
}
|
2025-06-18 18:16:48 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 12:20:23 +08:00
|
|
|
|
progressCallback?.Invoke(100, $"多线程下载完成({threadCount}线程)");
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 多线程下载完成({threadCount}线程)");
|
2025-07-24 23:18:44 +08:00
|
|
|
|
|
2025-07-26 12:20:23 +08:00
|
|
|
|
// 文件大小校验
|
|
|
|
|
|
FileInfo fileInfo = new FileInfo(destinationPath);
|
|
|
|
|
|
if (fileInfo.Length != totalSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 文件大小校验失败,本地:{fileInfo.Length},服务器:{totalSize}", LogHelper.LogType.Error);
|
|
|
|
|
|
File.Delete(destinationPath);
|
|
|
|
|
|
progressCallback?.Invoke(0, "文件大小校验失败,已删除损坏文件");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 12:20:23 +08:00
|
|
|
|
// ZIP文件完整性校验
|
|
|
|
|
|
if (destinationPath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
ZipFile.OpenRead(destinationPath).Dispose();
|
2025-07-26 12:20:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | ZIP文件解压测试失败,文件可能已损坏", LogHelper.LogType.Error);
|
|
|
|
|
|
File.Delete(destinationPath);
|
|
|
|
|
|
progressCallback?.Invoke(0, "ZIP文件解压测试失败,已删除损坏文件");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
2025-07-24 23:18:44 +08:00
|
|
|
|
{
|
2025-07-26 12:20:23 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 合并分块文件时出错: {ex.Message}", LogHelper.LogType.Error);
|
2025-07-24 23:18:44 +08:00
|
|
|
|
File.Delete(destinationPath);
|
2025-07-26 12:20:23 +08:00
|
|
|
|
progressCallback?.Invoke(0, $"合并分块文件时出错: {ex.Message}");
|
2025-07-24 23:18:44 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-07-26 12:20:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 单线程下载方法
|
|
|
|
|
|
private static async Task<bool> DownloadSingleThread(string fileUrl, string destinationPath, long totalSize, Action<double, string> progressCallback = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 开始单线程下载: {fileUrl}");
|
|
|
|
|
|
progressCallback?.Invoke(0, "开始单线程下载");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 12:20:23 +08:00
|
|
|
|
using (var client = new HttpClient())
|
2025-07-24 23:18:44 +08:00
|
|
|
|
{
|
2025-07-26 12:20:23 +08:00
|
|
|
|
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
|
|
|
|
|
client.Timeout = TimeSpan.FromMinutes(10); // 单线程下载设置更长的超时时间
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 12:20:23 +08:00
|
|
|
|
using (var resp = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead))
|
2025-07-24 23:18:44 +08:00
|
|
|
|
{
|
2025-07-26 12:20:23 +08:00
|
|
|
|
resp.EnsureSuccessStatusCode();
|
|
|
|
|
|
using (var fs = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
|
|
|
|
|
{
|
|
|
|
|
|
var stream = await resp.Content.ReadAsStreamAsync();
|
|
|
|
|
|
byte[] buffer = new byte[8192];
|
|
|
|
|
|
int read;
|
|
|
|
|
|
long downloaded = 0;
|
|
|
|
|
|
var lastProgressUpdate = DateTime.UtcNow;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 12:20:23 +08:00
|
|
|
|
while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
await fs.WriteAsync(buffer, 0, read);
|
|
|
|
|
|
downloaded += read;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 12:20:23 +08:00
|
|
|
|
// 限制进度更新频率,避免UI卡顿
|
|
|
|
|
|
if (DateTime.UtcNow - lastProgressUpdate > TimeSpan.FromMilliseconds(500))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (totalSize > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
double percent = (double)downloaded / totalSize * 100;
|
|
|
|
|
|
progressCallback?.Invoke(percent, $"单线程下载中: {percent:F1}%");
|
|
|
|
|
|
}
|
|
|
|
|
|
lastProgressUpdate = DateTime.UtcNow;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-24 23:18:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 12:20:23 +08:00
|
|
|
|
progressCallback?.Invoke(100, "单线程下载完成");
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 单线程下载完成");
|
2025-07-22 21:01:25 +08:00
|
|
|
|
return true;
|
2025-07-22 18:25:04 +08:00
|
|
|
|
}
|
2025-07-26 12:20:23 +08:00
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 单线程下载失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
progressCallback?.Invoke(0, $"单线程下载失败: {ex.Message}");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-07-22 18:25:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取文件总大小
|
|
|
|
|
|
private static async Task<long> GetContentLength(string fileUrl)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var client = new HttpClient())
|
2025-07-22 18:08:22 +08:00
|
|
|
|
{
|
2025-07-22 18:25:04 +08:00
|
|
|
|
var req = new HttpRequestMessage(HttpMethod.Head, fileUrl);
|
|
|
|
|
|
var resp = await client.SendAsync(req);
|
|
|
|
|
|
if (resp.IsSuccessStatusCode && resp.Content.Headers.ContentLength.HasValue)
|
|
|
|
|
|
return resp.Content.Headers.ContentLength.Value;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch { }
|
2025-07-22 18:25:04 +08:00
|
|
|
|
return -1;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
// 保存下载状态
|
2025-05-25 09:29:48 +08:00
|
|
|
|
private static void SaveDownloadStatus(bool isSuccess)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (statusFilePath == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
string directory = Path.GetDirectoryName(statusFilePath);
|
|
|
|
|
|
if (!Directory.Exists(directory))
|
|
|
|
|
|
{
|
|
|
|
|
|
Directory.CreateDirectory(directory);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
File.WriteAllText(statusFilePath, isSuccess.ToString());
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 保存下载状态时出错: {ex.Message}", LogHelper.LogType.Error);
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 安装新版本应用 - 优化版本,不使用命令行
|
2025-05-25 09:29:48 +08:00
|
|
|
|
public static void InstallNewVersionApp(string version, bool isInSilence)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-07-16 15:28:36 +08:00
|
|
|
|
// 在更新前备份设置文件
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (MainWindow.Settings.Advanced.IsAutoBackupBeforeUpdate)
|
|
|
|
|
|
{
|
|
|
|
|
|
string backupDir = Path.Combine(App.RootPath, "Backups");
|
|
|
|
|
|
if (!Directory.Exists(backupDir))
|
|
|
|
|
|
{
|
|
|
|
|
|
Directory.CreateDirectory(backupDir);
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"创建备份目录: {backupDir}");
|
2025-07-16 15:28:36 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-16 15:28:36 +08:00
|
|
|
|
string backupFileName = $"Settings_BeforeUpdate_v{version}_{DateTime.Now:yyyyMMdd_HHmmss}.json";
|
|
|
|
|
|
string backupPath = Path.Combine(backupDir, backupFileName);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
string settingsJson = JsonConvert.SerializeObject(MainWindow.Settings, Formatting.Indented);
|
2025-07-16 15:28:36 +08:00
|
|
|
|
File.WriteAllText(backupPath, settingsJson);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"更新前自动备份设置成功: {backupPath}");
|
2025-07-16 15:28:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("更新前自动备份功能已禁用,跳过备份");
|
2025-07-16 15:28:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"更新前自动备份设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-06-18 15:40:07 +08:00
|
|
|
|
string zipFilePath = Path.Combine(updatesFolderPath, $"InkCanvasForClass.CE.{version}.zip");
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 检查ZIP文件: {zipFilePath}");
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
2025-06-18 15:40:07 +08:00
|
|
|
|
if (!File.Exists(zipFilePath))
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | ZIP文件未找到: {zipFilePath}", LogHelper.LogType.Error);
|
2025-05-25 09:29:48 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 18:16:48 +08:00
|
|
|
|
FileInfo fileInfo = new FileInfo(zipFilePath);
|
|
|
|
|
|
if (fileInfo.Length == 0)
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | ZIP文件为空,无法继续", LogHelper.LogType.Error);
|
2025-06-18 18:16:48 +08:00
|
|
|
|
return;
|
2025-06-18 15:40:07 +08:00
|
|
|
|
}
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | ZIP文件大小: {fileInfo.Length} 字节");
|
2025-06-18 15:40:07 +08:00
|
|
|
|
|
|
|
|
|
|
string currentAppDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
2025-06-18 18:16:48 +08:00
|
|
|
|
string appPath = Assembly.GetExecutingAssembly().Location;
|
2025-08-30 14:11:39 +08:00
|
|
|
|
int currentProcessId = Process.GetCurrentProcess().Id;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 当前应用程序目录: {currentAppDir}");
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 当前进程ID: {currentProcessId}");
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 静默更新模式: {isInSilence}");
|
2025-06-18 15:40:07 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 创建解压目录
|
|
|
|
|
|
string extractPath = Path.Combine(updatesFolderPath, $"Extract_{version}");
|
|
|
|
|
|
if (Directory.Exists(extractPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
Directory.Delete(extractPath, true);
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 清理已存在的解压目录: {extractPath}");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 清理解压目录失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
Directory.CreateDirectory(extractPath);
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 创建解压目录: {extractPath}");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 创建解压目录失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-25 09:00:45 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 解压ZIP文件
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 开始解压ZIP文件到: {extractPath}");
|
|
|
|
|
|
ZipFile.ExtractToDirectory(zipFilePath, extractPath);
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | ZIP文件解压完成");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 解压ZIP文件失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 查找解压后的主程序文件
|
|
|
|
|
|
string newAppPath = null;
|
|
|
|
|
|
string[] possibleExeNames = { "InkCanvasForClass.exe", "Ink Canvas.exe", "InkCanvas.exe" };
|
|
|
|
|
|
|
|
|
|
|
|
foreach (string exeName in possibleExeNames)
|
|
|
|
|
|
{
|
|
|
|
|
|
string testPath = Path.Combine(extractPath, exeName);
|
|
|
|
|
|
if (File.Exists(testPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
newAppPath = testPath;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 找到新版本主程序: {newAppPath}");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
if (string.IsNullOrEmpty(newAppPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 在解压目录中未找到主程序文件", LogHelper.LogType.Error);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 启动新版本进程
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 准备启动新版本进程: {newAppPath}");
|
|
|
|
|
|
|
|
|
|
|
|
// 启动新版本进程(以更新模式)
|
|
|
|
|
|
string arguments = $"--update-mode --old-process-id={currentProcessId} --extract-path=\"{extractPath}\" --target-path=\"{currentAppDir}\" --is-silence={isInSilence}";
|
|
|
|
|
|
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 启动新进程的命令行: {newAppPath} {arguments}");
|
|
|
|
|
|
|
|
|
|
|
|
ProcessStartInfo startInfo = new ProcessStartInfo
|
|
|
|
|
|
{
|
|
|
|
|
|
FileName = newAppPath,
|
|
|
|
|
|
Arguments = arguments,
|
|
|
|
|
|
UseShellExecute = false,
|
|
|
|
|
|
CreateNoWindow = false
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Process.Start(startInfo);
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 新版本进程启动命令已执行");
|
|
|
|
|
|
|
|
|
|
|
|
// 等待一小段时间确保新进程启动
|
|
|
|
|
|
Thread.Sleep(2000);
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭当前旧软件进程
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 关闭当前旧软件进程");
|
|
|
|
|
|
App.IsAppExitByUser = true;
|
|
|
|
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
Application.Current.Shutdown();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 启动新版本进程时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 准备更新安装时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
if (ex.InnerException != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 内部异常: {ex.InnerException.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 处理更新模式的启动参数
|
|
|
|
|
|
public static bool HandleUpdateModeStartup(string[] args)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查是否以更新模式启动
|
|
|
|
|
|
if (args.Contains("--update-mode"))
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 检测到更新模式启动");
|
|
|
|
|
|
|
|
|
|
|
|
// 解析命令行参数
|
|
|
|
|
|
int oldProcessId = -1;
|
|
|
|
|
|
string extractPath = null;
|
|
|
|
|
|
string targetPath = null;
|
|
|
|
|
|
bool isSilence = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 记录所有参数用于调试
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 接收到的命令行参数: {string.Join(" ", args)}");
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < args.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
string arg = args[i];
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 处理参数 {i}: {arg}");
|
|
|
|
|
|
|
|
|
|
|
|
if (arg.StartsWith("--old-process-id="))
|
|
|
|
|
|
{
|
|
|
|
|
|
string processIdStr = arg.Substring("--old-process-id=".Length);
|
|
|
|
|
|
if (int.TryParse(processIdStr, out int pid))
|
|
|
|
|
|
{
|
|
|
|
|
|
oldProcessId = pid;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 解析到老进程ID: {oldProcessId}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (arg.StartsWith("--extract-path="))
|
|
|
|
|
|
{
|
|
|
|
|
|
extractPath = arg.Substring("--extract-path=".Length).Trim('"');
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 解析到解压路径: {extractPath}");
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (arg.StartsWith("--target-path="))
|
|
|
|
|
|
{
|
|
|
|
|
|
targetPath = arg.Substring("--target-path=".Length).Trim('"');
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 解析到目标路径: {targetPath}");
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (arg.StartsWith("--is-silence="))
|
|
|
|
|
|
{
|
|
|
|
|
|
string silenceStr = arg.Substring("--is-silence=".Length);
|
|
|
|
|
|
if (bool.TryParse(silenceStr, out bool silence))
|
|
|
|
|
|
{
|
|
|
|
|
|
isSilence = silence;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 解析到静默模式: {isSilence}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 更新参数 - 老进程ID: {oldProcessId}, 解压路径: {extractPath}, 目标路径: {targetPath}, 静默模式: {isSilence}");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
if (oldProcessId > 0 && !string.IsNullOrEmpty(extractPath) && !string.IsNullOrEmpty(targetPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 参数验证通过,启动更新任务");
|
|
|
|
|
|
// 启动更新任务
|
|
|
|
|
|
Task.Run(async () => await PerformUpdate(oldProcessId, extractPath, targetPath, isSilence));
|
|
|
|
|
|
return true; // 返回true表示是更新模式
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 参数验证失败 - 老进程ID: {oldProcessId}, 解压路径: {extractPath}, 目标路径: {targetPath}", LogHelper.LogType.Error);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false; // 返回false表示不是更新模式
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 处理更新模式启动时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 执行实际的更新操作
|
|
|
|
|
|
private static async Task PerformUpdate(int oldProcessId, string extractPath, string targetPath, bool isSilence)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 开始执行更新操作");
|
|
|
|
|
|
|
|
|
|
|
|
// 等待老进程完全退出
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 等待老进程 {oldProcessId} 退出");
|
|
|
|
|
|
int waitCount = 0;
|
|
|
|
|
|
const int maxWaitCount = 30; // 最多等待30秒
|
|
|
|
|
|
|
|
|
|
|
|
while (waitCount < maxWaitCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
Process oldProcess = Process.GetProcessById(oldProcessId);
|
|
|
|
|
|
if (oldProcess.HasExited)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 老进程已退出");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 老进程仍在运行,等待中... ({waitCount + 1}/{maxWaitCount})");
|
|
|
|
|
|
Thread.Sleep(1000);
|
|
|
|
|
|
waitCount++;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (ArgumentException)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 进程不存在,说明已经退出
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 老进程已退出(进程不存在)");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (waitCount >= maxWaitCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 等待老进程退出超时,尝试强制结束", LogHelper.LogType.Warning);
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
Process oldProcess = Process.GetProcessById(oldProcessId);
|
|
|
|
|
|
oldProcess.Kill();
|
|
|
|
|
|
Thread.Sleep(2000); // 等待进程完全结束
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 强制结束老进程失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 确保目标目录存在
|
|
|
|
|
|
if (!Directory.Exists(targetPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
Directory.CreateDirectory(targetPath);
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 创建目标目录: {targetPath}");
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 复制文件到目标目录
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 开始复制文件从 {extractPath} 到 {targetPath}");
|
|
|
|
|
|
|
|
|
|
|
|
try
|
2025-06-19 11:47:16 +08:00
|
|
|
|
{
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 使用递归复制方法,支持重试机制
|
|
|
|
|
|
bool copySuccess = await CopyDirectoryWithRetryAsync(extractPath, targetPath);
|
|
|
|
|
|
if (copySuccess)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 文件复制完成");
|
2025-06-19 11:47:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-08-30 14:11:39 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 文件复制失败,部分文件可能无法覆盖", LogHelper.LogType.Error);
|
|
|
|
|
|
|
|
|
|
|
|
if (!isSilence)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show("更新失败:部分文件无法覆盖,可能是文件正在使用中。\n请关闭所有相关程序后重试。", "更新失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 文件复制失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
|
|
|
|
|
|
if (!isSilence)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show($"更新失败:文件复制时出错\n{ex.Message}", "更新失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清理临时文件
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 清理临时文件");
|
|
|
|
|
|
|
|
|
|
|
|
// 删除解压目录
|
|
|
|
|
|
if (Directory.Exists(extractPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
Directory.Delete(extractPath, true);
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 删除解压目录: {extractPath}");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除ZIP文件
|
|
|
|
|
|
string zipFile = Path.Combine(updatesFolderPath, $"InkCanvasForClass.CE.*.zip");
|
|
|
|
|
|
string[] zipFiles = Directory.GetFiles(updatesFolderPath, "InkCanvasForClass.CE.*.zip");
|
|
|
|
|
|
foreach (string zip in zipFiles)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
File.Delete(zip);
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 删除ZIP文件: {zip}");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 删除ZIP文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 清理临时文件时出错: {ex.Message}", LogHelper.LogType.Warning);
|
2025-06-19 11:47:16 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 更新操作完成");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 启动更新后的应用程序
|
|
|
|
|
|
string newAppPath = Path.Combine(targetPath, "InkCanvasForClass.exe");
|
|
|
|
|
|
if (File.Exists(newAppPath))
|
2025-06-19 11:47:16 +08:00
|
|
|
|
{
|
2025-08-30 14:11:39 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 准备启动更新后的应用程序: {newAppPath}");
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前更新进程ID
|
|
|
|
|
|
int currentUpdateProcessId = Process.GetCurrentProcess().Id;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 当前更新进程ID: {currentUpdateProcessId}");
|
|
|
|
|
|
|
|
|
|
|
|
// 创建一个临时标记文件,用于新进程检测更新状态
|
|
|
|
|
|
string updateMarkerFile = Path.Combine(targetPath, "update_in_progress.tmp");
|
|
|
|
|
|
File.WriteAllText(updateMarkerFile, currentUpdateProcessId.ToString());
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 创建更新标记文件: {updateMarkerFile}");
|
|
|
|
|
|
|
|
|
|
|
|
// 启动更新后的应用程序
|
|
|
|
|
|
ProcessStartInfo startInfo = new ProcessStartInfo
|
|
|
|
|
|
{
|
|
|
|
|
|
FileName = newAppPath,
|
|
|
|
|
|
WorkingDirectory = targetPath,
|
|
|
|
|
|
UseShellExecute = false
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Process newProcess = Process.Start(startInfo);
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 最终应用程序启动成功,PID: {newProcess?.Id}");
|
|
|
|
|
|
|
|
|
|
|
|
// 等待一小段时间确保最终应用程序启动
|
|
|
|
|
|
Thread.Sleep(2000);
|
|
|
|
|
|
|
|
|
|
|
|
// 结束当前更新进程
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 更新流程完成,结束更新进程");
|
|
|
|
|
|
|
|
|
|
|
|
// 强制结束当前更新进程
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 强制结束更新进程");
|
|
|
|
|
|
Process.GetCurrentProcess().Kill();
|
|
|
|
|
|
// 如果Kill()没有立即生效,使用Environment.Exit强制退出
|
|
|
|
|
|
Thread.Sleep(100);
|
|
|
|
|
|
Environment.Exit(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 结束当前更新进程失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
Environment.Exit(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 启动更新后的应用程序失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
|
|
|
|
|
|
if (!isSilence)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show($"更新完成,但启动应用程序失败:{ex.Message}\n请手动启动应用程序。", "启动失败", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-19 11:47:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-08-30 14:11:39 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 更新后的应用程序文件不存在: {newAppPath}", LogHelper.LogType.Error);
|
|
|
|
|
|
|
|
|
|
|
|
if (!isSilence)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show($"更新完成,但未找到应用程序文件:{newAppPath}\n请检查更新是否成功。", "文件缺失", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
|
|
}
|
2025-06-19 11:47:16 +08:00
|
|
|
|
}
|
2025-08-30 14:11:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 执行更新操作时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
|
|
|
|
|
|
if (!isSilence)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show($"更新失败:{ex.Message}", "更新失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 异步复制目录的辅助方法(带重试机制)
|
|
|
|
|
|
private static async Task<bool> CopyDirectoryWithRetryAsync(string sourceDir, string destinationDir)
|
|
|
|
|
|
{
|
|
|
|
|
|
var dir = new DirectoryInfo(sourceDir);
|
|
|
|
|
|
DirectoryInfo[] dirs = dir.GetDirectories();
|
|
|
|
|
|
bool allFilesCopied = true;
|
2025-06-18 18:16:48 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 如果目标目录不存在,则创建它
|
|
|
|
|
|
if (!Directory.Exists(destinationDir))
|
|
|
|
|
|
{
|
|
|
|
|
|
Directory.CreateDirectory(destinationDir);
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 复制文件
|
|
|
|
|
|
foreach (FileInfo file in dir.GetFiles())
|
|
|
|
|
|
{
|
|
|
|
|
|
string targetFilePath = Path.Combine(destinationDir, file.Name);
|
|
|
|
|
|
bool fileCopied = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 重试机制,最多重试3次
|
|
|
|
|
|
for (int retry = 0; retry < 3; retry++)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果目标文件存在,先尝试删除
|
|
|
|
|
|
if (File.Exists(targetFilePath))
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
File.Delete(targetFilePath);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (IOException)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 文件可能正在使用,等待一下再重试
|
|
|
|
|
|
if (retry < 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
Thread.Sleep(1000);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await Task.Run(() => file.CopyTo(targetFilePath));
|
|
|
|
|
|
fileCopied = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 复制文件失败 (重试 {retry + 1}/3) {file.FullName} -> {targetFilePath}: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
|
|
|
|
|
|
if (retry < 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
Thread.Sleep(1000); // 等待1秒后重试
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!fileCopied)
|
|
|
|
|
|
{
|
|
|
|
|
|
allFilesCopied = false;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 文件复制最终失败: {file.FullName}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 递归复制子目录
|
|
|
|
|
|
foreach (DirectoryInfo subDir in dirs)
|
|
|
|
|
|
{
|
|
|
|
|
|
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
|
|
|
|
|
|
bool subDirCopied = await CopyDirectoryWithRetryAsync(subDir.FullName, newDestinationDir);
|
|
|
|
|
|
if (!subDirCopied)
|
2025-06-18 15:40:07 +08:00
|
|
|
|
{
|
2025-08-30 14:11:39 +08:00
|
|
|
|
allFilesCopied = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-08-30 14:11:39 +08:00
|
|
|
|
return allFilesCopied;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 异步复制目录的辅助方法(原版本,保留兼容性)
|
|
|
|
|
|
private static async Task CopyDirectoryAsync(string sourceDir, string destinationDir)
|
|
|
|
|
|
{
|
|
|
|
|
|
var dir = new DirectoryInfo(sourceDir);
|
|
|
|
|
|
DirectoryInfo[] dirs = dir.GetDirectories();
|
|
|
|
|
|
|
|
|
|
|
|
// 如果目标目录不存在,则创建它
|
|
|
|
|
|
if (!Directory.Exists(destinationDir))
|
|
|
|
|
|
{
|
|
|
|
|
|
Directory.CreateDirectory(destinationDir);
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
2025-08-30 14:11:39 +08:00
|
|
|
|
|
|
|
|
|
|
// 复制文件
|
|
|
|
|
|
foreach (FileInfo file in dir.GetFiles())
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-08-30 14:11:39 +08:00
|
|
|
|
string targetFilePath = Path.Combine(destinationDir, file.Name);
|
|
|
|
|
|
try
|
2025-06-18 18:16:48 +08:00
|
|
|
|
{
|
2025-08-30 14:11:39 +08:00
|
|
|
|
// 如果目标文件存在且正在使用,先删除
|
|
|
|
|
|
if (File.Exists(targetFilePath))
|
|
|
|
|
|
{
|
|
|
|
|
|
File.Delete(targetFilePath);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await Task.Run(() => file.CopyTo(targetFilePath));
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
2025-08-30 14:11:39 +08:00
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 复制文件失败 {file.FullName} -> {targetFilePath}: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
// 继续复制其他文件,不中断整个过程
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 递归复制子目录
|
|
|
|
|
|
foreach (DirectoryInfo subDir in dirs)
|
|
|
|
|
|
{
|
|
|
|
|
|
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
|
|
|
|
|
|
await CopyDirectoryAsync(subDir.FullName, newDestinationDir);
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
// 获取远程内容的通用方法
|
2025-07-06 15:39:37 +08:00
|
|
|
|
public static async Task<string> GetRemoteContent(string fileUrl)
|
2025-06-29 11:56:38 +08:00
|
|
|
|
{
|
2025-07-19 16:51:03 +08:00
|
|
|
|
// 检测是否为Windows 7
|
|
|
|
|
|
var osVersion = Environment.OSVersion;
|
|
|
|
|
|
bool isWindows7 = osVersion.Version.Major == 6 && osVersion.Version.Minor == 1;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
if (isWindows7)
|
2025-06-29 11:56:38 +08:00
|
|
|
|
{
|
2025-07-19 16:51:03 +08:00
|
|
|
|
// Windows 7使用特殊配置
|
|
|
|
|
|
using (var handler = new HttpClientHandler())
|
2025-06-29 11:56:38 +08:00
|
|
|
|
{
|
2025-07-19 16:51:03 +08:00
|
|
|
|
try
|
2025-06-29 11:56:38 +08:00
|
|
|
|
{
|
2025-07-19 16:51:03 +08:00
|
|
|
|
// 配置HttpClientHandler以支持Windows 7
|
|
|
|
|
|
handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
using (HttpClient client = new HttpClient(handler))
|
|
|
|
|
|
{
|
|
|
|
|
|
client.Timeout = RequestTimeout;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 发送HTTP请求到: {fileUrl}");
|
|
|
|
|
|
var downloadTask = client.GetAsync(fileUrl);
|
|
|
|
|
|
var timeoutTask = Task.Delay(RequestTimeout);
|
|
|
|
|
|
var completedTask = await Task.WhenAny(downloadTask, timeoutTask);
|
|
|
|
|
|
if (completedTask == timeoutTask)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 请求超时 ({RequestTimeout.TotalSeconds}秒)", LogHelper.LogType.Error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
HttpResponseMessage response = await downloadTask;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | HTTP响应状态: {response.StatusCode}");
|
|
|
|
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
|
|
string content = await response.Content.ReadAsStringAsync();
|
|
|
|
|
|
return content;
|
|
|
|
|
|
}
|
2025-06-29 11:56:38 +08:00
|
|
|
|
}
|
2025-07-19 16:51:03 +08:00
|
|
|
|
catch (HttpRequestException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | HTTP请求错误: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (TaskCanceledException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 请求超时: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 错误: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
2025-06-29 11:56:38 +08:00
|
|
|
|
}
|
2025-07-19 16:51:03 +08:00
|
|
|
|
}
|
2025-07-28 14:40:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 其他Windows版本使用标准配置
|
|
|
|
|
|
using (HttpClient client = new HttpClient())
|
2025-07-19 16:51:03 +08:00
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
try
|
2025-06-29 11:56:38 +08:00
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
client.Timeout = RequestTimeout;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 发送HTTP请求到: {fileUrl}");
|
|
|
|
|
|
var downloadTask = client.GetAsync(fileUrl);
|
|
|
|
|
|
var timeoutTask = Task.Delay(RequestTimeout);
|
|
|
|
|
|
var completedTask = await Task.WhenAny(downloadTask, timeoutTask);
|
|
|
|
|
|
if (completedTask == timeoutTask)
|
2025-07-19 16:51:03 +08:00
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 请求超时 ({RequestTimeout.TotalSeconds}秒)", LogHelper.LogType.Error);
|
|
|
|
|
|
return null;
|
2025-07-19 16:51:03 +08:00
|
|
|
|
}
|
2025-07-28 14:40:44 +08:00
|
|
|
|
HttpResponseMessage response = await downloadTask;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | HTTP响应状态: {response.StatusCode}");
|
|
|
|
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
|
|
string content = await response.Content.ReadAsStringAsync();
|
|
|
|
|
|
return content;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (HttpRequestException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | HTTP请求错误: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (TaskCanceledException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 请求超时: {ex.Message}", LogHelper.LogType.Error);
|
2025-06-29 11:56:38 +08:00
|
|
|
|
}
|
2025-07-28 14:40:44 +08:00
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 错误: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
2025-06-29 11:56:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-06 15:39:37 +08:00
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
// 使用指定线路组获取更新日志
|
|
|
|
|
|
public static async Task<string> GetUpdateLogWithLineGroup(UpdateLineGroup group)
|
|
|
|
|
|
{
|
|
|
|
|
|
return await GetRemoteContent(group.LogUrl);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取更新日志(自动选择最快线路组)
|
2025-07-06 15:39:37 +08:00
|
|
|
|
public static async Task<string> GetUpdateLog(UpdateChannel channel = UpdateChannel.Release)
|
|
|
|
|
|
{
|
|
|
|
|
|
var group = await GetFastestLineGroup(channel);
|
|
|
|
|
|
if (group == null) return "# 无法获取更新日志\n\n所有线路均不可用。";
|
|
|
|
|
|
return await GetUpdateLogWithLineGroup(group);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
// 删除更新文件夹
|
2025-07-06 15:39:37 +08:00
|
|
|
|
public static void DeleteUpdatesFolder()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Directory.Exists(updatesFolderPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (string file in Directory.GetFiles(updatesFolderPath, "*", SearchOption.AllDirectories))
|
|
|
|
|
|
{
|
|
|
|
|
|
try { File.Delete(file); } catch { }
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach (string dir in Directory.GetDirectories(updatesFolderPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
try { Directory.Delete(dir, true); } catch { }
|
|
|
|
|
|
}
|
|
|
|
|
|
try { Directory.Delete(updatesFolderPath, true); } catch { }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch { }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
// 版本修复方法,强制下载并安装指定通道的最新版本
|
2025-07-06 15:39:37 +08:00
|
|
|
|
public static async Task<bool> FixVersion(UpdateChannel channel = UpdateChannel.Release)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 开始修复版本,通道: {channel}");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-26 16:08:22 +08:00
|
|
|
|
// 获取远程版本号(自动选择最快线路组,始终下载远程版本,版本修复模式)
|
|
|
|
|
|
var (remoteVersion, group, _) = await CheckForUpdates(channel, true, true);
|
2025-07-06 15:39:37 +08:00
|
|
|
|
if (string.IsNullOrEmpty(remoteVersion) || group == null)
|
|
|
|
|
|
{
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 修复版本时获取远程版本失败", LogHelper.LogType.Error);
|
2025-07-06 15:39:37 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 修复版本远程版本: {remoteVersion}");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-06 15:39:37 +08:00
|
|
|
|
// 无论版本是否为最新,都下载远程版本
|
2025-07-19 15:36:05 +08:00
|
|
|
|
bool downloadResult = await DownloadSetupFile(remoteVersion, group);
|
2025-07-06 15:39:37 +08:00
|
|
|
|
if (!downloadResult)
|
|
|
|
|
|
{
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 修复版本时下载更新失败", LogHelper.LogType.Error);
|
2025-07-06 15:39:37 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-06 15:39:37 +08:00
|
|
|
|
// 执行安装,非静默模式
|
|
|
|
|
|
InstallNewVersionApp(remoteVersion, false);
|
|
|
|
|
|
App.IsAppExitByUser = true;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
2025-07-06 15:39:37 +08:00
|
|
|
|
Application.Current.Shutdown();
|
|
|
|
|
|
});
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-07-19 15:36:05 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | FixVersion错误: {ex.Message}", LogHelper.LogType.Error);
|
2025-07-06 15:39:37 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-19 16:51:03 +08:00
|
|
|
|
|
2025-07-22 17:07:27 +08:00
|
|
|
|
// 获取所有GitHub历史版本(Release)
|
|
|
|
|
|
public static async Task<List<(string version, string downloadUrl, string releaseNotes)>> GetAllGithubReleases(UpdateChannel channel = UpdateChannel.Release)
|
|
|
|
|
|
{
|
|
|
|
|
|
var result = new List<(string, string, string)>();
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
string apiUrl = channel == UpdateChannel.Beta
|
|
|
|
|
|
? "https://api.github.com/repos/InkCanvasForClass/community-beta/releases"
|
|
|
|
|
|
: "https://api.github.com/repos/InkCanvasForClass/community/releases";
|
|
|
|
|
|
using (var client = new HttpClient())
|
|
|
|
|
|
{
|
|
|
|
|
|
client.DefaultRequestHeaders.Add("User-Agent", "ICC-CE Auto Updater");
|
|
|
|
|
|
var response = await client.GetStringAsync(apiUrl);
|
|
|
|
|
|
var arr = JArray.Parse(response);
|
|
|
|
|
|
foreach (var item in arr)
|
|
|
|
|
|
{
|
|
|
|
|
|
string version = item["tag_name"]?.ToString();
|
|
|
|
|
|
string releaseNotes = item["body"]?.ToString();
|
|
|
|
|
|
string downloadUrl = item["assets"]?.First?["browser_download_url"]?.ToString();
|
|
|
|
|
|
if (!string.IsNullOrEmpty(version) && !string.IsNullOrEmpty(downloadUrl))
|
|
|
|
|
|
result.Add((version, downloadUrl, releaseNotes));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 获取历史版本失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
// 测试Windows 7 TLS连接的方法
|
|
|
|
|
|
public static async Task<bool> TestWindows7TlsConnection()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检测是否为Windows 7
|
|
|
|
|
|
var osVersion = Environment.OSVersion;
|
|
|
|
|
|
bool isWindows7 = osVersion.Version.Major == 6 && osVersion.Version.Minor == 1;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
if (!isWindows7)
|
|
|
|
|
|
{
|
2025-07-28 14:40:44 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 当前系统不是Windows 7,跳过TLS连接测试");
|
2025-07-19 16:51:03 +08:00
|
|
|
|
return true; // 非Windows 7系统直接返回成功
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 开始测试Windows 7 TLS连接...");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
// 测试GitHub连接
|
2025-08-25 09:00:45 +08:00
|
|
|
|
var testUrl = "https://github.com/InkCanvasForClass/community/raw/refs/heads/main/AutomaticUpdateVersionControl.txt";
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
using (var handler = new HttpClientHandler())
|
|
|
|
|
|
{
|
|
|
|
|
|
handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
using (var client = new HttpClient(handler))
|
|
|
|
|
|
{
|
|
|
|
|
|
client.Timeout = TimeSpan.FromSeconds(10);
|
|
|
|
|
|
var response = await client.GetAsync(testUrl);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:51:03 +08:00
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | Windows 7 TLS连接测试成功");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-07-28 14:40:44 +08:00
|
|
|
|
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | Windows 7 TLS连接测试失败,状态码: {response.StatusCode}", LogHelper.LogType.Error);
|
|
|
|
|
|
return false;
|
2025-07-19 16:51:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | Windows 7 TLS连接测试异常: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-07-26 16:22:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取指定版本的发布时间
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="version">版本号</param>
|
|
|
|
|
|
/// <param name="channel">更新通道</param>
|
|
|
|
|
|
/// <returns>版本发布时间,如果获取失败则返回null</returns>
|
|
|
|
|
|
public static async Task<DateTime?> GetVersionReleaseTime(string version, UpdateChannel channel = UpdateChannel.Release)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var (_, _, _, releaseTime) = await GetGithubReleaseByVersion(version, channel);
|
|
|
|
|
|
return releaseTime;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 获取版本 {version} 发布时间失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2025-07-19 16:51:03 +08:00
|
|
|
|
}
|
2025-07-22 18:36:37 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 启动手动指定版本的多线路多线程下载并自动安装(用于历史版本回滚等场景)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static async Task<bool> StartManualDownloadAndInstall(string version, UpdateChannel channel, Action<double, string> progressCallback = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-07-22 22:39:02 +08:00
|
|
|
|
// 先检测并排序所有可用线路组
|
|
|
|
|
|
var groups = await GetAvailableLineGroupsOrdered(channel);
|
2025-07-22 18:36:37 +08:00
|
|
|
|
bool downloadSuccess = await DownloadSetupFileWithFallback(version, groups, progressCallback);
|
|
|
|
|
|
if (!downloadSuccess)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 手动下载版本{version}失败");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 手动安装版本: {version}");
|
|
|
|
|
|
InstallNewVersionApp(version, false);
|
|
|
|
|
|
App.IsAppExitByUser = true;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
2025-07-22 18:36:37 +08:00
|
|
|
|
Application.Current.Shutdown();
|
|
|
|
|
|
});
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 手动下载或安装异常: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
progressCallback?.Invoke(0, $"下载异常: {ex.Message}");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal class AutoUpdateWithSilenceTimeComboBox
|
|
|
|
|
|
{
|
|
|
|
|
|
public static ObservableCollection<string> Hours { get; set; } = new ObservableCollection<string>();
|
|
|
|
|
|
public static ObservableCollection<string> Minutes { get; set; } = new ObservableCollection<string>();
|
|
|
|
|
|
|
|
|
|
|
|
public static void InitializeAutoUpdateWithSilenceTimeComboBoxOptions(ComboBox startTimeComboBox, ComboBox endTimeComboBox)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int hour = 0; hour <= 23; ++hour)
|
|
|
|
|
|
{
|
|
|
|
|
|
Hours.Add(hour.ToString("00"));
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int minute = 0; minute <= 59; minute += 20)
|
|
|
|
|
|
{
|
|
|
|
|
|
Minutes.Add(minute.ToString("00"));
|
|
|
|
|
|
}
|
|
|
|
|
|
startTimeComboBox.ItemsSource = Hours.SelectMany(h => Minutes.Select(m => $"{h}:{m}"));
|
|
|
|
|
|
endTimeComboBox.ItemsSource = Hours.SelectMany(h => Minutes.Select(m => $"{h}:{m}"));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static bool CheckIsInSilencePeriod(string startTime, string endTime)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (startTime == endTime) return true;
|
|
|
|
|
|
DateTime currentTime = DateTime.Now;
|
|
|
|
|
|
|
|
|
|
|
|
DateTime StartTime = DateTime.ParseExact(startTime, "HH:mm", null);
|
|
|
|
|
|
DateTime EndTime = DateTime.ParseExact(endTime, "HH:mm", null);
|
|
|
|
|
|
if (StartTime <= EndTime)
|
|
|
|
|
|
{ // 单日时间段
|
|
|
|
|
|
return currentTime >= StartTime && currentTime <= EndTime;
|
2025-07-28 14:40:44 +08:00
|
|
|
|
} // 跨越两天的时间段
|
|
|
|
|
|
return currentTime >= StartTime || currentTime <= EndTime;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|