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