From ca7c779e101bb801414cb75ddad90f2dd6cea836 Mon Sep 17 00:00:00 2001 From: uye <99072975+ABA2396@users.noreply.github.com> Date: Tue, 8 Jul 2025 21:54:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20fetch=20=E8=AF=B7=E6=B1=82=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=20If-Modified-Since?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HttpResponseLoggingExtension.cs | 8 +- src/MaaWpfGui/Helper/ETagCache.cs | 107 ++++++++++++------ src/MaaWpfGui/Services/StageManager.cs | 17 +-- 3 files changed, 81 insertions(+), 51 deletions(-) diff --git a/src/MaaWpfGui/Extensions/HttpResponseLoggingExtension.cs b/src/MaaWpfGui/Extensions/HttpResponseLoggingExtension.cs index a583793671..64704b5a65 100644 --- a/src/MaaWpfGui/Extensions/HttpResponseLoggingExtension.cs +++ b/src/MaaWpfGui/Extensions/HttpResponseLoggingExtension.cs @@ -28,15 +28,19 @@ namespace MaaWpfGui.Extensions var uri = response?.RequestMessage?.RequestUri; var statusCode = response?.StatusCode.ToString(); var etag = response?.Headers.ETag?.Tag; + var lastModified = response?.Content?.Headers?.LastModified?.ToString("R"); // RFC1123 if (response != null && (response.IsSuccessStatusCode || response.StatusCode == HttpStatusCode.NotModified)) { - _logger.Information("HTTP: {StatusCode} {Method} {Url} {Etag}", statusCode, method, uri?.GetLeftPart(uriPartial), etag); + _logger.Information("HTTP: {StatusCode} {Method} {Url} {ETag} {LastModified}", + statusCode, method, uri?.GetLeftPart(uriPartial), etag, lastModified); } else { - _logger.Warning("HTTP: {StatusCode} {Method} {Url} {Etag}", statusCode, method, uri?.GetLeftPart(uriPartial), etag); + _logger.Warning("HTTP: {StatusCode} {Method} {Url} {ETag} {LastModified}", + statusCode, method, uri?.GetLeftPart(uriPartial), etag, lastModified); } } + } } diff --git a/src/MaaWpfGui/Helper/ETagCache.cs b/src/MaaWpfGui/Helper/ETagCache.cs index ac3235644a..50b4ab0498 100644 --- a/src/MaaWpfGui/Helper/ETagCache.cs +++ b/src/MaaWpfGui/Helper/ETagCache.cs @@ -27,76 +27,115 @@ namespace MaaWpfGui.Helper { private static readonly ILogger _logger = Log.ForContext(); - private static readonly string _cacheFile = Path.Combine(Environment.CurrentDirectory, "cache/etag.json"); - private static Dictionary _cache = []; + private static readonly string _etagFile = Path.Combine(Environment.CurrentDirectory, "cache/etag.json"); + private static readonly string _lastModifiedFile = Path.Combine(Environment.CurrentDirectory, "cache/last_modified.json"); + private static Dictionary _etagCache = []; + private static Dictionary _lastModifiedCache = []; public static void Load() { - if (File.Exists(_cacheFile) is false) + // ETag + if (File.Exists(_etagFile)) { - _cache = []; - return; + try + { + var json = File.ReadAllText(_etagFile); + _etagCache = JsonConvert.DeserializeObject>(json) ?? []; + } + catch (Exception e) + { + _logger.Warning(e, "Failed to load ETag cache"); + _etagCache = []; + } } - try + // Last-Modified + if (File.Exists(_lastModifiedFile)) { - var jsonStr = File.ReadAllText(_cacheFile); - _cache = JsonConvert.DeserializeObject>(jsonStr) ?? []; - } - catch (Exception e) - { - _logger.Error(e.Message); + try + { + var json = File.ReadAllText(_lastModifiedFile); + _lastModifiedCache = JsonConvert.DeserializeObject>(json) ?? []; + } + catch (Exception e) + { + _logger.Warning(e, "Failed to load Last-Modified cache"); + _lastModifiedCache = []; + } } } public static void Save() { - var jsonStr = JsonConvert.SerializeObject(_cache); - File.WriteAllText(_cacheFile, jsonStr); + File.WriteAllText(_etagFile, JsonConvert.SerializeObject(_etagCache, Formatting.Indented)); + File.WriteAllText(_lastModifiedFile, JsonConvert.SerializeObject(_lastModifiedCache, Formatting.Indented)); } - // ReSharper disable once MemberCanBePrivate.Global - public static string Get(string? url) + public static string GetETag(string? url) => + url != null && _etagCache.TryGetValue(url, out var etag) ? etag : string.Empty; + + public static DateTimeOffset? GetLastModified(string? url) => + url != null && _lastModifiedCache.TryGetValue(url, out var lm) ? lm : null; + + public static void SetETag(string url, string etag) { - if (url is null) + _etagCache[url] = etag; + } + + public static void SetLastModified(string url, DateTimeOffset? dt) + { + if (dt.HasValue) { - return string.Empty; + _lastModifiedCache[url] = dt.Value; } - - return _cache.TryGetValue(url, out string? ret) ? ret : string.Empty; - } - - // ReSharper disable once MemberCanBePrivate.Global - public static void Set(string url, string etag) - { - _cache[url] = etag; - Save(); } // UPDATE: 重定向会导致 uri 变成其他地址,导致存的 ETag 无法匹配原始地址,所以要传入原始地址 public static void Set(HttpResponseMessage? response, string uri) { - var etag = response?.Headers.ETag?.Tag; - if (string.IsNullOrEmpty(uri) || string.IsNullOrEmpty(etag)) + if (response == null || string.IsNullOrEmpty(uri)) { return; } - Set(uri, etag); + var etag = response.Headers.ETag?.Tag; + var lastModified = response.Content?.Headers?.LastModified; + + if (!string.IsNullOrEmpty(etag)) + { + SetETag(uri, etag); + } + + if (lastModified.HasValue) + { + SetLastModified(uri, lastModified.Value); + } + + Save(); } public static async Task FetchResponseWithEtag(string url, bool force = false) { - var etag = force ? string.Empty : Get(url); - Dictionary headers = new Dictionary + var headers = new Dictionary { { "Accept", "application/octet-stream" }, { "Connection", "close" }, }; - if (!string.IsNullOrEmpty(etag)) + if (!force) { - headers["If-None-Match"] = etag; + var etag = GetETag(url); + var lastModified = GetLastModified(url); + + if (!string.IsNullOrEmpty(etag)) + { + headers["If-None-Match"] = etag; + } + + if (lastModified.HasValue) + { + headers["If-Modified-Since"] = lastModified.Value.ToUniversalTime().ToString("R"); + } } try diff --git a/src/MaaWpfGui/Services/StageManager.cs b/src/MaaWpfGui/Services/StageManager.cs index f85c254a5a..f39c27e8ad 100644 --- a/src/MaaWpfGui/Services/StageManager.cs +++ b/src/MaaWpfGui/Services/StageManager.cs @@ -65,7 +65,8 @@ namespace MaaWpfGui.Services // 清理旧的缓存文件 const string CacheAllFileDownloadComplete = "cache/allFileDownloadComplete.json"; const string LastUpdateTime = "cache/LastUpdateTime.json"; - var filesToClean = new[] { CacheAllFileDownloadComplete, LastUpdateTime }; + const string StageAndTasksUpdateTime = "cache/stageAndTasksUpdateTime.json"; + var filesToClean = new[] { CacheAllFileDownloadComplete, LastUpdateTime, StageAndTasksUpdateTime }; foreach (var file in filesToClean) { @@ -82,10 +83,7 @@ namespace MaaWpfGui.Services } } - const string StageAndTasksUpdateTime = "cache/stageAndTasksUpdateTime.json"; MergePermanentAndActivityStages(await LoadWebStages()); - var unixTimestamp = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; - await File.WriteAllTextAsync(StageAndTasksUpdateTime, GenerateJsonString("timestamp", new(unixTimestamp))); _ = Execute.OnUIThreadAsync(() => { @@ -98,17 +96,6 @@ namespace MaaWpfGui.Services }; Growl.Info(growlInfo); }); - - return; - - static string GenerateJsonString(string key, JValue value) - { - JObject json = new JObject - { - [key] = value, - }; - return JsonConvert.SerializeObject(json); - } } private static string GetClientType()