mirror of
https://github.com/HolographicHat/Yae.git
synced 2025-12-06 14:42:52 +08:00
refactor CacheFile
This commit is contained in:
18
.gitignore
vendored
18
.gitignore
vendored
@@ -1,15 +1,5 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
bin
|
||||
obj
|
||||
.idea
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
.vs/
|
||||
|
||||
src/Proto/*
|
||||
.vs
|
||||
publish
|
||||
|
||||
@@ -1,32 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Exe</OutputType>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ApplicationManifest>res\app.manifest</ApplicationManifest>
|
||||
<AssemblyVersion>5.3.0</AssemblyVersion>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<FileVersion>5.3.0</FileVersion>
|
||||
<AssemblyVersion>5.3.0</AssemblyVersion>
|
||||
<ApplicationIcon>res\icon.ico</ApplicationIcon>
|
||||
<ApplicationManifest>res\app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<DebugType>embedded</DebugType>
|
||||
<SelfContained>false</SelfContained>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<PropertyGroup>
|
||||
<PublishAot>true</PublishAot>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
||||
<CETCompat>false</CETCompat>
|
||||
|
||||
<IlcOptimizationPreference>Size</IlcOptimizationPreference>
|
||||
<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.29.0"/>
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.29.0"/>
|
||||
<PackageReference Include="Grpc.Tools" Version="2.67.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
@@ -55,4 +53,8 @@
|
||||
<Protobuf Include="res/proto/*.proto" ProtoRoot="res/proto" GrpcServices="None"/>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<CETCompat>false</CETCompat>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -15,9 +15,8 @@ public static partial class AppConfig {
|
||||
GamePath = argumentPath;
|
||||
return;
|
||||
}
|
||||
var pathCacheFile = new CacheFile("genshin_impact_game_path");
|
||||
if (pathCacheFile.Exists()) {
|
||||
var path = pathCacheFile.Read().Content.ToStringUtf8();
|
||||
if (CacheFile.TryRead("genshin_impact_game_path", out var cache)) {
|
||||
var path = cache.Content.ToStringUtf8();
|
||||
if (path != null && File.Exists(path)) {
|
||||
GamePath = path;
|
||||
return;
|
||||
@@ -40,12 +39,7 @@ public static partial class AppConfig {
|
||||
if (!File.Exists(path)) {
|
||||
return null;
|
||||
}
|
||||
var copiedLogFilePath = Path.GetTempFileName();
|
||||
File.Copy(path, copiedLogFilePath, true);
|
||||
var content = File.ReadAllText(copiedLogFilePath);
|
||||
try {
|
||||
File.Delete(copiedLogFilePath);
|
||||
} catch (Exception) { /* ignore */ }
|
||||
var content = File.ReadAllText(path);
|
||||
var matchResult = GamePathRegex().Match(content);
|
||||
if (!matchResult.Success) {
|
||||
return null;
|
||||
@@ -54,7 +48,7 @@ public static partial class AppConfig {
|
||||
return Path.GetFullPath(Path.Combine(matchResult.Value, "..", entryName));
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"(?m).:/.+(GenshinImpact_Data|YuanShen_Data)", RegexOptions.IgnoreCase)]
|
||||
[GeneratedRegex(@"(?m).:(?:\\|/).+(GenshinImpact_Data|YuanShen_Data)", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex GamePathRegex();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
public static class Extensions {
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static readonly Lazy<MD5> md5 = new (MD5.Create);
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static readonly Lazy<SHA1> sha1 = new (SHA1.Create);
|
||||
|
||||
public static byte[] ToBytes(this string text) {
|
||||
return Encoding.UTF8.GetBytes(text);
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static string MD5Hash(this string text) {
|
||||
return text.ToBytes().MD5Hash();
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static string MD5Hash(this byte[] data) {
|
||||
return md5.Value.ComputeHash(data).ToHex().ToLower();
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static string SHA1Hash(this string text, bool base64 = true) {
|
||||
var bytes = sha1.Value.ComputeHash(text.ToBytes());
|
||||
return base64 ? bytes.ToBase64() : bytes.ToHex();
|
||||
}
|
||||
|
||||
public static string ToHex(this byte[] bytes) {
|
||||
return Convert.ToHexString(bytes);
|
||||
}
|
||||
|
||||
public static string ToBase64(this byte[] bytes) {
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using Proto;
|
||||
using YaeAchievement.Utilities;
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
@@ -28,8 +27,6 @@ public static class GlobalVars {
|
||||
public const string RinBucketHost = "https://rin.holohat.work";
|
||||
public const string SakuraBucketHost = "https://cn-cd-1259389942.file.myqcloud.com";
|
||||
|
||||
public static CacheFile AchievementDataCache { get; } = new ("achievement_data");
|
||||
|
||||
[field:MaybeNull]
|
||||
public static AchievementInfo AchievementInfo =>
|
||||
field ??= AchievementInfo.Parser.ParseFrom(Utils.GetBucketFile("schicksal/metadata").GetAwaiter().GetResult());
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Google.Protobuf;
|
||||
using YaeAchievement.res;
|
||||
using YaeAchievement.Utilities;
|
||||
|
||||
namespace YaeAchievement.Parsers;
|
||||
|
||||
@@ -30,7 +31,7 @@ public class AchievementAllDataNotify {
|
||||
|
||||
public static bool OnReceive(BinaryReader reader) {
|
||||
var bytes = reader.ReadBytes(reader.ReadInt32());
|
||||
GlobalVars.AchievementDataCache.Write(bytes);
|
||||
CacheFile.Write("achievement_data", bytes);
|
||||
Instance = ParseFrom(bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Windows.Win32;
|
||||
using Windows.Win32.System.Console;
|
||||
using YaeAchievement.Parsers;
|
||||
using YaeAchievement.res;
|
||||
using YaeAchievement.Utilities;
|
||||
using static YaeAchievement.Utils;
|
||||
|
||||
namespace YaeAchievement;
|
||||
@@ -33,14 +34,14 @@ internal static class Program {
|
||||
|
||||
await CheckUpdate(ToBooleanOrFalse(args.GetOrNull(2)));
|
||||
|
||||
var historyCache = GlobalVars.AchievementDataCache;
|
||||
|
||||
AchievementAllDataNotify? data = null;
|
||||
try {
|
||||
data = AchievementAllDataNotify.ParseFrom(historyCache.Read().Content.ToByteArray());
|
||||
if (CacheFile.TryRead("achievement_data", out var cache)) {
|
||||
data = AchievementAllDataNotify.ParseFrom(cache.Content.ToByteArray());
|
||||
}
|
||||
} catch (Exception) { /* ignored */ }
|
||||
|
||||
if (historyCache.LastWriteTime.AddMinutes(60) > DateTime.UtcNow && data != null) {
|
||||
if (CacheFile.GetLastWriteTime("achievement_data").AddMinutes(60) > DateTime.UtcNow && data != null) {
|
||||
Console.WriteLine(App.UsePreviousData);
|
||||
if (Console.ReadLine()?.ToUpper() is "Y" or "YES") {
|
||||
Export.Choose(data);
|
||||
|
||||
@@ -1,39 +1,53 @@
|
||||
using System.IO.Compression;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO.Compression;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Google.Protobuf;
|
||||
using Proto;
|
||||
|
||||
namespace YaeAchievement.Utilities;
|
||||
|
||||
public class CacheFile(string identifier) {
|
||||
public static class CacheFile {
|
||||
|
||||
private readonly string _cacheName = Path.Combine(GlobalVars.CachePath, $"{identifier.MD5Hash()[..16]}.miko");
|
||||
private CacheItem? _content;
|
||||
|
||||
public DateTime LastWriteTime => Exists() ? File.GetLastWriteTimeUtc(_cacheName) : DateTime.UnixEpoch;
|
||||
|
||||
public bool Exists() => File.Exists(_cacheName);
|
||||
|
||||
public CacheItem Read() {
|
||||
if (_content == null) {
|
||||
using var fInput = File.OpenRead(_cacheName);
|
||||
using var dInput = new GZipStream(fInput, CompressionMode.Decompress);
|
||||
_content = CacheItem.Parser.ParseFrom(dInput);
|
||||
static CacheFile() {
|
||||
// remove deprecated cache
|
||||
foreach (var file in Directory.EnumerateFiles(GlobalVars.CachePath, "*.miko")) {
|
||||
File.Delete(file);
|
||||
}
|
||||
return _content;
|
||||
}
|
||||
|
||||
public void Write(string data, string? etag = null) => Write(ByteString.CopyFromUtf8(data), data.MD5Hash(), etag);
|
||||
public static DateTime GetLastWriteTime(string id) {
|
||||
var fileName = Path.Combine(GlobalVars.CachePath, $"{GetStrHash(id)}.nyan");
|
||||
return File.Exists(fileName) ? File.GetLastWriteTimeUtc(fileName) : DateTime.UnixEpoch;
|
||||
}
|
||||
|
||||
public void Write(byte[] data, string? etag = null) => Write(ByteString.CopyFrom(data), data.MD5Hash(), etag);
|
||||
public static bool TryRead(string id, [NotNullWhen(true)] out CacheItem? item) {
|
||||
item = null;
|
||||
try {
|
||||
var fileName = Path.Combine(GlobalVars.CachePath, $"{GetStrHash(id)}.nyan");
|
||||
using var fileStream = File.OpenRead(fileName);
|
||||
using var zipStream = new GZipStream(fileStream, CompressionMode.Decompress);
|
||||
item = CacheItem.Parser.ParseFrom(zipStream);
|
||||
return true;
|
||||
} catch (Exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Write(ByteString data, string hash, string? etag) {
|
||||
using var fOut = File.OpenWrite(_cacheName);
|
||||
using var cOut = new GZipStream(fOut, CompressionLevel.SmallestSize);
|
||||
public static void Write(string id, byte[] data, string? etag = null) {
|
||||
var fileName = Path.Combine(GlobalVars.CachePath, $"{GetStrHash(id)}.nyan");
|
||||
using var fileStream = File.Open(fileName, FileMode.Create);
|
||||
using var zipStream = new GZipStream(fileStream, CompressionLevel.SmallestSize);
|
||||
new CacheItem {
|
||||
Etag = etag ?? string.Empty,
|
||||
Version = 3,
|
||||
Checksum = hash,
|
||||
Content = data
|
||||
}.WriteTo(cOut);
|
||||
Checksum = GetBinHash(data),
|
||||
Content = ByteString.CopyFrom(data)
|
||||
}.WriteTo(zipStream);
|
||||
}
|
||||
|
||||
private static string GetStrHash(string value) => GetBinHash(Encoding.UTF8.GetBytes(value));
|
||||
|
||||
private static string GetBinHash(byte[] value) => Convert.ToHexStringLower(MD5.HashData(value));
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace YaeAchievement;
|
||||
|
||||
public static class Utils {
|
||||
|
||||
public static readonly HttpClient CHttpClient = new (new HttpClientHandler {
|
||||
public static HttpClient CHttpClient { get; } = new (new HttpClientHandler {
|
||||
AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.GZip
|
||||
}) {
|
||||
DefaultRequestHeaders = {
|
||||
@@ -25,7 +25,7 @@ public static class Utils {
|
||||
}
|
||||
};
|
||||
|
||||
public static async Task<byte[]> GetBucketFile(string path, bool cache = true) {
|
||||
public static async Task<byte[]> GetBucketFile(string path, bool useCache = true) {
|
||||
try {
|
||||
return await await Task.WhenAny(GetFile(GlobalVars.RinBucketHost), GetFile(GlobalVars.SakuraBucketHost));
|
||||
} catch (Exception e) when(e is SocketException or TaskCanceledException) {
|
||||
@@ -37,19 +37,19 @@ public static class Utils {
|
||||
using var msg = new HttpRequestMessage();
|
||||
msg.Method = HttpMethod.Get;
|
||||
msg.RequestUri = new Uri($"{host}/{path}");
|
||||
var cacheFile = new CacheFile(path);
|
||||
if (cache && cacheFile.Exists()) {
|
||||
msg.Headers.TryAddWithoutValidation("If-None-Match", $"{cacheFile.Read().Etag}");
|
||||
CacheItem? cache = null;
|
||||
if (useCache && CacheFile.TryRead(path, out cache)) {
|
||||
msg.Headers.TryAddWithoutValidation("If-None-Match", $"{cache.Etag}");
|
||||
}
|
||||
using var response = await CHttpClient.SendAsync(msg);
|
||||
if (cache && response.StatusCode == HttpStatusCode.NotModified) {
|
||||
return cacheFile.Read().Content.ToByteArray();
|
||||
if (cache != null && response.StatusCode == HttpStatusCode.NotModified) {
|
||||
return cache.Content.ToByteArray();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
var responseBytes = await response.Content.ReadAsByteArrayAsync();
|
||||
if (cache) {
|
||||
if (useCache) {
|
||||
var etag = response.Headers.ETag!.Tag;
|
||||
cacheFile.Write(responseBytes, etag);
|
||||
CacheFile.Write(path, responseBytes, etag);
|
||||
}
|
||||
return responseBytes;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user