• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

KSP-CKAN / CKAN / 17179122935

23 Aug 2025 07:06PM UTC coverage: 58.484% (+2.2%) from 56.332%
17179122935

push

github

HebaruSan
Merge #4420 More tests and fixes

4747 of 8436 branches covered (56.27%)

Branch coverage included in aggregate %.

70 of 132 new or added lines in 17 files covered. (53.03%)

40 existing lines in 3 files now uncovered.

10050 of 16865 relevant lines covered (59.59%)

1.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

24.94
/GUI/Model/ModList.cs
1
using System;
2
using System.Drawing;
3
using System.Collections.Generic;
4
using System.Collections.ObjectModel;
5
using System.Linq;
6
using System.Windows.Forms;
7
#if NET5_0_OR_GREATER
8
using System.Runtime.Versioning;
9
#endif
10

11
using Autofac;
12
using log4net;
13

14
using CKAN.Configuration;
15
using CKAN.Versioning;
16
using CKAN.Games;
17

18
namespace CKAN.GUI
19
{
20
    /// <summary>
21
    /// The holder of the list of mods to be shown.
22
    /// Should be a pure data model and avoid UI stuff, but it's not there yet.
23
    /// </summary>
24
    #if NET5_0_OR_GREATER
25
    [SupportedOSPlatform("windows")]
26
    #endif
27
    public class ModList
28
    {
29
        //identifier, row
30
        internal Dictionary<string, DataGridViewRow> full_list_of_mod_rows = new Dictionary<string, DataGridViewRow>();
2✔
31

32
        public event Action? ModFiltersUpdated;
33
        public IReadOnlyCollection<GUIMod> Modules { get; private set; } =
34
            new ReadOnlyCollection<GUIMod>(new List<GUIMod>());
2✔
35
        public bool HasAnyInstalled { get; private set; }
36

37
        // Unlike GUIMod.IsInstalled, DataGridViewRow.Visible can change on the fly without notifying us
38
        public bool HasVisibleInstalled()
39
            => full_list_of_mod_rows.Values.Any(row => ((row.Tag as GUIMod)?.IsInstalled ?? false)
×
40
                                                       && row.Visible);
41

42
        private List<ModSearch>? activeSearches = null;
2✔
43

44
        public void SetSearches(List<ModSearch> newSearches)
45
        {
×
46
            if (!SearchesEqual(activeSearches, newSearches)
×
47
                && Main.Instance?.configuration is GUIConfiguration cfg)
48
            {
×
49
                activeSearches = newSearches;
×
50
                cfg.DefaultSearches = activeSearches?.Select(s => s?.Combined ?? "")
×
51
                                                     .ToList()
52
                                                    ?? new List<string>() { "" };
53
                ModFiltersUpdated?.Invoke();
×
54
            }
×
55
        }
×
56

57
        private static bool SearchesEqual(List<ModSearch>? a, List<ModSearch>? b)
58
            => a == null ? b == null
×
59
                         : b != null && a.SequenceEqual(b);
60

61
        private static string FilterName(GUIModFilter filter,
62
                                         ModuleTag?   tag   = null,
63
                                         ModuleLabel? label = null)
NEW
64
            => filter switch
×
65
               {
NEW
66
                   GUIModFilter.Compatible               => Properties.Resources.MainFilterCompatible,
×
NEW
67
                   GUIModFilter.Incompatible             => Properties.Resources.MainFilterIncompatible,
×
NEW
68
                   GUIModFilter.Installed                => Properties.Resources.MainFilterInstalled,
×
NEW
69
                   GUIModFilter.NotInstalled             => Properties.Resources.MainFilterNotInstalled,
×
NEW
70
                   GUIModFilter.InstalledUpdateAvailable => Properties.Resources.MainFilterUpgradeable,
×
NEW
71
                   GUIModFilter.Replaceable              => Properties.Resources.MainFilterReplaceable,
×
NEW
72
                   GUIModFilter.Cached                   => Properties.Resources.MainFilterCached,
×
NEW
73
                   GUIModFilter.Uncached                 => Properties.Resources.MainFilterUncached,
×
NEW
74
                   GUIModFilter.NewInRepository          => Properties.Resources.MainFilterNew,
×
NEW
75
                   GUIModFilter.All                      => Properties.Resources.MainFilterAll,
×
NEW
76
                   GUIModFilter.CustomLabel              => string.Format(Properties.Resources.MainFilterLabel,
×
77
                                                                          label?.Name ?? "CUSTOM"),
NEW
78
                   GUIModFilter.Tag                      => tag == null
×
79
                                                                ? Properties.Resources.MainFilterUntagged
80
                                                                : string.Format(Properties.Resources.MainFilterTag,
81
                                                                                tag.Name),
NEW
82
                   _                                     => "",
×
83
               };
84

85
        public static SavedSearch FilterToSavedSearch(GameInstance instance,
86
                                                      GUIModFilter filter,
87
                                                      ModuleTag?   tag   = null,
88
                                                      ModuleLabel? label = null)
89
            => new SavedSearch()
×
90
            {
91
                Name   = FilterName(filter, tag, label),
92
                Values = new List<string>() { new ModSearch(ModuleLabelList.ModuleLabels, instance, filter, tag, label).Combined ?? "" },
93
            };
94

95
        private static RelationshipResolverOptions conflictOptions(StabilityToleranceConfig stabilityTolerance)
96
            => new RelationshipResolverOptions(stabilityTolerance)
×
97
            {
98
                without_toomanyprovides_kraken = true,
99
                proceed_with_inconsistencies   = true,
100
                without_enforce_consistency    = true,
101
                with_recommends                = false
102
            };
103

104
        /// <summary>
105
        /// Returns a changeset and conflicts based on the selections of the user.
106
        /// </summary>
107
        /// <param name="registry">The registry for getting available mods</param>
108
        /// <param name="changeSet">User's choices of installation and removal</param>
109
        /// <param name="game">Game of the game instance</param>
110
        /// <param name="stabilityTolerance">Prerelease configuration</param>
111
        /// <param name="version">The version of the current game instance</param>
112
        public Tuple<ICollection<ModChange>, Dictionary<CkanModule, string>, List<string>> ComputeFullChangeSetFromUserChangeSet(
113
            IRegistryQuerier         registry,
114
            HashSet<ModChange>       changeSet,
115
            IGame                    game,
116
            StabilityToleranceConfig stabilityTolerance,
117
            GameVersionCriteria      version)
118
        {
×
119
            var modules_to_install = new List<CkanModule>();
×
120
            var modules_to_remove = new HashSet<CkanModule>();
×
121
            var extraInstalls = new HashSet<CkanModule>();
×
122

123
            changeSet.UnionWith(changeSet.Where(ch => ch.ChangeType == GUIModChangeType.Replace)
×
124
                                         .Select(ch => registry.GetReplacement(ch.Mod, stabilityTolerance, version))
×
125
                                         .OfType<ModuleReplacement>()
126
                                         .GroupBy(repl => repl.ReplaceWith)
×
127
                                         .Select(grp => new ModChange(grp.Key, GUIModChangeType.Install,
×
128
                                                                      grp.Select(repl => new SelectionReason.Replacement(repl.ToReplace)),
×
129
                                                                      ServiceLocator.Container.Resolve<IConfiguration>()))
130
                                         .ToHashSet());
131

132
            foreach (var change in changeSet)
×
133
            {
×
134
                switch (change.ChangeType)
×
135
                {
136
                    case GUIModChangeType.None:
137
                        break;
×
138
                    case GUIModChangeType.Update:
139
                        var mod = (change as ModUpgrade)?.targetMod ?? change.Mod;
×
140
                        modules_to_install.Add(mod);
×
141
                        extraInstalls.Add(mod);
×
142
                        break;
×
143
                    case GUIModChangeType.Install:
144
                        modules_to_install.Add(change.Mod);
×
145
                        break;
×
146
                    case GUIModChangeType.Remove:
147
                        modules_to_remove.Add(change.Mod);
×
148
                        break;
×
149
                    case GUIModChangeType.Replace:
150
                        if (registry.GetReplacement(change.Mod, stabilityTolerance, version) is ModuleReplacement repl)
×
151
                        {
×
152
                            modules_to_remove.Add(repl.ToReplace);
×
153
                            extraInstalls.Add(repl.ReplaceWith);
×
154
                        }
×
155
                        break;
×
156
                    default:
157
                        throw new ArgumentOutOfRangeException(nameof(change),
×
158
                                                              change.ChangeType.ToString());
159
                }
160
            }
×
161

162
            var installed_modules = registry.InstalledModules.ToDictionary(
×
163
                imod => imod.Module.identifier,
×
164
                imod => imod.Module);
×
165

166
            foreach (var dependent in registry.FindReverseDependencies(
×
167
                modules_to_remove
168
                    .Select(mod => mod.identifier)
×
169
                    .Except(modules_to_install.Select(m => m.identifier))
×
170
                    .ToList(),
171
                modules_to_install))
172
            {
×
173
                if (!changeSet.Any(ch => ch.ChangeType == GUIModChangeType.Replace
×
174
                                      && ch.Mod.identifier == dependent)
175
                    && installed_modules.TryGetValue(dependent, out CkanModule? depMod)
176
                    && (registry.GetModuleByVersion(depMod.identifier, depMod.version)
177
                        ?? registry.InstalledModule(dependent)?.Module)
178
                        is CkanModule modByVer)
179
                {
×
180
                    changeSet.Add(new ModChange(modByVer, GUIModChangeType.Remove,
×
181
                                                new SelectionReason.DependencyRemoved(),
182
                                                ServiceLocator.Container.Resolve<IConfiguration>()));
183
                    modules_to_remove.Add(modByVer);
×
184
                }
×
185
            }
×
186

187
            foreach (var im in registry.FindRemovableAutoInstalled(
×
188
                InstalledAfterChanges(registry, changeSet).ToArray(),
189
                Array.Empty<CkanModule>(), game, stabilityTolerance, version))
190
            {
×
191
                changeSet.Add(new ModChange(im.Module, GUIModChangeType.Remove, new SelectionReason.NoLongerUsed(),
×
192
                                            ServiceLocator.Container.Resolve<IConfiguration>()));
193
                modules_to_remove.Add(im.Module);
×
194
            }
×
195

196
            // Get as many dependencies as we can, but leave decisions and prompts for installation time
197
            var resolver = new RelationshipResolver(
×
198
                modules_to_install, modules_to_remove,
199
                conflictOptions(stabilityTolerance), registry, game, version);
200

201
            // Replace Install entries in changeset with the ones from resolver to get all the reasons
202
            return new Tuple<ICollection<ModChange>, Dictionary<CkanModule, string>, List<string>>(
×
203
                changeSet.Where(ch => !(ch.ChangeType is GUIModChangeType.Install
×
204
                                        // Leave in replacements
205
                                        && !ch.Reasons.Any(r => r is SelectionReason.Replacement)))
×
206
                         .OrderBy(ch => ch.Mod.identifier)
×
207
                         .Union(resolver.ModList()
208
                                        // Changeset already contains changes for these
209
                                        .Except(extraInstalls)
210
                                        .Select(m => new ModChange(m, GUIModChangeType.Install, resolver.ReasonsFor(m),
×
211
                                                                   ServiceLocator.Container.Resolve<IConfiguration>())))
212
                         .ToArray(),
213
                resolver.ConflictList,
214
                resolver.ConflictDescriptions.ToList());
215
        }
×
216

217
        /// <summary>
218
        /// Get the InstalledModules that we'll have after the changeset,
219
        /// not including dependencies
220
        /// </summary>
221
        /// <param name="registry">Registry with currently installed modules</param>
222
        /// <param name="changeSet">Changes to be made to the installed modules</param>
223
        private static IEnumerable<InstalledModule> InstalledAfterChanges(
224
            IRegistryQuerier               registry,
225
            IReadOnlyCollection<ModChange> changeSet)
226
        {
2✔
227
            var removingIdents = changeSet
2✔
228
                .Where(ch => ch.ChangeType != GUIModChangeType.Install)
2✔
229
                .Select(ch => ch.Mod.identifier)
×
230
                .ToHashSet();
231
            return registry.InstalledModules
2✔
232
                .Where(im => !removingIdents.Contains(im.identifier))
×
233
                .Concat(changeSet
234
                    .Where(ch => ch.ChangeType is not GUIModChangeType.Remove
2!
235
                                              and not GUIModChangeType.Replace)
236
                    .Select(ch => new InstalledModule(
2✔
237
                        null,
238
                        (ch as ModUpgrade)?.targetMod ?? ch.Mod,
239
                        Enumerable.Empty<string>(),
240
                        false)));
241
        }
2✔
242

243
        public bool IsVisible(GUIMod mod, string instanceName, IGame game, Registry registry)
244
            => (activeSearches?.Any(s => s?.Matches(mod) ?? true) ?? true)
1!
245
                && !HiddenByTagsOrLabels(mod, instanceName, game, registry);
246

247
        private bool TagInSearches(ModuleTag tag)
248
            => activeSearches?.Any(s => s?.TagNames.Contains(tag.Name) ?? false) ?? false;
×
249

250
        private bool LabelInSearches(ModuleLabel label)
251
            => activeSearches?.Any(s => s?.LabelNames.Contains(label.Name) ?? false) ?? false;
1!
252

253
        private bool HiddenByTagsOrLabels(GUIMod m, string instanceName, IGame game, Registry registry)
254
            // "Hide" labels apply to all non-custom filters
255
            => (ModuleLabelList.ModuleLabels?.LabelsFor(instanceName)
2!
256
                                             .Where(l => !LabelInSearches(l) && l.Hide)
2!
257
                                             .Any(l => l.ContainsModule(game, m.Identifier))
2✔
258
                                            ?? false)
259
               || (registry.Tags?.Values
260
                                 .Where(t => !TagInSearches(t) && ModuleTagList.ModuleTags.HiddenTags.Contains(t.Name))
×
261
                                 .Any(t => t.ModuleIdentifiers.Contains(m.Identifier))
×
262
                                ?? false);
263

264
        public int CountModsBySearches(List<ModSearch> searches)
265
            => Modules.Count(mod => searches?.Any(s => s?.Matches(mod) ?? true) ?? true);
1!
266

267
        public int CountModsByFilter(GameInstance inst, GUIModFilter filter)
268
            => CountModsBySearches(new List<ModSearch>() { new ModSearch(ModuleLabelList.ModuleLabels, inst, filter, null, null) });
2✔
269

270
        /// <summary>
271
        /// Constructs the mod list suitable for display to the user.
272
        /// Manipulates <c>full_list_of_mod_rows</c>.
273
        /// </summary>
274
        /// <param name="modules">A list of modules that may require updating</param>
275
        /// <param name="instanceName">Name of the game instance for getting labels</param>
276
        /// <param name="game">Game of the game instance</param>
277
        /// <param name="mc">Changes the user has made</param>
278
        /// <returns>The mod list</returns>
279
        public IEnumerable<DataGridViewRow> ConstructModList(IReadOnlyCollection<GUIMod> modules,
280
                                                             string?                     instanceName,
281
                                                             IGame                       game,
282
                                                             IEnumerable<ModChange>?     mc = null)
283
        {
2✔
284
            Modules = modules;
2✔
285
            var changes = mc?.ToList();
2✔
286
            full_list_of_mod_rows = Modules.AsParallel()
2✔
287
                                           .ToDictionary(gm => gm.Identifier,
2✔
288
                                                         gm => MakeRow(gm, changes, instanceName, game));
2✔
289
            HasAnyInstalled = Modules.Any(m => m.IsInstalled);
2✔
290
            return full_list_of_mod_rows.Values;
2✔
291
        }
2✔
292

293
        private DataGridViewRow MakeRow(GUIMod mod, List<ModChange>? changes, string? instanceName, IGame game)
294
        {
2✔
295
            DataGridViewRow item = new DataGridViewRow() {Tag = mod};
2✔
296

297
            item.DefaultCellStyle.BackColor = GetRowBackground(mod, false, instanceName, game);
2✔
298
            item.DefaultCellStyle.ForeColor = item.DefaultCellStyle.BackColor.ForeColorForBackColor()
2✔
299
                                              ?? SystemColors.WindowText;
300
            item.DefaultCellStyle.SelectionBackColor = SelectionBlend(item.DefaultCellStyle.BackColor);
2✔
301
            item.DefaultCellStyle.SelectionForeColor = item.DefaultCellStyle.SelectionBackColor.ForeColorForBackColor()
2✔
302
                                                       ?? SystemColors.HighlightText;
303

304
            var myChange = changes?.FindLast(ch => ch.Mod.Equals(mod));
1✔
305

306
            var selecting = mod.IsAutodetected
2✔
307
                ? new DataGridViewTextBoxCell()
308
                {
309
                    Value = Properties.Resources.MainModListAutoDetected
310
                }
311
                : mod.IsInstallable()
312
                ? (DataGridViewCell) new DataGridViewCheckBoxCell()
313
                {
314
                    Value = myChange == null
315
                        ? mod.IsInstalled
316
                        : myChange.ChangeType == GUIModChangeType.Install
317
                          || (myChange.ChangeType != GUIModChangeType.Remove && mod.IsInstalled)
318
                }
319
                : new DataGridViewTextBoxCell()
320
                {
321
                    Value = "-"
322
                };
323

324
            var autoInstalled = mod.IsInstalled && !mod.IsAutodetected
2✔
325
                ? (DataGridViewCell) new DataGridViewCheckBoxCell()
326
                {
327
                    Value = mod.IsAutoInstalled,
328
                    ToolTipText = Properties.Resources.MainModListAutoInstalledToolTip,
329
                }
330
                : new DataGridViewTextBoxCell()
331
                {
332
                    Value = "-"
333
                };
334

335
            var updating = mod.IsInstallable() && mod.HasUpdate
2✔
336
                ? (DataGridViewCell) new DataGridViewCheckBoxCell()
337
                {
338
                    Value = myChange?.ChangeType == GUIModChangeType.Update
339
                }
340
                : new DataGridViewTextBoxCell()
341
                {
342
                    Value = "-"
343
                };
344

345
            var replacing = (mod.IsInstalled && mod.HasReplacement)
2✔
346
                ? (DataGridViewCell) new DataGridViewCheckBoxCell()
347
                {
348
                    Value = myChange?.ChangeType == GUIModChangeType.Replace
349
                }
350
                : new DataGridViewTextBoxCell()
351
                {
352
                    Value = "-"
353
                };
354

355
            var name   = new DataGridViewTextBoxCell { Value = ToGridText(mod.Name)                       };
2✔
356
            var author = new DataGridViewTextBoxCell { Value = ToGridText(string.Join(", ", mod.Authors)) };
2✔
357

358
            var installVersion = new DataGridViewTextBoxCell()
2✔
359
            {
360
                Value = mod.InstalledVersion
361
            };
362

363
            var latestVersion = new DataGridViewTextBoxCell()
2✔
364
            {
365
                Value = mod.LatestVersion
366
            };
367

368
            var downloadCount = new DataGridViewTextBoxCell { Value = $"{mod.DownloadCount:N0}"   };
2✔
369
            var compat        = new DataGridViewTextBoxCell { Value = mod.GameCompatibility       };
2✔
370
            var downloadSize  = new DataGridViewTextBoxCell { Value = mod.DownloadSize            };
2✔
371
            var installSize   = new DataGridViewTextBoxCell { Value = mod.InstallSize             };
2✔
372
            var releaseDate   = new DataGridViewTextBoxCell { Value = mod.ToModule().release_date };
2✔
373
            var installDate   = new DataGridViewTextBoxCell { Value = mod.InstallDate             };
2✔
374
            var desc          = new DataGridViewTextBoxCell { Value = ToGridText(mod.Abstract)    };
2✔
375

376
            item.Cells.AddRange(selecting, autoInstalled, updating, replacing, name, author, installVersion, latestVersion, compat, downloadSize, installSize, releaseDate, installDate, downloadCount, desc);
2✔
377

378
            selecting.ReadOnly     = selecting     is DataGridViewTextBoxCell;
2✔
379
            autoInstalled.ReadOnly = autoInstalled is DataGridViewTextBoxCell;
2✔
380
            updating.ReadOnly      = updating      is DataGridViewTextBoxCell;
2✔
381

382
            return item;
2✔
383
        }
2✔
384

385
        private static string ToGridText(string text)
386
            => Platform.IsMono ? text.Replace("&", "&&") : text;
2!
387

388
        public Color GetRowBackground(GUIMod mod, bool conflicted, string? instanceName, IGame game)
389
            => conflicted           ? conflictColor
2✔
390
             : instanceName != null ? Util.BlendColors(
391
                                          ModuleLabelList.ModuleLabels.LabelsFor(instanceName)
392
                                                                      .Where(l => l.ContainsModule(game, mod.Identifier))
2✔
393
                                                                      .Select(l => l.Color)
×
394
                                                                      .OfType<Color>()
395
                                                                      // No transparent blending
396
                                                                      .Where(c => c.A == byte.MaxValue)
×
397
                                                                      .ToArray())
398
             : Color.Transparent;
399

400
        private static readonly Color conflictColor = Color.FromArgb(255, 64, 64);
2✔
401

402
        /// <summary>
403
        /// Update the color and visible state of the given row
404
        /// after it has been added to or removed from a label group
405
        /// </summary>
406
        /// <param name="mod">The mod that needs an update</param>
407
        /// <param name="conflicted">True if mod should have a red background</param>
408
        /// <param name="instanceName">Name of the game instance for finding labels</param>
409
        /// <param name="game">Game of game instance for finding labels</param>
410
        /// <param name="registry">Registry for finding mods</param>
411
        public DataGridViewRow? ReapplyLabels(GUIMod mod, bool conflicted,
412
                                             string instanceName, IGame game, Registry registry)
413
        {
×
414
            if (full_list_of_mod_rows.TryGetValue(mod.Identifier, out DataGridViewRow? row))
×
415
            {
×
416
                row.DefaultCellStyle.BackColor = GetRowBackground(mod, conflicted, instanceName, game);
×
417
                row.DefaultCellStyle.ForeColor = row.DefaultCellStyle.BackColor.ForeColorForBackColor()
×
418
                                                 ?? SystemColors.WindowText;
419
                row.DefaultCellStyle.SelectionBackColor = SelectionBlend(row.DefaultCellStyle.BackColor);
×
420
                row.DefaultCellStyle.SelectionForeColor = row.DefaultCellStyle.SelectionBackColor.ForeColorForBackColor()
×
421
                                                          ?? SystemColors.HighlightText;
422
                row.Visible = IsVisible(mod, instanceName, game, registry);
×
423
                return row;
×
424
            }
425
            return null;
×
426
        }
×
427

428
        private static Color SelectionBlend(Color c)
429
            => c == Color.Empty
2✔
430
                ? SystemColors.Highlight
431
                : SystemColors.Highlight.AlphaBlendWith(selectionAlpha, c);
432

433
        private const float selectionAlpha = 0.4f;
434

435
        private static IEnumerable<ModChange> rowChanges(IRegistryQuerier    registry,
436
                                                         DataGridViewRow     row,
437
                                                         DataGridViewColumn? upgradeCol,
438
                                                         DataGridViewColumn? replaceCol)
439
            => row.Tag is GUIMod gmod ? gmod.GetModChanges(
2!
440
                   upgradeCol != null && upgradeCol.Visible
441
                   && row.Cells[upgradeCol.Index] is DataGridViewCheckBoxCell upgradeCell
442
                   && (bool)upgradeCell.Value,
443
                   replaceCol != null && replaceCol.Visible
444
                   && row.Cells[replaceCol.Index] is DataGridViewCheckBoxCell replaceCell
445
                   && (bool)replaceCell.Value,
446
                   registry.MetadataChanged(gmod.Identifier))
447
               : Enumerable.Empty<ModChange>();
448

449
        public HashSet<ModChange> ComputeUserChangeSet(IRegistryQuerier     registry,
450
                                                       GameVersionCriteria  crit,
451
                                                       GameInstance         instance,
452
                                                       DataGridViewColumn?  upgradeCol,
453
                                                       DataGridViewColumn?  replaceCol)
454
        {
2✔
455
            log.Debug("Computing user changeset");
2✔
456
            var modChanges = (full_list_of_mod_rows?.Values
2✔
457
                                                    .SelectMany(row => rowChanges(registry, row, upgradeCol, replaceCol))
2✔
458
                                                   ?? Enumerable.Empty<ModChange>())
459
                                                   .ToList();
460

461
            // Inter-mod dependencies can block some upgrades, which can sometimes but not always
462
            // be overcome by upgrading both mods. Try to pick the right target versions.
463
            var upgrades = modChanges.OfType<ModUpgrade>()
2✔
464
                                     // Skip reinstalls
465
                                     .Where(upg => upg.Mod != upg.targetMod)
×
466
                                     .ToArray();
467
            if (upgrades.Length > 0)
2!
468
            {
×
469
                var upgradeable = registry.CheckUpgradeable(instance,
×
470
                                                            // Hold identifiers not chosen for upgrading
471
                                                            registry.Installed(false)
472
                                                                    .Select(kvp => kvp.Key)
×
473
                                                                    .Except(upgrades.Select(ch => ch.Mod.identifier))
×
474
                                                                    .ToHashSet(),
475
                                                            ModuleLabelList.ModuleLabels
476
                                                                           .IgnoreMissingIdentifiers(instance)
477
                                                                           .ToHashSet())
478
                                          [true]
479
                                          .ToDictionary(m => m.identifier,
×
480
                                                        m => m);
×
481
                foreach (var change in upgrades)
×
482
                {
×
483
                    change.targetMod = upgradeable.TryGetValue(change.Mod.identifier,
×
484
                                                               out CkanModule? allowedMod)
485
                        // Upgrade to the version the registry says we should
486
                        ? allowedMod
487
                        // Not upgradeable!
488
                        : change.Mod;
489
                    if (change.Mod == change.targetMod)
×
490
                    {
×
491
                        // This upgrade was voided by dependencies or conflicts
492
                        modChanges.Remove(change);
×
493
                    }
×
494
                }
×
495
            }
×
496

497
            return modChanges.Union(
2✔
498
                    registry.FindRemovableAutoInstalled(InstalledAfterChanges(registry, modChanges).ToArray(),
499
                                                        Array.Empty<CkanModule>(),
500
                                                        instance.game, instance.StabilityToleranceConfig, crit)
501
                        .Select(im => new ModChange(
×
502
                            im.Module, GUIModChangeType.Remove,
503
                            new SelectionReason.NoLongerUsed(),
504
                            ServiceLocator.Container.Resolve<IConfiguration>())))
505
                .ToHashSet();
506
        }
2✔
507

508
        /// <summary>
509
        /// Check upgradeability of all rows and set GUIMod.HasUpdate appropriately
510
        /// </summary>
511
        /// <param name="inst">Current game instance</param>
512
        /// <param name="registry">Current instance's registry</param>
513
        /// <param name="ChangeSet">Currently pending changeset</param>
514
        /// <param name="rows">The grid rows in case we need to replace some</param>
515
        /// <returns>true if any mod can be updated, false otherwise</returns>
516
        public bool ResetHasUpdate(GameInstance              inst,
517
                                   IRegistryQuerier          registry,
518
                                   List<ModChange>?          ChangeSet,
519
                                   DataGridViewRowCollection rows)
520
        {
×
521
            var upgGroups = registry.CheckUpgradeable(inst,
×
522
                                                      ModuleLabelList.ModuleLabels.HeldIdentifiers(inst)
523
                                                                                  .ToHashSet(),
524
                                                      ModuleLabelList.ModuleLabels.IgnoreMissingIdentifiers(inst)
525
                                                                                  .ToHashSet());
526
            var dlls = registry.InstalledDlls.ToList();
×
527
            foreach ((var upgradeable, var mods) in upgGroups)
×
528
            {
×
529
                foreach (var ident in mods.Select(m => m.identifier))
×
530
                {
×
531
                    dlls.Remove(ident);
×
532
                    CheckRowUpgradeable(inst, ChangeSet, rows, ident, upgradeable);
×
533
                }
×
534
            }
×
535
            // AD mods don't have CkanModules in the return value of CheckUpgradeable
536
            foreach (var ident in dlls)
×
537
            {
×
538
                CheckRowUpgradeable(inst, ChangeSet, rows, ident, false);
×
539
            }
×
540
            return upgGroups[true].Count > 0;
×
541
        }
×
542

543
        private void CheckRowUpgradeable(GameInstance              inst,
544
                                         List<ModChange>?          ChangeSet,
545
                                         DataGridViewRowCollection rows,
546
                                         string                    ident,
547
                                         bool                      upgradeable)
548
        {
×
549
            if (full_list_of_mod_rows.TryGetValue(ident, out DataGridViewRow? row)
×
550
                && row.Tag is GUIMod gmod
551
                && gmod.HasUpdate != upgradeable)
552
            {
×
553
                gmod.HasUpdate = upgradeable;
×
554
                if (row.Visible)
×
555
                {
×
556
                    // Swap whether the row has an upgrade checkbox
557
                    var newRow =
×
558
                        full_list_of_mod_rows[ident] =
559
                            MakeRow(gmod, ChangeSet, inst.Name, inst.game);
560
                    var rowIndex = row.Index;
×
561
                    var selected = row.Selected;
×
562
                    rows.Remove(row);
×
563
                    rows.Insert(rowIndex, newRow);
×
564
                    if (selected)
×
565
                    {
×
566
                        rows[rowIndex].Selected = true;
×
567
                    }
×
568
                }
×
569
            }
×
570
        }
×
571

572
        /// <summary>
573
        /// Get all the GUI mods for the given instance.
574
        /// </summary>
575
        /// <param name="registry">Registry of the instance</param>
576
        /// <param name="repoData">Repo data of the instance</param>
577
        /// <param name="inst">Game instance</param>
578
        /// <param name="config">GUI config to use</param>
579
        /// <returns>Sequence of GUIMods</returns>
580
        public IEnumerable<GUIMod> GetGUIMods(IRegistryQuerier      registry,
581
                                              RepositoryDataManager repoData,
582
                                              GameInstance          inst,
583
                                              GUIConfiguration?     config)
584
            => GetGUIMods(registry, repoData, inst, inst.VersionCriteria(),
×
585
                          registry.InstalledModules.Select(im => im.identifier)
×
586
                                                   .ToHashSet(),
587
                          config?.HideEpochs ?? false, config?.HideV ?? false);
588

589
        private static IEnumerable<GUIMod> GetGUIMods(IRegistryQuerier      registry,
590
                                                      RepositoryDataManager repoData,
591
                                                      GameInstance          inst,
592
                                                      GameVersionCriteria   versionCriteria,
593
                                                      HashSet<string>       installedIdents,
594
                                                      bool                  hideEpochs,
595
                                                      bool                  hideV)
596
            => registry.CheckUpgradeable(inst,
×
597
                                         ModuleLabelList.ModuleLabels.HeldIdentifiers(inst)
598
                                                                     .ToHashSet(),
599
                                         ModuleLabelList.ModuleLabels.IgnoreMissingIdentifiers(inst)
600
                                                                     .ToHashSet())
601
                       .SelectMany(kvp => kvp.Value
×
602
                                             .Select(mod => registry.IsAutodetected(mod.identifier)
×
603
                                                            ? new GUIMod(mod, repoData, registry,
604
                                                                         inst.StabilityToleranceConfig,
605
                                                                         versionCriteria, null,
606
                                                                         hideEpochs, hideV)
607
                                                              {
608
                                                                  HasUpdate = kvp.Key,
609
                                                              }
610
                                                            : registry.InstalledModule(mod.identifier)
611
                                                              is InstalledModule found
612
                                                                ? new GUIMod(found,
613
                                                                         repoData, registry,
614
                                                                         inst.StabilityToleranceConfig,
615
                                                                         versionCriteria, null,
616
                                                                         hideEpochs, hideV)
617
                                                              {
618
                                                                  HasUpdate = kvp.Key,
619
                                                              }
620
                                                              : null))
621
                       .OfType<GUIMod>()
622
                       .Concat(registry.CompatibleModules(inst.StabilityToleranceConfig, versionCriteria)
623
                                       .Where(m => !installedIdents.Contains(m.identifier))
×
624
                                       .AsParallel()
625
                                       .Where(m => !m.IsDLC)
×
626
                                       .Select(m => new GUIMod(m, repoData, registry,
×
627
                                                               inst.StabilityToleranceConfig,
628
                                                               versionCriteria, null,
629
                                                               hideEpochs, hideV)))
630
                       .Concat(registry.IncompatibleModules(inst.StabilityToleranceConfig, versionCriteria)
631
                                       .Where(m => !installedIdents.Contains(m.identifier))
×
632
                                       .AsParallel()
633
                                       .Where(m => !m.IsDLC)
×
634
                                       .Select(m => new GUIMod(m, repoData, registry,
×
635
                                                               inst.StabilityToleranceConfig,
636
                                                               versionCriteria, true,
637
                                                               hideEpochs, hideV)));
638

639
        private static readonly ILog log = LogManager.GetLogger(typeof(ModList));
2✔
640
    }
641
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc