using System; using System.Globalization; using System.Collections.Generic; using System.Linq; using Semver; using UnityEngine; using UnityEditor.PackageManager.Requests; namespace UnityEditor.PackageManager.UI { internal abstract class UpmBaseOperation : IBaseOperation { public static string GroupName(PackageSource origin) { var group = PackageGroupOrigins.Packages.ToString(); if (origin == PackageSource.BuiltIn) group = PackageGroupOrigins.BuiltInPackages.ToString(); return group; } protected static IEnumerable FromUpmPackageInfo(PackageManager.PackageInfo info, bool isCurrent=true) { var packages = new List(); var displayName = info.displayName; if (string.IsNullOrEmpty(displayName)) { displayName = info.name.Replace("com.unity.modules.", ""); displayName = displayName.Replace("com.unity.", ""); displayName = new CultureInfo("en-US").TextInfo.ToTitleCase(displayName); } string author = info.author.name; if (string.IsNullOrEmpty(info.author.name) && info.name.StartsWith("com.unity.")) author = "Unity Technologies Inc."; var lastCompatible = info.versions.latestCompatible; var versions = new List(); versions.AddRange(info.versions.compatible); if (versions.FindIndex(version => version == info.version) == -1) { versions.Add(info.version); versions.Sort((left, right) => { if (left == null || right == null) return 0; SemVersion leftVersion = left; SemVersion righVersion = right; return leftVersion.CompareByPrecedence(righVersion); }); SemVersion packageVersion = info.version; if (!string.IsNullOrEmpty(lastCompatible)) { SemVersion lastCompatibleVersion = string.IsNullOrEmpty(lastCompatible) ? (SemVersion) null : lastCompatible; if (packageVersion != null && string.IsNullOrEmpty(packageVersion.Prerelease) && packageVersion.CompareByPrecedence(lastCompatibleVersion) > 0) lastCompatible = info.version; } else { if (packageVersion != null && string.IsNullOrEmpty(packageVersion.Prerelease)) lastCompatible = info.version; } } foreach(var version in versions) { var isVersionCurrent = version == info.version && isCurrent; var isBuiltIn = info.source == PackageSource.BuiltIn; var isVerified = string.IsNullOrEmpty(SemVersion.Parse(version).Prerelease) && version == info.versions.recommended; var state = (isBuiltIn || info.version == lastCompatible || !isCurrent ) ? PackageState.UpToDate : PackageState.Outdated; // Happens mostly when using a package that hasn't been in production yet. if (info.versions.all.Length <= 0) state = PackageState.UpToDate; if (info.errors.Length > 0) state = PackageState.Error; var packageInfo = new PackageInfo { Name = info.name, DisplayName = displayName, PackageId = version == info.version ? info.packageId : null, Version = version, Description = info.description, Category = info.category, IsCurrent = isVersionCurrent, IsLatest = version == lastCompatible, IsVerified = isVerified, Errors = info.errors.ToList(), Group = GroupName(info.source), State = state, Origin = isBuiltIn || isVersionCurrent ? info.source : PackageSource.Registry, Author = author, Info = info }; packages.Add(packageInfo); } return packages; } public static event Action OnOperationStart = delegate { }; public event Action OnOperationError = delegate { }; public event Action OnOperationFinalized = delegate { }; public Error ForceError { get; set; } // Allow external component to force an error on the requests (eg: testing) public Error Error { get; protected set; } // Keep last error public bool IsCompleted { get; private set; } protected abstract Request CreateRequest(); [SerializeField] protected Request CurrentRequest; public readonly ThreadedDelay Delay = new ThreadedDelay(); protected abstract void ProcessData(); protected void Start() { Error = null; OnOperationStart(this); Delay.Start(); if (TryForcedError()) return; EditorApplication.update += Progress; } // Common progress code for all classes private void Progress() { if (!Delay.IsDone) return; // Create the request after the delay if (CurrentRequest == null) { CurrentRequest = CreateRequest(); } // Since CurrentRequest's error property is private, we need to simulate // an error instead of just setting it. if (TryForcedError()) return; if (CurrentRequest.IsCompleted) { if (CurrentRequest.Status == StatusCode.Success) OnDone(); else if (CurrentRequest.Status >= StatusCode.Failure) OnError(CurrentRequest.Error); else Debug.LogError("Unsupported progress state " + CurrentRequest.Status); } } private void OnError(Error error) { try { Error = error; var message = "Cannot perform upm operation."; if (error != null) message = "Cannot perform upm operation: " + Error.message + " [" + Error.errorCode + "]"; Debug.LogError(message); OnOperationError(Error); } catch (Exception exception) { Debug.LogError("Package Manager Window had an error while reporting an error in an operation: " + exception); } FinalizeOperation(); } private void OnDone() { try { ProcessData(); } catch (Exception error) { Debug.LogError("Package Manager Window had an error while completing an operation: " + error); } FinalizeOperation(); } private void FinalizeOperation() { EditorApplication.update -= Progress; OnOperationFinalized(); IsCompleted = true; } public void Cancel() { EditorApplication.update -= Progress; OnOperationError = delegate { }; OnOperationFinalized = delegate { }; IsCompleted = true; } private bool TryForcedError() { if (ForceError != null) { OnError(ForceError); return true; } return false; } } }