update
This commit is contained in:
parent
9fb89cde6f
commit
ff93b80408
@ -1,136 +1,318 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace AlicizaX.PackageManager.Editor
|
namespace AlicizaX.PackageManager.Editor
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using UnityEngine;
|
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.PackageManager;
|
using UnityEditor.PackageManager;
|
||||||
using UnityEditor.PackageManager.Requests;
|
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
|
public static class PackageManagerCheckTool
|
||||||
{
|
{
|
||||||
private static Dictionary<string, string> cachedManifestDependencies;
|
private const string ManifestPath = "Packages/manifest.json";
|
||||||
private static ListRequest listRequest;
|
private const string PackagesLockPath = "Packages/packages-lock.json";
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, string> InstalledPackageVersions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private static Dictionary<string, string> s_manifestDependencies;
|
||||||
|
private static Dictionary<string, LockDependency> s_lockDependencies;
|
||||||
|
private static ListRequest s_listRequest;
|
||||||
|
private static Action<IReadOnlyDictionary<string, string>> s_listCallback;
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class ManifestDependencies
|
private sealed class ManifestFile
|
||||||
{
|
{
|
||||||
public Dictionary<string, string> dependencies = new Dictionary<string, string>();
|
public Dictionary<string, string> dependencies = new Dictionary<string, string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
private sealed class PackagesLockFile
|
||||||
|
{
|
||||||
|
public Dictionary<string, LockDependency> dependencies = new Dictionary<string, LockDependency>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
private sealed class LockDependency
|
||||||
|
{
|
||||||
|
public string version;
|
||||||
|
public string source;
|
||||||
|
public string hash;
|
||||||
|
}
|
||||||
|
|
||||||
public static void ValidatePackageStates(List<RepositoryPackageData> packages)
|
public static void ValidatePackageStates(List<RepositoryPackageData> packages)
|
||||||
{
|
{
|
||||||
var manifest = ReadManifestFile();
|
if (packages == null)
|
||||||
var installedPackages = GetInstalledPackagesFromPM();
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshLocalCache();
|
||||||
|
|
||||||
foreach (var package in packages)
|
foreach (var package in packages)
|
||||||
{
|
{
|
||||||
// 检查是否在manifest中存在
|
var localPackage = GetLocalPackageInfo(package.name);
|
||||||
if (manifest.dependencies.TryGetValue(package.name, out var gitUrl))
|
ApplyLocalState(package, localPackage);
|
||||||
{
|
|
||||||
// 检查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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FindInPackageCache(string packageName)
|
public static void RefreshInstalledPackages(Action<IReadOnlyDictionary<string, string>> callback)
|
||||||
{
|
{
|
||||||
var cacheRoot = Path.GetFullPath("Library/PackageCache");
|
if (s_listRequest != null && !s_listRequest.IsCompleted)
|
||||||
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<ManifestDependencies>(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<string> GetInstalledPackagesFromPM()
|
|
||||||
{
|
|
||||||
var packages = new List<string>();
|
|
||||||
listRequest = Client.List();
|
|
||||||
|
|
||||||
EditorApplication.CallbackFunction checkProgress = null;
|
|
||||||
checkProgress = () =>
|
|
||||||
{
|
{
|
||||||
if (listRequest.IsCompleted)
|
return;
|
||||||
{
|
}
|
||||||
if (listRequest.Status == StatusCode.Success)
|
|
||||||
{
|
|
||||||
foreach (var package in listRequest.Result)
|
|
||||||
{
|
|
||||||
packages.Add(package.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<bool, string> callback = null)
|
||||||
|
{
|
||||||
|
PackageOperationRunner.RunAdd(packageIdentifier, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpdatePackage(RepositoryPackageData package, Action<bool, string> 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<bool, string> 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;
|
if (string.IsNullOrEmpty(packageName))
|
||||||
return packages;
|
{
|
||||||
|
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 bool HasInstalledPackageVersion(string packageName, out string version)
|
||||||
public static string ExtractPackageNameFromUrl(string gitUrl)
|
|
||||||
{
|
{
|
||||||
|
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<string, string> ReadManifestDependencies()
|
||||||
|
{
|
||||||
|
var path = Path.GetFullPath(ManifestPath);
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Uri uri = new Uri(gitUrl);
|
var json = File.ReadAllText(path);
|
||||||
string path = uri.AbsolutePath;
|
var manifest = JsonConvert.DeserializeObject<ManifestFile>(json);
|
||||||
int startIndex = path.LastIndexOf('/') + 1;
|
return manifest?.dependencies != null
|
||||||
string name = path.Substring(startIndex);
|
? new Dictionary<string, string>(manifest.dependencies, StringComparer.OrdinalIgnoreCase)
|
||||||
return name.Replace(".git", "").Replace("_", ".").ToLower();
|
: new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
return Path.GetFileNameWithoutExtension(gitUrl);
|
UnityEngine.Debug.LogError($"Read manifest failed: {e.Message}");
|
||||||
|
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, LockDependency> ReadPackagesLockDependencies()
|
||||||
|
{
|
||||||
|
var path = Path.GetFullPath(PackagesLockPath);
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
return new Dictionary<string, LockDependency>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(path);
|
||||||
|
var packagesLock = JsonConvert.DeserializeObject<PackagesLockFile>(json);
|
||||||
|
return packagesLock?.dependencies != null
|
||||||
|
? new Dictionary<string, LockDependency>(packagesLock.dependencies, StringComparer.OrdinalIgnoreCase)
|
||||||
|
: new Dictionary<string, LockDependency>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Read packages-lock failed: {e.Message}");
|
||||||
|
return new Dictionary<string, LockDependency>(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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,11 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 491930ef63864219a4944a44ed820823
|
guid: 491930ef63864219a4944a44ed820823
|
||||||
timeCreated: 1740016880
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|||||||
97
Editor/PackageManager/PackageOperationRunner.cs
Normal file
97
Editor/PackageManager/PackageOperationRunner.cs
Normal file
@ -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<bool, string> s_callback;
|
||||||
|
private static string s_operationName;
|
||||||
|
|
||||||
|
public static bool IsBusy => s_addRequest != null || s_removeRequest != null;
|
||||||
|
|
||||||
|
public static void RunAdd(string packageIdentifier, Action<bool, string> 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<bool, string> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Editor/PackageManager/PackageOperationRunner.cs.meta
Normal file
11
Editor/PackageManager/PackageOperationRunner.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 049bb98ebc317284ab6077f713e50236
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@ -1,11 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using System.Text;
|
||||||
using UnityEngine.Networking;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Networking;
|
||||||
|
|
||||||
namespace AlicizaX.PackageManager.Editor
|
namespace AlicizaX.PackageManager.Editor
|
||||||
{
|
{
|
||||||
@ -20,20 +20,28 @@ namespace AlicizaX.PackageManager.Editor
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class RepositoryPackageData
|
public class RepositoryPackageData
|
||||||
{
|
{
|
||||||
// Repository info
|
|
||||||
public string name;
|
public string name;
|
||||||
public string description;
|
public string description;
|
||||||
public string cloneUrl;
|
public string cloneUrl;
|
||||||
public string htmlUrl;
|
public string htmlUrl;
|
||||||
public string updatedAt;
|
public string updatedAt;
|
||||||
|
public string defaultBranch;
|
||||||
|
public string apiUrl;
|
||||||
|
public string remoteHead;
|
||||||
|
public string ownerName;
|
||||||
|
|
||||||
// Package info
|
|
||||||
public string displayName;
|
public string displayName;
|
||||||
public string version;
|
public string version;
|
||||||
public string unityVersion;
|
public string unityVersion;
|
||||||
public string category;
|
public string category;
|
||||||
public string packageDescription;
|
public string packageDescription;
|
||||||
public string authorName;
|
public string authorName;
|
||||||
|
|
||||||
|
public string ManifestVersion;
|
||||||
|
public string InstalledVersion;
|
||||||
|
public string InstalledHash;
|
||||||
|
public string InstalledSource;
|
||||||
|
|
||||||
public PackageState PackageState;
|
public PackageState PackageState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,6 +60,8 @@ namespace AlicizaX.PackageManager.Editor
|
|||||||
public string clone_url;
|
public string clone_url;
|
||||||
public string html_url;
|
public string html_url;
|
||||||
public string updated_at;
|
public string updated_at;
|
||||||
|
public string default_branch;
|
||||||
|
public string url;
|
||||||
public Owner owner;
|
public Owner owner;
|
||||||
|
|
||||||
[Serializable]
|
[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]
|
[Serializable]
|
||||||
public class PackageJsonResponse
|
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 PackageManagerCoroutines Coroutines;
|
||||||
|
|
||||||
public static void CreateCoroutines()
|
public static void CreateCoroutines()
|
||||||
{
|
{
|
||||||
if (Coroutines == null)
|
if (Coroutines != null)
|
||||||
{
|
{
|
||||||
Coroutines = new GameObject("Coroutines").AddComponent<PackageManagerCoroutines>();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gameObject = new GameObject("PackageManagerCoroutines");
|
||||||
|
gameObject.hideFlags = HideFlags.HideAndDontSave;
|
||||||
|
Coroutines = gameObject.AddComponent<PackageManagerCoroutines>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DestroyCoroutines()
|
public static void DestroyCoroutines()
|
||||||
{
|
{
|
||||||
if (Coroutines != null)
|
if (Coroutines == null)
|
||||||
{
|
{
|
||||||
Coroutines.StopAllCoroutines();
|
return;
|
||||||
DestroyImmediate(Coroutines.gameObject);
|
|
||||||
Coroutines = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Coroutines.StopAllCoroutines();
|
||||||
|
DestroyImmediate(Coroutines.gameObject);
|
||||||
|
Coroutines = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RepositoryDataFetcher
|
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 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<List<RepositoryPackageData>> callback)
|
public static void FetchRepositoryData(Action<List<RepositoryPackageData>> callback)
|
||||||
{
|
{
|
||||||
PackageManagerCoroutines.CreateCoroutines();
|
PackageManagerCoroutines.CreateCoroutines();
|
||||||
EditorPrefs.SetString("PackageUpdateDate", DateTime.Now.ToString());
|
|
||||||
PackageManagerCoroutines.Coroutines.StartCoroutine(FetchDataRoutine(callback));
|
PackageManagerCoroutines.Coroutines.StartCoroutine(FetchDataRoutine(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetLastRefreshTime()
|
||||||
|
{
|
||||||
|
return EditorPrefs.GetString(LastUpdateEditorPrefsKey, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
private static IEnumerator FetchDataRoutine(Action<List<RepositoryPackageData>> callback)
|
private static IEnumerator FetchDataRoutine(Action<List<RepositoryPackageData>> callback)
|
||||||
{
|
{
|
||||||
// 第一阶段:获取仓库列表
|
List<RepositoryPackageData> results = null;
|
||||||
|
Exception fatalError = null;
|
||||||
|
|
||||||
using (var request = CreateWebRequest(BaseApiUrl))
|
using (var request = CreateWebRequest(BaseApiUrl))
|
||||||
{
|
{
|
||||||
|
|
||||||
yield return request.SendWebRequest();
|
yield return request.SendWebRequest();
|
||||||
if (!HandleRequestError(request, "Repository request failed"))
|
|
||||||
|
if (!TryValidateRequest(request, "Repository request failed"))
|
||||||
{
|
{
|
||||||
callback?.Invoke(null);
|
fatalError = new Exception(request.error);
|
||||||
yield break;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
var repoResponse = JsonConvert.DeserializeObject<RepoApiResponse>(request.downloadHandler.text);
|
|
||||||
if (!repoResponse.ok || repoResponse.data == null)
|
|
||||||
{
|
{
|
||||||
Debug.LogError("Invalid repository response data");
|
RepoApiResponse response = null;
|
||||||
callback?.Invoke(null);
|
try
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var results = new List<RepositoryPackageData>();
|
|
||||||
var pendingRequests = new Queue<RepoItem>(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))
|
|
||||||
{
|
{
|
||||||
yield return packageRequest.SendWebRequest();
|
response = JsonConvert.DeserializeObject<RepoApiResponse>(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<RepositoryPackageData>(response.data.Count);
|
||||||
|
foreach (var repo in response.data)
|
||||||
{
|
{
|
||||||
var packageInfo = JsonConvert.DeserializeObject<PackageJsonResponse>(
|
var data = CreateBaseRepositoryData(repo);
|
||||||
packageRequest.downloadHandler.text);
|
yield return FetchBranchHeadRoutine(data);
|
||||||
|
yield return FetchPackageInfoRoutine(data);
|
||||||
UpdateWithPackageInfo(ref packageData, packageInfo);
|
results.Add(data);
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.LogError($"JSON parse error for {repo.name}: {e.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
results.Add(packageData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
callback?.Invoke(results);
|
if (fatalError != null)
|
||||||
PackageManagerCoroutines.DestroyCoroutines();
|
{
|
||||||
|
Debug.LogError($"Fetch repository data failed: {fatalError.Message}");
|
||||||
|
callback?.Invoke(new List<RepositoryPackageData>());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorPrefs.SetString(LastUpdateEditorPrefsKey, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
callback?.Invoke(results ?? new List<RepositoryPackageData>());
|
||||||
|
}
|
||||||
|
|
||||||
|
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<RepoBranchResponse>(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<PackageJsonContentResponse>(request.downloadHandler.text);
|
||||||
|
var packageJson = DecodeContent(contentResponse);
|
||||||
|
if (string.IsNullOrEmpty(packageJson))
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var packageInfo = JsonConvert.DeserializeObject<PackageJsonResponse>(packageJson);
|
||||||
|
UpdateWithPackageInfo(packageData, packageInfo);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogError($"Package json parse failed for {packageData.name}: {e.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UnityWebRequest CreateWebRequest(string url)
|
private static UnityWebRequest CreateWebRequest(string url)
|
||||||
{
|
{
|
||||||
var request = new UnityWebRequest(url, "GET");
|
var request = UnityWebRequest.Get(url);
|
||||||
request.downloadHandler = new DownloadHandlerBuffer();
|
request.downloadHandler = new DownloadHandlerBuffer();
|
||||||
|
request.timeout = 20;
|
||||||
request.SetRequestHeader("accept", "application/json");
|
request.SetRequestHeader("accept", "application/json");
|
||||||
return request;
|
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 true;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
Debug.LogError($"{errorMessage}: {request.error}");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RepositoryPackageData CreateBaseRepositoryData(RepoItem repo)
|
private static RepositoryPackageData CreateBaseRepositoryData(RepoItem repo)
|
||||||
@ -202,23 +307,51 @@ namespace AlicizaX.PackageManager.Editor
|
|||||||
description = repo.description,
|
description = repo.description,
|
||||||
cloneUrl = repo.clone_url,
|
cloneUrl = repo.clone_url,
|
||||||
htmlUrl = repo.html_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;
|
if (package == null)
|
||||||
data.name = package.name;
|
{
|
||||||
data.displayName = package.displayName;
|
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.version = package.version;
|
||||||
data.unityVersion = package.unity;
|
data.unityVersion = package.unity;
|
||||||
data.category = package.category;
|
data.category = package.category;
|
||||||
data.packageDescription = package.description;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,11 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 311855e386934def95705701938ac3f5
|
guid: 311855e386934def95705701938ac3f5
|
||||||
timeCreated: 1739878350
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|||||||
@ -1,116 +1,144 @@
|
|||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
|
||||||
using System.IO;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEditor.PackageManager;
|
using UnityEditor;
|
||||||
using UnityEditor.PackageManager.Requests;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace AlicizaX.PackageManager.Editor
|
namespace AlicizaX.PackageManager.Editor
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 更新包帮助类
|
|
||||||
/// </summary>
|
|
||||||
internal static class UpdateAllPackageHelper
|
internal static class UpdateAllPackageHelper
|
||||||
{
|
{
|
||||||
private static AddRequest _addRequest;
|
|
||||||
private static readonly Queue<RepositoryPackageData> PackagesToUpdate = new Queue<RepositoryPackageData>();
|
private static readonly Queue<RepositoryPackageData> PackagesToUpdate = new Queue<RepositoryPackageData>();
|
||||||
private static int _allPackagesCount = 0;
|
private static FrameworkPackageManagerWindow s_window;
|
||||||
private static int _updatingPackagesIndex = 0;
|
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 (window == null)
|
||||||
if (result)
|
|
||||||
{
|
{
|
||||||
UpdatePackagesFromManifest(FrameworkPackageManagerWindow.Instance.RepositoryPackageDatas);
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
if (PackageOperationRunner.IsBusy)
|
||||||
/// 更新指定包
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="packageName">包名</param>
|
|
||||||
/// <param name="packageUrl">包地址</param>
|
|
||||||
public static void UpdatePackages(RepositoryPackageData repositoryPackageData)
|
|
||||||
{
|
|
||||||
PackagesToUpdate.Enqueue(repositoryPackageData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void UpdatePackagesFromManifest(List<RepositoryPackageData> RepositoryPackageDatas)
|
|
||||||
{
|
|
||||||
foreach (var package in RepositoryPackageDatas)
|
|
||||||
{
|
{
|
||||||
string packageUrl = package.cloneUrl;
|
EditorUtility.DisplayDialog("更新包", "当前有其他包操作正在执行。", "确定");
|
||||||
if (packageUrl.EndsWith(".git") && package.PackageState == PackageState.Update)
|
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<RepositoryPackageData> CollectUpdatablePackages(List<RepositoryPackageData> repositoryPackageDatas)
|
||||||
|
{
|
||||||
|
var result = new List<RepositoryPackageData>();
|
||||||
|
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();
|
return result;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 开始更新
|
|
||||||
/// </summary>
|
|
||||||
public static void StartUpdate()
|
|
||||||
{
|
|
||||||
_allPackagesCount = PackagesToUpdate.Count;
|
|
||||||
_updatingPackagesIndex = 0;
|
|
||||||
if (PackagesToUpdate.Count > 0)
|
|
||||||
{
|
|
||||||
UpdateNextPackage();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UnityEngine.Debug.Log("No packages to update.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdateNextPackage()
|
private static void UpdateNextPackage()
|
||||||
{
|
{
|
||||||
if (PackagesToUpdate.Count > 0)
|
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
|
|
||||||
{
|
{
|
||||||
EditorUtility.ClearProgressBar();
|
EditorUtility.ClearProgressBar();
|
||||||
UnityEngine.Debug.Log("All packages updated.");
|
s_window?.Refresh();
|
||||||
AssetDatabase.Refresh();
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void UpdatingProgressHandler()
|
s_currentIndex++;
|
||||||
{
|
var package = PackagesToUpdate.Dequeue();
|
||||||
if (_addRequest.IsCompleted)
|
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);
|
EditorUtility.ClearProgressBar();
|
||||||
UnityEngine.Debug.Log($"Updated package: {_addRequest.Result.name}");
|
return;
|
||||||
}
|
|
||||||
else if (_addRequest.Status >= StatusCode.Failure)
|
|
||||||
{
|
|
||||||
UnityEngine.Debug.LogError($"Failed to update package: {_addRequest.Error.message}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EditorApplication.update -= UpdatingProgressHandler;
|
|
||||||
UpdateNextPackage();
|
UpdateNextPackage();
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,11 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: f5dac61f7c2046f1a7dcd221b36c67cb
|
guid: f5dac61f7c2046f1a7dcd221b36c67cb
|
||||||
timeCreated: 1740027829
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -12,90 +11,142 @@ namespace AlicizaX.PackageManager.Editor
|
|||||||
internal class FrameworkPackageManagerWindow : EditorWindow
|
internal class FrameworkPackageManagerWindow : EditorWindow
|
||||||
{
|
{
|
||||||
private const string VisualAssetPath = "Packages/com.alicizax.unity.packagemanager/Editor/PackageManager/Res/AlicizaXPackageManagerWindow.uxml";
|
private const string VisualAssetPath = "Packages/com.alicizax.unity.packagemanager/Editor/PackageManager/Res/AlicizaXPackageManagerWindow.uxml";
|
||||||
public List<RepositoryPackageData> RepositoryPackageDatas = new();
|
|
||||||
public static FrameworkPackageManagerWindow Instance;
|
public static FrameworkPackageManagerWindow Instance;
|
||||||
|
|
||||||
|
public List<RepositoryPackageData> RepositoryPackageDatas { get; private set; } = new List<RepositoryPackageData>();
|
||||||
|
|
||||||
|
private PackageListView packageListView;
|
||||||
|
private PackageInfoView packageInfoView;
|
||||||
|
private PackageBottomStateBar bottomStateBar;
|
||||||
|
private bool isRefreshing;
|
||||||
|
|
||||||
public FrameworkPackageManagerWindow()
|
public FrameworkPackageManagerWindow()
|
||||||
{
|
{
|
||||||
Instance = this;
|
Instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshPackage(string packageName)
|
[MenuItem("AlicizaX/PackageManager")]
|
||||||
{
|
|
||||||
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")]
|
|
||||||
internal static void Open()
|
internal static void Open()
|
||||||
{
|
{
|
||||||
GetWindow<FrameworkPackageManagerWindow>().Show();
|
GetWindow<FrameworkPackageManagerWindow>().Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
if (isRefreshing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isRefreshing = true;
|
||||||
|
SetRefreshState("Refreshing packages...");
|
||||||
|
|
||||||
|
RepositoryDataFetcher.FetchRepositoryData(datas =>
|
||||||
|
{
|
||||||
|
RepositoryPackageDatas = datas ?? new List<RepositoryPackageData>();
|
||||||
|
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<RepositoryPackageData> { package });
|
||||||
|
|
||||||
|
RebuildList();
|
||||||
|
if (packageListView.selectedItem == package)
|
||||||
|
{
|
||||||
|
packageInfoView.RefreshView(package);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsBusy()
|
||||||
|
{
|
||||||
|
return isRefreshing || PackageOperationRunner.IsBusy;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
{
|
{
|
||||||
|
if (Instance == this)
|
||||||
|
{
|
||||||
|
Instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
PackageManagerCoroutines.DestroyCoroutines();
|
PackageManagerCoroutines.DestroyCoroutines();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateGUI()
|
private void CreateGUI()
|
||||||
{
|
{
|
||||||
VisualTreeAsset visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(VisualAssetPath);
|
var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(VisualAssetPath);
|
||||||
visualTree.CloneTree(rootVisualElement);
|
visualTree?.CloneTree(rootVisualElement);
|
||||||
CreateLeftList();
|
CreateLayout();
|
||||||
|
Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Refresh()
|
private void CreateLayout()
|
||||||
{
|
|
||||||
RefreshPackageData(() =>
|
|
||||||
{
|
|
||||||
packageListView.itemsSource = RepositoryPackageDatas;
|
|
||||||
packageListView.Rebuild();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private PackageListView packageListView;
|
|
||||||
private PackageInfoView packageInfoView;
|
|
||||||
|
|
||||||
private void CreateLeftList()
|
|
||||||
{
|
{
|
||||||
var leftContainer = rootVisualElement.Q<VisualElement>("LeftContainer");
|
var leftContainer = rootVisualElement.Q<VisualElement>("LeftContainer");
|
||||||
leftContainer.style.backgroundColor = new Color(0.1686275f, 0.1606275f, 0.1686275f);
|
leftContainer.style.backgroundColor = new Color(0.1686275f, 0.1606275f, 0.1686275f);
|
||||||
|
|
||||||
packageListView = new PackageListView();
|
packageListView = new PackageListView();
|
||||||
|
packageListView.OnSelectionChangedEvent += OnPackageSelectionChanged;
|
||||||
leftContainer.Add(packageListView);
|
leftContainer.Add(packageListView);
|
||||||
packageListView.OnSelectionChangedEvent += PackageListViewOnOnSelectionChangedEvent;
|
|
||||||
|
|
||||||
|
bottomStateBar = new PackageBottomStateBar(this);
|
||||||
leftContainer.Add(new PackageBottomStateBar());
|
leftContainer.Add(bottomStateBar);
|
||||||
|
|
||||||
var rightContainer = rootVisualElement.Q<VisualElement>("RightContainer");
|
var rightContainer = rootVisualElement.Q<VisualElement>("RightContainer");
|
||||||
|
packageInfoView = new PackageInfoView(this);
|
||||||
packageInfoView = new PackageInfoView();
|
|
||||||
rightContainer.Add(packageInfoView);
|
rightContainer.Add(packageInfoView);
|
||||||
|
|
||||||
|
|
||||||
packageListView.itemsSource = RepositoryPackageDatas;
|
packageListView.itemsSource = RepositoryPackageDatas;
|
||||||
|
|
||||||
Refresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PackageListViewOnOnSelectionChangedEvent(IEnumerable<RepositoryPackageData> 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<RepositoryPackageData> selectedItems)
|
||||||
|
{
|
||||||
|
packageInfoView.RefreshView(selectedItems.FirstOrDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetRefreshState(string text)
|
||||||
|
{
|
||||||
|
bottomStateBar?.SetStateText(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,11 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 29f884ddb4ba425fbb082406fe4da3cf
|
guid: 29f884ddb4ba425fbb082406fe4da3cf
|
||||||
timeCreated: 1739944505
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UIElements;
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
@ -6,40 +6,82 @@ namespace AlicizaX.PackageManager.Editor
|
|||||||
{
|
{
|
||||||
internal class PackageBottomStateBar : VisualElement
|
internal class PackageBottomStateBar : VisualElement
|
||||||
{
|
{
|
||||||
private readonly Label stateLabel;
|
private readonly FrameworkPackageManagerWindow window;
|
||||||
private readonly Button btnRefresh;
|
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.flexDirection = FlexDirection.Row;
|
||||||
style.flexShrink = 1;
|
style.flexShrink = 1;
|
||||||
style.justifyContent = Justify.FlexStart;
|
style.justifyContent = Justify.FlexEnd;
|
||||||
|
style.alignItems = Align.Center;
|
||||||
style.height = Length.Percent(5);
|
style.height = Length.Percent(5);
|
||||||
style.backgroundColor = new Color(0.2509804f, 0.2509804f, 0.2509804f);
|
style.backgroundColor = new Color(0.2509804f, 0.2509804f, 0.2509804f);
|
||||||
style.borderTopWidth = 1;
|
style.borderTopWidth = 1;
|
||||||
|
style.paddingLeft = 4;
|
||||||
|
style.paddingRight = 4;
|
||||||
|
|
||||||
stateLabel = new Label();
|
spacer = new VisualElement();
|
||||||
stateLabel.name = "stateLabel";
|
spacer.style.flexGrow = 1;
|
||||||
|
Add(spacer);
|
||||||
|
|
||||||
stateLabel.text = EditorPrefs.GetString("PackageUpdateDate", "");
|
refreshButton = CreateIconButton("btnRefresh", "d_Refresh", "Refresh", OnRefreshClicked);
|
||||||
stateLabel.style.alignSelf = Align.Center;
|
Add(refreshButton);
|
||||||
stateLabel.style.marginLeft = new Length(2, LengthUnit.Pixel);
|
|
||||||
stateLabel.style.flexGrow = 1; // 让 Label 占据剩余空间
|
|
||||||
Add(stateLabel);
|
|
||||||
|
|
||||||
btnRefresh = new Button(OnBtnRefreshClick);
|
updateAllButton = CreateIconButton("btnUpdateAll", "Update-Available", "Update All", OnUpdateAllClicked);
|
||||||
btnRefresh.name = "btnRefresh";
|
updateAllButton.style.marginLeft = 6;
|
||||||
btnRefresh.style.maxHeight = 30;
|
Add(updateAllButton);
|
||||||
|
|
||||||
Image icon = new Image();
|
|
||||||
icon.image = EditorGUIUtility.IconContent("d_Refresh").image;
|
|
||||||
btnRefresh.Add(icon);
|
|
||||||
Add(btnRefresh);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,11 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: ef4c9b64d076487cae6a6e58ab137fb4
|
guid: ef4c9b64d076487cae6a6e58ab137fb4
|
||||||
timeCreated: 1739950003
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|||||||
@ -25,7 +25,7 @@ namespace AlicizaX.PackageManager.Editor
|
|||||||
public void RefreshDataSource(RepositoryPackageData packageData)
|
public void RefreshDataSource(RepositoryPackageData packageData)
|
||||||
{
|
{
|
||||||
_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)
|
if (packageData.PackageState == PackageState.Update)
|
||||||
{
|
{
|
||||||
@ -40,7 +40,18 @@ namespace AlicizaX.PackageManager.Editor
|
|||||||
m_StateIcon.image = EditorGUIUtility.IconContent("d_CreateAddNew").image;
|
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()
|
private void BuildMainItem()
|
||||||
@ -112,5 +123,15 @@ namespace AlicizaX.PackageManager.Editor
|
|||||||
: Color.clear;
|
: Color.clear;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string ShortHash(string hash)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(hash))
|
||||||
|
{
|
||||||
|
return "HEAD";
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash.Length <= 6 ? hash : hash.Substring(0, 6);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,208 +1,199 @@
|
|||||||
using UnityEngine;
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
using UnityEngine.UIElements;
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
namespace AlicizaX.PackageManager.Editor
|
namespace AlicizaX.PackageManager.Editor
|
||||||
{
|
{
|
||||||
internal class PackageInfoView : VisualElement
|
internal class PackageInfoView : VisualElement
|
||||||
{
|
{
|
||||||
private VisualElement _mainContainer;
|
private readonly FrameworkPackageManagerWindow window;
|
||||||
private VisualElement _headContainer;
|
|
||||||
private Label _displayName;
|
|
||||||
private Label _versionWithDate;
|
|
||||||
private Label _authorLabel;
|
|
||||||
private Label _packageNameLabel;
|
|
||||||
private Label _projectDescription;
|
|
||||||
private Label _btnLinkHtml;
|
|
||||||
|
|
||||||
|
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;
|
public PackageInfoView(FrameworkPackageManagerWindow window)
|
||||||
private Button _btnInstall;
|
|
||||||
private Button _btnRemove;
|
|
||||||
|
|
||||||
|
|
||||||
private RepositoryPackageData _selectRepositoryPackageData;
|
|
||||||
|
|
||||||
public PackageInfoView()
|
|
||||||
{
|
{
|
||||||
|
this.window = window;
|
||||||
|
|
||||||
style.flexShrink = 1;
|
style.flexShrink = 1;
|
||||||
style.height = Length.Percent(100);
|
style.height = Length.Percent(100);
|
||||||
|
|
||||||
_mainContainer = new VisualElement()
|
mainContainer = new VisualElement { name = "MainContainer" };
|
||||||
{
|
mainContainer.style.flexShrink = 1;
|
||||||
name = "MainContainer"
|
mainContainer.style.height = Length.Percent(100);
|
||||||
};
|
mainContainer.style.paddingLeft = 10;
|
||||||
_mainContainer.style.flexShrink = 1;
|
mainContainer.style.paddingTop = 10;
|
||||||
_mainContainer.style.height = Length.Percent(100);
|
|
||||||
|
|
||||||
|
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()
|
sourceLinkLabel = CreateLabel("BtnLink", 12, FontStyle.Normal);
|
||||||
{
|
sourceLinkLabel.text = "Source Code";
|
||||||
name = "HeadContainer"
|
sourceLinkLabel.style.color = new Color(0.2980392f, 0.498f, 1f);
|
||||||
};
|
sourceLinkLabel.RegisterCallback<ClickEvent>(OnSourceClick);
|
||||||
_headContainer.style.flexShrink = 1;
|
|
||||||
_headContainer.style.height = Length.Percent(35);
|
|
||||||
_headContainer.style.paddingLeft = 10;
|
|
||||||
_headContainer.style.paddingTop = 10;
|
|
||||||
|
|
||||||
_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()
|
buttonContainer.Add(installButton);
|
||||||
{
|
buttonContainer.Add(updateButton);
|
||||||
name = "DisplayName"
|
buttonContainer.Add(removeButton);
|
||||||
};
|
|
||||||
_displayName.style.fontSize = 19;
|
|
||||||
_displayName.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
||||||
|
|
||||||
_versionWithDate = new Label()
|
mainContainer.Add(displayNameLabel);
|
||||||
{
|
mainContainer.Add(versionLabel);
|
||||||
name = "VersionWithDate"
|
mainContainer.Add(authorLabel);
|
||||||
};
|
mainContainer.Add(packageNameLabel);
|
||||||
_versionWithDate.style.fontSize = 13;
|
mainContainer.Add(installedStateLabel);
|
||||||
_versionWithDate.style.unityFontStyleAndWeight = FontStyle.Bold;
|
mainContainer.Add(descriptionLabel);
|
||||||
_versionWithDate.style.paddingTop = 5;
|
mainContainer.Add(sourceLinkLabel);
|
||||||
|
mainContainer.Add(buttonContainer);
|
||||||
|
|
||||||
|
Add(mainContainer);
|
||||||
_authorLabel = new Label()
|
RefreshView(null);
|
||||||
{
|
|
||||||
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<ClickEvent>(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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshView(RepositoryPackageData repositoryPackageData)
|
public void RefreshView(RepositoryPackageData repositoryPackageData)
|
||||||
{
|
{
|
||||||
|
selectedPackage = repositoryPackageData;
|
||||||
if (repositoryPackageData == null)
|
if (repositoryPackageData == null)
|
||||||
{
|
{
|
||||||
_mainContainer.visible = false;
|
mainContainer.visible = false;
|
||||||
return;
|
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;
|
installButton.style.display = repositoryPackageData.PackageState == PackageState.UnInstall ? DisplayStyle.Flex : DisplayStyle.None;
|
||||||
_displayName.text = repositoryPackageData.displayName;
|
updateButton.style.display = repositoryPackageData.PackageState == PackageState.Update ? DisplayStyle.Flex : DisplayStyle.None;
|
||||||
_versionWithDate.text = $"{repositoryPackageData.version}· {repositoryPackageData.updatedAt}";
|
removeButton.style.display = repositoryPackageData.PackageState == PackageState.Install || repositoryPackageData.PackageState == PackageState.Update
|
||||||
_authorLabel.text = $"Author: {repositoryPackageData.authorName}";
|
? DisplayStyle.Flex
|
||||||
_packageNameLabel.text = repositoryPackageData.name;
|
: DisplayStyle.None;
|
||||||
_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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,11 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 76b5dc77258547288eeaed68ca86a596
|
guid: 76b5dc77258547288eeaed68ca86a596
|
||||||
timeCreated: 1739957408
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user