diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/ModUpdateDialogViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/ModUpdateDialogViewModel.cs index 21cd96c1..4767e8ce 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/ModUpdateDialogViewModel.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Dialog/ModUpdateDialogViewModel.cs @@ -41,15 +41,26 @@ public class ModUpdateDialogViewModel : ObservableObject /// public bool CanDownload { get; set; } - /// - public ModUpdateDialogViewModel(Updater updater, ModUpdateSummary summary) + /// + /// Creates the ViewModel using precomputed update info. + /// + public ModUpdateDialogViewModel(Updater updater, ModUpdateSummary summary, ModUpdate[] updateInfo) { Updater = updater; Summary = summary; - UpdateInfo = Summary.GetUpdateInfo(); + UpdateInfo = updateInfo; TotalSize = UpdateInfo.Sum(x => x.UpdateSize); - SelectedUpdate = UpdateInfo[0]; - CanDownload = true; + + if (UpdateInfo.Length > 0) + { + SelectedUpdate = UpdateInfo[0]; + CanDownload = true; + } + else + { + SelectedUpdate = null; + CanDownload = false; + } } /// diff --git a/source/Reloaded.Mod.Launcher.Lib/Update.cs b/source/Reloaded.Mod.Launcher.Lib/Update.cs index 42021d8a..51a739f5 100644 --- a/source/Reloaded.Mod.Launcher.Lib/Update.cs +++ b/source/Reloaded.Mod.Launcher.Lib/Update.cs @@ -130,9 +130,14 @@ public static async Task CheckForModUpdatesAsync() if (updateDetails.HasUpdates()) { + // Fetch update metadata (download sizes, changelogs) on a background thread. + // Doing this inside the ViewModel constructor blocked the UI thread on + // unbounded network I/O, freezing the launcher on startup. See issue #910. + var updateInfo = await updateDetails.GetUpdateInfoAsync(); + Actions.SynchronizationContext.Send(_ => { - Actions.ShowModUpdateDialog.Invoke(new ModUpdateDialogViewModel(updater, updateDetails)); + Actions.ShowModUpdateDialog.Invoke(new ModUpdateDialogViewModel(updater, updateDetails, updateInfo)); }, null); return true; diff --git a/source/Reloaded.Mod.Loader.Update/Structures/ModUpdateSummary.cs b/source/Reloaded.Mod.Loader.Update/Structures/ModUpdateSummary.cs index dbd35f34..9e0cfece 100644 --- a/source/Reloaded.Mod.Loader.Update/Structures/ModUpdateSummary.cs +++ b/source/Reloaded.Mod.Loader.Update/Structures/ModUpdateSummary.cs @@ -39,61 +39,72 @@ public bool HasUpdates() /// /// Retrieves info about the individual updates. + /// Synchronous wrapper; do not call from the UI thread. Prefer . /// /// - public ModUpdate[] GetUpdateInfo() + public ModUpdate[] GetUpdateInfo() => Task.Run(GetUpdateInfoAsync).GetAwaiter().GetResult(); + + /// + /// Retrieves info about the individual updates without blocking the calling thread. + /// + /// + public async Task GetUpdateInfoAsync() { if (_updates != null) return _updates; - _updates = new ModUpdate[ManagerModResultPairs.Count]; - Task.Run(async () => + var updates = new ModUpdate[ManagerModResultPairs.Count]; + for (var x = 0; x < ManagerModResultPairs.Count; x++) { - for (var x = 0; x < ManagerModResultPairs.Count; x++) + var resultPairs = ManagerModResultPairs[x]; + var modName = resultPairs.ModTuple.Config.ModName; + var modId = resultPairs.ModTuple.Config.ModId; + var oldVersion = resultPairs.ModTuple.Config.ModVersion; + var newVersion = resultPairs.Result.LastVersion; + var resolver = resultPairs.Manager.Resolver; + var updateSize = (long)0; + string? changelog = null; + + if (resolver is IPackageResolverDownloadSize hasDownloadSize) { - var resultPairs = ManagerModResultPairs[x]; - var modName = resultPairs.ModTuple.Config.ModName; - var modId = resultPairs.ModTuple.Config.ModId; - var oldVersion = resultPairs.ModTuple.Config.ModVersion; - var newVersion = resultPairs.Result.LastVersion; - var resolver = ((IPackageResolverDownloadSize)resultPairs.Manager.Resolver); - var updateSize = (long)0; - string? changelog = null; - try { - updateSize = await resolver.GetDownloadFileSizeAsync(newVersion!, resultPairs.ModTuple.GetVerificationInfo()); + updateSize = await hasDownloadSize.GetDownloadFileSizeAsync(newVersion!, resultPairs.ModTuple.GetVerificationInfo()).ConfigureAwait(false); } catch (Exception) { /* Ignored */ } + } - // Get changelog from supported resolver. - if (resolver is IPackageResolverGetLatestReleaseMetadata getMetadata) + // Get changelog from supported resolver. + if (resolver is IPackageResolverGetLatestReleaseMetadata getMetadata) + { + try { - try - { - var releaseMetadata = await getMetadata.GetReleaseMetadataAsync(default); - var extraData = releaseMetadata?.GetExtraData(); - if (extraData != null) - changelog = extraData.Changelog; - } - catch (Exception) { /* Ignored */ } + var releaseMetadata = await getMetadata.GetReleaseMetadataAsync(default).ConfigureAwait(false); + var extraData = releaseMetadata?.GetExtraData(); + if (extraData != null) + changelog = extraData.Changelog; } + catch (Exception) { /* Ignored */ } + } - // NuGet has special case, since it doesn't support release metadata but supports changelogs in nuspec. - if (string.IsNullOrEmpty(changelog) && resolver is NuGetUpdateResolver nugetResolver) + // NuGet has special case, since it doesn't support release metadata but supports changelogs in nuspec. + if (string.IsNullOrEmpty(changelog) && resolver is NuGetUpdateResolver nugetResolver) + { + try { var copiedSettings = nugetResolver.GetResolverSettings(); var repository = NugetRepository.FromSourceUrl(copiedSettings.NugetRepository!.SourceUrl); - var reader = await repository.DownloadNuspecReaderAsync(new PackageIdentity(copiedSettings.PackageId, newVersion!)); + var reader = await repository.DownloadNuspecReaderAsync(new PackageIdentity(copiedSettings.PackageId, newVersion!)).ConfigureAwait(false); if (reader != null) changelog = reader?.GetReleaseNotes(); } - - _updates[x] = new ModUpdate(modId, NuGetVersion.Parse(oldVersion), newVersion!, updateSize, changelog, modName); + catch (Exception) { /* Ignored */ } } - }).Wait(); + updates[x] = new ModUpdate(modId, NuGetVersion.Parse(oldVersion), newVersion!, updateSize, changelog, modName); + } + _updates = updates; return _updates; }