Skip to content

Commit

Permalink
Added new command: status (#355)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bastians-Bits committed Feb 9, 2024
1 parent 9cbd4d1 commit 3a6fd86
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 0 deletions.
45 changes: 45 additions & 0 deletions SteamPrefill/CliCommands/Converters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,49 @@ public override PresetWorkload Convert(string rawValue)
return PresetWorkload.FromName(rawValue);
}
}

public sealed class SortOrderValidator : BindingValidator<SortOrder>
{
public override BindingValidationError Validate(SortOrder value)
{
if (value == null)
{
AnsiConsole.MarkupLine(Red($"A sort order must be specified when using {LightYellow("--sort-order")}"));
AnsiConsole.Markup(Red($"Valid sort orders include : {LightYellow("ascending/descending")}"));
throw new CommandException(".", 1, true);
}
return Ok();
}
}

public sealed class SortOrderConverter : BindingConverter<SortOrder>
{
public override SortOrder Convert(string rawValue)
{
if (!SortOrder.TryFromValue(rawValue, out var _))
{
AnsiConsole.MarkupLine(Red($"{White(rawValue)} is not a valid sort order!"));
AnsiConsole.Markup(Red($"Valid sort orders include : {LightYellow("ascending/descending")}"));
throw new CommandException(".", 1, true);
}
return SortOrder.FromValue(rawValue);
}
}

public sealed class SortColumnValidator : BindingValidator<string>
{
public override BindingValidationError Validate(string value)
{
if (string.IsNullOrEmpty(value)
&& (!value.Equals("app", StringComparison.OrdinalIgnoreCase)
|| !value.Equals("size", StringComparison.OrdinalIgnoreCase)))
{
AnsiConsole.MarkupLine($"Test: {value}");
AnsiConsole.MarkupLine(Red($"A sort column must be specified when using {LightYellow("--sort-column")}"));
AnsiConsole.Markup(Red($"Valid sort orders include : {LightYellow("app/size")}"));
throw new CommandException(".", 1, true);
}
return Ok();
}
}
}
69 changes: 69 additions & 0 deletions SteamPrefill/CliCommands/StatusCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@

namespace SteamPrefill.CliCommands
{
[UsedImplicitly]
[Command("status", Description = "List all currently selected apps and the used disk space.")]
public class StatusCommand : ICommand
{
[CommandOption("no-ansi",
Description = "Application output will be in plain text. " +
"Should only be used if terminal does not support Ansi Escape sequences, or when redirecting output to a file.",
Converter = typeof(NullableBoolConverter))]
public bool? NoAnsiEscapeSequences { get; init; }

[CommandOption("os", Description = "Specifies which operating system(s) games should be downloaded for. Can be windows/linux/macos",
Converter = typeof(OperatingSystemConverter), Validators = new[] { typeof(OperatingSystemValidator) })]
public IReadOnlyList<OperatingSystem> OperatingSystems { get; init; } = new List<OperatingSystem> { OperatingSystem.Windows };

[CommandOption("sort-order", Description = "Specifies in which way the data should be sorted. Can be ascending/descending",
Converter = typeof(SortOrderConverter), Validators = new [] { typeof(SortOrderValidator) })]
public SortOrder SortOrder { get; init; } = SortOrder.Ascending;

[CommandOption("sort-column", Description = "Specifies by which column the data should be sorted. Can be app/size",
Validators = new [] { typeof(SortColumnValidator) })]
public string SortColumn { get; init; } = "app";

private IAnsiConsole _ansiConsole;

public async ValueTask ExecuteAsync(IConsole console)
{
_ansiConsole = console.CreateAnsiConsole();
// Property must be set to false in order to disable ansi escape sequences
_ansiConsole.Profile.Capabilities.Ansi = !NoAnsiEscapeSequences ?? true;

var downloadArgs = new DownloadArguments
{
NoCache = AppConfig.NoLocalCache,
OperatingSystems = OperatingSystems.ToList()
};

using var steamManager = new SteamManager(_ansiConsole, downloadArgs);
ValidateUserHasSelectedApps(steamManager);

try
{
await steamManager.InitializeAsync();
await steamManager.CurrentlyDownloadedAsync(SortOrder, SortColumn);
}
finally
{
steamManager.Shutdown();
}
}

// Validates that the user has selected at least 1 app
private void ValidateUserHasSelectedApps(SteamManager steamManager)
{
var userSelectedApps = steamManager.LoadPreviouslySelectedApps();

if (!userSelectedApps.Any())
{
// User hasn't selected any apps yet
_ansiConsole.MarkupLine(Red("No apps have been selected for benchmark! At least 1 app is required!"));
_ansiConsole.Markup(Red($"See flags {LightYellow("--appid")}, {LightYellow("--all")} and {LightYellow("--use-selected")} to interactively choose which apps to prefill"));

throw new CommandException(".", 1, true);
}
}
}
}
9 changes: 9 additions & 0 deletions SteamPrefill/Models/Enums/SortOrder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace SteamPrefill.Models.Enums
{
[Intellenum(typeof(string))]
public sealed partial class SortOrder
{
public static readonly SortOrder Ascending = new("ascending");
public static readonly SortOrder Descending = new("descending");
}
}
75 changes: 75 additions & 0 deletions SteamPrefill/SteamManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -346,5 +346,80 @@ await _ansiConsole.CreateSpectreProgress(TransferSpeedUnit.Bytes, displayTransfe
}

#endregion

#region Status

public async Task CurrentlyDownloadedAsync(SortOrder sortOrder, string sortColumn)
{
await _cdnPool.PopulateAvailableServersAsync();

// Pre-Load all selected apps and their manifests
List<uint> appIds = LoadPreviouslySelectedApps();
await _appInfoHandler.RetrieveAppMetadataAsync(appIds);

ByteSize totalSize = new ByteSize();
Dictionary<string, ByteSize> index = new Dictionary<string, ByteSize>();

var timer = Stopwatch.StartNew();
_ansiConsole.LogMarkupLine("Loading Manifests");
foreach (uint appId in appIds)
{
AppInfo appInfo = await _appInfoHandler.GetAppInfoAsync(appId);

var filteredDepots = await _depotHandler.FilterDepotsToDownloadAsync(_downloadArgs, appInfo.Depots);
await _depotHandler.BuildLinkedDepotInfoAsync(filteredDepots);

var allChunksForApp = await _depotHandler.BuildChunkDownloadQueueAsync(filteredDepots);
var size = ByteSize.FromBytes(allChunksForApp.Sum(e => e.CompressedLength));
totalSize += size;

index.Add(appInfo.Name, size);
}
_ansiConsole.LogMarkupLine("Manifests Loaded", timer);

var table = new Table { Border = TableBorder.MinimalHeavyHead };
table.AddColumns(new TableColumn("App"), new TableColumn("Size"));

foreach (KeyValuePair<string, ByteSize> data in SortData(index, sortOrder, sortColumn))
{
string appName = data.Key;
ByteSize size = data.Value;
table.AddRow(appName, size.ToDecimalString());
}

table.AddEmptyRow();
table.AddRow("Total Size", totalSize.ToDecimalString());

_ansiConsole.Write(table);
}

private IOrderedEnumerable<KeyValuePair<string, ByteSize>> SortData(
Dictionary<string, ByteSize> index,
SortOrder sortOrder,
string sortColumn)
{
if (sortOrder == SortOrder.Ascending)
{
if (sortColumn.Equals("app", StringComparison.OrdinalIgnoreCase))
{
return index.OrderBy(o => o.Key);
} else if (sortColumn.Equals("size", StringComparison.OrdinalIgnoreCase))
{
return index.OrderBy(o => o.Value);
}
} else if (sortOrder == SortOrder.Descending)
{
if (sortColumn.Equals("app", StringComparison.OrdinalIgnoreCase))
{
return index.OrderByDescending(o => o.Key);
} else if (sortColumn.Equals("size", StringComparison.OrdinalIgnoreCase))
{
return index.OrderByDescending(o => o.Value);
}
}
return index.OrderBy(o => o.Key);
}

#endregion
}
}
30 changes: 30 additions & 0 deletions docs/mkdocs/detailed-command-usage/Status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# status

## Overview

Lists all selected apps and their disk usage.

-----

## Example usage

Checking the `status` is as simple as running the following from the terminal:
```powershell
./{{prefillName}} status
```

### Customized the sorting

An advanced usage with customized sorting can be used as the following from the terminal:
```powershell
./{{prefillName}} status --sort-order descending --sort-column size
```

## Options

| Option | | Values | Default | |
| --------------- | --- | --------------------- | ------------- | --- |
| --os | | windows, linux, macos | **windows** | Specifies which operating system(s) chunks should be filtered for. |
| --no-ansi | | | | Application output will be in plain text, rather than using the visually appealing colors and progress bars. Should only be used if terminal does not support Ansi Escape sequences, or when redirecting output to a file. |
| --sort-order | | ascending, descending | **ascending** | Specifies which sorting should be used for the data. |
| --sort-column | | app, size | **app** | Specifies which column should be used for the sorting. |

0 comments on commit 3a6fd86

Please sign in to comment.