Cleanup, Arguments
- Rewritten the Program.cs to get it clean and readable
- Rewritten the usage section
- Added ArgumentParser (!!! ATTENTION !!! some arguments have changed)
- ProgramArgs contains all possible parameters for the command line
Feel free to add some ;)
pull/261/head
parent
8b4dcc2339
commit
ce1d61f910
@ -0,0 +1,227 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace DepotDownloader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Quick argument parser
|
||||||
|
/// Need some optimisation, but it is functional ;)
|
||||||
|
/// </summary>
|
||||||
|
public static class ArgumentParser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parse command line arguments and set Properties in TContainer object
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">Command line arguments</param>
|
||||||
|
/// <typeparam name="TContainer">Type implementing IArgumentContainer</typeparam>
|
||||||
|
/// <returns>TContainer object with properties set</returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
public static TContainer Parse<TContainer>(string[] args) where TContainer : IArgumentContainer
|
||||||
|
{
|
||||||
|
var containerType = typeof(TContainer);
|
||||||
|
var container = (TContainer)Activator.CreateInstance(containerType);
|
||||||
|
|
||||||
|
if (container == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Type {containerType} has no empty constructor");
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = GetContainerOptions(containerType);
|
||||||
|
|
||||||
|
for (var i = 0; i < args.Length; i++)
|
||||||
|
{
|
||||||
|
if (!args[i].StartsWith("-"))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Unknown argument: {args[i]}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var arg = args[i].StartsWith("--") ? args[i].Substring(2) : args[i].Substring(1);
|
||||||
|
|
||||||
|
var (option, property) = (from op in options
|
||||||
|
where
|
||||||
|
arg.Length == 1 && op.Item1.ShortOption == arg[0]
|
||||||
|
|| op.Item1.LongOption.Equals(arg, StringComparison.Ordinal)
|
||||||
|
select op).FirstOrDefault();
|
||||||
|
|
||||||
|
if (option == null || property == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Unknown argument: '{arg}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.ParameterName != null || option.AllowMultiple)
|
||||||
|
{
|
||||||
|
if (i == args.Length - 1)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"No parameter for option '{arg}' found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!option.AllowMultiple)
|
||||||
|
{
|
||||||
|
var parameter = args[++i];
|
||||||
|
if (parameter.StartsWith("-"))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"No parameter for option '{arg}' found");
|
||||||
|
}
|
||||||
|
|
||||||
|
property.SetValue(container,
|
||||||
|
property.PropertyType == typeof(string)
|
||||||
|
? parameter
|
||||||
|
: TypeDescriptor.GetConverter(property.PropertyType).ConvertFromString(parameter));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var converter = property.PropertyType.IsGenericType
|
||||||
|
? TypeDescriptor.GetConverter(property.PropertyType.GenericTypeArguments[0])
|
||||||
|
: null;
|
||||||
|
|
||||||
|
var list = (IList)property.GetValue(container);
|
||||||
|
if (list == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Initialize List properties first!");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < args.Length && !args[i + 1].StartsWith("-"))
|
||||||
|
{
|
||||||
|
var parameter = converter != null ? converter.ConvertFromString(args[++i]) : args[++i];
|
||||||
|
list.Add(parameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO wrap
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a parameter table for given container type
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ident">number of whitespaces at the line beginning</param>
|
||||||
|
/// <param name="wrap">wrap long descriptions (not implemented yet)</param>
|
||||||
|
/// <typeparam name="T">Container type</typeparam>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetHelpList<T>(int ident = 4, bool wrap = false) where T : IArgumentContainer
|
||||||
|
{
|
||||||
|
var optionList = GetContainerOptions(typeof(T));
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
var lines = new List<(string, string)>();
|
||||||
|
var maxOpLength = 0;
|
||||||
|
foreach (var (option, _) in optionList)
|
||||||
|
{
|
||||||
|
var opStr = option.ShortOption != '\0' ? $"-{option.ShortOption}" : "";
|
||||||
|
if (!string.IsNullOrEmpty(option.LongOption))
|
||||||
|
{
|
||||||
|
opStr += (option.ShortOption != '\0' ? ", " : " ") + $"--{option.LongOption}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.ParameterName != null)
|
||||||
|
{
|
||||||
|
opStr += $" <{option.ParameterName}>";
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.Add((opStr, option.Description));
|
||||||
|
maxOpLength = Math.Max(maxOpLength, opStr.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
var identStr = "".PadRight(ident);
|
||||||
|
foreach (var (op, desc) in lines)
|
||||||
|
{
|
||||||
|
sb.AppendLine(identStr + op.PadRight(maxOpLength + 4) + desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<(OptionAttribute, PropertyInfo)> GetContainerOptions(Type containerType)
|
||||||
|
{
|
||||||
|
var resultList = new List<(OptionAttribute, PropertyInfo)>();
|
||||||
|
|
||||||
|
foreach (var prop in containerType.GetProperties())
|
||||||
|
{
|
||||||
|
// try to get OptionAttribute from property
|
||||||
|
var a = prop.GetCustomAttribute(typeof(OptionAttribute));
|
||||||
|
|
||||||
|
if (a == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check some things
|
||||||
|
var option = (OptionAttribute)a;
|
||||||
|
if (prop.SetMethod == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"No setter found for '{prop.Name}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only options with descriptions are allowed!
|
||||||
|
if (string.IsNullOrEmpty(option.Description))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"No description found for '{prop.Name}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need a short option or a long option
|
||||||
|
if (option.ShortOption == '\0' && string.IsNullOrEmpty(option.LongOption))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"You must at least permit ShortOption or LongOption. Property: '{prop.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowMultiple only allowed on list properties
|
||||||
|
if (option.AllowMultiple && !prop.PropertyType.IsAssignableTo(typeof(IList)))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"Options with AllowMultiple must be assignable to IList type. Property: '{prop.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!option.AllowMultiple && option.ParameterName == null && prop.PropertyType != typeof(bool))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"Property must be bool if there is no parameter required. Property: '{prop.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// if everything is ok, add it to the result list
|
||||||
|
resultList.Add((option, prop));
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public class OptionAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used for lists of parameters, seperated by whitespaces
|
||||||
|
/// </summary>
|
||||||
|
public bool AllowMultiple = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// </summary>
|
||||||
|
public string Description = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// long option name (e.g. --username)
|
||||||
|
/// </summary>
|
||||||
|
public string LongOption = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of parameter if parameter is needed (e.g. <user>)
|
||||||
|
/// </summary>
|
||||||
|
public string ParameterName = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// single character option (e.g. -u)
|
||||||
|
/// </summary>
|
||||||
|
public char ShortOption = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IArgumentContainer
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,141 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace DepotDownloader
|
||||||
|
{
|
||||||
|
internal partial class Program
|
||||||
|
{
|
||||||
|
private class ProgramArgs : IArgumentContainer
|
||||||
|
{
|
||||||
|
public ProgramArgs()
|
||||||
|
{
|
||||||
|
AppID = ContentDownloader.INVALID_APP_ID;
|
||||||
|
DepotIDs = new List<uint>();
|
||||||
|
ManifestIDs = new List<ulong>();
|
||||||
|
ManifestOnly = false;
|
||||||
|
UGC = ContentDownloader.INVALID_MANIFEST_ID;
|
||||||
|
PubFile = ContentDownloader.INVALID_MANIFEST_ID;
|
||||||
|
Validate = false;
|
||||||
|
CellID = 0;
|
||||||
|
Username = "";
|
||||||
|
Password = "";
|
||||||
|
RememberPassword = false;
|
||||||
|
LoginID = null;
|
||||||
|
BetaBranchName = ContentDownloader.DEFAULT_BRANCH;
|
||||||
|
BetaPassword = "";
|
||||||
|
AllPlatforms = false;
|
||||||
|
OperatingSystem = "";
|
||||||
|
Architecture = Environment.Is64BitOperatingSystem ? "64" : "32";
|
||||||
|
AllLanguages = false;
|
||||||
|
Language = "";
|
||||||
|
LowViolence = false;
|
||||||
|
InstallDir = "";
|
||||||
|
FileList = null;
|
||||||
|
MaxServers = 20;
|
||||||
|
MaxDownloads = 8;
|
||||||
|
Debug = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Option(ShortOption = 'a', LongOption = "app", ParameterName = "id",
|
||||||
|
Description = "The AppID to download")]
|
||||||
|
public uint AppID { get; set; }
|
||||||
|
|
||||||
|
[Option(ShortOption = 'd', LongOption = "depot", ParameterName = "id", AllowMultiple = true,
|
||||||
|
Description = "The DepotID to download, separate multiple ids with whitespace")]
|
||||||
|
public List<uint> DepotIDs { get; set;}
|
||||||
|
|
||||||
|
[Option(ShortOption = 'm', LongOption = "manifest", ParameterName = "id", AllowMultiple = true,
|
||||||
|
Description = "manifest id of content to download (requires -d|--depot, default: current for branch)")]
|
||||||
|
public List<ulong> ManifestIDs { get; set;}
|
||||||
|
|
||||||
|
[Option(LongOption = "manifest-only",
|
||||||
|
Description = "Downloads a human readable manifest for any depots that would be downloaded")]
|
||||||
|
public bool ManifestOnly { get; set;}
|
||||||
|
|
||||||
|
[Option(ShortOption = 'g', LongOption = "ugc", ParameterName = "id",
|
||||||
|
Description = "The UGC ID to download")]
|
||||||
|
public ulong UGC { get; set;}
|
||||||
|
|
||||||
|
[Option(LongOption = "pub-file", ParameterName = "id",
|
||||||
|
Description = "The Published-File-ID to download. (Will automatically resolve to UGC id)\n")]
|
||||||
|
public ulong PubFile { get; set;}
|
||||||
|
|
||||||
|
[Option(LongOption = "validate", Description = "Include checksum verification of files already downloaded")]
|
||||||
|
public bool Validate { get; set;}
|
||||||
|
|
||||||
|
[Option(ShortOption = 'c', LongOption = "cell-id", ParameterName = "id",
|
||||||
|
Description = "The overridden CellID of the content server to download from.")]
|
||||||
|
public int CellID { get; set;}
|
||||||
|
|
||||||
|
[Option(ShortOption = 'u', LongOption = "username", ParameterName = "user",
|
||||||
|
Description = "The username of the account to login to for restricted content")]
|
||||||
|
public string Username { get; set;}
|
||||||
|
|
||||||
|
[Option(ShortOption = 'p', LongOption = "password", ParameterName = "pass",
|
||||||
|
Description = "The password of the account to login to for restricted content.")]
|
||||||
|
public string Password { get;set; }
|
||||||
|
|
||||||
|
[Option(LongOption = "remember-password",
|
||||||
|
Description = "If set, remember the password for subsequent logins of this user\n")]
|
||||||
|
public bool RememberPassword { get; set;}
|
||||||
|
|
||||||
|
[Option(LongOption = "login-id", ParameterName = "id",
|
||||||
|
Description = "A unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently")]
|
||||||
|
public uint? LoginID { get; set;}
|
||||||
|
|
||||||
|
[Option(LongOption = "beta", ParameterName = "branch",
|
||||||
|
Description = "Download from specified beta branch if available (default: Public)")]
|
||||||
|
public string BetaBranchName { get; set;}
|
||||||
|
|
||||||
|
[Option(LongOption = "beta-password", ParameterName = "pass",
|
||||||
|
Description = "Beta branch password if applicable\n")]
|
||||||
|
public string BetaPassword { get; set;}
|
||||||
|
|
||||||
|
[Option(LongOption = "all-platforms",
|
||||||
|
Description = "Downloads all platform-specific depots when -a|--app is used")]
|
||||||
|
public bool AllPlatforms { get; set;}
|
||||||
|
|
||||||
|
[Option(ShortOption = 'o', LongOption = "os", ParameterName = "os",
|
||||||
|
Description = "The operating system for which to download the game (windows, macos or linux, default: OS the program is currently running on)")]
|
||||||
|
public string OperatingSystem { get; set;}
|
||||||
|
|
||||||
|
[Option(LongOption = "os-arch",
|
||||||
|
Description =
|
||||||
|
"The architecture for which to download the game (32 or 64, default: the host's architecture)\n",
|
||||||
|
ParameterName = "arch")]
|
||||||
|
public string Architecture { get; set;}
|
||||||
|
|
||||||
|
[Option(LongOption = "all-languages",
|
||||||
|
Description = "Download all language-specific depots when -a|--app is used")]
|
||||||
|
public bool AllLanguages { get; set;}
|
||||||
|
|
||||||
|
[Option(ShortOption = 'l', LongOption = "language", ParameterName = "lang",
|
||||||
|
Description = "The language for which to download the game (default: english)\n")]
|
||||||
|
public string Language { get; set;}
|
||||||
|
|
||||||
|
[Option(LongOption = "low-violence",
|
||||||
|
Description = "Download low violence depots when -a|--app is used")]
|
||||||
|
public bool LowViolence { get; set;}
|
||||||
|
|
||||||
|
[Option(LongOption = "install-dir", ParameterName = "dir",
|
||||||
|
Description = "The directory in which to place downloaded files")]
|
||||||
|
public string InstallDir { get; set;}
|
||||||
|
|
||||||
|
[Option(LongOption = "file-list", ParameterName = "file",
|
||||||
|
Description = "a list of files to download (from the manifest). Prefix file path with 'regex:' if you want to match with regex")]
|
||||||
|
public string FileList { get; set;}
|
||||||
|
|
||||||
|
[Option(LongOption = "max-servers", ParameterName = "count",
|
||||||
|
Description = "Maximum number of content servers to use (default: 20)")]
|
||||||
|
public int MaxServers { get; set;}
|
||||||
|
|
||||||
|
[Option(LongOption = "max-downloads", ParameterName = "count",
|
||||||
|
Description = "Maximum number of chunks to download concurrently (default: 8)")]
|
||||||
|
public int MaxDownloads { get; set;}
|
||||||
|
|
||||||
|
[Option(LongOption = "verbose",
|
||||||
|
Description = "Makes the DepotDownload more verbose/talkative. Mostly useful for debugging")]
|
||||||
|
public bool Debug { get; set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue