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