diff --git a/Editor/PackageManager/PackageManagerCheckTool.cs b/Editor/PackageManager/PackageManagerCheckTool.cs index 79802b4..0652cfe 100644 --- a/Editor/PackageManager/PackageManagerCheckTool.cs +++ b/Editor/PackageManager/PackageManagerCheckTool.cs @@ -1,136 +1,318 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; namespace AlicizaX.PackageManager.Editor { using System; using System.Collections.Generic; using System.IO; - using UnityEngine; using UnityEditor; using UnityEditor.PackageManager; using UnityEditor.PackageManager.Requests; - using Newtonsoft.Json.Linq; + + internal enum InstalledSource + { + None, + Git, + Embedded, + LocalPath, + Registry, + BuiltIn, + Unknown + } + + internal sealed class LocalPackageInfo + { + public string Name; + public string ManifestVersion; + public string LockVersion; + public string ResolvedHash; + public string InstalledVersion; + public InstalledSource Source; + public bool IsInstalled; + } public static class PackageManagerCheckTool { - private static Dictionary cachedManifestDependencies; - private static ListRequest listRequest; + private const string ManifestPath = "Packages/manifest.json"; + private const string PackagesLockPath = "Packages/packages-lock.json"; + + private static readonly Dictionary InstalledPackageVersions = new Dictionary(StringComparer.OrdinalIgnoreCase); + + private static Dictionary s_manifestDependencies; + private static Dictionary s_lockDependencies; + private static ListRequest s_listRequest; + private static Action> s_listCallback; [Serializable] - public class ManifestDependencies + private sealed class ManifestFile { public Dictionary dependencies = new Dictionary(); } + [Serializable] + private sealed class PackagesLockFile + { + public Dictionary dependencies = new Dictionary(); + } + + [Serializable] + private sealed class LockDependency + { + public string version; + public string source; + public string hash; + } + public static void ValidatePackageStates(List packages) { - var manifest = ReadManifestFile(); - var installedPackages = GetInstalledPackagesFromPM(); + if (packages == null) + { + return; + } + + RefreshLocalCache(); foreach (var package in packages) { - // 检查是否在manifest中存在 - if (manifest.dependencies.TryGetValue(package.name, out var gitUrl)) - { - // 检查PackageCache目录 - var cachePath = FindInPackageCache(package.name); - if (!string.IsNullOrEmpty(cachePath)) - { - var localTime = Directory.GetLastWriteTime(cachePath); - if (DateTime.TryParse(package.updatedAt, out var remoteTime)) - { - package.PackageState = remoteTime > localTime ? PackageState.Update : PackageState.Install; - } - else - { - package.PackageState = PackageState.Install; - } - } - else - { - package.PackageState = PackageState.Install; - } - } - else - { - // 检查Packages目录 - var packagePath = Path.GetFullPath($"Packages/{package.name}"); - package.PackageState = Directory.Exists(packagePath) ? PackageState.InstallLocal : PackageState.UnInstall; - } + var localPackage = GetLocalPackageInfo(package.name); + ApplyLocalState(package, localPackage); } } - private static string FindInPackageCache(string packageName) + public static void RefreshInstalledPackages(Action> callback) { - var cacheRoot = Path.GetFullPath("Library/PackageCache"); - if (!Directory.Exists(cacheRoot)) return null; - - return Path.Combine(cacheRoot, packageName); - } - - private static ManifestDependencies ReadManifestFile() - { - var manifestPath = Path.GetFullPath("Packages/manifest.json"); - var json = File.ReadAllText(manifestPath); - return JsonConvert.DeserializeObject(json); - } - - public static void InstallPackage(string gitUrl) - { - Client.Add(gitUrl); - } - - public static void UpdatePackage(string packageName) - { - Client.Add($"git:{packageName}"); - } - - public static void UninstallPackage(string packageName) - { - Client.Remove(packageName); - } - - private static List GetInstalledPackagesFromPM() - { - var packages = new List(); - listRequest = Client.List(); - - EditorApplication.CallbackFunction checkProgress = null; - checkProgress = () => + if (s_listRequest != null && !s_listRequest.IsCompleted) { - if (listRequest.IsCompleted) - { - if (listRequest.Status == StatusCode.Success) - { - foreach (var package in listRequest.Result) - { - packages.Add(package.name); - } - } + return; + } - EditorApplication.update -= checkProgress; - } + s_listCallback = callback; + s_listRequest = Client.List(true, false); + EditorApplication.update -= OnListRequestProgress; + EditorApplication.update += OnListRequestProgress; + } + + public static void InstallPackage(string packageIdentifier, Action callback = null) + { + PackageOperationRunner.RunAdd(packageIdentifier, callback); + } + + public static void UpdatePackage(RepositoryPackageData package, Action callback = null) + { + if (package == null) + { + callback?.Invoke(false, "Package data is null."); + return; + } + + PackageOperationRunner.RunAdd(package.cloneUrl, callback); + } + + public static void UninstallPackage(string packageName, Action callback = null) + { + PackageOperationRunner.RunRemove(packageName, callback); + } + + internal static LocalPackageInfo GetLocalPackageInfo(string packageName) + { + RefreshLocalCache(); + + var info = new LocalPackageInfo + { + Name = packageName, + Source = InstalledSource.None }; - EditorApplication.update += checkProgress; - return packages; + if (string.IsNullOrEmpty(packageName)) + { + return info; + } + + if (s_manifestDependencies != null && s_manifestDependencies.TryGetValue(packageName, out var manifestVersion)) + { + info.ManifestVersion = manifestVersion; + } + + if (s_lockDependencies != null && s_lockDependencies.TryGetValue(packageName, out var lockDependency)) + { + info.LockVersion = lockDependency.version; + info.ResolvedHash = lockDependency.hash; + info.Source = ParseSource(lockDependency.source); + info.IsInstalled = info.Source != InstalledSource.None; + } + + if (InstalledPackageVersions.TryGetValue(packageName, out var installedVersion)) + { + info.InstalledVersion = installedVersion; + info.IsInstalled = true; + } + + if (info.Source == InstalledSource.None && !string.IsNullOrEmpty(info.ManifestVersion)) + { + info.Source = GuessSourceFromManifest(info.ManifestVersion); + info.IsInstalled = info.Source == InstalledSource.Embedded || info.Source == InstalledSource.LocalPath; + } + + return info; } - // 辅助方法:从Git URL提取包名 - public static string ExtractPackageNameFromUrl(string gitUrl) + public static bool HasInstalledPackageVersion(string packageName, out string version) { + return InstalledPackageVersions.TryGetValue(packageName, out version); + } + + private static void OnListRequestProgress() + { + if (s_listRequest == null || !s_listRequest.IsCompleted) + { + return; + } + + EditorApplication.update -= OnListRequestProgress; + + InstalledPackageVersions.Clear(); + if (s_listRequest.Status == StatusCode.Success) + { + foreach (var package in s_listRequest.Result) + { + InstalledPackageVersions[package.name] = package.version; + } + } + + var callback = s_listCallback; + s_listCallback = null; + s_listRequest = null; + callback?.Invoke(InstalledPackageVersions); + } + + private static void RefreshLocalCache() + { + s_manifestDependencies = ReadManifestDependencies(); + s_lockDependencies = ReadPackagesLockDependencies(); + } + + private static void ApplyLocalState(RepositoryPackageData package, LocalPackageInfo localPackage) + { + package.ManifestVersion = localPackage.ManifestVersion; + package.InstalledVersion = !string.IsNullOrEmpty(localPackage.InstalledVersion) + ? localPackage.InstalledVersion + : localPackage.LockVersion; + package.InstalledHash = localPackage.ResolvedHash; + package.InstalledSource = localPackage.Source.ToString(); + + switch (localPackage.Source) + { + case InstalledSource.Embedded: + case InstalledSource.LocalPath: + package.PackageState = PackageState.InstallLocal; + break; + case InstalledSource.Git: + package.PackageState = HasRemoteUpdate(package, localPackage) + ? PackageState.Update + : PackageState.Install; + break; + case InstalledSource.Registry: + case InstalledSource.BuiltIn: + case InstalledSource.Unknown: + package.PackageState = localPackage.IsInstalled ? PackageState.Install : PackageState.UnInstall; + break; + default: + package.PackageState = localPackage.IsInstalled ? PackageState.Install : PackageState.UnInstall; + break; + } + } + + private static bool HasRemoteUpdate(RepositoryPackageData package, LocalPackageInfo localPackage) + { + if (string.IsNullOrEmpty(localPackage.ResolvedHash) || string.IsNullOrEmpty(package.remoteHead)) + { + return false; + } + + return !string.Equals(localPackage.ResolvedHash, package.remoteHead, StringComparison.OrdinalIgnoreCase); + } + + private static Dictionary ReadManifestDependencies() + { + var path = Path.GetFullPath(ManifestPath); + if (!File.Exists(path)) + { + return new Dictionary(StringComparer.OrdinalIgnoreCase); + } + try { - Uri uri = new Uri(gitUrl); - string path = uri.AbsolutePath; - int startIndex = path.LastIndexOf('/') + 1; - string name = path.Substring(startIndex); - return name.Replace(".git", "").Replace("_", ".").ToLower(); + var json = File.ReadAllText(path); + var manifest = JsonConvert.DeserializeObject(json); + return manifest?.dependencies != null + ? new Dictionary(manifest.dependencies, StringComparer.OrdinalIgnoreCase) + : new Dictionary(StringComparer.OrdinalIgnoreCase); } - catch + catch (Exception e) { - return Path.GetFileNameWithoutExtension(gitUrl); + UnityEngine.Debug.LogError($"Read manifest failed: {e.Message}"); + return new Dictionary(StringComparer.OrdinalIgnoreCase); } } + + private static Dictionary ReadPackagesLockDependencies() + { + var path = Path.GetFullPath(PackagesLockPath); + if (!File.Exists(path)) + { + return new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + try + { + var json = File.ReadAllText(path); + var packagesLock = JsonConvert.DeserializeObject(json); + return packagesLock?.dependencies != null + ? new Dictionary(packagesLock.dependencies, StringComparer.OrdinalIgnoreCase) + : new Dictionary(StringComparer.OrdinalIgnoreCase); + } + catch (Exception e) + { + UnityEngine.Debug.LogError($"Read packages-lock failed: {e.Message}"); + return new Dictionary(StringComparer.OrdinalIgnoreCase); + } + } + + private static InstalledSource ParseSource(string source) + { + return source switch + { + "git" => InstalledSource.Git, + "embedded" => InstalledSource.Embedded, + "local" => InstalledSource.LocalPath, + "registry" => InstalledSource.Registry, + "builtin" => InstalledSource.BuiltIn, + _ => string.IsNullOrEmpty(source) ? InstalledSource.None : InstalledSource.Unknown + }; + } + + private static InstalledSource GuessSourceFromManifest(string manifestVersion) + { + if (string.IsNullOrEmpty(manifestVersion)) + { + return InstalledSource.None; + } + + if (manifestVersion.StartsWith("file:", StringComparison.OrdinalIgnoreCase)) + { + return InstalledSource.LocalPath; + } + + if (manifestVersion.EndsWith(".git", StringComparison.OrdinalIgnoreCase) || + manifestVersion.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || + manifestVersion.StartsWith("https://", StringComparison.OrdinalIgnoreCase) || + manifestVersion.StartsWith("ssh://", StringComparison.OrdinalIgnoreCase) || + manifestVersion.StartsWith("git@", StringComparison.OrdinalIgnoreCase)) + { + return InstalledSource.Git; + } + + return InstalledSource.Registry; + } } } diff --git a/Editor/PackageManager/PackageManagerCheckTool.cs.meta b/Editor/PackageManager/PackageManagerCheckTool.cs.meta index bee6f74..260dd70 100644 --- a/Editor/PackageManager/PackageManagerCheckTool.cs.meta +++ b/Editor/PackageManager/PackageManagerCheckTool.cs.meta @@ -1,3 +1,11 @@ -fileFormatVersion: 2 +fileFormatVersion: 2 guid: 491930ef63864219a4944a44ed820823 -timeCreated: 1740016880 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PackageManager/PackageOperationRunner.cs b/Editor/PackageManager/PackageOperationRunner.cs new file mode 100644 index 0000000..2423465 --- /dev/null +++ b/Editor/PackageManager/PackageOperationRunner.cs @@ -0,0 +1,97 @@ +namespace AlicizaX.PackageManager.Editor +{ + using System; + using UnityEditor; + using UnityEditor.PackageManager; + using UnityEditor.PackageManager.Requests; + + internal static class PackageOperationRunner + { + private static AddRequest s_addRequest; + private static RemoveRequest s_removeRequest; + private static Action s_callback; + private static string s_operationName; + + public static bool IsBusy => s_addRequest != null || s_removeRequest != null; + + public static void RunAdd(string packageIdentifier, Action callback) + { + if (IsBusy) + { + callback?.Invoke(false, "Another package operation is in progress."); + return; + } + + s_operationName = $"Add {packageIdentifier}"; + s_callback = callback; + s_addRequest = Client.Add(packageIdentifier); + EditorApplication.update -= Poll; + EditorApplication.update += Poll; + } + + public static void RunRemove(string packageName, Action callback) + { + if (IsBusy) + { + callback?.Invoke(false, "Another package operation is in progress."); + return; + } + + s_operationName = $"Remove {packageName}"; + s_callback = callback; + s_removeRequest = Client.Remove(packageName); + EditorApplication.update -= Poll; + EditorApplication.update += Poll; + } + + private static void Poll() + { + if (s_addRequest != null) + { + if (!s_addRequest.IsCompleted) + { + return; + } + + var success = s_addRequest.Status == StatusCode.Success; + var message = success ? s_addRequest.Result?.name : s_addRequest.Error?.message; + s_addRequest = null; + Complete(success, message); + return; + } + + if (s_removeRequest != null) + { + if (!s_removeRequest.IsCompleted) + { + return; + } + + var success = s_removeRequest.Status == StatusCode.Success; + var message = success ? s_removeRequest.PackageIdOrName : s_removeRequest.Error?.message; + s_removeRequest = null; + Complete(success, message); + return; + } + + EditorApplication.update -= Poll; + } + + private static void Complete(bool success, string message) + { + EditorApplication.update -= Poll; + + var callback = s_callback; + var operationName = s_operationName; + s_callback = null; + s_operationName = null; + + if (!success) + { + UnityEngine.Debug.LogError($"{operationName} failed: {message}"); + } + + callback?.Invoke(success, message); + } + } +} diff --git a/Editor/PackageManager/PackageOperationRunner.cs.meta b/Editor/PackageManager/PackageOperationRunner.cs.meta new file mode 100644 index 0000000..7eae4d3 --- /dev/null +++ b/Editor/PackageManager/PackageOperationRunner.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 049bb98ebc317284ab6077f713e50236 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PackageManager/RepositoryDataFetcher.cs b/Editor/PackageManager/RepositoryDataFetcher.cs index 5ff4efb..e4af6d2 100644 --- a/Editor/PackageManager/RepositoryDataFetcher.cs +++ b/Editor/PackageManager/RepositoryDataFetcher.cs @@ -1,11 +1,11 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; -using UnityEngine; -using UnityEngine.Networking; +using System.Text; using Newtonsoft.Json; using UnityEditor; - +using UnityEngine; +using UnityEngine.Networking; namespace AlicizaX.PackageManager.Editor { @@ -20,20 +20,28 @@ namespace AlicizaX.PackageManager.Editor [Serializable] public class RepositoryPackageData { - // Repository info public string name; public string description; public string cloneUrl; public string htmlUrl; public string updatedAt; + public string defaultBranch; + public string apiUrl; + public string remoteHead; + public string ownerName; - // Package info public string displayName; public string version; public string unityVersion; public string category; public string packageDescription; public string authorName; + + public string ManifestVersion; + public string InstalledVersion; + public string InstalledHash; + public string InstalledSource; + public PackageState PackageState; } @@ -52,6 +60,8 @@ namespace AlicizaX.PackageManager.Editor public string clone_url; public string html_url; public string updated_at; + public string default_branch; + public string url; public Owner owner; [Serializable] @@ -61,6 +71,26 @@ namespace AlicizaX.PackageManager.Editor } } + [Serializable] + public class RepoBranchResponse + { + public string name; + public BranchCommit commit; + + [Serializable] + public class BranchCommit + { + public string id; + } + } + + [Serializable] + public class PackageJsonContentResponse + { + public string content; + public string encoding; + } + [Serializable] public class PackageJsonResponse { @@ -79,119 +109,194 @@ namespace AlicizaX.PackageManager.Editor } } - internal class PackageManagerCoroutines : MonoBehaviour + internal sealed class PackageManagerCoroutines : MonoBehaviour { public static PackageManagerCoroutines Coroutines; public static void CreateCoroutines() { - if (Coroutines == null) + if (Coroutines != null) { - Coroutines = new GameObject("Coroutines").AddComponent(); + return; } + + var gameObject = new GameObject("PackageManagerCoroutines"); + gameObject.hideFlags = HideFlags.HideAndDontSave; + Coroutines = gameObject.AddComponent(); } public static void DestroyCoroutines() { - if (Coroutines != null) + if (Coroutines == null) { - Coroutines.StopAllCoroutines(); - DestroyImmediate(Coroutines.gameObject); - Coroutines = null; + return; } + + Coroutines.StopAllCoroutines(); + DestroyImmediate(Coroutines.gameObject); + Coroutines = null; } } public static class RepositoryDataFetcher { private const string BaseApiUrl = "http://101.34.252.46:3000/api/v1/repos/search?q=unity&topic=false"; - private const string PackageJsonPath = "/raw/branch/main/package.json"; - + private const string PackageJsonPathTemplate = "/contents/package.json?ref={0}"; + private const string BranchPathTemplate = "/branches/{0}"; + private const string LastUpdateEditorPrefsKey = "PackageUpdateDate"; public static void FetchRepositoryData(Action> callback) { PackageManagerCoroutines.CreateCoroutines(); - EditorPrefs.SetString("PackageUpdateDate", DateTime.Now.ToString()); PackageManagerCoroutines.Coroutines.StartCoroutine(FetchDataRoutine(callback)); } + public static string GetLastRefreshTime() + { + return EditorPrefs.GetString(LastUpdateEditorPrefsKey, string.Empty); + } + private static IEnumerator FetchDataRoutine(Action> callback) { - // 第一阶段:获取仓库列表 + List results = null; + Exception fatalError = null; + using (var request = CreateWebRequest(BaseApiUrl)) { - yield return request.SendWebRequest(); - if (!HandleRequestError(request, "Repository request failed")) + + if (!TryValidateRequest(request, "Repository request failed")) { - callback?.Invoke(null); - yield break; + fatalError = new Exception(request.error); } - - var repoResponse = JsonConvert.DeserializeObject(request.downloadHandler.text); - if (!repoResponse.ok || repoResponse.data == null) + else { - Debug.LogError("Invalid repository response data"); - callback?.Invoke(null); - yield break; - } - - var results = new List(); - var pendingRequests = new Queue(repoResponse.data); - - // 第二阶段:逐个获取package.json信息 - while (pendingRequests.Count > 0) - { - var repo = pendingRequests.Dequeue(); - var packageData = CreateBaseRepositoryData(repo); - - var packageUrl = repo.html_url + PackageJsonPath; - - using (var packageRequest = CreateWebRequest(packageUrl)) + RepoApiResponse response = null; + try { - yield return packageRequest.SendWebRequest(); + response = JsonConvert.DeserializeObject(request.downloadHandler.text); + } + catch (Exception e) + { + fatalError = e; + } - if (HandleRequestError(packageRequest, $"Package request failed for {repo.name}")) + if (fatalError == null) + { + if (response?.ok != true || response.data == null) { - try + fatalError = new Exception("Repository response is invalid."); + } + else + { + results = new List(response.data.Count); + foreach (var repo in response.data) { - var packageInfo = JsonConvert.DeserializeObject( - packageRequest.downloadHandler.text); - - UpdateWithPackageInfo(ref packageData, packageInfo); - } - catch (Exception e) - { - Debug.LogError($"JSON parse error for {repo.name}: {e.Message}"); + var data = CreateBaseRepositoryData(repo); + yield return FetchBranchHeadRoutine(data); + yield return FetchPackageInfoRoutine(data); + results.Add(data); } } - - results.Add(packageData); } } + } - callback?.Invoke(results); - PackageManagerCoroutines.DestroyCoroutines(); + if (fatalError != null) + { + Debug.LogError($"Fetch repository data failed: {fatalError.Message}"); + callback?.Invoke(new List()); + } + else + { + EditorPrefs.SetString(LastUpdateEditorPrefsKey, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + callback?.Invoke(results ?? new List()); + } + + PackageManagerCoroutines.DestroyCoroutines(); + } + + private static IEnumerator FetchBranchHeadRoutine(RepositoryPackageData packageData) + { + if (string.IsNullOrEmpty(packageData.apiUrl) || string.IsNullOrEmpty(packageData.defaultBranch)) + { + yield break; + } + + var branchUrl = packageData.apiUrl + string.Format(BranchPathTemplate, UnityWebRequest.EscapeURL(packageData.defaultBranch)); + using (var request = CreateWebRequest(branchUrl)) + { + yield return request.SendWebRequest(); + if (!TryValidateRequest(request, $"Branch request failed for {packageData.name}")) + { + yield break; + } + + try + { + var branch = JsonConvert.DeserializeObject(request.downloadHandler.text); + packageData.remoteHead = branch?.commit?.id; + } + catch (Exception e) + { + Debug.LogError($"Branch response parse failed for {packageData.name}: {e.Message}"); + } + } + } + + private static IEnumerator FetchPackageInfoRoutine(RepositoryPackageData packageData) + { + if (string.IsNullOrEmpty(packageData.apiUrl) || string.IsNullOrEmpty(packageData.defaultBranch)) + { + yield break; + } + + var packageUrl = packageData.apiUrl + string.Format(PackageJsonPathTemplate, UnityWebRequest.EscapeURL(packageData.defaultBranch)); + using (var request = CreateWebRequest(packageUrl)) + { + yield return request.SendWebRequest(); + if (!TryValidateRequest(request, $"Package request failed for {packageData.name}")) + { + yield break; + } + + try + { + var contentResponse = JsonConvert.DeserializeObject(request.downloadHandler.text); + var packageJson = DecodeContent(contentResponse); + if (string.IsNullOrEmpty(packageJson)) + { + yield break; + } + + var packageInfo = JsonConvert.DeserializeObject(packageJson); + UpdateWithPackageInfo(packageData, packageInfo); + } + catch (Exception e) + { + Debug.LogError($"Package json parse failed for {packageData.name}: {e.Message}"); + } } } private static UnityWebRequest CreateWebRequest(string url) { - var request = new UnityWebRequest(url, "GET"); + var request = UnityWebRequest.Get(url); request.downloadHandler = new DownloadHandlerBuffer(); + request.timeout = 20; request.SetRequestHeader("accept", "application/json"); return request; } - private static bool HandleRequestError(UnityWebRequest request, string errorMessage) + private static bool TryValidateRequest(UnityWebRequest request, string errorMessage) { - if (request.result != UnityWebRequest.Result.Success) + if (request.result == UnityWebRequest.Result.Success) { - Debug.LogError($"{errorMessage}: {request.error}"); - return false; + return true; } - return true; + Debug.LogError($"{errorMessage}: {request.error}"); + return false; } private static RepositoryPackageData CreateBaseRepositoryData(RepoItem repo) @@ -202,23 +307,51 @@ namespace AlicizaX.PackageManager.Editor description = repo.description, cloneUrl = repo.clone_url, htmlUrl = repo.html_url, - updatedAt = repo.updated_at + updatedAt = repo.updated_at, + defaultBranch = string.IsNullOrEmpty(repo.default_branch) ? "main" : repo.default_branch, + apiUrl = repo.url, + ownerName = repo.owner?.login }; } - private static void UpdateWithPackageInfo(ref RepositoryPackageData data, PackageJsonResponse package) + private static void UpdateWithPackageInfo(RepositoryPackageData data, PackageJsonResponse package) { - if (package == null) return; - data.name = package.name; - data.displayName = package.displayName; + if (package == null) + { + return; + } + + data.name = string.IsNullOrEmpty(package.name) ? data.name : package.name; + data.displayName = string.IsNullOrEmpty(package.displayName) ? data.name : package.displayName; data.version = package.version; data.unityVersion = package.unity; data.category = package.category; data.packageDescription = package.description; + data.authorName = package.author?.name; + } - if (package.author != null) + private static string DecodeContent(PackageJsonContentResponse contentResponse) + { + if (contentResponse == null || string.IsNullOrEmpty(contentResponse.content)) { - data.authorName = package.author.name; + return null; + } + + if (!string.Equals(contentResponse.encoding, "base64", StringComparison.OrdinalIgnoreCase)) + { + return contentResponse.content; + } + + try + { + var normalized = contentResponse.content.Replace("\n", string.Empty).Replace("\r", string.Empty); + var bytes = Convert.FromBase64String(normalized); + return Encoding.UTF8.GetString(bytes); + } + catch (Exception e) + { + Debug.LogError($"Decode package json content failed: {e.Message}"); + return null; } } } diff --git a/Editor/PackageManager/RepositoryDataFetcher.cs.meta b/Editor/PackageManager/RepositoryDataFetcher.cs.meta index f1d6965..7cce1a9 100644 --- a/Editor/PackageManager/RepositoryDataFetcher.cs.meta +++ b/Editor/PackageManager/RepositoryDataFetcher.cs.meta @@ -1,3 +1,11 @@ fileFormatVersion: 2 guid: 311855e386934def95705701938ac3f5 -timeCreated: 1739878350 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PackageManager/UpdateAllPackageHelper.cs b/Editor/PackageManager/UpdateAllPackageHelper.cs index 117334e..3b5fcc2 100644 --- a/Editor/PackageManager/UpdateAllPackageHelper.cs +++ b/Editor/PackageManager/UpdateAllPackageHelper.cs @@ -1,116 +1,144 @@ -using UnityEditor; -using UnityEngine; -using System.IO; using System.Collections.Generic; -using UnityEditor.PackageManager; -using UnityEditor.PackageManager.Requests; -using Newtonsoft.Json.Linq; +using UnityEditor; namespace AlicizaX.PackageManager.Editor { - /// - /// 更新包帮助类 - /// internal static class UpdateAllPackageHelper { - private static AddRequest _addRequest; private static readonly Queue PackagesToUpdate = new Queue(); - private static int _allPackagesCount = 0; - private static int _updatingPackagesIndex = 0; + private static FrameworkPackageManagerWindow s_window; + private static int s_totalCount; + private static int s_currentIndex; - public static void UpdatePackages() + public static void UpdatePackages(FrameworkPackageManagerWindow window) { - var result = EditorUtility.DisplayDialog("更新包提示", "是否更新所有包?\n 更新完成之后可能需要重启Unity", "是", "否"); - if (result) + if (window == null) { - UpdatePackagesFromManifest(FrameworkPackageManagerWindow.Instance.RepositoryPackageDatas); + return; } - } - /// - /// 更新指定包 - /// - /// 包名 - /// 包地址 - public static void UpdatePackages(RepositoryPackageData repositoryPackageData) - { - PackagesToUpdate.Enqueue(repositoryPackageData); - } - - private static void UpdatePackagesFromManifest(List RepositoryPackageDatas) - { - foreach (var package in RepositoryPackageDatas) + if (PackageOperationRunner.IsBusy) { - string packageUrl = package.cloneUrl; - if (packageUrl.EndsWith(".git") && package.PackageState == PackageState.Update) + EditorUtility.DisplayDialog("更新包", "当前有其他包操作正在执行。", "确定"); + return; + } + + var updatablePackages = CollectUpdatablePackages(window.RepositoryPackageDatas); + if (updatablePackages.Count == 0) + { + EditorUtility.DisplayDialog("更新包", "当前没有可更新的包。", "确定"); + return; + } + + var confirm = EditorUtility.DisplayDialog( + "更新包提示", + $"检测到 {updatablePackages.Count} 个包可更新,是否继续?\n更新完成后建议重新打开 Unity。", + "是", + "否"); + + if (!confirm) + { + return; + } + + s_window = window; + PackagesToUpdate.Clear(); + foreach (var package in updatablePackages) + { + PackagesToUpdate.Enqueue(package); + } + + s_totalCount = PackagesToUpdate.Count; + s_currentIndex = 0; + UpdateNextPackage(); + } + + public static void UpdatePackage(FrameworkPackageManagerWindow window, RepositoryPackageData package) + { + if (window == null || package == null) + { + return; + } + + if (PackageOperationRunner.IsBusy) + { + EditorUtility.DisplayDialog("更新包", "当前有其他包操作正在执行。", "确定"); + return; + } + + if (package.PackageState != PackageState.Update) + { + EditorUtility.DisplayDialog("更新包", "当前包没有检测到远端更新。", "确定"); + return; + } + + s_window = window; + PackagesToUpdate.Clear(); + PackagesToUpdate.Enqueue(package); + s_totalCount = 1; + s_currentIndex = 0; + UpdateNextPackage(); + } + + private static List CollectUpdatablePackages(List repositoryPackageDatas) + { + var result = new List(); + if (repositoryPackageDatas == null) + { + return result; + } + + foreach (var package in repositoryPackageDatas) + { + if (package == null || package.PackageState != PackageState.Update) { - UpdatePackages(package); + continue; } + + if (string.IsNullOrEmpty(package.cloneUrl)) + { + continue; + } + + result.Add(package); } - StartUpdate(); - } - - /// - /// 开始更新 - /// - public static void StartUpdate() - { - _allPackagesCount = PackagesToUpdate.Count; - _updatingPackagesIndex = 0; - if (PackagesToUpdate.Count > 0) - { - UpdateNextPackage(); - } - else - { - UnityEngine.Debug.Log("No packages to update."); - } + return result; } private static void UpdateNextPackage() { - if (PackagesToUpdate.Count > 0) - { - _updatingPackagesIndex++; - var repositoryPackageData = PackagesToUpdate.Dequeue(); - _addRequest = Client.Add(repositoryPackageData.cloneUrl); - EditorApplication.update += UpdatingProgressHandler; - var isCancelableProgressBar = EditorUtility.DisplayCancelableProgressBar("正在更新包", $"{_updatingPackagesIndex}/{_allPackagesCount} ({repositoryPackageData.name})", (float)_updatingPackagesIndex / _allPackagesCount); - if (isCancelableProgressBar) - { - EditorUtility.DisplayProgressBar("正在取消更新", "请等待...", 0.5f); - PackagesToUpdate.Clear(); - EditorUtility.ClearProgressBar(); - EditorApplication.update -= UpdatingProgressHandler; - AssetDatabase.Refresh(); - } - } - else + if (PackagesToUpdate.Count == 0) { EditorUtility.ClearProgressBar(); - UnityEngine.Debug.Log("All packages updated."); - AssetDatabase.Refresh(); + s_window?.Refresh(); + return; } - } - private static void UpdatingProgressHandler() - { - if (_addRequest.IsCompleted) + s_currentIndex++; + var package = PackagesToUpdate.Dequeue(); + var canceled = EditorUtility.DisplayCancelableProgressBar( + "正在更新包", + $"{s_currentIndex}/{s_totalCount} {package.name}", + (float)s_currentIndex / s_totalCount); + + if (canceled) { - if (_addRequest.Status == StatusCode.Success) + PackagesToUpdate.Clear(); + EditorUtility.ClearProgressBar(); + return; + } + + PackageManagerCheckTool.UpdatePackage(package, (success, _) => + { + if (!success) { - FrameworkPackageManagerWindow.Instance.RefreshPackage(_addRequest.Result.name); - UnityEngine.Debug.Log($"Updated package: {_addRequest.Result.name}"); - } - else if (_addRequest.Status >= StatusCode.Failure) - { - UnityEngine.Debug.LogError($"Failed to update package: {_addRequest.Error.message}"); + EditorUtility.ClearProgressBar(); + return; } - EditorApplication.update -= UpdatingProgressHandler; UpdateNextPackage(); - } + }); } } } diff --git a/Editor/PackageManager/UpdateAllPackageHelper.cs.meta b/Editor/PackageManager/UpdateAllPackageHelper.cs.meta index b159330..b95051d 100644 --- a/Editor/PackageManager/UpdateAllPackageHelper.cs.meta +++ b/Editor/PackageManager/UpdateAllPackageHelper.cs.meta @@ -1,3 +1,11 @@ -fileFormatVersion: 2 +fileFormatVersion: 2 guid: f5dac61f7c2046f1a7dcd221b36c67cb -timeCreated: 1740027829 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs b/Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs index ba6def3..be0a690 100644 --- a/Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs +++ b/Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs @@ -1,6 +1,5 @@ -using System; +using System; using System.Collections.Generic; -using System.IO; using System.Linq; using UnityEditor; using UnityEngine; @@ -12,90 +11,142 @@ namespace AlicizaX.PackageManager.Editor internal class FrameworkPackageManagerWindow : EditorWindow { private const string VisualAssetPath = "Packages/com.alicizax.unity.packagemanager/Editor/PackageManager/Res/AlicizaXPackageManagerWindow.uxml"; - public List RepositoryPackageDatas = new(); + public static FrameworkPackageManagerWindow Instance; + public List RepositoryPackageDatas { get; private set; } = new List(); + + private PackageListView packageListView; + private PackageInfoView packageInfoView; + private PackageBottomStateBar bottomStateBar; + private bool isRefreshing; + public FrameworkPackageManagerWindow() { Instance = this; } - public void RefreshPackage(string packageName) - { - var package = RepositoryPackageDatas.Find(t => t.name.Equals(packageName)); - if (package != null) - { - packageListView.itemsSource = RepositoryPackageDatas; - packageListView.Rebuild(); - } - } - - private void RefreshPackageData(Action callBack) - { - RepositoryDataFetcher.FetchRepositoryData((datas) => - { - PackageManagerCheckTool.ValidatePackageStates(datas); - RepositoryPackageDatas = datas; - callBack?.Invoke(); - }); - } - - [MenuItem("Tools/AlicizaX/PackageManager")] + [MenuItem("AlicizaX/PackageManager")] internal static void Open() { GetWindow().Show(); } + public void Refresh() + { + if (isRefreshing) + { + return; + } + + isRefreshing = true; + SetRefreshState("Refreshing packages..."); + + RepositoryDataFetcher.FetchRepositoryData(datas => + { + RepositoryPackageDatas = datas ?? new List(); + PackageManagerCheckTool.RefreshInstalledPackages(_ => + { + PackageManagerCheckTool.ValidatePackageStates(RepositoryPackageDatas); + RebuildList(); + isRefreshing = false; + SetRefreshState($"Last refresh: {RepositoryDataFetcher.GetLastRefreshTime()}"); + }); + }); + } + + public void RefreshPackage(string packageName) + { + if (string.IsNullOrEmpty(packageName)) + { + return; + } + + var package = RepositoryPackageDatas.FirstOrDefault(t => string.Equals(t.name, packageName, StringComparison.OrdinalIgnoreCase)); + if (package == null) + { + return; + } + + PackageManagerCheckTool.ValidatePackageStates(new List { package }); + + RebuildList(); + if (packageListView.selectedItem == package) + { + packageInfoView.RefreshView(package); + } + } + + public bool IsBusy() + { + return isRefreshing || PackageOperationRunner.IsBusy; + } + private void OnDestroy() { + if (Instance == this) + { + Instance = null; + } + PackageManagerCoroutines.DestroyCoroutines(); } private void CreateGUI() { - VisualTreeAsset visualTree = AssetDatabase.LoadAssetAtPath(VisualAssetPath); - visualTree.CloneTree(rootVisualElement); - CreateLeftList(); + var visualTree = AssetDatabase.LoadAssetAtPath(VisualAssetPath); + visualTree?.CloneTree(rootVisualElement); + CreateLayout(); + Refresh(); } - private void Refresh() - { - RefreshPackageData(() => - { - packageListView.itemsSource = RepositoryPackageDatas; - packageListView.Rebuild(); - }); - } - - private PackageListView packageListView; - private PackageInfoView packageInfoView; - - private void CreateLeftList() + private void CreateLayout() { var leftContainer = rootVisualElement.Q("LeftContainer"); leftContainer.style.backgroundColor = new Color(0.1686275f, 0.1606275f, 0.1686275f); packageListView = new PackageListView(); + packageListView.OnSelectionChangedEvent += OnPackageSelectionChanged; leftContainer.Add(packageListView); - packageListView.OnSelectionChangedEvent += PackageListViewOnOnSelectionChangedEvent; - - leftContainer.Add(new PackageBottomStateBar()); + bottomStateBar = new PackageBottomStateBar(this); + leftContainer.Add(bottomStateBar); var rightContainer = rootVisualElement.Q("RightContainer"); - - packageInfoView = new PackageInfoView(); + packageInfoView = new PackageInfoView(this); rightContainer.Add(packageInfoView); - packageListView.itemsSource = RepositoryPackageDatas; - - Refresh(); } - private void PackageListViewOnOnSelectionChangedEvent(IEnumerable obj) + private void RebuildList() { - packageInfoView.RefreshView(obj.First()); + packageListView.itemsSource = RepositoryPackageDatas; + packageListView.Rebuild(); + + if (RepositoryPackageDatas.Count > 0 && packageListView.selectedIndex < 0) + { + packageListView.SetSelection(0); + packageInfoView.RefreshView(RepositoryPackageDatas[0]); + } + else if (packageListView.selectedItem is RepositoryPackageData selected) + { + packageInfoView.RefreshView(selected); + } + else + { + packageInfoView.RefreshView(null); + } + } + + private void OnPackageSelectionChanged(IEnumerable selectedItems) + { + packageInfoView.RefreshView(selectedItems.FirstOrDefault()); + } + + private void SetRefreshState(string text) + { + bottomStateBar?.SetStateText(text); } } } diff --git a/Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs.meta b/Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs.meta index 5e3b856..5f3e6a1 100644 --- a/Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs.meta +++ b/Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs.meta @@ -1,3 +1,11 @@ fileFormatVersion: 2 guid: 29f884ddb4ba425fbb082406fe4da3cf -timeCreated: 1739944505 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PackageManager/Window/PackageBottomStateBar.cs b/Editor/PackageManager/Window/PackageBottomStateBar.cs index 2c39daf..a250f7b 100644 --- a/Editor/PackageManager/Window/PackageBottomStateBar.cs +++ b/Editor/PackageManager/Window/PackageBottomStateBar.cs @@ -1,4 +1,4 @@ -using UnityEditor; +using UnityEditor; using UnityEngine; using UnityEngine.UIElements; @@ -6,40 +6,82 @@ namespace AlicizaX.PackageManager.Editor { internal class PackageBottomStateBar : VisualElement { - private readonly Label stateLabel; - private readonly Button btnRefresh; + private readonly FrameworkPackageManagerWindow window; + private readonly Button refreshButton; + private readonly Button updateAllButton; + private readonly VisualElement spacer; - public PackageBottomStateBar() + public PackageBottomStateBar(FrameworkPackageManagerWindow window) { + this.window = window; + style.flexDirection = FlexDirection.Row; style.flexShrink = 1; - style.justifyContent = Justify.FlexStart; + style.justifyContent = Justify.FlexEnd; + style.alignItems = Align.Center; style.height = Length.Percent(5); style.backgroundColor = new Color(0.2509804f, 0.2509804f, 0.2509804f); style.borderTopWidth = 1; + style.paddingLeft = 4; + style.paddingRight = 4; - stateLabel = new Label(); - stateLabel.name = "stateLabel"; + spacer = new VisualElement(); + spacer.style.flexGrow = 1; + Add(spacer); - stateLabel.text = EditorPrefs.GetString("PackageUpdateDate", ""); - stateLabel.style.alignSelf = Align.Center; - stateLabel.style.marginLeft = new Length(2, LengthUnit.Pixel); - stateLabel.style.flexGrow = 1; // 让 Label 占据剩余空间 - Add(stateLabel); + refreshButton = CreateIconButton("btnRefresh", "d_Refresh", "Refresh", OnRefreshClicked); + Add(refreshButton); - btnRefresh = new Button(OnBtnRefreshClick); - btnRefresh.name = "btnRefresh"; - btnRefresh.style.maxHeight = 30; - - Image icon = new Image(); - icon.image = EditorGUIUtility.IconContent("d_Refresh").image; - btnRefresh.Add(icon); - Add(btnRefresh); + updateAllButton = CreateIconButton("btnUpdateAll", "Update-Available", "Update All", OnUpdateAllClicked); + updateAllButton.style.marginLeft = 6; + Add(updateAllButton); } - private void OnBtnRefreshClick() + public void SetStateText(string text) { - UpdateAllPackageHelper.UpdatePackages(); + } + + private static Button CreateIconButton(string name, string iconName, string tooltip, System.Action clickAction) + { + var button = new Button(clickAction) + { + name = name, + tooltip = tooltip + }; + + button.style.width = 26; + button.style.height = 22; + button.style.paddingLeft = 0; + button.style.paddingRight = 0; + button.style.paddingTop = 0; + button.style.paddingBottom = 0; + button.style.unityTextAlign = TextAnchor.MiddleCenter; + + var icon = new Image + { + image = EditorGUIUtility.IconContent(iconName).image + }; + icon.style.width = 16; + icon.style.height = 16; + icon.style.alignSelf = Align.Center; + button.Add(icon); + + return button; + } + + private void OnRefreshClicked() + { + if (window.IsBusy()) + { + return; + } + + window.Refresh(); + } + + private void OnUpdateAllClicked() + { + UpdateAllPackageHelper.UpdatePackages(window); } } } diff --git a/Editor/PackageManager/Window/PackageBottomStateBar.cs.meta b/Editor/PackageManager/Window/PackageBottomStateBar.cs.meta index 4cab78d..77d8c6b 100644 --- a/Editor/PackageManager/Window/PackageBottomStateBar.cs.meta +++ b/Editor/PackageManager/Window/PackageBottomStateBar.cs.meta @@ -1,3 +1,11 @@ -fileFormatVersion: 2 +fileFormatVersion: 2 guid: ef4c9b64d076487cae6a6e58ab137fb4 -timeCreated: 1739950003 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PackageManager/Window/PackageInfoListItem.cs b/Editor/PackageManager/Window/PackageInfoListItem.cs index 36cc25f..6437869 100644 --- a/Editor/PackageManager/Window/PackageInfoListItem.cs +++ b/Editor/PackageManager/Window/PackageInfoListItem.cs @@ -25,7 +25,7 @@ namespace AlicizaX.PackageManager.Editor public void RefreshDataSource(RepositoryPackageData packageData) { _repositoryPackageData = packageData; - m_NameLabel.text = packageData.displayName; + m_NameLabel.text = string.IsNullOrEmpty(packageData.displayName) ? packageData.name : packageData.displayName; if (packageData.PackageState == PackageState.Update) { @@ -40,7 +40,18 @@ namespace AlicizaX.PackageManager.Editor m_StateIcon.image = EditorGUIUtility.IconContent("d_CreateAddNew").image; } - m_VersionLabel.text = packageData.PackageState == PackageState.InstallLocal ? "[Local]" : packageData.version; + if (packageData.PackageState == PackageState.InstallLocal) + { + m_VersionLabel.text = "[Local]"; + } + else if (packageData.PackageState == PackageState.Update) + { + m_VersionLabel.text = $"{packageData.version} -> {ShortHash(packageData.remoteHead)}"; + } + else + { + m_VersionLabel.text = packageData.version; + } } private void BuildMainItem() @@ -112,5 +123,15 @@ namespace AlicizaX.PackageManager.Editor : Color.clear; } } + + private static string ShortHash(string hash) + { + if (string.IsNullOrEmpty(hash)) + { + return "HEAD"; + } + + return hash.Length <= 6 ? hash : hash.Substring(0, 6); + } } } diff --git a/Editor/PackageManager/Window/PackageInfoView.cs b/Editor/PackageManager/Window/PackageInfoView.cs index db281eb..a41aac7 100644 --- a/Editor/PackageManager/Window/PackageInfoView.cs +++ b/Editor/PackageManager/Window/PackageInfoView.cs @@ -1,208 +1,199 @@ -using UnityEngine; +using UnityEditor; +using UnityEngine; using UnityEngine.UIElements; namespace AlicizaX.PackageManager.Editor { internal class PackageInfoView : VisualElement { - private VisualElement _mainContainer; - private VisualElement _headContainer; - private Label _displayName; - private Label _versionWithDate; - private Label _authorLabel; - private Label _packageNameLabel; - private Label _projectDescription; - private Label _btnLinkHtml; + private readonly FrameworkPackageManagerWindow window; + private VisualElement mainContainer; + private Label displayNameLabel; + private Label versionLabel; + private Label authorLabel; + private Label packageNameLabel; + private Label descriptionLabel; + private Label sourceLinkLabel; + private Label installedStateLabel; - private VisualElement packageBtnContainer; + private Button updateButton; + private Button installButton; + private Button removeButton; + private RepositoryPackageData selectedPackage; - private Button _btnUpdate; - private Button _btnInstall; - private Button _btnRemove; - - - private RepositoryPackageData _selectRepositoryPackageData; - - public PackageInfoView() + public PackageInfoView(FrameworkPackageManagerWindow window) { + this.window = window; + style.flexShrink = 1; style.height = Length.Percent(100); - _mainContainer = new VisualElement() - { - name = "MainContainer" - }; - _mainContainer.style.flexShrink = 1; - _mainContainer.style.height = Length.Percent(100); + mainContainer = new VisualElement { name = "MainContainer" }; + mainContainer.style.flexShrink = 1; + mainContainer.style.height = Length.Percent(100); + mainContainer.style.paddingLeft = 10; + mainContainer.style.paddingTop = 10; + displayNameLabel = CreateLabel("DisplayName", 19, FontStyle.Bold); + versionLabel = CreateLabel("VersionWithDate", 13, FontStyle.Bold); + authorLabel = CreateLabel("AuthorLabel", 12, FontStyle.Italic); + packageNameLabel = CreateLabel("PackageName", 12, FontStyle.Italic); + installedStateLabel = CreateLabel("InstalledState", 12, FontStyle.Normal); + descriptionLabel = CreateLabel("ProjectDescription", 12, FontStyle.Normal); - _headContainer = new VisualElement() - { - name = "HeadContainer" - }; - _headContainer.style.flexShrink = 1; - _headContainer.style.height = Length.Percent(35); - _headContainer.style.paddingLeft = 10; - _headContainer.style.paddingTop = 10; + sourceLinkLabel = CreateLabel("BtnLink", 12, FontStyle.Normal); + sourceLinkLabel.text = "Source Code"; + sourceLinkLabel.style.color = new Color(0.2980392f, 0.498f, 1f); + sourceLinkLabel.RegisterCallback(OnSourceClick); - _mainContainer.Add(_headContainer); + var buttonContainer = new VisualElement { name = "PackageBtnContainer" }; + buttonContainer.style.flexDirection = FlexDirection.Row; + buttonContainer.style.paddingTop = 10; + installButton = CreateButton("BtnInstall", "Install", OnInstallClick); + updateButton = CreateButton("BtnUpdate", "Update", OnUpdateClick); + removeButton = CreateButton("BtnRemove", "Remove", OnRemoveClick); - _displayName = new Label() - { - name = "DisplayName" - }; - _displayName.style.fontSize = 19; - _displayName.style.unityFontStyleAndWeight = FontStyle.Bold; + buttonContainer.Add(installButton); + buttonContainer.Add(updateButton); + buttonContainer.Add(removeButton); - _versionWithDate = new Label() - { - name = "VersionWithDate" - }; - _versionWithDate.style.fontSize = 13; - _versionWithDate.style.unityFontStyleAndWeight = FontStyle.Bold; - _versionWithDate.style.paddingTop = 5; + mainContainer.Add(displayNameLabel); + mainContainer.Add(versionLabel); + mainContainer.Add(authorLabel); + mainContainer.Add(packageNameLabel); + mainContainer.Add(installedStateLabel); + mainContainer.Add(descriptionLabel); + mainContainer.Add(sourceLinkLabel); + mainContainer.Add(buttonContainer); - - _authorLabel = new Label() - { - name = "AuthorLabel" - }; - _authorLabel.style.fontSize = 12; - _authorLabel.style.paddingTop = 5; - _authorLabel.style.unityFontStyleAndWeight = FontStyle.Italic; - - _packageNameLabel = new Label() - { - name = "PackageName" - }; - _packageNameLabel.style.fontSize = 12; - _packageNameLabel.style.paddingTop = 5; - _packageNameLabel.style.unityFontStyleAndWeight = FontStyle.Italic; - - _projectDescription = new Label() - { - name = "ProjectDescription" - }; - _projectDescription.style.fontSize = 12; - _projectDescription.style.paddingTop = 5; - - - _btnLinkHtml = new Label() - { - name = "BtnLink" - }; - - _btnLinkHtml.text = "Source Code"; - _btnLinkHtml.style.fontSize = 12; - _btnLinkHtml.style.paddingTop = 5; - _btnLinkHtml.style.marginLeft = 0; - _btnLinkHtml.style.paddingLeft = 0; - _btnLinkHtml.style.flexGrow = 0; - _btnLinkHtml.style.minWidth = new Length(20, LengthUnit.Pixel); - _btnLinkHtml.style.maxWidth = new Length(300, LengthUnit.Pixel); - _btnLinkHtml.style.color = new Color(0.2980392f, 0.498f, 1f); - _btnLinkHtml.style.backgroundColor = Color.clear; - _btnLinkHtml.RegisterCallback(OnBtnLinkClick); - - packageBtnContainer = new VisualElement() - { - name = "PackageBtnContainer" - }; - - packageBtnContainer.style.flexShrink = 1; - packageBtnContainer.style.height = Length.Percent(100); - packageBtnContainer.style.flexDirection = FlexDirection.Row; - packageBtnContainer.style.paddingTop = 10; - - - _headContainer.Add(_displayName); - _headContainer.Add(_versionWithDate); - _headContainer.Add(_authorLabel); - _headContainer.Add(_packageNameLabel); - _headContainer.Add(_projectDescription); - _headContainer.Add(_btnLinkHtml); - _headContainer.Add(packageBtnContainer); - - _btnUpdate = new Button(OnBtnUpdate) - { - name = "BtnUpdate" - }; - _btnUpdate.text = "Update"; - _btnUpdate.style.width = new Length(90, LengthUnit.Pixel); - _btnUpdate.style.height = new Length(24, LengthUnit.Pixel); - _btnUpdate.style.marginLeft = 0; - - _btnInstall = new Button(OnBtnInstall) - { - name = "BtnInstall" - }; - _btnInstall.text = "Install"; - _btnInstall.style.width = new Length(90, LengthUnit.Pixel); - _btnInstall.style.height = new Length(24, LengthUnit.Pixel); - _btnInstall.style.marginLeft = 0; - - _btnRemove = new Button(OnBtnRemove) - { - name = "BtnRemove" - }; - _btnRemove.text = "Remove"; - _btnRemove.style.width = new Length(90, LengthUnit.Pixel); - _btnRemove.style.height = new Length(24, LengthUnit.Pixel); - _btnRemove.style.marginLeft = 0; - - - packageBtnContainer.Add(_btnInstall); - packageBtnContainer.Add(_btnUpdate); - packageBtnContainer.Add(_btnRemove); - - Add(_mainContainer); - RefreshView(_selectRepositoryPackageData); + Add(mainContainer); + RefreshView(null); } public void RefreshView(RepositoryPackageData repositoryPackageData) { + selectedPackage = repositoryPackageData; if (repositoryPackageData == null) { - _mainContainer.visible = false; + mainContainer.visible = false; return; } - _selectRepositoryPackageData = repositoryPackageData; + mainContainer.visible = true; + displayNameLabel.text = string.IsNullOrEmpty(repositoryPackageData.displayName) + ? repositoryPackageData.name + : repositoryPackageData.displayName; + versionLabel.text = $"Remote: {repositoryPackageData.version} HEAD: {ShortHash(repositoryPackageData.remoteHead)}"; + authorLabel.text = $"Author: {repositoryPackageData.authorName}"; + packageNameLabel.text = repositoryPackageData.name; + installedStateLabel.text = BuildInstalledState(repositoryPackageData); + descriptionLabel.text = string.IsNullOrEmpty(repositoryPackageData.packageDescription) + ? "Description: N/A" + : repositoryPackageData.packageDescription; - _mainContainer.visible = true; - _displayName.text = repositoryPackageData.displayName; - _versionWithDate.text = $"{repositoryPackageData.version}· {repositoryPackageData.updatedAt}"; - _authorLabel.text = $"Author: {repositoryPackageData.authorName}"; - _packageNameLabel.text = repositoryPackageData.name; - _projectDescription.text = string.IsNullOrEmpty(repositoryPackageData.description) ? "Description:TODO" : repositoryPackageData.description; - - - _btnInstall.style.display = repositoryPackageData.PackageState == PackageState.UnInstall ? DisplayStyle.Flex : DisplayStyle.None; - _btnUpdate.style.display = repositoryPackageData.PackageState == PackageState.Update ? DisplayStyle.Flex : DisplayStyle.None; - _btnRemove.style.display = repositoryPackageData.PackageState == PackageState.Update || repositoryPackageData.PackageState == PackageState.Install ? DisplayStyle.Flex : DisplayStyle.None; + installButton.style.display = repositoryPackageData.PackageState == PackageState.UnInstall ? DisplayStyle.Flex : DisplayStyle.None; + updateButton.style.display = repositoryPackageData.PackageState == PackageState.Update ? DisplayStyle.Flex : DisplayStyle.None; + removeButton.style.display = repositoryPackageData.PackageState == PackageState.Install || repositoryPackageData.PackageState == PackageState.Update + ? DisplayStyle.Flex + : DisplayStyle.None; } - private void OnBtnLinkClick(ClickEvent e) + private void OnSourceClick(ClickEvent evt) { - Application.OpenURL(_selectRepositoryPackageData.htmlUrl); + if (selectedPackage == null || string.IsNullOrEmpty(selectedPackage.htmlUrl)) + { + return; + } + + Application.OpenURL(selectedPackage.htmlUrl); } - private void OnBtnUpdate() + private void OnInstallClick() { - PackageManagerCheckTool.InstallPackage(_selectRepositoryPackageData.cloneUrl); + if (selectedPackage == null) + { + return; + } + + PackageManagerCheckTool.InstallPackage(selectedPackage.cloneUrl, (success, _) => + { + if (success) + { + window.Refresh(); + } + }); } - private void OnBtnInstall() + private void OnUpdateClick() { - PackageManagerCheckTool.InstallPackage(_selectRepositoryPackageData.cloneUrl); + if (selectedPackage == null) + { + return; + } + + UpdateAllPackageHelper.UpdatePackage(window, selectedPackage); } - private void OnBtnRemove() + private void OnRemoveClick() { - PackageManagerCheckTool.UninstallPackage(_selectRepositoryPackageData.name); + if (selectedPackage == null) + { + return; + } + + var confirm = EditorUtility.DisplayDialog("Remove Package", $"Remove {selectedPackage.name}?", "Yes", "No"); + if (!confirm) + { + return; + } + + PackageManagerCheckTool.UninstallPackage(selectedPackage.name, (success, _) => + { + if (success) + { + window.Refresh(); + } + }); + } + + private static Label CreateLabel(string name, int fontSize, FontStyle fontStyle) + { + var label = new Label { name = name }; + label.style.fontSize = fontSize; + label.style.unityFontStyleAndWeight = fontStyle; + label.style.paddingTop = 5; + return label; + } + + private static Button CreateButton(string name, string text, System.Action clickAction) + { + var button = new Button(clickAction) { name = name, text = text }; + button.style.width = 90; + button.style.height = 24; + button.style.marginLeft = 0; + button.style.marginRight = 4; + return button; + } + + private static string BuildInstalledState(RepositoryPackageData package) + { + var installedVersion = string.IsNullOrEmpty(package.InstalledVersion) ? "N/A" : package.InstalledVersion; + var installedHash = string.IsNullOrEmpty(package.InstalledHash) ? "N/A" : ShortHash(package.InstalledHash); + var source = string.IsNullOrEmpty(package.InstalledSource) ? "None" : package.InstalledSource; + return $"Installed: {installedVersion} Source: {source} Local HEAD: {installedHash}"; + } + + private static string ShortHash(string hash) + { + if (string.IsNullOrEmpty(hash)) + { + return "N/A"; + } + + return hash.Length <= 8 ? hash : hash.Substring(0, 8); } } } diff --git a/Editor/PackageManager/Window/PackageInfoView.cs.meta b/Editor/PackageManager/Window/PackageInfoView.cs.meta index a291bae..9236b58 100644 --- a/Editor/PackageManager/Window/PackageInfoView.cs.meta +++ b/Editor/PackageManager/Window/PackageInfoView.cs.meta @@ -1,3 +1,11 @@ -fileFormatVersion: 2 +fileFormatVersion: 2 guid: 76b5dc77258547288eeaed68ca86a596 -timeCreated: 1739957408 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: