Skip to content

Commit

Permalink
Merge #4195 Usability improvements for missing files
Browse files Browse the repository at this point in the history
  • Loading branch information
HebaruSan committed Sep 26, 2024
2 parents 228c887 + b1609dd commit 15690d3
Show file tree
Hide file tree
Showing 19 changed files with 110 additions and 65 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ All notable changes to this project will be documented in this file.
- [ConsoleUI] Add downloads column for ConsoleUI (#4063 by: HebaruSan)
- [ConsoleUI] Play game option for ConsoleUI (#4064 by: HebaruSan)
- [ConsoleUI] ConsoleUI prompt to delete non-empty folders after uninstall (#4066 by: HebaruSan)
- [Multiple] Treat mods with missing files as upgradeable/reinstallable (#4067 by: HebaruSan)
- [Multiple] Treat mods with missing files as upgradeable/reinstallable (#4067, #4195 by: HebaruSan)
- [ConsoleUI] Conflicting recommendations check for ConsoleUI (#4085 by: HebaruSan)
- [Build] Linux: Improve desktop entries (#4092 by: mmvanheusden; reviewed: HebaruSan)
- [ConsoleUI] Install from .ckan file option for ConsoleUI (#4103 by: HebaruSan)
Expand Down
18 changes: 9 additions & 9 deletions Core/ModuleInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -584,21 +584,21 @@ public static List<InstallableFile> FindInstallableFiles(CkanModule module, stri

/// <summary>
/// Returns the module contents if and only if we have it
/// available in our cache. Returns null, otherwise.
/// available in our cache, empty sequence otherwise.
///
/// Intended for previews.
/// </summary>
public static IEnumerable<string> GetModuleContentsList(NetModuleCache Cache,
GameInstance instance,

CkanModule module)
public static IEnumerable<InstallableFile> GetModuleContents(NetModuleCache Cache,
GameInstance instance,
CkanModule module,
HashSet<string> filters)
=> (Cache.GetCachedFilename(module) is string filename
? Utilities.DefaultIfThrows(() => FindInstallableFiles(module, filename, instance)
// Skip folders
.Where(f => !f.source.IsDirectory)
.Select(f => instance.ToRelativeGameDir(f.destination)))
.Where(instF => !filters.Any(filt =>
instF.destination != null
&& instF.destination.Contains(filt))))
: null)
?? Enumerable.Empty<string>();
?? Enumerable.Empty<InstallableFile>();

#endregion

Expand Down
2 changes: 1 addition & 1 deletion Core/Registry/IRegistryQuerier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ public static Dictionary<bool, List<CkanModule>> CheckUpgradeable(this IRegistry
};
}

private static bool MetadataChanged(this IRegistryQuerier querier, string identifier)
public static bool MetadataChanged(this IRegistryQuerier querier, string identifier)
{
try
{
Expand Down
2 changes: 1 addition & 1 deletion GUI/Controls/ManageMods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1291,7 +1291,7 @@ private void reinstallToolStripMenuItem_Click(object? sender, EventArgs? e)
registry.GetModuleByVersion(module.identifier,
module.version)
?? module,
true)
true, false)
}, null);
}
}
Expand Down
1 change: 1 addition & 0 deletions GUI/Controls/ModInfoTabs/Contents.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

79 changes: 56 additions & 23 deletions GUI/Controls/ModInfoTabs/Contents.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
Expand All @@ -9,6 +8,9 @@
using System.Runtime.Versioning;
#endif

using Autofac;

using CKAN.Configuration;
using CKAN.GUI.Attributes;

namespace CKAN.GUI
Expand Down Expand Up @@ -53,9 +55,9 @@ public void RefreshModContentsTree()

private void ContentsPreviewTree_NodeMouseDoubleClick(object? sender, TreeNodeMouseClickEventArgs? e)
{
if (e != null)
if (e != null && manager?.CurrentInstance is GameInstance inst)
{
Utilities.OpenFileBrowser(e.Node.Name);
Utilities.OpenFileBrowser(inst.ToAbsoluteGameDir(e.Node.Name));
}
}

Expand Down Expand Up @@ -119,9 +121,9 @@ private void _UpdateModContentsTree(CkanModule? module, bool force = false)
ContentsOpenButton.Enabled = false;
ContentsPreviewTree.Enabled = false;
}
else if (manager != null
&& manager?.CurrentInstance != null
else if (manager?.CurrentInstance is GameInstance inst
&& manager?.Cache != null
&& selectedModule != null
&& Main.Instance?.currentUser != null)
{
rootNode.Text = Path.GetFileName(
Expand All @@ -134,23 +136,33 @@ private void _UpdateModContentsTree(CkanModule? module, bool force = false)
UseWaitCursor = true;
Task.Factory.StartNew(() =>
{
var paths = ModuleInstaller.GetModuleContentsList(manager.Cache, manager.CurrentInstance, module)
// Load fully in bg
.ToArray();
var filters = ServiceLocator.Container.Resolve<IConfiguration>().GlobalInstallFilters
.Concat(inst.InstallFilters)
.ToHashSet();
var tuples = ModuleInstaller.GetModuleContents(manager.Cache, inst, module, filters)
.Select(f => (path: inst.ToRelativeGameDir(f.destination),
dir: f.source.IsDirectory,
exists: !selectedModule.IsInstalled
|| File.Exists(f.destination)
|| Directory.Exists(f.destination)))
.ToArray();
// Stop if user switched to another mod
if (rootNode.TreeView != null)
{
Util.Invoke(this, () =>
{
ContentsPreviewTree.BeginUpdate();
foreach (string path in paths)
foreach ((string path, bool dir, bool exists) in tuples)
{
AddContentPieces(
rootNode,
path.Split(new char[] {'/'}));
AddContentPieces(inst, rootNode,
path.Split(new char[] {'/'}),
dir, exists);
}
rootNode.ExpandAll();
rootNode.EnsureVisible();
var initialFocus = FirstMatching(rootNode,
n => n.ForeColor == Color.Red)
?? rootNode;
initialFocus.EnsureVisible();
ContentsPreviewTree.EndUpdate();
UseWaitCursor = false;
});
Expand All @@ -161,24 +173,45 @@ private void _UpdateModContentsTree(CkanModule? module, bool force = false)
}
}

private static void AddContentPieces(TreeNode parent, IEnumerable<string> pieces)
private static void AddContentPieces(GameInstance inst,
TreeNode parent,
string[] pieces,
bool dir,
bool exists)
{
var firstPiece = pieces.FirstOrDefault();
if (firstPiece != null)
{
if (parent.ImageKey == "file")
{
parent.SelectedImageKey = parent.ImageKey = "folder";
}
// Key/Name needs to be the full relative path for double click to work
var key = string.IsNullOrEmpty(parent.Name)
? firstPiece
: $"{parent.Name}/{firstPiece}";
var node = parent.Nodes[key]
?? parent.Nodes.Add(key, firstPiece, "file", "file");
AddContentPieces(node, pieces.Skip(1));
? firstPiece
: $"{parent.Name}/{firstPiece}";
var node = parent.Nodes[key];
if (node == null)
{
var iconKey = dir || pieces.Length > 1 ? "folder" : "file";
node = parent.Nodes.Add(key, firstPiece, iconKey, iconKey);
if (!exists && (pieces.Length == 1 || !Directory.Exists(inst.ToAbsoluteGameDir(key))))
{
node.ForeColor = Color.Red;
node.ToolTipText = iconKey == "folder"
? Properties.Resources.ModInfoFolderNotFound
: Properties.Resources.ModInfoFileNotFound;
}
}
if (pieces.Length > 1)
{
AddContentPieces(inst, node, pieces.Skip(1).ToArray(), dir, exists);
}
}
}

private static TreeNode? FirstMatching(TreeNode root, Func<TreeNode, bool> predicate)
=> predicate(root) ? root
: root.Nodes.OfType<TreeNode>()
.Select(n => FirstMatching(n, predicate))
.OfType<TreeNode>()
.FirstOrDefault();

}
}
8 changes: 5 additions & 3 deletions GUI/Model/GUIMod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,9 @@ public CkanModule ToCkanModule()
/// <returns>The CkanModule associated with this GUIMod or null if there is none</returns>
public CkanModule ToModule() => Mod;

public IEnumerable<ModChange> GetModChanges(bool upgradeChecked, bool replaceChecked)
public IEnumerable<ModChange> GetModChanges(bool upgradeChecked,
bool replaceChecked,
bool metadataChanged)
{
if (replaceChecked)
{
Expand All @@ -309,7 +311,7 @@ public IEnumerable<ModChange> GetModChanges(bool upgradeChecked, bool replaceChe
yield return new ModUpgrade(Mod,
GUIModChangeType.Update,
SelectedMod,
false);
false, false);
}
else
{
Expand All @@ -329,7 +331,7 @@ public IEnumerable<ModChange> GetModChanges(bool upgradeChecked, bool replaceChe
yield return new ModUpgrade(Mod,
GUIModChangeType.Update,
SelectedMod,
false);
false, metadataChanged);
}
}

Expand Down
14 changes: 9 additions & 5 deletions GUI/Model/ModChange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,20 +138,23 @@ public class ModUpgrade : ModChange
public ModUpgrade(CkanModule mod,
GUIModChangeType changeType,
CkanModule targetMod,
bool userReinstall)
bool userReinstall,
bool metadataChanged)
: base(mod, changeType)
{
this.targetMod = targetMod;
this.userReinstall = userReinstall;
this.targetMod = targetMod;
this.userReinstall = userReinstall;
this.metadataChanged = metadataChanged;
}

public override string? NameAndStatus
=> Main.Instance?.Manager?.Cache?.DescribeAvailability(targetMod);

public override string Description
=> IsReinstall
? userReinstall ? Properties.Resources.MainChangesetUserReinstall
: Properties.Resources.MainChangesetReinstall
? userReinstall ? Properties.Resources.MainChangesetReinstallUser
: metadataChanged ? Properties.Resources.MainChangesetReinstallMetadataChanged
: Properties.Resources.MainChangesetReinstallMissing
: string.Format(Properties.Resources.MainChangesetUpdateSelected,
targetMod.version);

Expand All @@ -165,5 +168,6 @@ private bool IsReinstall
&& targetMod.version == Mod.version;

private readonly bool userReinstall;
private readonly bool metadataChanged;
}
}
18 changes: 10 additions & 8 deletions GUI/Model/ModList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -434,27 +434,29 @@ public string StripEpoch(string version)
private static readonly Regex ContainsEpoch = new Regex(@"^[0-9][0-9]*:[^:]+$", RegexOptions.Compiled);
private static readonly Regex RemoveEpoch = new Regex(@"^([^:]+):([^:]+)$", RegexOptions.Compiled);

private static IEnumerable<ModChange> rowChanges(DataGridViewRow row,
DataGridViewColumn? upgradeCol,
DataGridViewColumn? replaceCol)
=> (row.Tag as GUIMod)?.GetModChanges(
private static IEnumerable<ModChange> rowChanges(IRegistryQuerier registry,
DataGridViewRow row,
DataGridViewColumn? upgradeCol,
DataGridViewColumn? replaceCol)
=> row.Tag is GUIMod gmod ? gmod.GetModChanges(
upgradeCol != null && upgradeCol.Visible
&& row.Cells[upgradeCol.Index] is DataGridViewCheckBoxCell upgradeCell
&& (bool)upgradeCell.Value,
replaceCol != null && replaceCol.Visible
&& row.Cells[replaceCol.Index] is DataGridViewCheckBoxCell replaceCell
&& (bool)replaceCell.Value)
?? Enumerable.Empty<ModChange>();
&& (bool)replaceCell.Value,
registry.MetadataChanged(gmod.Identifier))
: Enumerable.Empty<ModChange>();

public HashSet<ModChange> ComputeUserChangeSet(IRegistryQuerier? registry,
public HashSet<ModChange> ComputeUserChangeSet(IRegistryQuerier registry,
GameVersionCriteria? crit,
GameInstance? instance,
DataGridViewColumn? upgradeCol,
DataGridViewColumn? replaceCol)
{
log.Debug("Computing user changeset");
var modChanges = full_list_of_mod_rows?.Values
.SelectMany(row => rowChanges(row, upgradeCol, replaceCol))
.SelectMany(row => rowChanges(registry, row, upgradeCol, replaceCol))
.ToList()
?? new List<ModChange>();

Expand Down
2 changes: 1 addition & 1 deletion GUI/Properties/Resources.de-DE.resx
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ Möchten Sie es wirklich installieren?</value></data>
<data name="AllModVersionsInstallYes" xml:space="preserve"><value>Installieren</value></data>
<data name="AllModVersionsInstallNo" xml:space="preserve"><value>Abbrechen</value></data>
<data name="MainChangesetUpdateSelected" xml:space="preserve"><value>Mod-Update ausgewählt vom Nutzer {0}.</value></data>
<data name="MainChangesetReinstall" xml:space="preserve"><value>Neu installieren (Metadaten geändert)</value></data>
<data name="MainChangesetReinstallMetadataChanged" xml:space="preserve"><value>Neu installieren (Metadaten geändert)</value></data>
<data name="MainImportTitle" xml:space="preserve"><value>Mod-Import</value></data>
<data name="MainImportWaitTitle" xml:space="preserve"><value>Statuslog</value></data>
<data name="MainInstallWaitTitle" xml:space="preserve"><value>Statuslog</value></data>
Expand Down
4 changes: 2 additions & 2 deletions GUI/Properties/Resources.fr-FR.resx
Original file line number Diff line number Diff line change
Expand Up @@ -359,10 +359,10 @@ Voulez-vous vraiment les installer ? L'annulation annulera toute l'installation.
<data name="MainChangesetUpdateSelected" xml:space="preserve">
<value>Mise à jour demandée vers la version {0}.</value>
</data>
<data name="MainChangesetReinstall" xml:space="preserve">
<data name="MainChangesetReinstallMetadataChanged" xml:space="preserve">
<value>Réinstaller (métadonnées modifiées)</value>
</data>
<data name="MainChangesetUserReinstall" xml:space="preserve">
<data name="MainChangesetReinstallUser" xml:space="preserve">
<value>Réinstallation (à la demande de l'utilisateur)</value>
</data>
<data name="MainImportTitle" xml:space="preserve">
Expand Down
2 changes: 1 addition & 1 deletion GUI/Properties/Resources.it-IT.resx
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ Sei sicuro di volerli installare? L'annullamento interromperà l'intera installa
<data name="MainChangesetUpdateSelected" xml:space="preserve">
<value>Aggiorna la versione selezionata dall'utente alla versione {0}.</value>
</data>
<data name="MainChangesetReinstall" xml:space="preserve">
<data name="MainChangesetReinstallMetadataChanged" xml:space="preserve">
<value>Reinstallazione (Metadati cambiati)</value>
</data>
<data name="MainImportTitle" xml:space="preserve">
Expand Down
2 changes: 1 addition & 1 deletion GUI/Properties/Resources.ja-JP.resx
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ Try to move {2} out of {3} and restart CKAN.</value></data>
<data name="AllModVersionsInstallYes" xml:space="preserve"><value>インストール</value></data>
<data name="AllModVersionsInstallNo" xml:space="preserve"><value>取消</value></data>
<data name="MainChangesetUpdateSelected" xml:space="preserve"><value>選択されたものをバージョン{0}にアップデートする。</value></data>
<data name="MainChangesetReinstall" xml:space="preserve"><value> ()</value></data>
<data name="MainChangesetReinstallMetadataChanged" xml:space="preserve"><value> ()</value></data>
<data name="MainImportTitle" xml:space="preserve"><value>Modインポート</value></data>
<data name="MainImportFilter" xml:space="preserve"><value>Mod (*.zip)|*.zip</value></data>
<data name="MainImportWaitTitle" xml:space="preserve"><value>ステータスログ</value></data>
Expand Down
Loading

0 comments on commit 15690d3

Please sign in to comment.