From b80ed09118dc4aa99ce8eb05c76c6666c41176ed Mon Sep 17 00:00:00 2001 From: laolarou Date: Tue, 9 Apr 2024 17:10:31 -0700 Subject: [PATCH 1/8] add support for CurseForge fuzzy search --- .../Class/Helper/CurseForgeAPIHelper.cs | 27 +++++++++++--- .../CurseForgeFuzzySearchResponseModel.cs | 37 +++++++++++++++++++ .../CurseForge/CurseForgeManifestModel.cs | 4 +- .../Modrinth/ModrinthModPackIndexModel.cs | 4 +- 4 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeFuzzySearchResponseModel.cs diff --git a/ProjBobcat/ProjBobcat/Class/Helper/CurseForgeAPIHelper.cs b/ProjBobcat/ProjBobcat/Class/Helper/CurseForgeAPIHelper.cs index 53f8e1a..9d39a77 100644 --- a/ProjBobcat/ProjBobcat/Class/Helper/CurseForgeAPIHelper.cs +++ b/ProjBobcat/ProjBobcat/Class/Helper/CurseForgeAPIHelper.cs @@ -16,20 +16,20 @@ namespace ProjBobcat.Class.Helper; record AddonInfoReqModel(IEnumerable modIds); record FileInfoReqModel(IEnumerable fileIds); +record FuzzyFingerPrintReqModel(IEnumerable fingerprints); [JsonSerializable(typeof(AddonInfoReqModel))] [JsonSerializable(typeof(FileInfoReqModel))] +[JsonSerializable(typeof(FuzzyFingerPrintReqModel))] [JsonSerializable(typeof(DataModelWithPagination))] [JsonSerializable(typeof(DataModel))] [JsonSerializable(typeof(DataModel))] [JsonSerializable(typeof(DataModel))] [JsonSerializable(typeof(DataModel))] [JsonSerializable(typeof(DataModel))] +[JsonSerializable(typeof(DataModel))] [JsonSerializable(typeof(DataModel))] -partial class CurseForgeModelContext : JsonSerializerContext -{ - -} +partial class CurseForgeModelContext : JsonSerializerContext; #endregion @@ -109,7 +109,7 @@ public static void SetApiKey(string apiKey) public static async Task GetFiles(IEnumerable fileIds) { - var reqUrl = $"{BaseUrl}/mods/files"; + const string reqUrl = $"{BaseUrl}/mods/files"; var data = JsonSerializer.Serialize(new FileInfoReqModel(fileIds), CurseForgeModelContext.Default.FileInfoReqModel); @@ -173,4 +173,21 @@ public static void SetApiKey(string apiKey) return (await res.Content.ReadFromJsonAsync(CurseForgeModelContext.Default.DataModelString))?.Data; } + + public static async Task TryFuzzySearchFile(long fingerprint, int gameId = 432) + { + var reqUrl = $"{BaseUrl}/fingerprints/{gameId}"; + + var data = JsonSerializer.Serialize(new FuzzyFingerPrintReqModel([fingerprint]), + CurseForgeModelContext.Default.FuzzyFingerPrintReqModel); + + using var req = Req(HttpMethod.Post, reqUrl); + req.Content = new StringContent(data, Encoding.UTF8, "application/json"); + + using var res = await Client.SendAsync(req); + res.EnsureSuccessStatusCode(); + + return (await res.Content.ReadFromJsonAsync(CurseForgeModelContext.Default + .DataModelCurseForgeFuzzySearchResponseModel))?.Data; + } } \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeFuzzySearchResponseModel.cs b/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeFuzzySearchResponseModel.cs new file mode 100644 index 0000000..d09cdd2 --- /dev/null +++ b/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeFuzzySearchResponseModel.cs @@ -0,0 +1,37 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace ProjBobcat.Class.Model.CurseForge; + +public class CurseForgeFuzzySearchFileModel +{ + [JsonPropertyName("id")] + public long Id { get; set; } + + [JsonPropertyName("file")] + public CurseForgeLatestFileModel? File { get; set; } +} + +public class CurseForgeFuzzySearchResponseModel +{ + [JsonPropertyName("isCacheBuilt")] + public bool IsCacheBuilt { get; set; } + + [JsonPropertyName("exactMatches")] + public CurseForgeFuzzySearchFileModel[]? ExactMatches { get; set; } + + [JsonPropertyName("exactFingerprints")] + public long[]? ExactFingerprints { get; set; } + + [JsonPropertyName("partialMatches")] + public CurseForgeFuzzySearchFileModel[]? PartialMatches { get; set; } + + [JsonPropertyName("partialMatchFingerprints")] + public JsonElement? PartialMatchFingerprints { get; set; } + + [JsonPropertyName("installedFingerprints")] + public long[]? InstalledFingerprints { get; set; } + + [JsonPropertyName("unmatchedFingerprints")] + public long[]? UnmatchedFingerprints { get; set; } +} \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeManifestModel.cs b/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeManifestModel.cs index 045f875..bb2c65d 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeManifestModel.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeManifestModel.cs @@ -22,6 +22,4 @@ public class CurseForgeManifestModel } [JsonSerializable(typeof(CurseForgeManifestModel))] -partial class CurseForgeManifestModelContext : JsonSerializerContext -{ -} \ No newline at end of file +public partial class CurseForgeManifestModelContext : JsonSerializerContext; \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthModPackIndexModel.cs b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthModPackIndexModel.cs index 13a87c4..3ffd216 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthModPackIndexModel.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthModPackIndexModel.cs @@ -22,6 +22,4 @@ public class ModrinthModPackIndexModel } [JsonSerializable(typeof(ModrinthModPackIndexModel))] -partial class ModrinthModPackIndexModelContext : JsonSerializerContext -{ -} \ No newline at end of file +public partial class ModrinthModPackIndexModelContext : JsonSerializerContext; \ No newline at end of file From ab0a040c2229c22027640a21b482a365e3c0807f Mon Sep 17 00:00:00 2001 From: laolarou Date: Tue, 9 Apr 2024 18:46:01 -0700 Subject: [PATCH 2/8] add Modrinth hash search API --- .../Class/Helper/ModrinthAPIHelper.cs | 22 ++++++++++++++++++- .../Modrinth/ModrinthProjectDependencyInfo.cs | 4 +--- .../Model/Modrinth/ModrinthProjectInfo.cs | 4 +--- .../Model/Modrinth/ModrinthVersionInfo.cs | 6 ++--- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/ProjBobcat/ProjBobcat/Class/Helper/ModrinthAPIHelper.cs b/ProjBobcat/ProjBobcat/Class/Helper/ModrinthAPIHelper.cs index 58cfb17..0b9fbe9 100644 --- a/ProjBobcat/ProjBobcat/Class/Helper/ModrinthAPIHelper.cs +++ b/ProjBobcat/ProjBobcat/Class/Helper/ModrinthAPIHelper.cs @@ -1,7 +1,9 @@ -using System.Net.Http; +using System; +using System.Net.Http; using System.Net.Http.Json; using System.Threading; using System.Threading.Tasks; +using ProjBobcat.Class.Model; using ProjBobcat.Class.Model.Modrinth; namespace ProjBobcat.Class.Helper; @@ -80,4 +82,22 @@ await res.Content.ReadFromJsonAsync(ModrinthProjectDependencyInfoContext.Default return resModel; } + + public static async Task TryMatchVersionFileByHash( + string hash, + HashType hashType) + { + var para = hashType switch + { + HashType.SHA1 => "?algorithm=sha1", + HashType.SHA512 => "?algorithm=sha512", + _ => throw new ArgumentOutOfRangeException(nameof(hashType), hashType, null) + }; + var reqUrl = $"{BaseUrl}/version_file/{hash}{para}"; + + using var res = await Get(reqUrl); + var resModel = await res.Content.ReadFromJsonAsync(ModrinthVersionInfoContext.Default.ModrinthVersionInfo); + + return resModel; + } } \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthProjectDependencyInfo.cs b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthProjectDependencyInfo.cs index 0d712a8..6b5bf90 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthProjectDependencyInfo.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthProjectDependencyInfo.cs @@ -10,6 +10,4 @@ public class ModrinthProjectDependencyInfo } [JsonSerializable(typeof(ModrinthProjectDependencyInfo))] -partial class ModrinthProjectDependencyInfoContext : JsonSerializerContext -{ -} \ No newline at end of file +partial class ModrinthProjectDependencyInfoContext : JsonSerializerContext; \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthProjectInfo.cs b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthProjectInfo.cs index 6b1a99c..31897bc 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthProjectInfo.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthProjectInfo.cs @@ -48,6 +48,4 @@ public class ModrinthProjectInfo : ModrinthProjectInfoBase } [JsonSerializable(typeof(ModrinthProjectInfo))] -partial class ModrinthProjectInfoContext : JsonSerializerContext -{ -} \ No newline at end of file +partial class ModrinthProjectInfoContext : JsonSerializerContext; \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthVersionInfo.cs b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthVersionInfo.cs index dba44f1..dc4f18e 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthVersionInfo.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthVersionInfo.cs @@ -13,6 +13,8 @@ public class ModrinthFileInfo [JsonPropertyName("filename")] public required string FileName { get; init; } [JsonPropertyName("primary")] public bool Primary { get; set; } + + [JsonPropertyName("size")] public long Size { get; set; } } public class ModrinthDependency @@ -59,6 +61,4 @@ public class ModrinthVersionInfo [JsonSerializable(typeof(ModrinthVersionInfo))] [JsonSerializable(typeof(ModrinthVersionInfo[]))] -partial class ModrinthVersionInfoContext : JsonSerializerContext -{ -} \ No newline at end of file +partial class ModrinthVersionInfoContext : JsonSerializerContext; \ No newline at end of file From 8c4d81798b3b0fd17f30164d73d0e977018dc278 Mon Sep 17 00:00:00 2001 From: laolarou Date: Tue, 9 Apr 2024 19:30:14 -0700 Subject: [PATCH 3/8] improve JSON mapping --- .../Class/Model/CurseForge/CurseForgeAddonInfo.cs | 3 ++- .../ProjBobcat/Class/Model/Forge/ForgeInstallProfile.cs | 3 ++- ProjBobcat/ProjBobcat/Class/Model/GameRulesModel.cs | 7 +++---- .../Class/Model/Modrinth/ModrinthModPackFileModel.cs | 3 ++- .../Class/Model/Modrinth/ModrinthModPackIndexModel.cs | 3 ++- .../Class/Model/Mojang/UserProfilePropertyValue.cs | 2 +- ProjBobcat/ProjBobcat/Class/Model/RawVersionModel.cs | 4 +--- 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeAddonInfo.cs b/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeAddonInfo.cs index 55a2059..88074df 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeAddonInfo.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeAddonInfo.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Text.Json.Serialization; namespace ProjBobcat.Class.Model.CurseForge; @@ -20,7 +21,7 @@ public class CurseForgeAddonInfo [JsonPropertyName("gameId")] public int GameId { get; set; } [JsonPropertyName("summary")] public string? Summary { get; set; } - [JsonPropertyName("links")] public IReadOnlyDictionary Links { get; set; } = new Dictionary(); + [JsonPropertyName("links")] public IReadOnlyDictionary Links { get; set; } = ImmutableDictionary.Empty; [JsonPropertyName("defaultFileId")] public int DefaultFileId { get; set; } [JsonPropertyName("releaseType")] public int ReleaseType { get; set; } diff --git a/ProjBobcat/ProjBobcat/Class/Model/Forge/ForgeInstallProfile.cs b/ProjBobcat/ProjBobcat/Class/Model/Forge/ForgeInstallProfile.cs index d44626a..8cea8b5 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/Forge/ForgeInstallProfile.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/Forge/ForgeInstallProfile.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Text.Json.Serialization; namespace ProjBobcat.Class.Model.Forge; @@ -47,7 +48,7 @@ public class ForgeInstallProfile [JsonPropertyName("data")] public IReadOnlyDictionary Data { get; set; } = - new Dictionary(); + ImmutableDictionary.Empty; [JsonPropertyName("processors")] public ForgeInstallProfileProcessor[] Processors { get; set; } = []; diff --git a/ProjBobcat/ProjBobcat/Class/Model/GameRulesModel.cs b/ProjBobcat/ProjBobcat/Class/Model/GameRulesModel.cs index 572b599..590c3c8 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/GameRulesModel.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/GameRulesModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Text.Json.Serialization; namespace ProjBobcat.Class.Model; @@ -8,11 +9,9 @@ public class GameRules [JsonPropertyName("action")] public required string Action { get; init; } [JsonPropertyName("features")] - public IReadOnlyDictionary Features { get; set; } = new Dictionary(); + public IReadOnlyDictionary Features { get; set; } = ImmutableDictionary.Empty; } [JsonSerializable(typeof(GameRules))] [JsonSerializable(typeof(GameRules[]))] -partial class GameRulesContext : JsonSerializerContext -{ -} \ No newline at end of file +partial class GameRulesContext : JsonSerializerContext; \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthModPackFileModel.cs b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthModPackFileModel.cs index d12844d..e363dc5 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthModPackFileModel.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthModPackFileModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Text.Json.Serialization; namespace ProjBobcat.Class.Model.Modrinth; @@ -8,7 +9,7 @@ public class ModrinthModPackFileModel [JsonPropertyName("path")] public string? Path { get; set; } [JsonPropertyName("hashes")] - public IReadOnlyDictionary Hashes { get; set; } = new Dictionary(); + public IReadOnlyDictionary Hashes { get; set; } = ImmutableDictionary.Empty; [JsonPropertyName("downloads")] public string[] Downloads { get; set; } = []; diff --git a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthModPackIndexModel.cs b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthModPackIndexModel.cs index 3ffd216..c6fb947 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthModPackIndexModel.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthModPackIndexModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Text.Json.Serialization; namespace ProjBobcat.Class.Model.Modrinth; @@ -18,7 +19,7 @@ public class ModrinthModPackIndexModel [JsonPropertyName("files")] public ModrinthModPackFileModel[] Files { get; set; } = []; [JsonPropertyName("dependencies")] - public IReadOnlyDictionary Dependencies { get; set; } = new Dictionary(); + public IReadOnlyDictionary Dependencies { get; set; } = ImmutableDictionary.Empty; } [JsonSerializable(typeof(ModrinthModPackIndexModel))] diff --git a/ProjBobcat/ProjBobcat/Class/Model/Mojang/UserProfilePropertyValue.cs b/ProjBobcat/ProjBobcat/Class/Model/Mojang/UserProfilePropertyValue.cs index 8be82ce..dd944e9 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/Mojang/UserProfilePropertyValue.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/Mojang/UserProfilePropertyValue.cs @@ -17,4 +17,4 @@ public class UserProfilePropertyValue } [JsonSerializable(typeof(UserProfilePropertyValue))] -public partial class UserProfilePropertyValueContext : JsonSerializerContext{} \ No newline at end of file +public partial class UserProfilePropertyValueContext : JsonSerializerContext; \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat/Class/Model/RawVersionModel.cs b/ProjBobcat/ProjBobcat/Class/Model/RawVersionModel.cs index 2b5d205..95f3781 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/RawVersionModel.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/RawVersionModel.cs @@ -230,6 +230,4 @@ public class RawVersionModel } [JsonSerializable(typeof(RawVersionModel))] -public partial class RawVersionModelContext : JsonSerializerContext -{ -} \ No newline at end of file +public partial class RawVersionModelContext : JsonSerializerContext; \ No newline at end of file From e303af39bf3b1033c6f2969a4339c0eecc1a5fda Mon Sep 17 00:00:00 2001 From: laolarou Date: Wed, 10 Apr 2024 13:29:41 -0700 Subject: [PATCH 4/8] Update RetryHandler.cs --- ProjBobcat/ProjBobcat/Handler/RetryHandler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ProjBobcat/ProjBobcat/Handler/RetryHandler.cs b/ProjBobcat/ProjBobcat/Handler/RetryHandler.cs index fcd69f4..acf353b 100644 --- a/ProjBobcat/ProjBobcat/Handler/RetryHandler.cs +++ b/ProjBobcat/ProjBobcat/Handler/RetryHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using System.Net.Http; using System.Net.Sockets; using System.Threading; @@ -42,7 +43,7 @@ protected override async Task SendAsync( return response; } - return response!; + return response ?? new HttpResponseMessage(HttpStatusCode.BadRequest); } static bool IsNetworkError(Exception ex) From 551ec3393a511ffe5d35fad62eab226b6821624c Mon Sep 17 00:00:00 2001 From: laolarou Date: Wed, 10 Apr 2024 13:30:21 -0700 Subject: [PATCH 5/8] Update ProjBobcat.csproj --- ProjBobcat/ProjBobcat/ProjBobcat.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjBobcat/ProjBobcat/ProjBobcat.csproj b/ProjBobcat/ProjBobcat/ProjBobcat.csproj index 48b6adb..2e969ac 100644 --- a/ProjBobcat/ProjBobcat/ProjBobcat.csproj +++ b/ProjBobcat/ProjBobcat/ProjBobcat.csproj @@ -64,7 +64,7 @@ resolved the issue that LaunchWrapper may not return the correct exit code - + 0.36.0 From 1b08a8c94c93e508bb781be1abcad32861f5c622 Mon Sep 17 00:00:00 2001 From: laolarou Date: Wed, 10 Apr 2024 16:16:32 -0700 Subject: [PATCH 6/8] Update DefaultResourceCompleter.cs --- .../DefaultResourceCompleter.cs | 99 +++++++------------ 1 file changed, 38 insertions(+), 61 deletions(-) diff --git a/ProjBobcat/ProjBobcat/DefaultComponent/DefaultResourceCompleter.cs b/ProjBobcat/ProjBobcat/DefaultComponent/DefaultResourceCompleter.cs index 48303d7..8d851a9 100644 --- a/ProjBobcat/ProjBobcat/DefaultComponent/DefaultResourceCompleter.cs +++ b/ProjBobcat/ProjBobcat/DefaultComponent/DefaultResourceCompleter.cs @@ -58,55 +58,22 @@ public class DefaultResourceCompleter : IResourceCompleter Timeout = (int)TimeoutPerFile.TotalMilliseconds }; - async Task ReceiveGameResourceTask(IAsyncEnumerable asyncEnumerable) - { - var count = 0UL; - var refreshCounter = 0; - - await foreach (var element in asyncEnumerable) - { - count++; - - OnResolveComplete(this, new GameResourceInfoResolveEventArgs - { - Progress = 0, - Status = $"发现未下载的 {element.FileName.CropStr()}({element.Type}),已加入下载队列" - }); - - var dF = new DownloadFile - { - DownloadPath = element.Path, - DownloadUri = element.Url, - FileName = element.FileName, - FileSize = element.FileSize, - CheckSum = element.CheckSum, - FileType = element.Type - }; - dF.Completed += WhenCompleted; - - downloadBag.Add(dF); - - refreshCounter++; - - if (refreshCounter % 10 == 0) - { - Interlocked.Add(ref _needToDownload, count); - count = 0; - } - } - - Interlocked.Add(ref _needToDownload, count); - } - OnResolveComplete(this, new GameResourceInfoResolveEventArgs { Progress = 0, Status = "正在进行资源检查" }); - var checkAction = new ActionBlock(resolver => + var checkAction = new ActionBlock(async resolver => { - var asyncEnumerable = resolver.ResolveResourceAsync(); + resolver.GameResourceInfoResolveEvent += FireResolveEvent; + + await Parallel.ForEachAsync( + resolver.ResolveResourceAsync(), + new ParallelOptions { MaxDegreeOfParallelism = MaxDegreeOfParallelism }, + ReceiveGameResourceTask); + + return; void FireResolveEvent(object? sender, GameResourceInfoResolveEventArgs e) { @@ -118,22 +85,6 @@ void FireResolveEvent(object? sender, GameResourceInfoResolveEventArgs e) OnResolveComplete(sender, e); } - - resolver.GameResourceInfoResolveEvent += FireResolveEvent; - - var action = new ActionBlock>( - ReceiveGameResourceTask, - new ExecutionDataflowBlockOptions - { - BoundedCapacity = MaxDegreeOfParallelism, - MaxDegreeOfParallelism = MaxDegreeOfParallelism - }); - - for (var i = 0; i < DownloadThread; i++) - action.Post(asyncEnumerable); - action.Complete(); - - return action.Completion; }, new ExecutionDataflowBlockOptions { BoundedCapacity = MaxDegreeOfParallelism, @@ -171,13 +122,39 @@ void FireResolveEvent(object? sender, GameResourceInfoResolveEventArgs e) _ => TaskResultStatus.Success }; - var resultTuple = (result, new ResourceCompleterCheckResult + var resultArgs = new ResourceCompleterCheckResult { IsLibDownloadFailed = isLibraryFailed, FailedFiles = _failedFiles - }); + }; - return new TaskResult(resultTuple.result, value: resultTuple.Item2); + return new TaskResult(result, value: resultArgs); + + ValueTask ReceiveGameResourceTask(IGameResource element, CancellationToken ct) + { + OnResolveComplete(this, new GameResourceInfoResolveEventArgs + { + Progress = 0, + Status = $"发现未下载的 {element.FileName.CropStr()}({element.Type}),已加入下载队列" + }); + + var dF = new DownloadFile + { + DownloadPath = element.Path, + DownloadUri = element.Url, + FileName = element.FileName, + FileSize = element.FileSize, + CheckSum = element.CheckSum, + FileType = element.Type + }; + dF.Completed += WhenCompleted; + + downloadBag.Add(dF); + + Interlocked.Add(ref _needToDownload, 1); + + return default; + } } public void Dispose() From f0bf316f05545b251ccaf74e85b6274d9559d428 Mon Sep 17 00:00:00 2001 From: laolarou Date: Wed, 10 Apr 2024 20:46:02 -0700 Subject: [PATCH 7/8] add InheritsFrom --- ProjBobcat/ProjBobcat/Class/Model/VersionInfo.cs | 2 ++ .../DefaultComponent/Launch/DefaultVersionLocator.cs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ProjBobcat/ProjBobcat/Class/Model/VersionInfo.cs b/ProjBobcat/ProjBobcat/Class/Model/VersionInfo.cs index 9e223c6..90c873b 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/VersionInfo.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/VersionInfo.cs @@ -18,6 +18,8 @@ public class VersionInfo public required string DirName { get; init; } + public required string? InheritsFrom { get; set; } + public required string GameBaseVersion { get; init; } public JavaVersionModel? JavaVersion { get; set; } diff --git a/ProjBobcat/ProjBobcat/DefaultComponent/Launch/DefaultVersionLocator.cs b/ProjBobcat/ProjBobcat/DefaultComponent/Launch/DefaultVersionLocator.cs index ad6b51a..3bcb543 100644 --- a/ProjBobcat/ProjBobcat/DefaultComponent/Launch/DefaultVersionLocator.cs +++ b/ProjBobcat/ProjBobcat/DefaultComponent/Launch/DefaultVersionLocator.cs @@ -21,7 +21,7 @@ namespace ProjBobcat.DefaultComponent.Launch; /// public sealed class DefaultVersionLocator : VersionLocatorBase { - public NativeReplacementPolicy NativeReplacementPolicy { get; set; } = NativeReplacementPolicy.LegacyOnly; + public NativeReplacementPolicy NativeReplacementPolicy { get; init; } = NativeReplacementPolicy.LegacyOnly; /// /// 构造函数。 @@ -390,6 +390,7 @@ public override (List, List) GetNatives(Library[] libr Natives = [], Logging = rawVersion.Logging, Id = rawVersion.Id, + InheritsFrom = rawVersion.InheritsFrom, GameBaseVersion = GameVersionHelper.TryGetMcVersion([.. (inherits ?? []), rawVersion]) ?? id, DirName = id, Name = id, From 9a596fc1bced66d1d0099d51759a8b7d1a0ac7db Mon Sep 17 00:00:00 2001 From: laolarou Date: Fri, 12 Apr 2024 22:31:34 -0700 Subject: [PATCH 8/8] add more CF/Modrinth APIs --- .../Class/Helper/ModrinthAPIHelper.cs | 10 +++++ .../Model/CurseForge/API/SearchOptions.cs | 6 ++- .../CurseForge/CurseForgeLatestFileModel.cs | 2 +- .../CurseForgeSearchCategoryModel.cs | 2 + .../Model/Modrinth/ModrinthVersionInfo.cs | 16 ++++++++ .../ModPackInstaller/CurseForgeInstaller.cs | 39 ++++++++++++------- 6 files changed, 58 insertions(+), 17 deletions(-) diff --git a/ProjBobcat/ProjBobcat/Class/Helper/ModrinthAPIHelper.cs b/ProjBobcat/ProjBobcat/Class/Helper/ModrinthAPIHelper.cs index 0b9fbe9..00c5a8f 100644 --- a/ProjBobcat/ProjBobcat/Class/Helper/ModrinthAPIHelper.cs +++ b/ProjBobcat/ProjBobcat/Class/Helper/ModrinthAPIHelper.cs @@ -100,4 +100,14 @@ await res.Content.ReadFromJsonAsync(ModrinthProjectDependencyInfoContext.Default return resModel; } + + public static async Task GetVersionInfo(string projectId, string versionId) + { + var reqUrl = $"{BaseUrl}/project/{projectId}/version/{versionId}"; + + using var res = await Get(reqUrl); + var resModel = await res.Content.ReadFromJsonAsync(ModrinthVersionInfoContext.Default.ModrinthVersionInfo); + + return resModel; + } } \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat/Class/Model/CurseForge/API/SearchOptions.cs b/ProjBobcat/ProjBobcat/Class/Model/CurseForge/API/SearchOptions.cs index 36f6802..87a0702 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/CurseForge/API/SearchOptions.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/CurseForge/API/SearchOptions.cs @@ -3,6 +3,8 @@ public class SearchOptions { public int? CategoryId { get; init; } + public int? ClassId { get; init; } + public int? ParentCategoryId { get; init; } public int? GameId { get; init; } public string? GameVersion { get; init; } public int? Index { get; init; } @@ -27,8 +29,10 @@ public override string ToString() result += $"&gameVersion={GameVersion}"; if (!string.IsNullOrEmpty(SearchFilter)) result += $"&searchFilter={SearchFilter}"; + if (ClassId != null && ParentCategoryId is null) + result += $"&classId={ClassId}"; if (CategoryId != null) - result += $"&classId={CategoryId}"; + result += $"&categoryId={CategoryId}"; return result; } diff --git a/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeLatestFileModel.cs b/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeLatestFileModel.cs index 3e7ceef..b87f09e 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeLatestFileModel.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeLatestFileModel.cs @@ -20,7 +20,7 @@ public class CurseForgeLatestFileModel [JsonPropertyName("fileStatus")] public int FileStatus { get; set; } - [JsonPropertyName("downloadUrl")] public required string DownloadUrl { get; init; } + [JsonPropertyName("downloadUrl")] public string? DownloadUrl { get; set; } [JsonPropertyName("isAlternate")] public bool IsAlternate { get; set; } diff --git a/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeSearchCategoryModel.cs b/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeSearchCategoryModel.cs index f7cef8e..01dd971 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeSearchCategoryModel.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeSearchCategoryModel.cs @@ -23,5 +23,7 @@ public class CurseForgeSearchCategoryModel [JsonPropertyName("gameId")] public int? GameId { get; set; } + [JsonPropertyName("classId")] public int? ClassId { get; set; } + [JsonPropertyName("isClass")] public bool? IsClass { get; set; } } \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthVersionInfo.cs b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthVersionInfo.cs index dc4f18e..0c38d72 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthVersionInfo.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthVersionInfo.cs @@ -4,6 +4,22 @@ namespace ProjBobcat.Class.Model.Modrinth; +public class ModrinthDependencyModelComparer : IEqualityComparer +{ + public bool Equals(ModrinthDependency? x, ModrinthDependency? y) + { + if (x == null && y == null) return false; + if (x == null || y == null) return false; + + return x.ProjectId == y.ProjectId; + } + + public int GetHashCode(ModrinthDependency obj) + { + return (obj.ProjectId ?? string.Empty).GetHashCode(); + } +} + public class ModrinthFileInfo { [JsonPropertyName("hashes")] public IReadOnlyDictionary? Hashes { get; set; } diff --git a/ProjBobcat/ProjBobcat/DefaultComponent/Installer/ModPackInstaller/CurseForgeInstaller.cs b/ProjBobcat/ProjBobcat/DefaultComponent/Installer/ModPackInstaller/CurseForgeInstaller.cs index 6bcc0e5..37b9154 100644 --- a/ProjBobcat/ProjBobcat/DefaultComponent/Installer/ModPackInstaller/CurseForgeInstaller.cs +++ b/ProjBobcat/ProjBobcat/DefaultComponent/Installer/ModPackInstaller/CurseForgeInstaller.cs @@ -28,17 +28,17 @@ public void Install() InstallTaskAsync().Wait(); } - async ValueTask<(bool, DownloadFile?)> TryGuessModDownloadLink(long fileId, string downloadPath) + public static async ValueTask<(string? FileName, string? Url)> TryGuessModDownloadLink(long fileId) { try { var files = await CurseForgeAPIHelper.GetFiles(new[] { fileId }); - if (files == null || files.Length == 0) return (false, null); + if (files == null || files.Length == 0) return default; var file = files.FirstOrDefault(f => f.Id == fileId); - if (file == null || string.IsNullOrEmpty(file.FileName)) return (false, null); + if (file == null || string.IsNullOrEmpty(file.FileName)) return default; var fileName = file.FileName; var fileIdStr = fileId.ToString(); @@ -57,27 +57,36 @@ public void Install() if (!checkRes.IsSuccessStatusCode) continue; - var df = new DownloadFile - { - DownloadPath = downloadPath, - DownloadUri = url, - FileName = fileName - }; - - df.Completed += WhenCompleted; - - return (true, df); + return (fileName, url); } - return (false, null); + return default; } catch (Exception e) { Debug.WriteLine(e); - return (false, null); + return default; } } + async ValueTask<(bool, DownloadFile?)> TryGuessModDownloadLink(long fileId, string downloadPath) + { + var pair = await TryGuessModDownloadLink(fileId); + + if (string.IsNullOrEmpty(pair.FileName) || string.IsNullOrEmpty(pair.Url)) return (false, null); + + var df = new DownloadFile + { + DownloadPath = downloadPath, + DownloadUri = pair.Url, + FileName = pair.FileName + }; + + df.Completed += WhenCompleted; + + return (true, df); + } + public async Task InstallTaskAsync() { ArgumentException.ThrowIfNullOrEmpty(GameId);