Migrate to CommandLine library

This commit is contained in:
Matt Nadareski
2026-02-26 22:54:53 -05:00
parent eea5dd66ff
commit d2ef8744b1
12 changed files with 562 additions and 400 deletions

View File

@@ -1,187 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using SabreTools.RedumpLib.Web;
namespace RedumpTool
{
/// <summary>
/// Contains logic for dealing with downloads
/// </summary>
public class Downloader
{
#region Properties
/// <summary>
/// Which Redump feature is being used
/// </summary>
public Feature Feature { get; set; }
/// <summary>
/// Minimum ID for downloading page information (Feature.Site, Feature.WIP only)
/// </summary>
public int MinimumId { get; set; }
/// <summary>
/// Maximum ID for downloading page information (Feature.Site, Feature.WIP only)
/// </summary>
public int MaximumId { get; set; }
/// <summary>
/// Quicksearch text for downloading
/// </summary>
public string? QueryString { get; set; }
/// <summary>
/// Directory to save all outputted files to
/// </summary>
public string? OutDir { get; set; }
/// <summary>
/// Use named subfolders for discrete download sets (Feature.Packs only)
/// </summary>
public bool UseSubfolders { get; set; }
/// <summary>
/// Use the last modified page to try to grab all new discs (Feature.Site, Feature.WIP only)
/// </summary>
public bool OnlyNew { get; set; }
/// <summary>
/// Only list the page IDs but don't download
/// </summary>
public bool OnlyList { get; set; }
/// <summary>
/// Don't replace forward slashes with `-` in queries
/// </summary>
public bool NoSlash { get; set; }
/// <summary>
/// Force continuing downloads until user cancels or pages run out
/// </summary>
public bool Force { get; set; }
/// <summary>
/// Redump username
/// </summary>
public string? Username { get; set; }
/// <summary>
/// Redump password
/// </summary>
public string? Password { get; set; }
#endregion
#region Private Vars
/// <summary>
/// Current HTTP rc to use
/// </summary>
private readonly RedumpClient _client;
#endregion
/// <summary>
/// Constructor
/// </summary>
public Downloader()
{
_client = new RedumpClient();
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="client">Preconfigured client</param>
public Downloader(RedumpClient client)
{
_client = client;
}
/// <summary>
/// Run the downloads that should go
/// </summary>
/// <returns>List of IDs that were processed on success, empty on error</returns>
/// <remarks>Packs will never return anything other than empty</remarks>
public async Task<List<int>> Download()
{
// Login to Redump, if possible
if (!_client.LoggedIn)
await _client.Login(Username ?? string.Empty, Password ?? string.Empty);
// Create output list
List<int> processedIds = [];
switch (Feature)
{
case Feature.Packs:
await Packs.DownloadPacks(_client, OutDir, UseSubfolders);
break;
case Feature.Quicksearch:
processedIds = await ProcessQuicksearch();
break;
case Feature.Site:
processedIds = await ProcessSite();
break;
case Feature.User:
processedIds = await ProcessUser();
break;
case Feature.WIP:
processedIds = await ProcessWIP();
break;
case Feature.NONE:
default:
return [];
}
return processedIds;
}
/// <summary>
/// Process the Quicksearch feature
/// </summary>
private async Task<List<int>> ProcessQuicksearch()
{
if (OnlyList)
return await Search.ListSearchResults(_client, QueryString, NoSlash);
else
return await Search.DownloadSearchResults(_client, QueryString, OutDir, NoSlash);
}
/// <summary>
/// Process the Site feature
/// </summary>
private async Task<List<int>> ProcessSite()
{
if (OnlyNew)
return await Discs.DownloadLastModified(_client, OutDir, Force);
else
return await Discs.DownloadSiteRange(_client, OutDir, MinimumId, MaximumId);
}
/// <summary>
/// Process the User feature
/// </summary>
private async Task<List<int>> ProcessUser()
{
if (OnlyList)
return await User.ListUser(_client, Username);
else if (OnlyNew)
return await User.DownloadUserLastModified(_client, Username, OutDir);
else
return await User.DownloadUser(_client, Username, OutDir);
}
/// <summary>
/// Process the WIP feature
/// </summary>
private async Task<List<int>> ProcessWIP()
{
if (OnlyNew)
return await WIP.DownloadLastSubmitted(_client, OutDir) ?? [];
else
return await WIP.DownloadWIPRange(_client, OutDir, MinimumId, MaximumId);
}
}
}

View File

@@ -1,15 +0,0 @@
namespace RedumpTool
{
/// <summary>
/// Determines what download type to initate
/// </summary>
public enum Feature
{
NONE,
Site,
WIP,
Packs,
User,
Quicksearch,
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.IO;
using SabreTools.CommandLine;
using SabreTools.CommandLine.Inputs;
using SabreTools.RedumpLib.Web;
namespace RedumpTool.Features
{
internal abstract class BaseFeature : Feature
{
#region Inputs
private const string _debugName = "debug";
internal readonly FlagInput DebugInput = new(_debugName, ["-d", "--debug"], "Enable debug mode");
private const string _outputName = "output";
internal readonly StringInput OutputInput = new(_outputName, ["-o", "--output"], "Set the base output directory");
private const string _passwordName = "password";
internal readonly StringInput PasswordInput = new(_passwordName, ["-p", "--password"], "Redump password");
private const string _usernameName = "username";
internal readonly StringInput UsernameInput = new(_usernameName, ["-u", "--username"], "Redump username");
#endregion
#region Fields
/// <summary>
/// Client to use for external connections
/// </summary>
protected RedumpClient _client;
#endregion
public BaseFeature(string name, string[] flags, string description, string? detailed = null)
: base(name, flags, description, detailed)
{
_client = new RedumpClient();
}
#region Helpers
/// <summary>
/// Validate and create the possible supplied output directory
/// </summary>
/// <param name="directory">Full directory path</param>
/// <returns>True if the output directory was validated and created, false otherwise</returns>
protected static bool ValidateAndCreateOutputDirectory(string? directory)
{
if (string.IsNullOrEmpty(directory))
{
Console.Error.WriteLine("No output directory set!");
return false;
}
else
{
// Create the output directory, if it doesn't exist
try
{
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
return true;
}
catch (Exception ex)
{
Console.Error.WriteLine($"An exception has occurred: {ex}");
return false;
}
}
}
/// <summary>
/// Output the set of processed IDs to console, if possible
/// </summary>
/// <param name="processedIds">Set of processed IDs</param>
/// <returns>True if there were any IDs to process, false otherwise</returns>
protected static bool PrintProcessedIds(List<int> processedIds)
{
if (processedIds.Count > 0)
{
string formattedIds = string.Join(", ", [.. processedIds.ConvertAll(i => i.ToString())]);
Console.WriteLine($"Processed IDs: {formattedIds}");
return true;
}
else
{
Console.WriteLine("No results were found");
return false;
}
}
#endregion
}
}

View File

@@ -0,0 +1,65 @@
using SabreTools.CommandLine.Inputs;
using SabreTools.RedumpLib.Web;
namespace RedumpTool.Features
{
internal sealed class PacksFeature : BaseFeature
{
#region Feature Definition
public const string DisplayName = "packs";
private static readonly string[] _flags = ["packs"];
private const string _description = "Download available packs";
#endregion
#region Inputs
private const string _subfoldersName = "subfolders";
internal readonly FlagInput SubfoldersInput = new(_subfoldersName, ["-s", "--subfolders"], "Download packs to named subfolders");
#endregion
public PacksFeature()
: base(DisplayName, _flags, _description)
{
RequiresInputs = false;
// Common
Add(DebugInput);
Add(OutputInput);
Add(UsernameInput);
Add(PasswordInput);
// Specific
Add(SubfoldersInput);
}
/// <inheritdoc/>
public override bool Execute()
{
// Get values needed more than once
string? outputDirectory = OutputInput.Value;
// Output directory validation
if (!ValidateAndCreateOutputDirectory(outputDirectory))
return false;
// Login to Redump, if necessary
if (!_client.LoggedIn)
_client.Login(UsernameInput.Value ?? string.Empty, PasswordInput.Value ?? string.Empty).Wait();
// Start the processing
var processingTask = Packs.DownloadPacks(_client, outputDirectory, SubfoldersInput.Value);
// Retrieve the result
processingTask.Wait();
return processingTask.Result;
}
/// <inheritdoc/>
public override bool VerifyInputs() => true;
}
}

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using SabreTools.CommandLine.Inputs;
using SabreTools.RedumpLib.Web;
namespace RedumpTool.Features
{
internal sealed class QueryFeature : BaseFeature
{
#region Feature Definition
public const string DisplayName = "query";
private static readonly string[] _flags = ["query"];
private const string _description = "Download pages and related files from a Redump-compatible query";
#endregion
#region Inputs
private const string _listName = "list";
internal readonly FlagInput ListInput = new(_listName, ["-l", "--list"], "Only list the page IDs for that query");
private const string _noSlashName = "noslash";
internal readonly FlagInput NoSlashInput = new(_noSlashName, ["-ns", "--noslash"], "Don't replace forward slashes with '-'");
private const string _queryName = "query";
internal readonly StringInput QueryInput = new(_queryName, ["-q", "--query"], "Redump-compatible query to run");
#endregion
public QueryFeature()
: base(DisplayName, _flags, _description)
{
RequiresInputs = false;
// Common
Add(DebugInput);
Add(OutputInput);
Add(UsernameInput);
Add(PasswordInput);
// Specific
Add(QueryInput);
Add(ListInput);
Add(NoSlashInput);
}
/// <inheritdoc/>
public override bool Execute()
{
// Get values needed more than once
bool onlyList = ListInput.Value;
string? outputDirectory = OutputInput.Value;
string? queryString = QueryInput.Value;
// Output directory validation
if (!onlyList && !ValidateAndCreateOutputDirectory(outputDirectory))
return false;
// Query verification (and cleanup)
if (string.IsNullOrEmpty(queryString))
{
Console.Error.WriteLine("Please enter a query for searching");
return false;
}
// Login to Redump, if necessary
if (!_client.LoggedIn)
_client.Login(UsernameInput.Value ?? string.Empty, PasswordInput.Value ?? string.Empty).Wait();
// Start the processing
Task<List<int>> processingTask;
if (onlyList)
processingTask = Search.ListSearchResults(_client, queryString, NoSlashInput.Value);
else
processingTask = Search.DownloadSearchResults(_client, queryString, outputDirectory, NoSlashInput.Value);
// Retrieve the result
processingTask.Wait();
var processedIds = processingTask.Result;
// Display the processed IDs
return PrintProcessedIds(processedIds);
}
/// <inheritdoc/>
public override bool VerifyInputs() => true;
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using SabreTools.CommandLine.Inputs;
using SabreTools.RedumpLib.Web;
namespace RedumpTool.Features
{
internal sealed class SiteFeature : BaseFeature
{
#region Feature Definition
public const string DisplayName = "site";
private static readonly string[] _flags = ["site"];
private const string _description = "Download pages and related files from the main site";
#endregion
#region Inputs
private const string _forceName = "force";
internal readonly FlagInput ForceInput = new(_forceName, ["-f", "--force"], "Force continuing downloads until user cancels (requires only new)");
private const string _maximumName = "maximum";
internal readonly Int32Input MaximumInput = new(_maximumName, ["-max", "--maximum"], "Upper bound for page numbers (cannot be used with only new)");
private const string _minimumName = "minimum";
internal readonly Int32Input MinimumInput = new(_minimumName, ["-min", "--minimum"], "Lower bound for page numbers (cannot be used with only new)");
private const string _onlyNewName = "onlynew";
internal readonly FlagInput OnlyNewInput = new(_onlyNewName, ["-n", "--onlynew"], "Use the last modified view (cannot be used with min and max)");
#endregion
public SiteFeature()
: base(DisplayName, _flags, _description)
{
RequiresInputs = false;
// Common
Add(DebugInput);
Add(OutputInput);
Add(UsernameInput);
Add(PasswordInput);
// Specific
Add(MinimumInput);
Add(MaximumInput);
Add(OnlyNewInput);
Add(ForceInput);
}
/// <inheritdoc/>
public override bool Execute()
{
// Get values needed more than once
string? outputDirectory = OutputInput.Value;
int minId = MinimumInput.Value ?? -1;
int maxId = MaximumInput.Value ?? -1;
bool onlyNew = OnlyNewInput.Value;
// Output directory validation
if (!ValidateAndCreateOutputDirectory(outputDirectory))
return false;
// Range verification
if (!onlyNew && (minId < 0 || maxId < 0))
{
Console.WriteLine("Please enter a valid range of Redump IDs");
return false;
}
// Login to Redump, if necessary
if (!_client.LoggedIn)
_client.Login(UsernameInput.Value ?? string.Empty, PasswordInput.Value ?? string.Empty).Wait();
// Start the processing
Task<List<int>> processingTask;
if (onlyNew)
processingTask = Discs.DownloadLastModified(_client, outputDirectory, ForceInput.Value);
else
processingTask = Discs.DownloadSiteRange(_client, outputDirectory, minId, maxId);
// Retrieve the result
processingTask.Wait();
var processedIds = processingTask.Result;
// Display the processed IDs
return PrintProcessedIds(processedIds);
}
/// <inheritdoc/>
public override bool VerifyInputs() => true;
}
}

View File

@@ -0,0 +1,81 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using SabreTools.CommandLine.Inputs;
using SabreTools.RedumpLib.Web;
namespace RedumpTool.Features
{
internal sealed class UserFeature : BaseFeature
{
#region Feature Definition
public const string DisplayName = "user";
private static readonly string[] _flags = ["user"];
private const string _description = "Download pages and related files for a particular user";
#endregion
#region Inputs
private const string _listName = "list";
internal readonly FlagInput ListInput = new(_listName, ["-l", "--list"], "Only list the page IDs for that user");
private const string _onlyNewName = "onlynew";
internal readonly FlagInput OnlyNewInput = new(_onlyNewName, ["-n", "--onlynew"], "Use the last modified view instead of sequential parsing");
#endregion
public UserFeature()
: base(DisplayName, _flags, _description)
{
RequiresInputs = false;
// Common
Add(DebugInput);
Add(OutputInput);
Add(UsernameInput);
Add(PasswordInput);
// Specific
Add(OnlyNewInput);
Add(ListInput);
}
/// <inheritdoc/>
public override bool Execute()
{
// Get values needed more than once
bool onlyList = ListInput.Value;
string? outputDirectory = OutputInput.Value;
// Output directory validation
if (!onlyList && !ValidateAndCreateOutputDirectory(outputDirectory))
return false;
// Login to Redump, if necessary
if (!_client.LoggedIn)
_client.Login(UsernameInput.Value ?? string.Empty, PasswordInput.Value ?? string.Empty).Wait();
// Start the processing
Task<List<int>> processingTask;
if (onlyList)
processingTask = User.ListUser(_client, UsernameInput.Value);
else if (OnlyNewInput.Value)
processingTask = User.DownloadUserLastModified(_client, UsernameInput.Value, outputDirectory);
else
processingTask = User.DownloadUser(_client, UsernameInput.Value, outputDirectory);
// Retrieve the result
processingTask.Wait();
var processedIds = processingTask.Result;
// Display the processed IDs
return PrintProcessedIds(processedIds);
}
/// <inheritdoc/>
public override bool VerifyInputs() => true;
}
}

View File

@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using SabreTools.CommandLine.Inputs;
using SabreTools.RedumpLib.Web;
namespace RedumpTool.Features
{
internal sealed class WIPFeature : BaseFeature
{
#region Feature Definition
public const string DisplayName = "wip";
private static readonly string[] _flags = ["wip"];
private const string _description = "Download pages and related files from the WIP list";
#endregion
#region Inputs
private const string _maximumName = "maximum";
internal readonly Int32Input MaximumInput = new(_maximumName, ["-max", "--maximum"], "Upper bound for page numbers (cannot be used with only new)");
private const string _minimumName = "minimum";
internal readonly Int32Input MinimumInput = new(_minimumName, ["-min", "--minimum"], "Lower bound for page numbers (cannot be used with only new)");
private const string _onlyNewName = "onlynew";
internal readonly FlagInput OnlyNewInput = new(_onlyNewName, ["-n", "--onlynew"], "Use the last modified view (cannot be used with min and max)");
#endregion
public WIPFeature()
: base(DisplayName, _flags, _description)
{
RequiresInputs = false;
// Common
Add(DebugInput);
Add(OutputInput);
Add(UsernameInput);
Add(PasswordInput);
// Specific
Add(MinimumInput);
Add(MaximumInput);
Add(OnlyNewInput);
}
/// <inheritdoc/>
public override bool Execute()
{
// Get values needed more than once
string? outputDirectory = OutputInput.Value;
int minId = MinimumInput.Value ?? -1;
int maxId = MaximumInput.Value ?? -1;
bool onlyNew = OnlyNewInput.Value;
// Output directory validation
if (!ValidateAndCreateOutputDirectory(outputDirectory))
return false;
// Range verification
if (!onlyNew && (minId < 0 || maxId < 0))
{
Console.WriteLine("Please enter a valid range of WIP IDs");
return false;
}
// Login to Redump, if necessary
if (!_client.LoggedIn)
_client.Login(UsernameInput.Value ?? string.Empty, PasswordInput.Value ?? string.Empty).Wait();
// Start the processing
Task<List<int>> processingTask;
if (onlyNew)
processingTask = WIP.DownloadLastSubmitted(_client, outputDirectory);
else
processingTask = WIP.DownloadWIPRange(_client, outputDirectory, minId, maxId);
// Retrieve the result
processingTask.Wait();
var processedIds = processingTask.Result;
// Display the processed IDs
return PrintProcessedIds(processedIds);
}
/// <inheritdoc/>
public override bool VerifyInputs() => true;
}
}

View File

@@ -1,5 +1,7 @@
using System;
using System.IO;
using RedumpTool.Features;
using SabreTools.CommandLine;
using SabreTools.CommandLine.Features;
namespace RedumpTool
{
@@ -7,6 +9,9 @@ namespace RedumpTool
{
public static void Main(string[] args)
{
// Create the command set
var commandSet = CreateCommands();
// Show help if nothing is input
if (args.Length == 0)
{
@@ -15,210 +20,41 @@ namespace RedumpTool
return;
}
// Derive the feature, if possible
Feature feature = DeriveFeature(args[0]);
if (feature == Feature.NONE)
{
Console.WriteLine("The feature could not be derived");
ShowHelp();
return;
}
// Cache the first argument and starting index
string featureName = args[0];
// Create a new Downloader
var downloader = CreateDownloader(feature, args);
if (downloader is null)
// Try processing the standalone arguments
var topLevel = commandSet.GetTopLevel(featureName);
switch (topLevel)
{
Console.WriteLine("A downloader could not be created from the inputs");
ShowHelp();
return;
}
case Help help: help.ProcessArgs(args, 0, commandSet); return;
case SiteFeature sf: sf.ProcessArgs(args, 1); sf.Execute(); return;
case WIPFeature wf: wf.ProcessArgs(args, 1); wf.Execute(); return;
case PacksFeature pf: pf.ProcessArgs(args, 1); pf.Execute(); return;
case QueryFeature qf: qf.ProcessArgs(args, 1); qf.Execute(); return;
// Run the download task
var downloaderTask = downloader.Download();
downloaderTask.Wait();
// Get the downloader task results and print, if necessary
var downloaderResult = downloaderTask.Result;
if (downloaderResult.Count > 0)
{
string processedIds = string.Join(", ", [.. downloaderResult.ConvertAll(i => i.ToString())]);
Console.WriteLine($"Processed IDs: {processedIds}");
}
else if (downloaderResult.Count == 0 && downloader.Feature != Feature.Packs)
{
Console.WriteLine("No results were found");
ShowHelp();
default:
Console.WriteLine($"{featureName} is not a known feature");
ShowHelp();
return;
}
}
/// <summary>
/// Derive the feature from the supplied argument
/// Create the command set for the program
/// </summary>
/// <param name="feature">Possible feature name to derive from</param>
/// <returns>True if the feature was set, false otherwise</returns>
private static Feature DeriveFeature(string feature)
private static CommandSet CreateCommands()
{
return feature.ToLowerInvariant() switch
{
"site" => Feature.Site,
"wip" => Feature.WIP,
"packs" => Feature.Packs,
"user" => Feature.User,
"search" => Feature.Quicksearch,
"query" => Feature.Quicksearch,
_ => Feature.NONE,
};
}
var commandSet = new CommandSet();
/// <summary>
/// Create a Downloader from a feature and a set of arguments
/// </summary>
/// <param name="feature">Primary feature to use</param>
/// <param name="args">Arguments list to parse</param>
/// <returns>Initialized Downloader on success, null otherwise</returns>
private static Downloader? CreateDownloader(Feature feature, string[] args)
{
var downloader = new Downloader()
{
Feature = feature,
MinimumId = -1,
MaximumId = -1,
};
commandSet.Add(new Help(["-?", "-h", "--help"]));
commandSet.Add(new SiteFeature());
commandSet.Add(new WIPFeature());
commandSet.Add(new PacksFeature());
commandSet.Add(new UserFeature());
commandSet.Add(new QueryFeature());
// Loop through all of the arguments
try
{
for (int i = 1; i < args.Length; i++)
{
switch (args[i])
{
// Output directory
case "-o":
case "--output":
downloader.OutDir = args[++i].Trim('"');
break;
// Username
case "-u":
case "--username":
downloader.Username = args[++i];
break;
// Password
case "-p":
case "--password":
downloader.Password = args[++i];
break;
// Minimum Redump ID
case "-min":
case "--minimum":
if (!int.TryParse(args[++i], out int minimumId))
minimumId = -1;
downloader.MinimumId = minimumId;
break;
// Maximum Redump ID
case "-max":
case "--maximum":
if (!int.TryParse(args[++i], out int maximumId))
maximumId = -1;
downloader.MaximumId = maximumId;
break;
// Quicksearch text
case "-q":
case "--query":
downloader.QueryString = args[++i];
break;
// Packs subfolders
case "-s":
case "--subfolders":
downloader.UseSubfolders = true;
break;
// Use last modified
case "-n":
case "--onlynew":
downloader.OnlyNew = true;
break;
// List instead of download
case "-l":
case "--list":
downloader.OnlyList = true;
break;
// Don't filter forward slashes from queries
case "-ns":
case "--noslash":
downloader.NoSlash = true;
break;
// Force continuation
case "-f":
case "--force":
downloader.Force = true;
break;
// Everything else
default:
Console.WriteLine($"Unrecognized flag: {args[i]}");
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"An exception has occurred: {ex}");
return null;
}
// Output directory validation
if (!downloader.OnlyList && string.IsNullOrEmpty(downloader.OutDir))
{
Console.WriteLine("No output directory set!");
return null;
}
else if (!downloader.OnlyList && !string.IsNullOrEmpty(downloader.OutDir))
{
// Create the output directory, if it doesn't exist
try
{
if (!Directory.Exists(downloader.OutDir))
Directory.CreateDirectory(downloader.OutDir!);
}
catch (Exception ex)
{
Console.WriteLine($"An exception has occurred: {ex}");
return null;
}
}
// Range verification
if (feature == Feature.Site && !downloader.OnlyNew && (downloader.MinimumId < 0 || downloader.MaximumId < 0))
{
Console.WriteLine("Please enter a valid range of Redump IDs");
return null;
}
else if (feature == Feature.WIP && !downloader.OnlyNew && (downloader.MinimumId < 0 || downloader.MaximumId < 0))
{
Console.WriteLine("Please enter a valid range of WIP IDs");
return null;
}
// Query verification (and cleanup)
if (feature == Feature.Quicksearch && string.IsNullOrEmpty(downloader.QueryString))
{
Console.WriteLine("Please enter a query for searching");
return null;
}
// Return the downloader
return downloader;
return commandSet;
}
/// <summary>
@@ -239,7 +75,7 @@ namespace RedumpTool
Console.WriteLine(" -min <MinId>, --minimum <MinId> - Lower bound for page numbers (cannot be used with only new)");
Console.WriteLine(" -max <MaxId>, --maximum <MaxId> - Upper bound for page numbers (cannot be used with only new)");
Console.WriteLine(" -n, --onlynew - Use the last modified view (cannot be used with min and max)");
Console.WriteLine(" -f, --force - Force continuing downloads until user cancels (used with only new)");
Console.WriteLine(" -f, --force - Force continuing downloads until user cancels (requires only new)");
Console.WriteLine();
Console.WriteLine("wip - Download pages and related files from the WIP list");
Console.WriteLine(" -min <MinId>, --minimum <MinId> - Lower bound for page numbers (cannot be used with only new)");

View File

@@ -29,5 +29,8 @@
<ItemGroup>
<ProjectReference Include="..\SabreTools.RedumpLib\SabreTools.RedumpLib.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.CommandLine" Version="[1.4.0]" />
</ItemGroup>
</Project>

View File

@@ -145,7 +145,7 @@ namespace SabreTools.RedumpLib.Web
{
try
{
Console.WriteLine($"Login attempt {i} of {MaxLoginAttempts}");
Console.WriteLine($"Login attempt {i + 1} of {MaxLoginAttempts}");
// Get the current token from the login page
var loginPage = await DownloadString(Constants.LoginUrl);

View File

@@ -16,9 +16,9 @@ namespace SabreTools.RedumpLib.Web
/// <param name="rc">RedumpClient for connectivity</param>
/// <param name="outDir">Output directory to save data to</param>
/// <returns>All disc IDs in last submitted range, empty on error</returns>
public static async Task<List<int>?> DownloadLastSubmitted(RedumpClient rc, string? outDir)
public static async Task<List<int>> DownloadLastSubmitted(RedumpClient rc, string? outDir)
{
return await rc.CheckSingleWIPPage(Constants.WipDumpsUrl, outDir, false);
return await rc.CheckSingleWIPPage(Constants.WipDumpsUrl, outDir, false) ?? [];
}
/// <summary>