Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start installing mods while downloads are still in progress #4249

Merged
merged 12 commits into from
Nov 6, 2024
29 changes: 26 additions & 3 deletions Cmdline/Action/Available.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

using CommandLine;

Expand All @@ -17,11 +19,25 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options)
AvailableOptions opts = (AvailableOptions)raw_options;
IRegistryQuerier registry = RegistryManager.Instance(instance, repoData).registry;

var compatible = registry
IEnumerable<CkanModule> compatible = registry
.CompatibleModules(instance.VersionCriteria())
.Where(m => !m.IsDLC)
.OrderBy(m => m.identifier);

if (opts.globs != null)
{
var regexes = opts.globs
.Select(str => new Regex("^"
+ str.Replace("?", ".")
.Replace("*", ".*")
+ "$",
RegexOptions.Compiled
| RegexOptions.IgnoreCase))
.ToArray();
compatible = compatible.Where(m => regexes.Any(re => re.IsMatch(m.identifier)
|| re.IsMatch(m.name)));
}

user.RaiseMessage(Properties.Resources.AvailableHeader,
instance.game.ShortName,
instance.Version()?.ToString() ?? "");
Expand All @@ -31,14 +47,18 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options)
{
foreach (CkanModule module in compatible)
{
user.RaiseMessage("* {0} ({1}) - {2} - {3}", module.identifier, module.version, module.name, module.@abstract);
user.RaiseMessage("* {0} ({1}) - {2} - {3}",
module.identifier, module.version,
module.name, module.@abstract);
}
}
else
{
foreach (CkanModule module in compatible)
{
user.RaiseMessage("* {0} ({1}) - {2}", module.identifier, module.version, module.name);
user.RaiseMessage("* {0} ({1}) - {2}",
module.identifier, module.version,
module.name);
}
}

Expand All @@ -53,6 +73,9 @@ internal class AvailableOptions : InstanceSpecificOptions
{
[Option("detail", HelpText = "Show short description of each module")]
public bool detail { get; set; }

[ValueList(typeof(List<string>))]
public List<string>? globs { get; set; }
}

}
2 changes: 1 addition & 1 deletion Cmdline/Action/Install.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options)
.Select(arg => new NetAsyncDownloader.DownloadTargetFile(getUri(arg)))
.ToArray();
log.DebugFormat("Urls: {0}", targets.SelectMany(t => t.urls));
new NetAsyncDownloader(new NullUser(), options.NetUserAgent).DownloadAndWait(targets);
new NetAsyncDownloader(new NullUser(), () => null, options.NetUserAgent).DownloadAndWait(targets);
log.DebugFormat("Files: {0}", targets.Select(t => t.filename));
modules = targets.Select(t => MainClass.LoadCkanFromFile(t.filename))
.ToList();
Expand Down
6 changes: 4 additions & 2 deletions Cmdline/Action/Update.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ private void UpdateRepositories(CKAN.GameInstance instance, string? userAgent, b
var registry = RegistryManager.Instance(instance, repoData).registry;
var result = repoData.Update(registry.Repositories.Values.ToArray(),
instance.game, force,
new NetAsyncDownloader(user, userAgent), user, userAgent);
new NetAsyncDownloader(user, () => null, userAgent), user, userAgent);
if (result == RepositoryDataManager.UpdateResult.Updated)
{
user.RaiseMessage(Properties.Resources.UpdateSummary,
Expand All @@ -197,7 +197,9 @@ private void UpdateRepositories(CKAN.GameInstance instance, string? userAgent, b
/// <param name="repos">Repositories to update</param>
private void UpdateRepositories(IGame game, Repository[] repos, string? userAgent, bool force = false)
{
var result = repoData.Update(repos, game, force, new NetAsyncDownloader(user, userAgent), user, userAgent);
var result = repoData.Update(repos, game, force,
new NetAsyncDownloader(user, () => null, userAgent),
user, userAgent);
if (result == RepositoryDataManager.UpdateResult.Updated)
{
user.RaiseMessage(Properties.Resources.UpdateSummary,
Expand Down
22 changes: 11 additions & 11 deletions Cmdline/Action/Upgrade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,18 +184,18 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options)
/// <param name="user">IUser object for output</param>
/// <param name="instance">Game instance to use</param>
/// <param name="modules">List of modules to upgrade</param>
private void UpgradeModules(NetModuleCache cache,
string? userAgent,
IUser user,
CKAN.GameInstance instance,
List<CkanModule> modules)
private void UpgradeModules(NetModuleCache cache,
string? userAgent,
IUser user,
CKAN.GameInstance instance,
List<CkanModule> modules)
{
UpgradeModules(
cache, userAgent, user, instance, repoData,
(ModuleInstaller installer, NetAsyncModulesDownloader downloader, RegistryManager regMgr, ref HashSet<string>? possibleConfigOnlyDirs) =>
installer.Upgrade(modules, downloader,
ref possibleConfigOnlyDirs,
regMgr, true, true, true),
regMgr, true, true),
modules.Add);
}

Expand All @@ -206,11 +206,11 @@ private void UpgradeModules(NetModuleCache cache,
/// <param name="user">IUser object for output</param>
/// <param name="instance">Game instance to use</param>
/// <param name="identsAndVersions">List of identifier[=version] to upgrade</param>
private void UpgradeModules(NetModuleCache cache,
string? userAgent,
IUser user,
CKAN.GameInstance instance,
List<string> identsAndVersions)
private void UpgradeModules(NetModuleCache cache,
string? userAgent,
IUser user,
CKAN.GameInstance instance,
List<string> identsAndVersions)
{
UpgradeModules(
cache, userAgent, user, instance, repoData,
Expand Down
22 changes: 7 additions & 15 deletions Cmdline/ConsoleUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,29 +258,21 @@ public void RaiseProgress(string message, int percent)
{
if (message != lastProgressMessage)
{
// The percent looks weird on non-download messages.
// The leading newline makes sure we don't end up with a mess from previous
// download messages.
GoToStartOfLine();
Console.Write("{0}", message);
// The percent looks weird on non-download messages
Console.WriteLine("{0}", message);
lastProgressMessage = message;
}

// This message leaves the cursor at the end of a line of text
atStartOfLine = false;
atStartOfLine = true;
}

public void RaiseProgress(int percent, long bytesPerSecond, long bytesLeft)
public void RaiseProgress(ByteRateCounter rateCounter)
{
if (!Headless || percent != previousPercent)
if (!Headless || rateCounter.Percent != previousPercent)
{
GoToStartOfLine();
var fullMsg = string.Format(CKAN.Properties.Resources.NetAsyncDownloaderProgress,
CkanModule.FmtSize(bytesPerSecond),
CkanModule.FmtSize(bytesLeft));
// The \r at the front here causes download messages to *overwrite* each other.
Console.Write("\r{0} - {1}% ", fullMsg, percent);
previousPercent = percent;
Console.Write("\r{0} ", rateCounter.Summary);
previousPercent = rateCounter.Percent;

// This message leaves the cursor at the end of a line of text
atStartOfLine = false;
Expand Down
4 changes: 3 additions & 1 deletion Cmdline/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,12 @@ public static IEnumerable<string> GetHelp(string verb)
case "import":
yield return $"{Properties.Resources.Usage}: ckan {verb} [{Properties.Resources.Options}] path [path2 ...]";
break;
case "available":
yield return $"{Properties.Resources.Usage}: ckan {verb} [{Properties.Resources.Options}] [glob ...]";
break;

// Commands with only --flag type options
case "gui":
case "available":
case "list":
case "update":
case "scan":
Expand Down
4 changes: 2 additions & 2 deletions ConsoleUI/InstallScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public override void Run(Action? process = null)
HashSet<string>? possibleConfigOnlyDirs = null;

ModuleInstaller inst = new ModuleInstaller(manager.CurrentInstance, manager.Cache, this, userAgent);
inst.onReportModInstalled += OnModInstalled;
inst.OneComplete += OnModInstalled;
if (plan.Remove.Count > 0) {
inst.UninstallList(plan.Remove, ref possibleConfigOnlyDirs, regMgr, true, new List<CkanModule>(plan.Install));
plan.Remove.Clear();
Expand Down Expand Up @@ -113,7 +113,7 @@ public override void Run(Action? process = null)
}

trans.Complete();
inst.onReportModInstalled -= OnModInstalled;
inst.OneComplete -= OnModInstalled;
// Don't let the installer re-use old screen references
inst.User = new NullUser();

Expand Down
2 changes: 1 addition & 1 deletion ConsoleUI/ModListScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ private bool UpdateRegistry(bool showNewModsPrompt = true)
repoData.Update(registry.Repositories.Values.ToArray(),
manager.CurrentInstance.game,
false,
new NetAsyncDownloader(ps, userAgent),
new NetAsyncDownloader(ps, () => null, userAgent),
ps,
userAgent);
} catch (Exception ex) {
Expand Down
2 changes: 1 addition & 1 deletion ConsoleUI/SplashScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public bool Run(ConsoleTheme theme)
if (ksp != null
&& !GameInstanceListScreen.TryGetInstance(theme, ksp, repoData,
(ConsoleTheme th) => Draw(th, false),
new Progress<int>(p => drawProgressBar(theme, 22, 20, p)))) {
new ProgressImmediate<int>(p => drawProgressBar(theme, 22, 20, p)))) {
Console.ResetColor();
Console.Clear();
Console.CursorVisible = true;
Expand Down
4 changes: 2 additions & 2 deletions ConsoleUI/Toolkit/ConsoleFileMultiSelectDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ public ConsoleFileMultiSelectDialog(ConsoleTheme theme,
return false;
});

AddTip("F10", Properties.Resources.Sort);
AddBinding(Keys.F10, (object sender) => {
AddTip(ConsoleScreen.MainMenuKeyTip, Properties.Resources.Sort);
AddBinding(ConsoleScreen.MainMenuKeys, (object sender) => {
fileList.SortMenu().Run(theme, right - 2, top + 2);
DrawBackground();
return true;
Expand Down
30 changes: 20 additions & 10 deletions ConsoleUI/Toolkit/ConsoleScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ protected ConsoleScreen(ConsoleTheme theme)
: base(theme)
{
AddTip(
"F10", MenuTip(),
MainMenuKeyTip, MenuTip(),
() => mainMenu != null
);
AddBinding(new ConsoleKeyInfo[] {Keys.F10, Keys.Apps}, (object sender) => {
AddBinding(MainMenuKeys, (object sender) => {
bool val = true;
if (mainMenu != null) {
DrawSelectedHamburger();
Expand All @@ -47,6 +47,21 @@ protected void LaunchSubScreen(ConsoleScreen cs, Action? newProc = null)
Draw();
}

/// <summary>
/// String that describes which key to press to open the main menu
/// MacOS has a conflicting default key bind for F10 (the CUA standard for opening the menu)
/// </summary>
public static readonly string MainMenuKeyTip = Platform.IsMac ? "Ctrl+T" : "F10";

/// <summary>
/// Keys that open the main menu
/// </summary>
public static readonly ConsoleKeyInfo[] MainMenuKeys = new ConsoleKeyInfo[] {
Keys.F10,
Keys.CtrlT,
Keys.Apps,
};

/// <summary>
/// Function returning text to be shown at the left edge of the top header bar
/// </summary>
Expand Down Expand Up @@ -208,15 +223,10 @@ public void RaiseProgress(string message, int percent)
/// <summary>
/// Update a user visible progress bar
/// </summary>
/// <param name="percent">Value 0-100 representing the progress</param>
/// <param name="bytesPerSecond">Current download rate</param>
/// <param name="bytesLeft">Bytes remaining in the downloads</param>
public void RaiseProgress(int percent, long bytesPerSecond, long bytesLeft)
/// <param name="rateCounter">Object with the progress info</param>
public void RaiseProgress(ByteRateCounter rateCounter)
{
var fullMsg = string.Format(CKAN.Properties.Resources.NetAsyncDownloaderProgress,
CkanModule.FmtSize(bytesPerSecond),
CkanModule.FmtSize(bytesLeft));
Progress(fullMsg, percent);
Progress(rateCounter.Summary, rateCounter.Percent);
Draw();
}

Expand Down
7 changes: 7 additions & 0 deletions ConsoleUI/Toolkit/Keys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ public static class Keys {
(char)18, ConsoleKey.R, false, false, true
);

/// <summary>
/// Representation of Ctrl+R for key bindings
/// </summary>
public static readonly ConsoleKeyInfo CtrlT = new ConsoleKeyInfo(
(char)20, ConsoleKey.T, false, false, true
);

/// <summary>
/// Representation of Ctrl+U for key bindings
/// </summary>
Expand Down
9 changes: 4 additions & 5 deletions Core/CKANPathUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,15 @@ public static void CheckFreeSpace(DirectoryInfo where,
{
if (bytesToStore > 0)
{
var bytesFree = where.GetDrive()?.AvailableFreeSpace;
if (bytesFree.HasValue && bytesToStore > bytesFree.Value) {
var bytesFree = where.GetDrive().AvailableFreeSpace;
if (bytesToStore > bytesFree) {
throw new NotEnoughSpaceKraken(errorDescription, where,
bytesFree.Value, bytesToStore);
bytesFree, bytesToStore);
}
log.DebugFormat("Storing {0} to {1} ({2} free)...",
CkanModule.FmtSize(bytesToStore),
where.FullName,
bytesFree.HasValue ? CkanModule.FmtSize(bytesFree.Value)
: "unknown bytes");
CkanModule.FmtSize(bytesFree));
}
}

Expand Down
14 changes: 12 additions & 2 deletions Core/Extensions/CryptoExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ public static byte[] ComputeHash(this HashAlgorithm hashAlgo,
Stream stream,
IProgress<int>? progress,
CancellationToken? cancelToken = default)
{
PartialHash(hashAlgo, stream, progress, cancelToken);
var buffer = new byte[16];
hashAlgo.TransformFinalBlock(buffer, 0, 0);
return hashAlgo.Hash ?? Array.Empty<byte>();
}

public static void PartialHash(this HashAlgorithm hashAlgo,
Stream stream,
IProgress<int>? progress,
CancellationToken? cancelToken = default)
{
const int bufSize = 1024 * 1024;
var buffer = new byte[bufSize];
Expand All @@ -36,7 +47,7 @@ public static byte[] ComputeHash(this HashAlgorithm hashAlgo,
if (bytesRead < bufSize)
{
// Done!
hashAlgo.TransformFinalBlock(buffer, 0, bytesRead);
hashAlgo.TransformBlock(buffer, 0, bytesRead, buffer, 0);
progress?.Report(100);
break;
}
Expand All @@ -48,7 +59,6 @@ public static byte[] ComputeHash(this HashAlgorithm hashAlgo,
totalBytesRead += bytesRead;
progress?.Report((int)(100 * totalBytesRead / stream.Length));
}
return hashAlgo.Hash ?? Array.Empty<byte>();
}
}
}
9 changes: 9 additions & 0 deletions Core/Extensions/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Linq;
using System.Collections.Generic;

Expand All @@ -23,5 +24,13 @@ public static bool DictionaryEquals<K, V>(this IDictionary<K, V>? a,
return val;
}

public static IEnumerable<Tuple<K, V1, V2>> KeyZip<K, V1, V2>(this IDictionary<K, V1> source,
IDictionary<K, V2> other)
where K : notnull
=> source.Select(kvp => other.TryGetValue(kvp.Key, out V2? val2)
? Tuple.Create(kvp.Key, kvp.Value, val2)
: null)
.OfType<Tuple<K, V1, V2>>();

}
}
Loading