326 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			326 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using System.Collections.Generic; | ||
|  | using UnityEditor; | ||
|  | using UnityEditor.PackageManager; | ||
|  | using UnityEditor.PackageManager.Requests; | ||
|  | using UnityEngine; | ||
|  | 
 | ||
|  | namespace Unity.Multiplayer.Center | ||
|  | { | ||
|  |     internal static class PackageManagement | ||
|  |     { | ||
|  |         static PackageInstaller s_Installer; | ||
|  |          | ||
|  |         /// <summary> | ||
|  |         /// Opens the package manager window with selected package name and hides error | ||
|  |         /// </summary> | ||
|  |         public static void OpenPackageManager(string packageName) | ||
|  |         { | ||
|  |             try | ||
|  |             { | ||
|  |                 UnityEditor.PackageManager.UI.Window.Open(packageName); | ||
|  |             } | ||
|  |             catch (Exception) | ||
|  |             { | ||
|  |                 // Hide the error in the PackageManager API until the team fixes it | ||
|  |                 // Debug.Log("Error opening Package Manager: " + e.Message); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Checks if the package is a direct dependency of the project | ||
|  |         /// </summary> | ||
|  |         /// <param name="packageId">The package name/id e.g. com.unity.netcode</param> | ||
|  |         /// <returns>True if the package is a direct dependency</returns> | ||
|  |         public static bool IsDirectDependency(string packageId) | ||
|  |         { | ||
|  |             var package = GetInstalledPackage(packageId); | ||
|  |             return package != null && package.isDirectDependency; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Checks if a package is installed. | ||
|  |         /// </summary> | ||
|  |         /// <param name="packageId">The package name, e.g. com.unity.netcode</param> | ||
|  |         /// <returns>True if the package is installed, false otherwise</returns> | ||
|  |         public static bool IsInstalled(string packageId) => GetInstalledPackage(packageId) != null; | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Checks if a package is embedded, linked locally, installed via Git or local Tarball. | ||
|  |         /// </summary> | ||
|  |         /// <param name="packageId">The package name, e.g. com.unity.netcode</param> | ||
|  |         /// <returns>True if the package is linked locally, false otherwise</returns> | ||
|  |         public static bool IsLinkedLocallyOrEmbeddedOrViaGit(string packageId) =>  | ||
|  |             GetInstalledPackage(packageId) is { source: PackageSource.Embedded or PackageSource.Local or PackageSource.Git or PackageSource.LocalTarball }; | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Finds the installed package with the given packageId or returns null. | ||
|  |         /// </summary> | ||
|  |         /// <param name="packageId">The package name/id e.g. com.unity.netcode</param> | ||
|  |         /// <returns>The package info</returns> | ||
|  |         public static UnityEditor.PackageManager.PackageInfo GetInstalledPackage(string packageId) | ||
|  |         { | ||
|  |             return UnityEditor.PackageManager.PackageInfo.FindForPackageName(packageId); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Filters out the packages that are already embedded, linked locally, installed via Git or local Tarball and returns this new list. | ||
|  |         /// </summary> | ||
|  |         /// <param name="installCandidates">A list of package IDs that are candidates for installation.</param> | ||
|  |         /// <returns>A new filtered list of packages.</returns> | ||
|  |         public static IEnumerable<string> RemoveLocallyLinkedOrEmbeddedOrViaGitPackagesFromList(IEnumerable<string> installCandidates) | ||
|  |         { | ||
|  |             var filteredList = new List<string>(); | ||
|  | 
 | ||
|  |             foreach (var packageId in installCandidates) | ||
|  |             { | ||
|  |                 if (!IsLinkedLocallyOrEmbeddedOrViaGit(packageId)) | ||
|  |                 { | ||
|  |                     filteredList.Add(packageId); | ||
|  |                 } | ||
|  |                 else | ||
|  |                 { | ||
|  |                     Debug.Log($"Removing {packageId} from install candidates.\n" + | ||
|  |                         "This package is already embedded, linked locally, installed via Git, or from a local tarball. " + | ||
|  |                         "Please check the Package Manager for more information or to upgrade manually."); | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             return filteredList; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Returns true if any of the given packageIds is installed. | ||
|  |         /// </summary> | ||
|  |         /// <param name="packageIds">List of package is e.g com.unity.netcode</param> | ||
|  |         /// <returns>True if any package is installed, false otherwise</returns> | ||
|  |         public static bool IsAnyPackageInstalled(params string[] packageIds) | ||
|  |         { | ||
|  |             var installedPackages = UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages(); | ||
|  |             var hashset = new HashSet<string>(); | ||
|  | 
 | ||
|  |             foreach (var package in installedPackages) | ||
|  |             { | ||
|  |                 hashset.Add(package.name); | ||
|  |             } | ||
|  | 
 | ||
|  |             foreach (var packageId in packageIds) | ||
|  |             { | ||
|  |                 if (hashset.Contains(packageId)) | ||
|  |                 { | ||
|  |                     return true; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             return false; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Installs a single package and invokes the callback when the package is installed/when the install failed. | ||
|  |         /// </summary> | ||
|  |         /// <param name="packageId">The package name/id e.g. com.unity.netcode</param> | ||
|  |         /// <param name="onInstalled">The callback</param> | ||
|  |         public static void InstallPackage(string packageId, Action<bool> onInstalled = null) | ||
|  |         { | ||
|  |             s_Installer = new PackageInstaller(packageId); | ||
|  |             s_Installer.OnInstalled += onInstalled; | ||
|  |             s_Installer.OnInstalled += _ => s_Installer = null; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Register to an existing installation callback. This has no effect if no installation is ongoing (check | ||
|  |         /// <see cref="IsInstallationFinished"/> to see if that is the case). | ||
|  |         /// </summary> | ||
|  |         /// <param name="onInstalled">The callback</param> | ||
|  |         public static void RegisterToExistingInstaller(Action<bool> onInstalled) | ||
|  |         { | ||
|  |             if (s_Installer != null) | ||
|  |             { | ||
|  |                 s_Installer.OnInstalled += onInstalled; | ||
|  |             } | ||
|  |         } | ||
|  |          | ||
|  |         /// <summary> | ||
|  |         /// Installs several packages and invokes the callback when all packages are installed/when the installation failed. | ||
|  |         /// </summary> | ||
|  |         /// <param name="packageIds">The package names/ids e.g. com.unity.netcode</param> | ||
|  |         /// <param name="onAllInstalled">The callback</param> | ||
|  |         /// <param name="packageIdsToRemove">Optional package name/ids to remove</param> | ||
|  |         public static void InstallPackages(IEnumerable<string> packageIds, Action<bool> onAllInstalled = null, IEnumerable<string> packageIdsToRemove = null) | ||
|  |         { | ||
|  |             s_Installer = new PackageInstaller(RemoveLocallyLinkedOrEmbeddedOrViaGitPackagesFromList(packageIds), packageIdsToRemove); | ||
|  |             s_Installer.OnInstalled += onAllInstalled; | ||
|  |             s_Installer.OnInstalled += _ => s_Installer = null; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Create a dictionary with package names as keys and versions as values     | ||
|  |         /// </summary> | ||
|  |         /// <returns>The mapping (package id, installed version) </returns> | ||
|  |         internal static Dictionary<string, string> InstalledPackageDictionary() | ||
|  |         { | ||
|  |             var installedPackages = UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages(); | ||
|  |             var installedPackageDictionary = new Dictionary<string, string>(); | ||
|  | 
 | ||
|  |             foreach (var package in installedPackages) | ||
|  |             { | ||
|  |                 var splitPackageId = package.packageId.Split('@'); | ||
|  |                 if (splitPackageId.Length == 2) | ||
|  |                 { | ||
|  |                     installedPackageDictionary[splitPackageId[0]] = splitPackageId[1]; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             return installedPackageDictionary; | ||
|  |         } | ||
|  |          | ||
|  |         internal class VersionChecker | ||
|  |         { | ||
|  |             SearchRequest m_Request; | ||
|  |             public VersionChecker(string packageID) | ||
|  |             { | ||
|  |                 m_Request = Client.Search(packageID, false); | ||
|  |                 EditorApplication.update += Progress; | ||
|  |             } | ||
|  | 
 | ||
|  |             public event Action<UnityEditor.PackageManager.PackageInfo> OnVersionFound; | ||
|  | 
 | ||
|  |             void Progress() | ||
|  |             { | ||
|  |                 if (!m_Request.IsCompleted) return; | ||
|  |              | ||
|  |                 EditorApplication.update -= Progress; | ||
|  |                 var foundPackage = m_Request.Result; | ||
|  |                 foreach (var packageInfo in foundPackage) | ||
|  |                 { | ||
|  |                     OnVersionFound?.Invoke(packageInfo); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         class PackageInstaller | ||
|  |         { | ||
|  |             Request m_Request; | ||
|  |             string[] m_PackagesToAddIds; | ||
|  |             public event Action<bool> OnInstalled; | ||
|  | 
 | ||
|  |             public PackageInstaller(string packageId) | ||
|  |             { | ||
|  |                 // Add a package to the project | ||
|  |                 m_Request = Client.Add(packageId); | ||
|  |                 m_PackagesToAddIds = new[] {packageId}; | ||
|  |                 EditorApplication.update += Progress; | ||
|  |             } | ||
|  | 
 | ||
|  |             public PackageInstaller(IEnumerable<string> packageIds, IEnumerable<string> packageIdsToRemove = null) | ||
|  |             { | ||
|  |                 var packageIdsList = new List<string>(); | ||
|  |                 foreach (var id in packageIds) | ||
|  |                 { | ||
|  |                     packageIdsList.Add(id); | ||
|  |                 } | ||
|  |                  | ||
|  |                 var packageIdsArray = packageIdsList.ToArray(); | ||
|  |                  | ||
|  |                 string[] packageIdsToRemoveArray = null; | ||
|  |                 if (packageIdsToRemove != null) | ||
|  |                 { | ||
|  |                     var packageIdsToRemoveList = new List<string>(); | ||
|  |                     foreach (var id in packageIdsToRemove) | ||
|  |                     { | ||
|  |                         packageIdsToRemoveList.Add(id); | ||
|  |                     } | ||
|  |                     packageIdsToRemoveArray = packageIdsToRemoveList.ToArray(); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 // Add a package to the project | ||
|  |                 m_Request = Client.AddAndRemove(packageIdsArray, packageIdsToRemoveArray); | ||
|  |                 m_PackagesToAddIds = packageIdsArray; | ||
|  |                 EditorApplication.update += Progress; | ||
|  |             } | ||
|  | 
 | ||
|  |             public bool IsCompleted() | ||
|  |             { | ||
|  |                 return m_Request == null || m_Request.IsCompleted; | ||
|  |             } | ||
|  |              | ||
|  |             void Progress() | ||
|  |             { | ||
|  |                 if (!m_Request.IsCompleted) return; | ||
|  | 
 | ||
|  |                 EditorApplication.update -= Progress; | ||
|  |                 if (m_Request.Status == StatusCode.Success) | ||
|  |                 { | ||
|  |                     Debug.Log("Installed: " + GetInstalledPackageId()); | ||
|  |                 } | ||
|  |                 else if (m_Request.Status >= StatusCode.Failure) | ||
|  |                 { | ||
|  |                     // if the request has more than one package, it will only prompt error message for one   | ||
|  |                     // We should prompt all the failed packages | ||
|  |                     Debug.Log("Package installation request with selected packages: " + String.Join(", ", m_PackagesToAddIds) + | ||
|  |                         " failed. \n Reason: "+ m_Request.Error.message); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 OnInstalled?.Invoke(m_Request.Status == StatusCode.Success); | ||
|  |             } | ||
|  | 
 | ||
|  |             string GetInstalledPackageId() | ||
|  |             { | ||
|  |                 switch (m_Request) | ||
|  |                 { | ||
|  |                     case AddRequest addRequest: | ||
|  |                         return addRequest.Result.packageId; | ||
|  |                     case AddAndRemoveRequest addAndRemoveRequest: | ||
|  |                         var packageIds = new List<string>(); | ||
|  |                         foreach (var packageInfo in addAndRemoveRequest.Result) | ||
|  |                         { | ||
|  |                             packageIds.Add(packageInfo.packageId); | ||
|  |                         } | ||
|  |                         return string.Join(", ", packageIds); | ||
|  |                     default: | ||
|  |                         throw new InvalidOperationException("Unknown request type"); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Detects if any multiplayer package is installed by checking for services and Netcode installed packages. | ||
|  |         /// </summary> | ||
|  |         /// <returns>True if any package was detected, False otherwise</returns> | ||
|  |         public static bool IsAnyMultiplayerPackageInstalled() | ||
|  |         { | ||
|  |             var packagesToCheck = new [] | ||
|  |             { | ||
|  |                 "com.unity.netcode", | ||
|  |                 "com.unity.netcode.gameobjects", | ||
|  |                 "com.unity.services.multiplayer", | ||
|  |                 "com.unity.transport", | ||
|  |                 "com.unity.dedicated-server", | ||
|  |                 "com.unity.services.cloudcode", | ||
|  |                 "com.unity.multiplayer.playmode", | ||
|  |                 "com.unity.services.vivox" | ||
|  |                 // Note about "com.unity.services.core": it used to be installed only with multiplayer packages, but it is also a dependency of the analytics, which is now always installed. | ||
|  |             }; | ||
|  | 
 | ||
|  |             foreach (var package in packagesToCheck) | ||
|  |             { | ||
|  |                 if (IsInstalled(package)) | ||
|  |                 { | ||
|  |                     return true; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             return false; | ||
|  |         } | ||
|  |          | ||
|  |         /// <summary> | ||
|  |         /// Checks if the installation process has finished. | ||
|  |         /// </summary> | ||
|  |         /// <returns>True if there is no current installer instance or installation is finished on the installer</returns> | ||
|  |         public static bool IsInstallationFinished() | ||
|  |         { | ||
|  |             return s_Installer == null || s_Installer.IsCompleted(); | ||
|  |         } | ||
|  |     } | ||
|  | } |