diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 29b0253b8..d45f0b5ea 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -711,8 +711,12 @@ internal class PluginInstallerWindow : Window, IDisposable { this.updateStatus = OperationStatus.InProgress; this.loadingIndicatorKind = LoadingIndicatorKind.UpdatingAll; + + var toUpdate = this.pluginListUpdatable + .Where(x => x.InstalledPlugin.IsLoaded) + .ToList(); - Task.Run(() => pluginManager.UpdatePluginsAsync(true, false)) + Task.Run(() => pluginManager.UpdatePluginsAsync(toUpdate, false)) .ContinueWith(task => { this.updateStatus = OperationStatus.Complete; diff --git a/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs b/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs index 3e12ef600..869f0c114 100644 --- a/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs +++ b/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs @@ -255,8 +255,6 @@ internal class AutoUpdateManager : IServiceType private async Task RunAutoUpdates(ICollection updatablePlugins) { - var pluginStates = new List(); - Log.Information("Found {UpdatablePluginsCount} plugins to update", updatablePlugins.Count); if (updatablePlugins.Count == 0) @@ -274,35 +272,16 @@ internal class AutoUpdateManager : IServiceType Icon = INotificationIcon.From(FontAwesomeIcon.Download), Minimized = false, }); - - var numDone = 0; - // TODO: This is NOT correct, we need to do this inside PM to be able to avoid notifying for each of these and avoid - // refreshing the plugin list until we're done. See PluginManager::UpdatePluginsAsync(). - // Maybe have a function in PM that can take a list of AvailablePluginUpdate instead and update them all, - // and get rid of UpdatePluginsAsync()? Will have to change the installer a bit but that might be for the better API-wise. - foreach (var plugin in updatablePlugins) + + var progress = new Progress(); + progress.ProgressChanged += (_, progress) => { - try - { - notification.Content = $"Updating {plugin.InstalledPlugin.Manifest.Name}..."; - notification.Progress = (float)numDone / updatablePlugins.Count; - - if (this.isDryRun.Value) - { - await Task.Delay(5000); - } - - var status = await this.pluginManager.UpdateSinglePluginAsync(plugin, true, this.isDryRun.Value); - pluginStates.Add(status); - } - catch (Exception ex) - { - Log.Error(ex, "Failed to auto-update plugin {PluginName}", plugin.InstalledPlugin.Manifest.Name); - } - - numDone++; - } + notification.Content = $"Updating {progress.CurrentPluginManifest.Name}..."; + notification.Progress = (float)progress.PluginsProcessed / progress.TotalPlugins; + }; + var pluginStates = await this.pluginManager.UpdatePluginsAsync(updatablePlugins, this.isDryRun.Value, true, progress); + notification.Progress = 1; notification.UserDismissable = true; notification.HardExpiry = DateTime.Now.AddSeconds(30); @@ -318,7 +297,8 @@ internal class AutoUpdateManager : IServiceType }; // Update the notification to show the final state - if (pluginStates.All(x => x.Status == PluginUpdateStatus.StatusKind.Success)) + var pluginUpdateStatusEnumerable = pluginStates as PluginUpdateStatus[] ?? pluginStates.ToArray(); + if (pluginUpdateStatusEnumerable.All(x => x.Status == PluginUpdateStatus.StatusKind.Success)) { notification.Minimized = true; @@ -337,7 +317,7 @@ internal class AutoUpdateManager : IServiceType notification.Type = NotificationType.Error; notification.Content = "Some plugins failed to update. Please check the plugin installer for more information."; - var failedPlugins = pluginStates + var failedPlugins = pluginUpdateStatusEnumerable .Where(x => x.Status != PluginUpdateStatus.StatusKind.Success) .Select(x => x.Name).ToList(); diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 7517ae413..60d2bbe28 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -977,32 +977,39 @@ internal class PluginManager : IInternalDisposableService /// /// Update all non-dev plugins. /// - /// Ignore disabled plugins. + /// List of plugins to update. /// Perform a dry run, don't install anything. /// If this action was performed as part of an auto-update. + /// An implementation to receive progress updates about the installation status. /// Success or failure and a list of updated plugin metadata. - public async Task> UpdatePluginsAsync(bool ignoreDisabled, bool dryRun, bool autoUpdate = false) + public async Task> UpdatePluginsAsync( + ICollection toUpdate, + bool dryRun, + bool autoUpdate = false, + IProgress? progress = null) { Log.Information("Starting plugin update"); var updateTasks = new List>(); + var totalPlugins = toUpdate.Count; + var processedPlugins = 0; // Prevent collection was modified errors lock (this.pluginListLock) { - foreach (var plugin in this.updatablePluginsList) + foreach (var plugin in toUpdate) { // Can't update that! if (plugin.InstalledPlugin.IsDev) continue; - if (!plugin.InstalledPlugin.IsWantedByAnyProfile && ignoreDisabled) + if (!plugin.InstalledPlugin.IsWantedByAnyProfile) continue; if (plugin.InstalledPlugin.Manifest.ScheduledForDeletion) continue; - updateTasks.Add(this.UpdateSinglePluginAsync(plugin, false, dryRun)); + updateTasks.Add(UpdateSinglePluginWithProgressAsync(plugin)); } } @@ -1013,9 +1020,26 @@ internal class PluginManager : IInternalDisposableService autoUpdate ? PluginListInvalidationKind.AutoUpdate : PluginListInvalidationKind.Update, updatedList.Select(x => x.InternalName)); - Log.Information("Plugin update OK. {updateCount} plugins updated.", updatedList.Length); + Log.Information("Plugin update OK. {UpdateCount} plugins updated", updatedList.Length); return updatedList; + + async Task UpdateSinglePluginWithProgressAsync(AvailablePluginUpdate plugin) + { + var result = await this.UpdateSinglePluginAsync(plugin, false, dryRun); + + // Update the progress + if (progress != null) + { + var newProcessedAmount = Interlocked.Increment(ref processedPlugins); + progress.Report(new PluginUpdateProgress( + newProcessedAmount, + totalPlugins, + plugin.InstalledPlugin.Manifest)); + } + + return result; + } } /// @@ -1832,6 +1856,11 @@ internal class PluginManager : IInternalDisposableService } } + /// + /// Class representing progress of an update operation. + /// + public record PluginUpdateProgress(int PluginsProcessed, int TotalPlugins, IPluginManifest CurrentPluginManifest); + /// /// Simple class that tracks the internal names and public names of plugins that we are planning to load at startup, /// and are still actively loading.