From 5f8b62fcc0c2dc83c0e195992c96356c23b5a02f Mon Sep 17 00:00:00 2001 From: PassivePicasso Date: Fri, 3 Mar 2023 01:21:47 -0500 Subject: [PATCH 001/126] initial spacedock.info support this adds basic support for loading mods from spacedock.info this currently does not handle dependencies or dependency resolution there are issues with the new package loading design that need to be resolved --- Editor/Core/Data/PackageSource.cs | 28 ++++ Editor/Core/Data/PackageSourceSettings.cs | 1 - Editor/Core/Windows/PackageManager.cs | 25 +++ Editor/SpaceDock.meta | 8 + Editor/SpaceDock/PackageListing.cs | 28 ++++ Editor/SpaceDock/PackageListing.cs.meta | 11 ++ Editor/SpaceDock/PackageVersion.cs | 17 ++ Editor/SpaceDock/PackageVersion.cs.meta | 11 ++ Editor/SpaceDock/PackagesResponse.cs | 13 ++ Editor/SpaceDock/PackagesResponse.cs.meta | 11 ++ Editor/SpaceDock/SpaceDockSource.cs | 153 ++++++++++++++++++ Editor/SpaceDock/SpaceDockSource.cs.meta | 11 ++ Editor/SpaceDock/ThunderKit.SpaceDock.asmdef | 17 ++ .../ThunderKit.SpaceDock.asmdef.meta | 7 + Editor/SpaceDock/csc.rsp | 1 + Editor/SpaceDock/csc.rsp.meta | 7 + Editor/SpaceDock/mcs.rsp | 1 + Editor/SpaceDock/mcs.rsp.meta | 7 + .../Thunderstore/LocalThunderstoreSource.cs | 6 + Editor/Thunderstore/ThunderstoreSource.cs | 24 +-- UXML/PackageManager/PackageSource.uss | 12 ++ UXML/PackageManager/PackageSource.uxml | 1 + UXML/Settings/SpaceDockSource.UXML | 10 ++ UXML/Settings/SpaceDockSource.UXML.meta | 11 ++ 24 files changed, 404 insertions(+), 17 deletions(-) create mode 100644 Editor/SpaceDock.meta create mode 100644 Editor/SpaceDock/PackageListing.cs create mode 100644 Editor/SpaceDock/PackageListing.cs.meta create mode 100644 Editor/SpaceDock/PackageVersion.cs create mode 100644 Editor/SpaceDock/PackageVersion.cs.meta create mode 100644 Editor/SpaceDock/PackagesResponse.cs create mode 100644 Editor/SpaceDock/PackagesResponse.cs.meta create mode 100644 Editor/SpaceDock/SpaceDockSource.cs create mode 100644 Editor/SpaceDock/SpaceDockSource.cs.meta create mode 100644 Editor/SpaceDock/ThunderKit.SpaceDock.asmdef create mode 100644 Editor/SpaceDock/ThunderKit.SpaceDock.asmdef.meta create mode 100644 Editor/SpaceDock/csc.rsp create mode 100644 Editor/SpaceDock/csc.rsp.meta create mode 100644 Editor/SpaceDock/mcs.rsp create mode 100644 Editor/SpaceDock/mcs.rsp.meta create mode 100644 UXML/Settings/SpaceDockSource.UXML create mode 100644 UXML/Settings/SpaceDockSource.UXML.meta diff --git a/Editor/Core/Data/PackageSource.cs b/Editor/Core/Data/PackageSource.cs index e9d737dc..4a71504b 100644 --- a/Editor/Core/Data/PackageSource.cs +++ b/Editor/Core/Data/PackageSource.cs @@ -17,6 +17,10 @@ public abstract class PackageSource : ScriptableObject, IEquatable Packages private Dictionary groupMap; private List packages; + protected abstract Task ReloadPagesAsyncInternal(); + public async Task ReloadPagesAsync() + { + if (IsLoadingPages) return; + IsLoadingPages = true; + try + { + OnLoadingStarted?.Invoke(); + await ReloadPagesAsyncInternal(); + } + finally + { + IsLoadingPages = false; + OnLoadingStopped?.Invoke(); + } + } + + public void ReloadPages(bool force = false) + { + if (force) IsLoadingPages = false; + _ = ReloadPagesAsync(); + + } + /// /// Generates a new PackageGroup for this PackageSource /// diff --git a/Editor/Core/Data/PackageSourceSettings.cs b/Editor/Core/Data/PackageSourceSettings.cs index 8730d4aa..9d70a835 100644 --- a/Editor/Core/Data/PackageSourceSettings.cs +++ b/Editor/Core/Data/PackageSourceSettings.cs @@ -6,7 +6,6 @@ using ThunderKit.Core.UIElements; using ThunderKit.Common; using ThunderKit.Core.Utilities; -using System.Security.AccessControl; #if UNITY_2019_1_OR_NEWER using UnityEditor.UIElements; using UnityEngine.UIElements; diff --git a/Editor/Core/Windows/PackageManager.cs b/Editor/Core/Windows/PackageManager.cs index 3259238c..94753ddd 100644 --- a/Editor/Core/Windows/PackageManager.cs +++ b/Editor/Core/Windows/PackageManager.cs @@ -121,6 +121,30 @@ private void ConstructPackageSourceList(List packageSources) else packageSource.RemoveFromClassList("grow"); }); + var loadingIndicator = packageSource.Q(name: "tkpm-package-source-loading-indicator"); + + int m_Rotation = 0; + source.OnLoadingStarted += Source_OnLoadingStarted; + source.OnLoadingStopped += Source_OnLoadingStopped; + void UpdateProgress() + { + loadingIndicator.transform.rotation = Quaternion.Euler(0, 0, m_Rotation); + m_Rotation += 3; + if (m_Rotation > 360) + m_Rotation -= 360; + } + void Source_OnLoadingStopped() + { + loadingIndicator.AddToClassList("hidden"); + EditorApplication.update -= UpdateProgress; + } + + void Source_OnLoadingStarted() + { + loadingIndicator.RemoveFromClassList("hidden"); + m_Rotation = 0; + EditorApplication.update += UpdateProgress; + } packageSource.AddToClassList("tkpm-package-source"); packageSource.name = groupName; @@ -150,6 +174,7 @@ private void ConstructPackageSourceList(List packageSources) UpdatePackageList(); } + private void FiltersClicked() { var menu = new GenericMenu(); diff --git a/Editor/SpaceDock.meta b/Editor/SpaceDock.meta new file mode 100644 index 00000000..6ee58dd6 --- /dev/null +++ b/Editor/SpaceDock.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 497d30ef1fc8c0a43859649488c85ddb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/SpaceDock/PackageListing.cs b/Editor/SpaceDock/PackageListing.cs new file mode 100644 index 00000000..ac73c44f --- /dev/null +++ b/Editor/SpaceDock/PackageListing.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace ThunderKit.Integrations.SpaceDock +{ + [Serializable] + public partial class PackageListing + { + public string name; + public int id; + public string game; + public int game_id; + public string short_description; + public int downloads; + public int followers; + public string author; + public int default_version_id; + public List shared_authors; + public string background; + public int bg_offset_y; + public string license; + public string website; + public string donations; + public string source_code; + public string url; + public List versions; + } +} \ No newline at end of file diff --git a/Editor/SpaceDock/PackageListing.cs.meta b/Editor/SpaceDock/PackageListing.cs.meta new file mode 100644 index 00000000..0d42ce5a --- /dev/null +++ b/Editor/SpaceDock/PackageListing.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a68486fcf81aa0a44840daab36a61599 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/SpaceDock/PackageVersion.cs b/Editor/SpaceDock/PackageVersion.cs new file mode 100644 index 00000000..b00a7965 --- /dev/null +++ b/Editor/SpaceDock/PackageVersion.cs @@ -0,0 +1,17 @@ +using System; + +namespace ThunderKit.Integrations.SpaceDock +{ + + [Serializable] + public partial class PackageVersion + { + public string friendly_version; + public string game_version; + public int id; + public DateTime created; + public string download_path; + public string changelog; + public int downloads; + } +} \ No newline at end of file diff --git a/Editor/SpaceDock/PackageVersion.cs.meta b/Editor/SpaceDock/PackageVersion.cs.meta new file mode 100644 index 00000000..6cc177cd --- /dev/null +++ b/Editor/SpaceDock/PackageVersion.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c6518c45b4f0a5c49900cb61bdf3cbdd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/SpaceDock/PackagesResponse.cs b/Editor/SpaceDock/PackagesResponse.cs new file mode 100644 index 00000000..50089325 --- /dev/null +++ b/Editor/SpaceDock/PackagesResponse.cs @@ -0,0 +1,13 @@ +using System; + +namespace ThunderKit.Integrations.SpaceDock +{ + [Serializable] + public class PackagesResponse + { + public int count; + public int pages; + public int page; + public PackageListing[] result; + } +} \ No newline at end of file diff --git a/Editor/SpaceDock/PackagesResponse.cs.meta b/Editor/SpaceDock/PackagesResponse.cs.meta new file mode 100644 index 00000000..79e85f39 --- /dev/null +++ b/Editor/SpaceDock/PackagesResponse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5e0856be16b4778498240e95fb4d0386 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/SpaceDock/SpaceDockSource.cs b/Editor/SpaceDock/SpaceDockSource.cs new file mode 100644 index 00000000..3efff93a --- /dev/null +++ b/Editor/SpaceDock/SpaceDockSource.cs @@ -0,0 +1,153 @@ +using SharpCompress.Archives; +using SharpCompress.Readers; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using ThunderKit.Core.Data; +using UnityEngine; + +namespace ThunderKit.Integrations.SpaceDock +{ + using PV = Core.Data.PackageVersion; + public class SpaceDockSource : PackageSource + { + enum OrderBy { name, updated, created } + [Serializable] + public struct SDateTime + { + public long ticks; + public SDateTime(long ticks) + { + this.ticks = ticks; + } + public static implicit operator DateTime(SDateTime sdt) => new DateTime(sdt.ticks); + public static implicit operator SDateTime(DateTime sdt) => new SDateTime(sdt.Ticks); + } + + class GZipWebClient : WebClient + { + protected override WebRequest GetWebRequest(Uri address) + { + HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address); + request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + return request; + } + } + internal class ThunderstoreLoadBehaviour : MonoBehaviour { } + + private PackageListing[] packageListings; + + public override string Name => "SpaceDock Source"; + public override string SourceGroup => "SpaceDock"; + + private void OnEnable() + { + InitializeSources -= Initialize; + InitializeSources += Initialize; + } + private void OnDisable() + { + InitializeSources -= Initialize; + } + private void OnDestroy() + { + InitializeSources -= Initialize; + } + + private void Initialize(object sender, EventArgs e) + { + ReloadPages(); + } + + protected override string VersionIdToGroupId(string dependencyId) => dependencyId.Substring(0, dependencyId.LastIndexOf("-")); + + protected override void OnLoadPackages() + { + //var orderByPinThenName = realMods.OrderByDescending(tsp => tsp.is_pinned).ThenBy(tsp => tsp.name); + foreach (var tsp in packageListings) + { + var versions = tsp.versions.Select(v => new PackageVersionInfo(v.friendly_version, tsp.name, Array.Empty())); + AddPackageGroup(tsp.author, tsp.name, tsp.short_description, tsp.name, Array.Empty(), versions); + } + SourceUpdated(); + } + + protected override void OnInstallPackageFiles(PV version, string packageDirectory) + { + var tsPackage = LookupPackage(version.group.DependencyId).First(); + var tsPackageVersion = tsPackage.versions.First(tspv => tspv.friendly_version.Equals(version.version)); + var filePath = Path.Combine(packageDirectory, $"{tsPackage.name}-{tsPackageVersion.friendly_version}.zip"); + + using (var client = new WebClient()) + { + client.DownloadFile(tsPackageVersion.download_path, filePath); + } + + using (var archive = ArchiveFactory.Open(filePath)) + { + foreach (var entry in archive.Entries.Where(entry => entry.IsDirectory)) + { + var path = Path.Combine(packageDirectory, entry.Key); + Directory.CreateDirectory(path); + } + + var extractOptions = new ExtractionOptions { ExtractFullPath = true, Overwrite = true }; + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + entry.WriteToDirectory(packageDirectory, extractOptions); + } + + File.Delete(filePath); + } + + private string PackageListApi(int count, int page, OrderBy orderby) + => $"https://spacedock.info/api/browse?count={count}&page={page}&orderby={orderby}"; + + protected override async Task ReloadPagesAsyncInternal() + { + using (var client = new GZipWebClient()) + { + var aggregate = Enumerable.Empty(); + var endPage = 1; + for (int p = 1; p <= endPage; p++) + { + var address = new Uri(PackageListApi(500, p, OrderBy.name)); + var result = await client.DownloadStringTaskAsync(address); + var response = JsonUtility.FromJson(result); + aggregate = aggregate.Union(response.result.Where(pl => + { + if (pl.game_id == 22407) return true; + return false; + })); + endPage = response.pages; + } + + packageListings = aggregate.ToArray(); + } + LoadPackages(); + } + + public IEnumerable LookupPackage(string name) + { + if (packageListings.Length == 0) + return Enumerable.Empty(); + else + return packageListings.Where(package => IsMatch(package, name)).ToArray(); + } + + bool IsMatch(PackageListing package, string name) + { + CompareInfo comparer = CultureInfo.CurrentCulture.CompareInfo; + var compareOptions = CompareOptions.IgnoreCase; + var nameMatch = comparer.IndexOf(package.name, name, compareOptions) >= 0; + var fullNameMatch = comparer.IndexOf(package.name, name, compareOptions) >= 0; + + var latest = package.versions.OrderByDescending(pck => pck.id).First(); + var latestFullNameMatch = comparer.IndexOf(package.name, name, compareOptions) >= 0; + return nameMatch || fullNameMatch || latestFullNameMatch; + } + } +} \ No newline at end of file diff --git a/Editor/SpaceDock/SpaceDockSource.cs.meta b/Editor/SpaceDock/SpaceDockSource.cs.meta new file mode 100644 index 00000000..b0f4548f --- /dev/null +++ b/Editor/SpaceDock/SpaceDockSource.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d6c7f6c8ae699ed4f98edfca9154853f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 917f110b180892a4191b2bf32c4671d4, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/SpaceDock/ThunderKit.SpaceDock.asmdef b/Editor/SpaceDock/ThunderKit.SpaceDock.asmdef new file mode 100644 index 00000000..f54cbaeb --- /dev/null +++ b/Editor/SpaceDock/ThunderKit.SpaceDock.asmdef @@ -0,0 +1,17 @@ +{ + "name": "ThunderKit.SpaceDock", + "rootNamespace": "", + "references": [ + "GUID:280569ac301428d4d950fdf647900922", + "GUID:48abf88f565f4bb4fb35fbe2385f0894" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Editor/SpaceDock/ThunderKit.SpaceDock.asmdef.meta b/Editor/SpaceDock/ThunderKit.SpaceDock.asmdef.meta new file mode 100644 index 00000000..a673aee6 --- /dev/null +++ b/Editor/SpaceDock/ThunderKit.SpaceDock.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1494e623f6d59c94a9329a8d2f62d397 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/SpaceDock/csc.rsp b/Editor/SpaceDock/csc.rsp new file mode 100644 index 00000000..b708d67a --- /dev/null +++ b/Editor/SpaceDock/csc.rsp @@ -0,0 +1 @@ +-langversion:Latest \ No newline at end of file diff --git a/Editor/SpaceDock/csc.rsp.meta b/Editor/SpaceDock/csc.rsp.meta new file mode 100644 index 00000000..e582bf88 --- /dev/null +++ b/Editor/SpaceDock/csc.rsp.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: fec6e21000ba4bd489a3c7eed21cbd7f +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/SpaceDock/mcs.rsp b/Editor/SpaceDock/mcs.rsp new file mode 100644 index 00000000..b708d67a --- /dev/null +++ b/Editor/SpaceDock/mcs.rsp @@ -0,0 +1 @@ +-langversion:Latest \ No newline at end of file diff --git a/Editor/SpaceDock/mcs.rsp.meta b/Editor/SpaceDock/mcs.rsp.meta new file mode 100644 index 00000000..16739478 --- /dev/null +++ b/Editor/SpaceDock/mcs.rsp.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 47b97830bbb66a44bb83df1dc3137de9 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Thunderstore/LocalThunderstoreSource.cs b/Editor/Thunderstore/LocalThunderstoreSource.cs index dd73de4c..49e29ff0 100644 --- a/Editor/Thunderstore/LocalThunderstoreSource.cs +++ b/Editor/Thunderstore/LocalThunderstoreSource.cs @@ -2,6 +2,7 @@ using SharpCompress.Readers; using System.IO; using System.Linq; +using System.Threading.Tasks; using ThunderKit.Core.Data; using UnityEngine; @@ -32,6 +33,7 @@ private void OnDestroy() public override string Name => "Local Thunderstore"; public override string SourceGroup => "Thunderstore"; + protected override string VersionIdToGroupId(string dependencyId) => dependencyId.Substring(0, dependencyId.LastIndexOf("-")); protected override void OnLoadPackages() { @@ -88,5 +90,9 @@ protected override void OnInstallPackageFiles(PV version, string packageDirector } } + protected override Task ReloadPagesAsyncInternal() + { + return Task.CompletedTask; + } } } \ No newline at end of file diff --git a/Editor/Thunderstore/ThunderstoreSource.cs b/Editor/Thunderstore/ThunderstoreSource.cs index 7c29cc99..cab2be72 100644 --- a/Editor/Thunderstore/ThunderstoreSource.cs +++ b/Editor/Thunderstore/ThunderstoreSource.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Net; +using System.Threading.Tasks; using ThunderKit.Core.Data; using ThunderKit.Core.Utilities; using UnityEditor; @@ -64,7 +65,6 @@ protected override WebRequest GetWebRequest(Uri address) internal class ThunderstoreLoadBehaviour : MonoBehaviour { } private PackageListing[] packageListings; - private bool isLoadingPages = false; public string Url = "https://thunderstore.io"; @@ -88,7 +88,7 @@ private void OnDestroy() private void Initialize(object sender, EventArgs e) { - ReloadPages(); + ReloadPages(true); } protected override string VersionIdToGroupId(string dependencyId) => dependencyId.Substring(0, dependencyId.LastIndexOf("-")); @@ -132,27 +132,19 @@ protected override void OnInstallPackageFiles(PV version, string packageDirector File.Delete(filePath); } - public void ReloadPages(bool force = false) + protected override async Task ReloadPagesAsyncInternal() { - if (isLoadingPages) return; using (var client = new GZipWebClient()) { - client.DownloadStringCompleted += Client_DownloadStringCompleted; var address = new Uri(PackageListApi); - client.DownloadStringAsync(address); - isLoadingPages = true; + var result = await client.DownloadStringTaskAsync(address); + var json = $"{{ \"{nameof(PackagesResponse.results)}\": {result} }}"; + var response = JsonUtility.FromJson(json); + packageListings = response.results; + LoadPackages(); } } - private void Client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) - { - var json = $"{{ \"{nameof(PackagesResponse.results)}\": {e.Result} }}"; - var response = JsonUtility.FromJson(json); - packageListings = response.results; - LoadPackages(); - isLoadingPages = false; - } - public IEnumerable LookupPackage(string name) { if (packageListings.Length == 0) diff --git a/UXML/PackageManager/PackageSource.uss b/UXML/PackageManager/PackageSource.uss index a5871b19..f14c3b3d 100644 --- a/UXML/PackageManager/PackageSource.uss +++ b/UXML/PackageManager/PackageSource.uss @@ -3,6 +3,14 @@ margin-left: 20px; } +.loading-spinner { + background-image: resource("icons/packagemanager/dark/loading.png"); + position: absolute; + right: 0px; + width: 16px; + height: 16px; +} + #tkpm-package-source-header { position: absolute; left: 0px; @@ -38,4 +46,8 @@ margin-top: 4px; margin-left: 0px; flex-grow: 1; +} + +.hidden { + visibility: hidden; } \ No newline at end of file diff --git a/UXML/PackageManager/PackageSource.uxml b/UXML/PackageManager/PackageSource.uxml index 02d45802..3b01a8fd 100644 --- a/UXML/PackageManager/PackageSource.uxml +++ b/UXML/PackageManager/PackageSource.uxml @@ -14,6 +14,7 @@ xsi:schemaLocation=" "> + diff --git a/UXML/Settings/SpaceDockSource.UXML b/UXML/Settings/SpaceDockSource.UXML new file mode 100644 index 00000000..bde3cf7b --- /dev/null +++ b/UXML/Settings/SpaceDockSource.UXML @@ -0,0 +1,10 @@ + +