-
Notifications
You must be signed in to change notification settings - Fork 808
Expand file tree
/
Copy pathAbstractPackageLoader.cs
More file actions
343 lines (298 loc) · 11.5 KB
/
AbstractPackageLoader.cs
File metadata and controls
343 lines (298 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
using System.Collections.Concurrent;
using UniGetUI.Core.Tools;
using UniGetUI.PackageEngine.Interfaces;
namespace UniGetUI.PackageEngine.PackageLoader
{
public class PackagesChangedEvent
{
public PackagesChangedEvent(bool proceduralChange, IReadOnlyList<IPackage> addedPackages, IReadOnlyList<IPackage> removedPackages)
{
ProceduralChange = proceduralChange;
AddedPackages = addedPackages;
RemovedPackages = removedPackages;
}
public readonly bool ProceduralChange;
public readonly IReadOnlyList<IPackage> AddedPackages;
public readonly IReadOnlyList<IPackage> RemovedPackages;
}
public abstract class AbstractPackageLoader
{
/// <summary>
/// Checks if the loader has loaded packages
/// </summary>
public bool IsLoaded { get; private set; }
/// <summary>
/// Checks if the loader is fetching new packages right now
/// </summary>
public bool IsLoading { get; protected set; }
public bool Any()
{
return !PackageReference.IsEmpty;
}
/// <summary>
/// The collection of currently available packages
/// </summary>
public List<IPackage> Packages
{
get => PackageReference.Values.ToList();
}
protected readonly ConcurrentDictionary<long, IPackage> PackageReference;
/// <summary>
/// Fires when a block of packages (one package or more) is added or removed to the loader
/// </summary>
public event EventHandler<PackagesChangedEvent>? PackagesChanged;
/// <summary>
/// Fires when the loader finishes fetching packages
/// </summary>
public event EventHandler<EventArgs>? FinishedLoading;
/// <summary>
/// Fires when the manager starts fetching packages
/// </summary>
public event EventHandler<EventArgs>? StartedLoading;
private readonly bool ALLOW_MULTIPLE_PACKAGE_VERSIONS;
private readonly bool DISABLE_RELOAD;
private readonly bool PACKAGES_CHECKED_BY_DEFAULT;
private readonly bool REQUIRES_INTERNET;
protected string LOADER_IDENTIFIER;
private int LoadOperationIdentifier;
protected IReadOnlyList<IPackageManager> Managers { get; private set; }
public AbstractPackageLoader(
IReadOnlyList<IPackageManager> managers,
string identifier,
bool AllowMultiplePackageVersions,
bool DisableReload,
bool CheckedBydefault,
bool RequiresInternet)
{
Managers = managers;
PackageReference = new ConcurrentDictionary<long, IPackage>();
IsLoaded = false;
IsLoading = false;
PACKAGES_CHECKED_BY_DEFAULT = CheckedBydefault;
DISABLE_RELOAD = DisableReload;
ALLOW_MULTIPLE_PACKAGE_VERSIONS = AllowMultiplePackageVersions;
LOADER_IDENTIFIER = identifier;
ALLOW_MULTIPLE_PACKAGE_VERSIONS = AllowMultiplePackageVersions;
REQUIRES_INTERNET = RequiresInternet;
}
/// <summary>
/// Stops the current loading process
/// </summary>
public void StopLoading(bool emitFinishSignal = true)
{
LoadOperationIdentifier = -1;
IsLoaded = false;
IsLoading = false;
if (emitFinishSignal) InvokeFinishedLoadingEvent();
}
protected void InvokePackagesChangedEvent(bool proceduralChange, IReadOnlyList<IPackage> toAdd, IReadOnlyList<IPackage> toRemove)
{
PackagesChanged?.Invoke(this, new(proceduralChange, toAdd, toRemove));
}
protected void InvokeStartedLoadingEvent()
{
StartedLoading?.Invoke(this, EventArgs.Empty);
}
protected void InvokeFinishedLoadingEvent()
{
FinishedLoading?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Will trigger a forceful reload of the packages
/// </summary>
public virtual async Task ReloadPackages()
{
if (DISABLE_RELOAD)
{
InvokePackagesChangedEvent(false, [], []);
return;
}
ClearPackages(emitFinishSignal: false);
LoadOperationIdentifier = new Random().Next();
int current_identifier = LoadOperationIdentifier;
IsLoading = true;
StartedLoading?.Invoke(this, EventArgs.Empty);
if (REQUIRES_INTERNET)
{
await CoreTools.WaitForInternetConnection();
}
List<Task<IReadOnlyList<IPackage>>> tasks = [];
foreach (IPackageManager manager in Managers)
{
if (manager.IsReady())
{
Task<IReadOnlyList<IPackage>> task = Task.Run(() => LoadPackagesFromManager(manager));
tasks.Add(task);
}
}
while (tasks.Count > 0)
{
foreach (Task<IReadOnlyList<IPackage>> task in tasks.ToArray())
{
if (!task.IsCompleted)
{
await Task.Delay(100).ConfigureAwait(false);
}
if (task.IsCompleted)
{
if (LoadOperationIdentifier == current_identifier && task.IsCompletedSuccessfully)
{
foreach (IPackage package in task.Result)
{
if (Contains(package) || !await IsPackageValid(package))
{
continue;
}
AddPackage(package);
await WhenAddingPackage(package);
}
InvokePackagesChangedEvent(true, task.Result.ToArray(), []);
}
tasks.Remove(task);
}
}
}
if (LoadOperationIdentifier == current_identifier)
{
InvokeFinishedLoadingEvent();
IsLoaded = true;
}
IsLoading = false;
}
/// <summary>
/// Resets the packages available on the loader
/// </summary>
public void ClearPackages(bool emitFinishSignal = true)
{
StopLoading(emitFinishSignal);
PackageReference.Clear();
IsLoaded = false;
IsLoading = false;
InvokePackagesChangedEvent(false, [], []);
}
/// <summary>
/// Loads the packages from the given manager
/// </summary>
/// <param name="manager">The manager from which to load packages</param>
/// <returns>A task that will load the packages</returns>
protected abstract IReadOnlyList<IPackage> LoadPackagesFromManager(IPackageManager manager);
/// <summary>
/// Checks whether the package is valid or must be skipped
/// </summary>
/// <param name="package">The package to check</param>
/// <returns>True if the package can be added, false otherwise</returns>
protected abstract Task<bool> IsPackageValid(IPackage package);
/// <summary>
/// A method to post-process packages after they have been added.
/// </summary>
/// <param name="package">The package to process</param>
protected abstract Task WhenAddingPackage(IPackage package);
/// <summary>
/// Checks whether a package is contained on the current Loader
/// </summary>
/// <param name="package">The package to check against</param>
public bool Contains(IPackage package)
{
return PackageReference.ContainsKey(HashPackage(package));
}
/// <summary>
/// Returns the appropriate hash of the package, according to the current loader configuration
/// </summary>
/// <param name="package">The package to hash</param>
/// <returns>A long int containing the hash</returns>
protected long HashPackage(IPackage package)
{
return ALLOW_MULTIPLE_PACKAGE_VERSIONS ? package.GetVersionedHash() : package.GetHash();
}
protected void AddPackage(IPackage package)
{
if (Contains(package))
{
return;
}
package.IsChecked = PACKAGES_CHECKED_BY_DEFAULT;
PackageReference.TryAdd(HashPackage(package), package);
}
/// <summary>
/// Adds a foreign package to the current loader. Perhaps a package has been recently installed and it needs to be added to the installed packages loader
/// </summary>
/// <param name="package">The package to add</param>
public void AddForeign(IPackage? package)
{
if (package is null)
{
return;
}
AddPackage(package);
InvokePackagesChangedEvent(true, [package], []);
}
/// <summary>
/// Removes the given package from the list.
/// </summary>
public void Remove(IPackage? package)
{
if (package is null)
{
return;
}
if (!Contains(package))
{
return;
}
PackageReference.Remove(HashPackage(package), out IPackage? _);
InvokePackagesChangedEvent(true, [], [package]);
}
/// <summary>
/// Gets the corresponding package on the current loader.
/// This method follows the equivalence settings for this loader
/// </summary>
/// <returns>A Package? object</returns>
public IPackage? GetEquivalentPackage(IPackage? package)
{
if (package is null)
{
return null;
}
PackageReference.TryGetValue(HashPackage(package), out IPackage? eq);
return eq;
}
/// <summary>
/// Gets ALL of the equivalent packages on this loader.
/// This method does NOT follow the equivalence settings for this loader
/// </summary>
/// <param name="package">The package for which to find the equivalent packages</param>
/// <returns>A IReadOnlyList<Package> object</returns>
public IReadOnlyList<IPackage> GetEquivalentPackages(IPackage? package)
{
if (package is null)
{
return [];
}
List<IPackage> result = [];
long hash_to_match = package.GetHash();
foreach (IPackage local_package in Packages)
{
if (local_package.GetHash() == hash_to_match)
{
result.Add(local_package);
}
}
return result;
}
public IPackage? GetPackageForId(string id, string? sourceName = null)
{
foreach (IPackage package in Packages)
{
if (package.Id == id && (sourceName is null || package.Source.Name == sourceName))
{
return package;
}
}
return null;
}
public int Count()
{
return PackageReference.Count;
}
}
}