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