|
1 | 1 | using System;
|
| 2 | +using System.Collections.Generic; |
2 | 3 | using System.IO;
|
3 | 4 | using System.IO.Compression;
|
4 | 5 | using System.Linq;
|
@@ -277,6 +278,122 @@ await DownloadFileAsync(
|
277 | 278 | }
|
278 | 279 | }
|
279 | 280 |
|
| 281 | + /// <summary> |
| 282 | + /// Updates the plugin to the latest version available from its source. |
| 283 | + /// </summary> |
| 284 | + /// <param name="updateAllPlugins">Action to execute when the user chooses to update all plugins.</param> |
| 285 | + /// <param name="silentUpdate">If true, do not show any messages when there is no update available.</param> |
| 286 | + /// <param name="usePrimaryUrlOnly">If true, only use the primary URL for updates.</param> |
| 287 | + /// <param name="token">Cancellation token to cancel the update operation.</param> |
| 288 | + /// <returns></returns> |
| 289 | + public static async Task CheckForPluginUpdatesAsync(Action<List<PluginUpdateInfo>> updateAllPlugins, bool silentUpdate = true, bool usePrimaryUrlOnly = false, CancellationToken token = default) |
| 290 | + { |
| 291 | + // Update the plugin manifest |
| 292 | + await API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token); |
| 293 | + |
| 294 | + // Get all plugins that can be updated |
| 295 | + var resultsForUpdate = ( |
| 296 | + from existingPlugin in API.GetAllPlugins() |
| 297 | + join pluginUpdateSource in API.GetPluginManifest() |
| 298 | + on existingPlugin.Metadata.ID equals pluginUpdateSource.ID |
| 299 | + where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version, |
| 300 | + StringComparison.InvariantCulture) < |
| 301 | + 0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest) |
| 302 | + && !API.PluginModified(existingPlugin.Metadata.ID) |
| 303 | + select |
| 304 | + new PluginUpdateInfo() |
| 305 | + { |
| 306 | + ID = existingPlugin.Metadata.ID, |
| 307 | + Name = existingPlugin.Metadata.Name, |
| 308 | + Author = existingPlugin.Metadata.Author, |
| 309 | + CurrentVersion = existingPlugin.Metadata.Version, |
| 310 | + NewVersion = pluginUpdateSource.Version, |
| 311 | + IcoPath = existingPlugin.Metadata.IcoPath, |
| 312 | + PluginExistingMetadata = existingPlugin.Metadata, |
| 313 | + PluginNewUserPlugin = pluginUpdateSource |
| 314 | + }).ToList(); |
| 315 | + |
| 316 | + // No updates |
| 317 | + if (!resultsForUpdate.Any()) |
| 318 | + { |
| 319 | + if (!silentUpdate) |
| 320 | + { |
| 321 | + API.ShowMsg(API.GetTranslation("updateNoResultTitle"), API.GetTranslation("updateNoResultSubtitle")); |
| 322 | + } |
| 323 | + return; |
| 324 | + } |
| 325 | + |
| 326 | + // If all plugins are modified, just return |
| 327 | + if (resultsForUpdate.All(x => API.PluginModified(x.ID))) |
| 328 | + { |
| 329 | + return; |
| 330 | + } |
| 331 | + |
| 332 | + // Show message box with button to update all plugins |
| 333 | + API.ShowMsgWithButton( |
| 334 | + API.GetTranslation("updateAllPluginsTitle"), |
| 335 | + API.GetTranslation("updateAllPluginsButtonContent"), |
| 336 | + () => |
| 337 | + { |
| 338 | + updateAllPlugins(resultsForUpdate); |
| 339 | + }, |
| 340 | + string.Join(", ", resultsForUpdate.Select(x => x.PluginExistingMetadata.Name))); |
| 341 | + } |
| 342 | + |
| 343 | + /// <summary> |
| 344 | + /// Updates all plugins that have available updates. |
| 345 | + /// </summary> |
| 346 | + /// <param name="resultsForUpdate"></param> |
| 347 | + /// <param name="restart"></param> |
| 348 | + public static async Task UpdateAllPluginsAsync(IEnumerable<PluginUpdateInfo> resultsForUpdate, bool restart) |
| 349 | + { |
| 350 | + var anyPluginSuccess = false; |
| 351 | + await Task.WhenAll(resultsForUpdate.Select(async plugin => |
| 352 | + { |
| 353 | + var downloadToFilePath = Path.Combine(Path.GetTempPath(), $"{plugin.Name}-{plugin.NewVersion}.zip"); |
| 354 | + |
| 355 | + try |
| 356 | + { |
| 357 | + using var cts = new CancellationTokenSource(); |
| 358 | + |
| 359 | + await DownloadFileAsync( |
| 360 | + $"{API.GetTranslation("DownloadingPlugin")} {plugin.PluginNewUserPlugin.Name}", |
| 361 | + plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath, cts); |
| 362 | + |
| 363 | + // check if user cancelled download before installing plugin |
| 364 | + if (cts.IsCancellationRequested) |
| 365 | + { |
| 366 | + return; |
| 367 | + } |
| 368 | + |
| 369 | + if (!await API.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath)) |
| 370 | + { |
| 371 | + return; |
| 372 | + } |
| 373 | + |
| 374 | + anyPluginSuccess = true; |
| 375 | + } |
| 376 | + catch (Exception e) |
| 377 | + { |
| 378 | + API.LogException(ClassName, "Failed to update plugin", e); |
| 379 | + API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin")); |
| 380 | + } |
| 381 | + })); |
| 382 | + |
| 383 | + if (!anyPluginSuccess) return; |
| 384 | + |
| 385 | + if (restart) |
| 386 | + { |
| 387 | + API.RestartApp(); |
| 388 | + } |
| 389 | + else |
| 390 | + { |
| 391 | + API.ShowMsg( |
| 392 | + API.GetTranslation("updatebtn"), |
| 393 | + API.GetTranslation("PluginsUpdateSuccessNoRestart")); |
| 394 | + } |
| 395 | + } |
| 396 | + |
280 | 397 | /// <summary>
|
281 | 398 | /// Downloads a file from a URL to a local path, optionally showing a progress box and handling cancellation.
|
282 | 399 | /// </summary>
|
@@ -351,3 +468,15 @@ private static bool InstallSourceKnown(string url)
|
351 | 468 | );
|
352 | 469 | }
|
353 | 470 | }
|
| 471 | + |
| 472 | +public record PluginUpdateInfo |
| 473 | +{ |
| 474 | + public string ID { get; init; } |
| 475 | + public string Name { get; init; } |
| 476 | + public string Author { get; init; } |
| 477 | + public string CurrentVersion { get; init; } |
| 478 | + public string NewVersion { get; init; } |
| 479 | + public string IcoPath { get; init; } |
| 480 | + public PluginMetadata PluginExistingMetadata { get; init; } |
| 481 | + public UserPlugin PluginNewUserPlugin { get; init; } |
| 482 | +} |
0 commit comments