using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace CheckDownload { public partial class Update : Form { private const string LocalMd5File = "md5.json"; private const string DnsQueryDomain = "test.file.ipoi.cn"; private const string BaseDownloadUrl = "http://localhost:8000/"; private const int MaxConcurrentDownloads = 5; private string _onlineMd5File = ""; private int _completedFiles = 0; public Update() { InitializeComponent(); ConfigureProgressBar(); } private void ConfigureProgressBar() { Update_Pro.Minimum = 0; Update_Pro.Maximum = 100; Update_Pro.Value = 0; Update_Pro.Step = 1; } public async void Update_Load(object sender, EventArgs e) { await UpdateFile(); this.Close(); } private async Task UpdateFile() { try { var localData = await ReadLocalMd5File(); if (!ValidateLocalData(localData.Version, localData.Md5, localData.Data)) return; var onlineData = await GetOnlineMd5File(); if (!ValidateOnlineData(onlineData.Version, onlineData.Md5)) return; if (!ShouldUpdate(localData.Version, onlineData.Version)) return; var onlineFileData = await DownloadOnlineMd5File(); if (onlineFileData == null) return; var differences = CompareDataDifferences(localData.Data, onlineFileData); if (differences.Count > 0) { await DownloadUpdatedFiles(differences); } ReplaceLocalMd5File(); } catch (Exception ex) { UpdateStatus($"发生错误: {ex.Message}"); } } private bool ValidateLocalData(string version, string md5, JObject data) { if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(md5) || data == null) { UpdateStatus("本地MD5文件无效"); return false; } return true; } private bool ValidateOnlineData(string version, string md5) { if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(md5)) { UpdateStatus("无法获取在线MD5信息"); return false; } return true; } private async Task<(string Version, string Md5, JObject Data)> ReadLocalMd5File() { try { UpdateStatus("读取md5.json..."); string json = await Task.Run(() => File.ReadAllText(LocalMd5File)); var obj = JObject.Parse(json); string version = obj["version"]?.ToString(); var data = (JObject)obj["data"]; string jsonMd5 = CalculateMD5(json); return (version, jsonMd5, data); } catch (Exception ex) when (ex is FileNotFoundException || ex is JsonException) { UpdateStatus($"读取本地MD5文件失败: {ex.Message}"); return (null, null, null); } } private async Task<(string Version, string Md5)> GetOnlineMd5File() { try { UpdateStatus("解析在线md5.json..."); string responseData = await QueryDnsAsync(); string firstUnescaped = JsonConvert.DeserializeObject(responseData); var dataJson = JObject.Parse(firstUnescaped); string version = dataJson["version"]?.ToString(); string md5 = dataJson["md5"]?.ToString(); _onlineMd5File = $"{md5}.json"; return (version, md5); } catch (Exception ex) { UpdateStatus($"获取在线MD5信息失败: {ex.Message}"); return (null, null); } } private bool ShouldUpdate(string localVersion, string onlineVersion) { try { UpdateStatus("校验信息..."); var localVer = new Version(localVersion); var onlineVer = new Version(onlineVersion); return localVer.CompareTo(onlineVer) < 0; } catch (Exception ex) when (ex is FormatException || ex is ArgumentNullException) { UpdateStatus($"版本比较失败: {ex.Message}"); return false; } } private async Task DownloadOnlineMd5File() { try { UpdateStatus("下载在线md5.json文件..."); using (var client = new WebClient()) { await client.DownloadFileTaskAsync( new Uri($"{BaseDownloadUrl}MD5/{_onlineMd5File}"), _onlineMd5File); } UpdateStatus("读取在线md5.json文件..."); string json = await Task.Run(() => File.ReadAllText(_onlineMd5File)); var obj = JObject.Parse(json); return (JObject)obj["data"]; } catch (Exception ex) { UpdateStatus($"下载在线MD5文件失败: {ex.Message}"); return null; } } private Dictionary CompareDataDifferences(JObject localData, JObject onlineData, string currentPath = "") { var differences = new Dictionary(); foreach (var onlineProperty in onlineData.Properties()) { string key = onlineProperty.Name; JToken onlineValue = onlineProperty.Value; string fullPath = string.IsNullOrEmpty(currentPath) ? key : $"{currentPath}/{key}"; if (onlineValue.Type == JTokenType.String) { string expectedMd5 = onlineValue.ToString(); if (ShouldDownloadFile(localData, key, expectedMd5, fullPath)) { differences[fullPath] = expectedMd5; } } else if (onlineValue.Type == JTokenType.Object) { JObject localSubData = GetSubData(localData, key); var subDifferences = CompareDataDifferences(localSubData, (JObject)onlineValue, fullPath); foreach (var diff in subDifferences) { differences[diff.Key] = diff.Value; } } } return differences; } private bool ShouldDownloadFile(JObject localData, string key, string expectedMd5, string fullPath) { bool fileMissing = !localData.ContainsKey(key); bool md5Mismatch = localData.ContainsKey(key) && (localData[key].Type != JTokenType.String || localData[key].ToString() != expectedMd5); bool physicalFileMissing = !File.Exists(Path.Combine(".", fullPath.Replace('/', '\\'))); return fileMissing || md5Mismatch || physicalFileMissing; } private JObject GetSubData(JObject localData, string key) { return localData.ContainsKey(key) && localData[key].Type == JTokenType.Object ? (JObject)localData[key] : new JObject(); } private async Task DownloadUpdatedFiles(Dictionary differences) { Update_Pro.Maximum = differences.Count; Update_Pro.Value = 0; var semaphore = new SemaphoreSlim(MaxConcurrentDownloads); var downloadTasks = new List(); foreach (var file in differences) { downloadTasks.Add(DownloadFileWithSemaphore(file, semaphore)); } await Task.WhenAll(downloadTasks); } private async Task DownloadFileWithSemaphore(KeyValuePair file, SemaphoreSlim semaphore) { await semaphore.WaitAsync(); try { await DownloadAndVerifyFile(file.Key, file.Value); UpdateProgress(); } catch (Exception ex) { UpdateStatus($"下载失败: {file.Key} - {ex.Message}"); } finally { semaphore.Release(); } } private async Task DownloadAndVerifyFile(string relativePath, string expectedMd5) { UpdateStatus($"正在下载 ({Update_Pro.Value + 1}/{Update_Pro.Maximum}): {relativePath}"); string localPath = GetLocalPath(relativePath); string tempPath = $"{localPath}.tmp"; EnsureDirectoryExists(localPath); using (var client = new WebClient()) { await client.DownloadFileTaskAsync( new Uri($"{BaseDownloadUrl}{relativePath}"), tempPath); } VerifyFileMd5(tempPath, expectedMd5, relativePath); ReplaceExistingFile(tempPath, localPath); } private string GetLocalPath(string relativePath) { return Path.Combine(".", relativePath.Replace('/', '\\')); } private void EnsureDirectoryExists(string filePath) { string directory = Path.GetDirectoryName(filePath); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } } private void VerifyFileMd5(string filePath, string expectedMd5, string relativePath) { string actualMd5 = CalculateFileMD5(filePath); if (actualMd5 != expectedMd5) { File.Delete(filePath); throw new Exception($"MD5校验失败: {relativePath}"); } } private void ReplaceExistingFile(string tempPath, string localPath) { if (File.Exists(localPath)) { File.Delete(localPath); } File.Move(tempPath, localPath); } private void UpdateProgress() { this.Invoke((MethodInvoker)delegate { Update_Pro.Value++; }); } private void ReplaceLocalMd5File() { try { if (File.Exists(LocalMd5File)) { File.Delete(LocalMd5File); } File.Move(_onlineMd5File, LocalMd5File); UpdateStatus("已更新本地版本信息"); } catch (Exception ex) { UpdateStatus($"更新本地版本信息失败: {ex.Message}"); } } private void UpdateStatus(string message) { this.Invoke((MethodInvoker)delegate { Status_Box.Text = message; }); } private string CalculateMD5(string input) { using (var md5 = MD5.Create()) { byte[] inputBytes = Encoding.UTF8.GetBytes(input); byte[] hashBytes = md5.ComputeHash(inputBytes); return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); } } private string CalculateFileMD5(string filePath) { using (var md5 = MD5.Create()) using (var stream = File.OpenRead(filePath)) { byte[] hashBytes = md5.ComputeHash(stream); return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); } } private static async Task QueryDnsAsync() { var dohServers = new List { "https://cloudflare-dns.com/dns-query", "https://dns.cloudflare.com/dns-query", "https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query", "https://dns.google/resolve", "https://sm2.doh.pub/dns-query", "https://doh.pub/dns-query", "https://dns.alidns.com/resolve", "https://223.5.5.5/resolve", "https://223.6.6.6/resolve", "https://doh.360.cn/resolve" }; using (var httpClient = new HttpClient()) { var cts = new CancellationTokenSource(); var tasks = new List>(); foreach (var server in dohServers) { tasks.Add(QueryDnsServer(httpClient, server, cts.Token)); } var completedTask = await Task.WhenAny(tasks); cts.Cancel(); return await completedTask; } } private static async Task QueryDnsServer(HttpClient client, string server, CancellationToken token) { try { var url = $"{server}?name={DnsQueryDomain}&type=TXT"; var response = await client.GetStringAsync(url); var jsonResponse = JObject.Parse(response); foreach (var record in jsonResponse["Answer"]) { string txtRecord = record["data"]?.ToString(); if (!string.IsNullOrEmpty(txtRecord)) { return txtRecord; } } return string.Empty; } catch { return string.Empty; } } } }