PackageCollection.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine;
  5. namespace UnityEditor.PackageManager.UI
  6. {
  7. [Serializable]
  8. internal class PackageCollection
  9. {
  10. private const string k_UnityPackage = "Unity";
  11. private static PackageCollection instance = new PackageCollection();
  12. public static PackageCollection Instance { get { return instance; } }
  13. public event Action<IEnumerable<Package>> OnPackagesChanged = delegate {};
  14. public event Action<PackageFilter> OnFilterChanged = delegate {};
  15. private readonly Dictionary<string, Package> packages;
  16. private PackageFilter filter;
  17. private string selectedListPackage;
  18. private string selectedSearchUnityPackage;
  19. private string selectedSearchOtherPackage;
  20. private List<string> collapsedListGroups;
  21. private List<string> collapsedSearchUnityGroups;
  22. private List<string> collapsedSearchOtherGroups;
  23. internal string lastUpdateTime;
  24. private List<PackageInfo> listPackagesOffline;
  25. private List<PackageInfo> listPackages;
  26. private List<PackageInfo> searchUnityPackages;
  27. private List<PackageInfo> searchOtherPackages;
  28. private List<PackageError> packageErrors;
  29. private int listPackagesVersion;
  30. private int listPackagesOfflineVersion;
  31. private bool searchOperationOngoing;
  32. private bool listOperationOngoing;
  33. private bool listOperationOfflineOngoing;
  34. private IListOperation listOperationOffline;
  35. private IListOperation listOperation;
  36. private ISearchOperation searchOperation;
  37. public readonly OperationSignal<ISearchOperation> SearchSignal = new OperationSignal<ISearchOperation>();
  38. public readonly OperationSignal<IListOperation> ListSignal = new OperationSignal<IListOperation>();
  39. public static void InitInstance(ref PackageCollection value)
  40. {
  41. if (value == null) // UI window opened
  42. {
  43. value = instance;
  44. Instance.OnPackagesChanged = delegate {};
  45. Instance.OnFilterChanged = delegate {};
  46. Instance.SearchSignal.ResetEvents();
  47. Instance.ListSignal.ResetEvents();
  48. Instance.FetchListOfflineCache(true);
  49. Instance.FetchListCache(true);
  50. Instance.FetchSearchCache(true);
  51. }
  52. else // Domain reload
  53. {
  54. instance = value;
  55. Instance.RebuildPackageDictionary();
  56. // Resume operations interrupted by domain reload
  57. Instance.FetchListOfflineCache(Instance.listOperationOfflineOngoing);
  58. Instance.FetchListCache(Instance.listOperationOngoing);
  59. Instance.FetchSearchCache(Instance.searchOperationOngoing);
  60. }
  61. }
  62. public PackageFilter Filter
  63. {
  64. get { return filter; }
  65. // For public usage, use SetFilter() instead
  66. private set
  67. {
  68. var changed = value != filter;
  69. if (changed)
  70. {
  71. var selectedPackageName = SelectedPackage;
  72. Package package;
  73. if (!string.IsNullOrEmpty(selectedPackageName) && packages.TryGetValue(selectedPackageName, out package))
  74. {
  75. var groupName = GetGroupName(package);
  76. if (CollapsedGroups.Contains(groupName))
  77. CollapsedGroups.Remove(groupName);
  78. }
  79. }
  80. filter = value;
  81. if (changed)
  82. OnFilterChanged(filter);
  83. }
  84. }
  85. public List<PackageInfo> LatestListPackages
  86. {
  87. get { return listPackagesVersion > listPackagesOfflineVersion ? listPackages : listPackagesOffline; }
  88. }
  89. public List<PackageInfo> LatestSearchUnityPackages { get { return searchUnityPackages; } }
  90. public List<PackageInfo> LatestSearchOtherPackages { get { return searchOtherPackages; } }
  91. public string SelectedPackage
  92. {
  93. get
  94. {
  95. switch (Filter)
  96. {
  97. case PackageFilter.Unity:
  98. return selectedSearchUnityPackage;
  99. case PackageFilter.Other:
  100. return selectedSearchOtherPackage;
  101. default:
  102. return selectedListPackage;
  103. }
  104. }
  105. set
  106. {
  107. switch (Filter)
  108. {
  109. case PackageFilter.Unity:
  110. selectedSearchUnityPackage = value;
  111. break;
  112. case PackageFilter.Other:
  113. selectedSearchOtherPackage = value;
  114. break;
  115. default:
  116. selectedListPackage = value;
  117. break;
  118. }
  119. }
  120. }
  121. public List<string> CollapsedGroups
  122. {
  123. get
  124. {
  125. switch (Filter)
  126. {
  127. case PackageFilter.Unity:
  128. return collapsedSearchUnityGroups;
  129. case PackageFilter.Other:
  130. return collapsedSearchOtherGroups;
  131. case PackageFilter.Local:
  132. return collapsedListGroups;
  133. default:
  134. return new List<string>();
  135. }
  136. }
  137. }
  138. public static string GetGroupName(Package package)
  139. {
  140. if (package.IsBuiltIn)
  141. return PackageGroupOrigins.BuiltInPackages.ToString();
  142. else if (package.IsUnityPackage)
  143. return k_UnityPackage;
  144. else
  145. return package.Latest != null ? package.Latest.Author : package.Current.Author;
  146. }
  147. private PackageCollection()
  148. {
  149. packages = new Dictionary<string, Package>();
  150. listPackagesOffline = new List<PackageInfo>();
  151. listPackages = new List<PackageInfo>();
  152. searchUnityPackages = new List<PackageInfo>();
  153. searchOtherPackages = new List<PackageInfo>();
  154. collapsedListGroups = new List<string>();
  155. collapsedSearchUnityGroups = new List<string>();
  156. collapsedSearchOtherGroups = new List<string>();
  157. packageErrors = new List<PackageError>();
  158. listPackagesVersion = 0;
  159. listPackagesOfflineVersion = 0;
  160. searchOperationOngoing = false;
  161. listOperationOngoing = false;
  162. listOperationOfflineOngoing = false;
  163. Filter = PackageFilter.Unity;
  164. }
  165. public bool SetFilter(PackageFilter value, bool refresh = true)
  166. {
  167. if (value == Filter)
  168. return false;
  169. Filter = value;
  170. if (refresh)
  171. {
  172. UpdatePackageCollection();
  173. }
  174. return true;
  175. }
  176. public void UpdatePackageCollection(bool rebuildDictionary = false)
  177. {
  178. if (rebuildDictionary)
  179. {
  180. lastUpdateTime = DateTime.Now.ToString("HH:mm");
  181. RebuildPackageDictionary();
  182. }
  183. if (packages.Any())
  184. OnPackagesChanged(OrderedPackages());
  185. }
  186. internal void FetchListOfflineCache(bool forceRefetch = false)
  187. {
  188. if (!forceRefetch && (listOperationOfflineOngoing || listPackagesOffline.Any())) return;
  189. if (listOperationOffline != null)
  190. listOperationOffline.Cancel();
  191. listOperationOfflineOngoing = true;
  192. listOperationOffline = OperationFactory.Instance.CreateListOperation(true);
  193. listOperationOffline.OnOperationFinalized += () =>
  194. {
  195. listOperationOfflineOngoing = false;
  196. UpdatePackageCollection(true);
  197. };
  198. listOperationOffline.GetPackageListAsync(
  199. infos =>
  200. {
  201. var version = listPackagesVersion;
  202. UpdateListPackageInfosOffline(infos, version);
  203. },
  204. error => { Debug.LogError("Error fetching package list (offline mode)."); });
  205. }
  206. internal void FetchListCache(bool forceRefetch = false)
  207. {
  208. if (!forceRefetch && (listOperationOngoing || listPackages.Any())) return;
  209. if (listOperation != null)
  210. listOperation.Cancel();
  211. listOperationOngoing = true;
  212. listOperation = OperationFactory.Instance.CreateListOperation();
  213. listOperation.OnOperationFinalized += () =>
  214. {
  215. listOperationOngoing = false;
  216. UpdatePackageCollection(true);
  217. };
  218. listOperation.GetPackageListAsync(UpdateListPackageInfos,
  219. error => { Debug.LogError("Error fetching package list."); });
  220. ListSignal.SetOperation(listOperation);
  221. }
  222. internal void FetchSearchCache(bool forceRefetch = false)
  223. {
  224. if (!forceRefetch && (searchOperationOngoing || searchUnityPackages.Any() || searchOtherPackages.Any())) return;
  225. if (searchOperation != null)
  226. searchOperation.Cancel();
  227. searchOperationOngoing = true;
  228. searchOperation = OperationFactory.Instance.CreateSearchOperation();
  229. searchOperation.OnOperationFinalized += () =>
  230. {
  231. searchOperationOngoing = false;
  232. UpdatePackageCollection(true);
  233. };
  234. searchOperation.GetAllPackageAsync(UpdateSearchPackageInfos,
  235. error => { Debug.LogError("Error searching packages online."); });
  236. SearchSignal.SetOperation(searchOperation);
  237. }
  238. private void UpdateListPackageInfosOffline(IEnumerable<PackageInfo> newInfos, int version)
  239. {
  240. listPackagesOfflineVersion = version;
  241. listPackagesOffline = newInfos.Where(p => p.IsUserVisible).ToList();
  242. }
  243. private void UpdateListPackageInfos(IEnumerable<PackageInfo> newInfos)
  244. {
  245. // Each time we fetch list packages, the cache for offline mode will be updated
  246. // We keep track of the list packages version so that we know which version of cache
  247. // we are getting with the offline fetch operation.
  248. listPackagesVersion++;
  249. listPackages = newInfos.Where(p => p.IsUserVisible).ToList();
  250. listPackagesOffline = listPackages;
  251. }
  252. private void UpdateSearchPackageInfos(IEnumerable<PackageInfo> newInfos)
  253. {
  254. searchUnityPackages = newInfos.Where(p => p.IsUserVisible && p.IsUnityPackage).ToList();
  255. searchOtherPackages = newInfos.Where(p => p.IsUserVisible && !p.IsUnityPackage).ToList();
  256. if (!searchOtherPackages.Any() && Filter == PackageFilter.Other)
  257. {
  258. SetFilter(PackageFilter.Unity);
  259. }
  260. }
  261. private IEnumerable<Package> OrderedPackages()
  262. {
  263. return packages.Values.OrderByDescending(pkg => pkg.IsUnityPackage).
  264. ThenBy(pkg => pkg.VersionToDisplay.Author == "Other").
  265. ThenBy(pkg => pkg.VersionToDisplay.Author).
  266. ThenBy(pkg => pkg.Versions.LastOrDefault() == null ? pkg.Name : pkg.Versions.Last().DisplayName);
  267. }
  268. public Package GetPackageByName(string name)
  269. {
  270. Package package;
  271. packages.TryGetValue(name, out package);
  272. return package;
  273. }
  274. public Error GetPackageError(Package package)
  275. {
  276. if (null == package) return null;
  277. var firstMatchingError = packageErrors.FirstOrDefault(p => p.PackageName == package.Name);
  278. return firstMatchingError != null ? firstMatchingError.Error : null;
  279. }
  280. public void AddPackageError(Package package, Error error)
  281. {
  282. if (null == package || null == error) return;
  283. packageErrors.Add(new PackageError(package.Name, error));
  284. }
  285. public void RemovePackageErrors(Package package)
  286. {
  287. if (null == package) return;
  288. packageErrors.RemoveAll(p => p.PackageName == package.Name);
  289. }
  290. public void ResetExpandedGroups()
  291. {
  292. collapsedListGroups = new List<string>();
  293. collapsedSearchUnityGroups = new List<string>();
  294. collapsedSearchOtherGroups = new List<string>();
  295. }
  296. private void RebuildPackageDictionary()
  297. {
  298. // Merge list & search packages
  299. var allPackageInfos = new List<PackageInfo>(LatestListPackages);
  300. var installedPackageIds = new HashSet<string>(allPackageInfos.Select(p => p.PackageId));
  301. allPackageInfos.AddRange(searchUnityPackages.Where(p => !installedPackageIds.Contains(p.PackageId)));
  302. allPackageInfos.AddRange(searchOtherPackages.Where(p => !installedPackageIds.Contains(p.PackageId)));
  303. if (!PackageManagerPrefs.ShowPreviewPackages)
  304. {
  305. allPackageInfos = allPackageInfos.Where(p => !p.IsPreRelease || installedPackageIds.Contains(p.PackageId)).ToList();
  306. }
  307. // Rebuild packages dictionary
  308. packages.Clear();
  309. foreach (var p in allPackageInfos)
  310. {
  311. var packageName = p.Name;
  312. if (packages.ContainsKey(packageName))
  313. continue;
  314. var packageQuery = from pkg in allPackageInfos where pkg.Name == packageName select pkg;
  315. var package = new Package(packageName, packageQuery);
  316. packages[packageName] = package;
  317. }
  318. }
  319. }
  320. }