diff --git a/Core/ModuleInstaller.cs b/Core/ModuleInstaller.cs index 4164f30da..23f43ee06 100644 --- a/Core/ModuleInstaller.cs +++ b/Core/ModuleInstaller.cs @@ -110,8 +110,8 @@ public static string CachedOrDownload(CkanModule module, string? userAgent, NetM /// private void DownloadModules(IEnumerable mods, IDownloader downloader) { - List downloads = mods.Where(module => !Cache.IsCached(module)).ToList(); - + var downloads = mods.Where(module => !module.IsMetapackage && !Cache.IsCached(module)) + .ToList(); if (downloads.Count > 0) { downloader.DownloadModules(downloads); @@ -166,7 +166,9 @@ public void InstallList(ICollection modules, foreach (var module in modsToInstall) { User.RaiseMessage(" * {0}", Cache.DescribeAvailability(module)); - if (!Cache.IsMaybeCachedZip(module)) + // Alert about attempts to install DLC before downloading or installing anything + CheckKindInstallationKraken(module); + if (!module.IsMetapackage && !Cache.IsMaybeCachedZip(module)) { downloads.Add(module); } @@ -242,8 +244,7 @@ private void Install(CkanModule module, bool autoInstalled, Registry registry, ref HashSet? possibleConfigOnlyDirs, - IProgress? progress, - string? filename = null) + IProgress? progress) { CheckKindInstallationKraken(module); var version = registry.InstalledVersion(module.identifier); @@ -256,15 +257,19 @@ private void Install(CkanModule module, return; } - // Find ZIP in the cache if we don't already have it. - filename ??= Cache.GetCachedFilename(module); - - // If we *still* don't have a file, then kraken bitterly. - if (filename == null) + string? filename = null; + if (!module.IsMetapackage) { - throw new FileNotFoundKraken(null, - string.Format(Properties.Resources.ModuleInstallerZIPNotInCache, - module)); + // Find ZIP in the cache if we don't already have it. + filename ??= Cache.GetCachedFilename(module); + + // If we *still* don't have a file, then kraken bitterly. + if (filename == null) + { + throw new FileNotFoundKraken(null, + string.Format(Properties.Resources.ModuleInstallerZIPNotInCache, + module)); + } } using (var transaction = CkanTransaction.CreateTransactionScope()) @@ -285,19 +290,14 @@ private void Install(CkanModule module, // Fire our callback that we've installed a module, if we have one. onReportModInstalled?.Invoke(module); - } /// - /// Check if the given module is a metapackage: + /// Check if the given module is a DLC: /// if it is, throws a BadCommandKraken. /// private static void CheckKindInstallationKraken(CkanModule module) { - if (module.IsMetapackage) - { - throw new BadCommandKraken(Properties.Resources.ModuleInstallerMetapackage); - } if (module.IsDLC) { throw new BadCommandKraken(Properties.Resources.ModuleInstallerDLC); @@ -313,14 +313,18 @@ private static void CheckKindInstallationKraken(CkanModule module) /// Propagates a FileExistsKraken if we were going to overwrite a file. /// private List InstallModule(CkanModule module, - string zip_filename, + string? zip_filename, Registry registry, ref HashSet? possibleConfigOnlyDirs, IProgress? moduleProgress) { - CheckKindInstallationKraken(module); var createdPaths = new List(); - + if (module.IsMetapackage || zip_filename == null) + { + // It's OK to include metapackages in changesets, + // but there's no work to do for them + return createdPaths; + } using (ZipFile zipfile = new ZipFile(zip_filename)) { var filters = ServiceLocator.Container.Resolve().GlobalInstallFilters @@ -1057,23 +1061,22 @@ public HashSet AddParentDirectories(HashSet directories) /// This *will* save the registry. /// /// Modules to add + /// true or false for each item in `add` /// Modules to remove /// true if newly installed modules should be marked auto-installed, false otherwise private void AddRemove(ref HashSet? possibleConfigOnlyDirs, RegistryManager registry_manager, - IEnumerable add, - IEnumerable remove, - bool enforceConsistency, - bool newModulesAreAutoInstalled) + ICollection add, + ICollection autoInstalled, + ICollection remove, + bool enforceConsistency) { // TODO: We should do a consistency check up-front, rather than relying // upon our registry catching inconsistencies at the end. using (var tx = CkanTransaction.CreateTransactionScope()) { - remove = remove.Memoize(); - add = add.Memoize(); - int totSteps = remove.Count() + add.Count(); + int totSteps = remove.Count + add.Count; int step = 0; foreach (InstalledModule instMod in remove) { @@ -1085,7 +1088,7 @@ private void AddRemove(ref HashSet? possibleConfigOnlyDirs, Uninstall(instMod.Module.identifier, ref possibleConfigOnlyDirs, registry_manager.registry); } - foreach (CkanModule module in add) + foreach ((CkanModule module, bool autoInst) in add.Zip(autoInstalled)) { var previous = remove?.FirstOrDefault(im => im.Module.identifier == module.identifier); int percent_complete = (step++ * 70) / totSteps; @@ -1095,7 +1098,7 @@ private void AddRemove(ref HashSet? possibleConfigOnlyDirs, // For upgrading, new modules are dependencies and should be marked auto-installed, // for replacing, new modules are the replacements and should not be marked auto-installed Install(module, - previous?.AutoInstalled ?? newModulesAreAutoInstalled, + previous?.AutoInstalled ?? autoInst, registry_manager.registry, ref possibleConfigOnlyDirs, null); @@ -1116,7 +1119,7 @@ private void AddRemove(ref HashSet? possibleConfigOnlyDirs, /// Will *re-install* or *downgrade* (with a warning) as well as upgrade. /// Throws ModuleNotFoundKraken if a module is not installed. /// - public void Upgrade(IEnumerable modules, + public void Upgrade(ICollection modules, IDownloader netAsyncDownloader, ref HashSet? possibleConfigOnlyDirs, RegistryManager registry_manager, @@ -1124,8 +1127,8 @@ public void Upgrade(IEnumerable modules, bool resolveRelationships = false, bool ConfirmPrompt = true) { - modules = modules.Memoize(); var registry = registry_manager.registry; + var autoInstalled = modules.ToDictionary(m => m, m => true); if (resolveRelationships) { @@ -1135,9 +1138,9 @@ public void Upgrade(IEnumerable modules, .OfType(), RelationshipResolverOptions.DependsOnlyOpts(), registry, - instance.VersionCriteria() - ); - modules = resolver.ModList().Memoize(); + instance.VersionCriteria()); + modules = resolver.ModList().ToArray(); + autoInstalled = modules.ToDictionary(m => m, resolver.IsAutoInstalled); } User.RaiseMessage(Properties.Resources.ModuleInstallerAboutToUpgrade); @@ -1258,9 +1261,9 @@ public void Upgrade(IEnumerable modules, AddRemove(ref possibleConfigOnlyDirs, registry_manager, modules, + modules.Select(m => autoInstalled[m]).ToArray(), to_remove, - enforceConsistency, - true); + enforceConsistency); User.RaiseProgress(Properties.Resources.ModuleInstallerDone, 100); } @@ -1353,14 +1356,12 @@ public void Replace(IEnumerable replacements, } var resolver = new RelationshipResolver(modsToInstall, null, options, registry_manager.registry, instance.VersionCriteria()); var resolvedModsToInstall = resolver.ModList().ToList(); - AddRemove( - ref possibleConfigOnlyDirs, - registry_manager, - resolvedModsToInstall, - modsToRemove, - enforceConsistency, - false - ); + AddRemove(ref possibleConfigOnlyDirs, + registry_manager, + resolvedModsToInstall, + resolvedModsToInstall.Select(m => false).ToArray(), + modsToRemove, + enforceConsistency); User.RaiseProgress(Properties.Resources.ModuleInstallerDone, 100); } diff --git a/Core/Relationships/RelationshipResolver.cs b/Core/Relationships/RelationshipResolver.cs index 287aaee75..40c6aeb91 100644 --- a/Core/Relationships/RelationshipResolver.cs +++ b/Core/Relationships/RelationshipResolver.cs @@ -421,11 +421,6 @@ private string conflictingModDescription(CkanModule? mod, CkanModule? parent) /// private void Add(CkanModule module, SelectionReason reason) { - if (module.IsMetapackage) - { - AddReason(module, reason); - return; - } if (module.IsDLC) { throw new ModuleIsDLCKraken(module); diff --git a/GUI/Controls/ManageMods.cs b/GUI/Controls/ManageMods.cs index 3281e957f..61a3df718 100644 --- a/GUI/Controls/ManageMods.cs +++ b/GUI/Controls/ManageMods.cs @@ -1000,6 +1000,12 @@ private void guiModule_PropertyChanged(object? sender, PropertyChangedEventArgs? RegistryManager.Instance(currentInstance, repoData).registry); }); break; + + case "IsAutoInstalled": + // Update the changeset + UpdateChangeSetAndConflicts(currentInstance, + RegistryManager.Instance(currentInstance, repoData).registry); + break; } } } diff --git a/GUI/Main/MainChangeset.cs b/GUI/Main/MainChangeset.cs index 537ef6757..cfe322c87 100644 --- a/GUI/Main/MainChangeset.cs +++ b/GUI/Main/MainChangeset.cs @@ -46,7 +46,7 @@ private void Changeset_OnConfirmChanges(List changeset) { Wait.StartWaiting(InstallMods, PostInstallMods, true, new InstallArgument( - // Only pass along user requested mods, so auto-installed can be determined + // Only pass along user requested mods, so auto-installed can be determined changeset.Where(ch => ch.Reasons.Any(r => r is SelectionReason.UserRequested) // Include all removes and upgrades || ch.ChangeType != GUIModChangeType.Install) diff --git a/GUI/Model/GUIMod.cs b/GUI/Model/GUIMod.cs index da908f4e4..947494855 100644 --- a/GUI/Model/GUIMod.cs +++ b/GUI/Model/GUIMod.cs @@ -56,7 +56,7 @@ private void OnPropertyChanged([CallerMemberName] string? name = null) public string Name { get; private set; } public bool IsInstalled { get; private set; } - public bool IsAutoInstalled { get; private set; } + public bool IsAutoInstalled => InstalledMod?.AutoInstalled ?? false; public bool HasUpdate { get; set; } public bool HasReplacement { get; private set; } public bool IsIncompatible { get; private set; } @@ -128,7 +128,6 @@ public GUIMod(InstalledModule instMod, InstalledMod = instMod; selectedMod = registry.GetModuleByVersion(instMod.identifier, instMod.Module.version) ?? instMod.Module; - IsAutoInstalled = instMod.AutoInstalled; InstallDate = instMod.InstallTime; InstalledVersion = instMod.Module.version.ToString(hideEpochs, hideV); @@ -342,8 +341,8 @@ public void SetAutoInstallChecked(DataGridViewRow row, DataGridViewColumn col, b var old_value = (bool) auto_cell.Value; bool value = set_value_to ?? old_value; - IsAutoInstalled = value; InstalledMod.AutoInstalled = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsAutoInstalled")); if (old_value != value) { diff --git a/GUI/Model/ModList.cs b/GUI/Model/ModList.cs index 6cce0b361..a2cf963fc 100644 --- a/GUI/Model/ModList.cs +++ b/GUI/Model/ModList.cs @@ -214,7 +214,6 @@ public Tuple, Dictionary, List !m.IsMetapackage) .Select(m => new ModChange(m, GUIModChangeType.Install, resolver.ReasonsFor(m)))), resolver.ConflictList, resolver.ConflictDescriptions.ToList()); diff --git a/Netkan/Transformers/GithubTransformer.cs b/Netkan/Transformers/GithubTransformer.cs index a7eb53b30..edde9565e 100644 --- a/Netkan/Transformers/GithubTransformer.cs +++ b/Netkan/Transformers/GithubTransformer.cs @@ -173,6 +173,14 @@ private Metadata TransformOne(Metadata metadata, json.SafeAdd("author", () => GetAuthors(ghRepo, ghRelease)); json.Remove("$kref"); json.SafeAdd("download", ghAsset.Download.ToString()); + if (ghRef.UseSourceArchive) + { + // https://docs.github.com/en/rest/repos/contents#download-a-repository-archive-zip + // GitHub requires clients to specify JSON format + // to download a source ZIP (!!!) + json.SafeAdd("download_content_type", + "application/vnd.github+json"); + } json.SafeAdd(Metadata.UpdatedPropertyName, ghAsset.Updated); if (ghRef.Project.Contains('_')) diff --git a/Tests/Core/ModuleInstallerTests.cs b/Tests/Core/ModuleInstallerTests.cs index e5f8929ea..e5e76762d 100644 --- a/Tests/Core/ModuleInstallerTests.cs +++ b/Tests/Core/ModuleInstallerTests.cs @@ -1226,7 +1226,8 @@ public void Upgrade_WithAutoInst_RemovesAutoRemovable(string[] regularMods, // Act installer.Upgrade(upgradeIdentifiers.Select(ident => registry.LatestAvailable(ident, inst.KSP.VersionCriteria())) - .OfType(), + .OfType() + .ToArray(), downloader, ref possibleConfigOnlyDirs, regMgr, false); // Assert