diff --git a/.gitignore b/.gitignore index 8e4d453..ebdb2de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,11 @@ +### VisualStudio template ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore # User-specific files +*.rsuser *.suo *.user *.userosscache @@ -12,6 +14,9 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs +# Mono auto generated files +mono_crash.* + # Build results [Dd]ebug/ [Dd]ebugPublic/ @@ -19,43 +24,62 @@ [Rr]eleases/ x64/ x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ +[Ll]ogs/ -# Visual Studio 2015 cache/options directory +# Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ +# Visual Studio 2017 auto generated files +Generated\ Files/ + # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* -# NUNIT +# NUnit *.VisualState.xml TestResult.xml +nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c +# Benchmark Results +BenchmarkDotNet.Artifacts/ + # .NET Core project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio *_i.c *_p.c -*_i.h +*_h.h *.ilk *.meta *.obj +*.iobj *.pch *.pdb +*.ipdb *.pgc *.pgd *.rsp @@ -65,7 +89,9 @@ artifacts/ *.tlh *.tmp *.tmp_proj +*_wpftmp.csproj *.log +*.tlog *.vspscc *.vssscc .builds @@ -93,6 +119,9 @@ ipch/ *.vspx *.sap +# Visual Studio Trace Files +*.e2e + # TFS 2012 Local Workspace $tf/ @@ -104,15 +133,21 @@ _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user -# JustCode is a .NET coding add-in -.JustCode - # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + # Visual Studio code coverage results *.coverage *.coveragexml @@ -148,7 +183,7 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings +# Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj @@ -160,12 +195,14 @@ PublishScripts/ # NuGet Packages *.nupkg +# NuGet Symbol Packages +*.snupkg # The packages folder can be ignored because of Package Restore -**/packages/* +**/[Pp]ackages/* # except build/, which is used as an MSBuild target. -!**/packages/build/ +!**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config +#!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets @@ -183,12 +220,15 @@ AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt +*.appx +*.appxbundle +*.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache -!*.[Cc]ache/ +!?*.[Cc]ache/ # Others ClientBin/ @@ -201,6 +241,10 @@ ClientBin/ *.publishsettings orleans.codegen.cs +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ @@ -215,6 +259,8 @@ _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak # SQL Server files *.mdf @@ -225,6 +271,10 @@ UpgradeLog*.htm *.rdl.data *.bim.layout *.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ @@ -236,9 +286,6 @@ FakesAssemblies/ .ntvs_analysis.dat node_modules/ -# Typescript v1 declaration files -typings/ - # Visual Studio 6 build log *.plg @@ -248,6 +295,17 @@ typings/ # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -263,12 +321,8 @@ paket-files/ # FAKE - F# Make .fake/ -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ +# CodeRush personal settings +.cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ @@ -278,6 +332,9 @@ __pycache__/ # tools/** # !tools/packages.config +# Tabs Studio +*.tss + # Telerik's JustMock configuration file *.jmconfig @@ -287,5 +344,139 @@ __pycache__/ *.odx.cs *.xsd.cs +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + # WiX harvested files *.heat.wxs + +# JetBrains Rider +*.sln.iml + +### Rider template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 0000000..291faa1 --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,41 @@ +[*] + +# Microsoft .NET properties +csharp_new_line_before_members_in_object_initializers = false +csharp_preferred_modifier_order = private, public, protected, internal, file, new, static, sealed, override, virtual, abstract, async, extern, unsafe, volatile, readonly, required:suggestion +csharp_style_namespace_declarations = block_scoped:suggestion +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:none + +# ReSharper properties +resharper_align_linq_query = true +resharper_align_multiline_binary_expressions_chain = false +resharper_align_multiline_statement_conditions = false +resharper_blank_lines_after_block_statements = 0 +resharper_blank_lines_around_single_line_auto_property = 1 +resharper_braces_for_foreach = required +resharper_braces_for_using = required +resharper_braces_for_while = required +resharper_braces_redundant = false +resharper_csharp_blank_lines_around_field = 0 +resharper_csharp_blank_lines_around_region = 0 +resharper_csharp_blank_lines_around_single_line_invocable = 1 +resharper_csharp_insert_final_newline = true +resharper_csharp_max_line_length = 201 +resharper_csharp_remove_blank_lines_near_braces_in_code = false +resharper_csharp_remove_blank_lines_near_braces_in_declarations = false +resharper_indent_pars = outside_and_inside +resharper_instance_members_qualify_declared_in = +resharper_keep_existing_attribute_arrangement = true +resharper_nested_ternary_style = simple_wrap +resharper_object_creation_when_type_evident = explicitly_typed +resharper_parentheses_redundancy_style = remove +resharper_parentheses_same_type_operations = true +resharper_place_accessorholder_attribute_on_same_line = false +resharper_place_expr_accessor_on_single_line = true +resharper_place_expr_method_on_single_line = true +resharper_place_simple_embedded_statement_on_same_line = false +resharper_place_simple_initializer_on_single_line = false +resharper_space_within_single_line_array_initializer_braces = false +resharper_wrap_object_and_collection_initializer_style = chop_always diff --git a/src/.idea/.idea.QBittorrent.CommandLineInterface/.idea/.gitignore b/src/.idea/.idea.QBittorrent.CommandLineInterface/.idea/.gitignore new file mode 100644 index 0000000..8c363ae --- /dev/null +++ b/src/.idea/.idea.QBittorrent.CommandLineInterface/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/contentModel.xml +/.idea.QBittorrent.CommandLineInterface.iml +/projectSettingsUpdater.xml +/modules.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/src/.idea/.idea.QBittorrent.CommandLineInterface/.idea/.name b/src/.idea/.idea.QBittorrent.CommandLineInterface/.idea/.name new file mode 100644 index 0000000..f236102 --- /dev/null +++ b/src/.idea/.idea.QBittorrent.CommandLineInterface/.idea/.name @@ -0,0 +1 @@ +QBittorrent.CommandLineInterface \ No newline at end of file diff --git a/src/.idea/.idea.QBittorrent.CommandLineInterface/.idea/encodings.xml b/src/.idea/.idea.QBittorrent.CommandLineInterface/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/src/.idea/.idea.QBittorrent.CommandLineInterface/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/.idea/.idea.QBittorrent.CommandLineInterface/.idea/indexLayout.xml b/src/.idea/.idea.QBittorrent.CommandLineInterface/.idea/indexLayout.xml new file mode 100644 index 0000000..f9b49bd --- /dev/null +++ b/src/.idea/.idea.QBittorrent.CommandLineInterface/.idea/indexLayout.xml @@ -0,0 +1,10 @@ + + + + + ../../qbittorrent-cli + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.QBittorrent.CommandLineInterface/.idea/vcs.xml b/src/.idea/.idea.QBittorrent.CommandLineInterface/.idea/vcs.xml new file mode 100644 index 0000000..62bd7a0 --- /dev/null +++ b/src/.idea/.idea.QBittorrent.CommandLineInterface/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/QBittorrent.CommandLineInterface.sln.DotSettings b/src/QBittorrent.CommandLineInterface.sln.DotSettings index eb72693..4f34420 100644 --- a/src/QBittorrent.CommandLineInterface.sln.DotSettings +++ b/src/QBittorrent.CommandLineInterface.sln.DotSettings @@ -1,47 +1,4 @@  - UI - True - True - True - 3 - True - 0 - True - 2 - True - completeSmart() - 1 - True - True - 2.0 - InCSharpTypeMember - jdprop - True - /// <summary> -/// -/// </summary> -[JsonProperty("$JSON_NAME$")] -[Display(Name = "$DISP_NAME$")] -public $TYPE$ $PROP$ { get; set; } - True - True - 0 - True - 2 - True - completeSmart() - 1 - jprop - JSON Property - True - True - True - True - InCSharpTypeMember - 2.0 - /// <summary> -/// -/// </summary> -[JsonProperty("$JSON_NAME$")] -public $TYPE$ $PROP$ { get; set; } - True \ No newline at end of file + BT + DHT + UI \ No newline at end of file diff --git a/src/QBittorrent.CommandLineInterface/App.config b/src/QBittorrent.CommandLineInterface/App.config deleted file mode 100644 index badb1a5..0000000 --- a/src/QBittorrent.CommandLineInterface/App.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/QBittorrent.CommandLineInterface/Attributes/EnumValidationAttribute.cs b/src/QBittorrent.CommandLineInterface/Attributes/EnumValidationAttribute.cs index 8ea419e..0494fc3 100644 --- a/src/QBittorrent.CommandLineInterface/Attributes/EnumValidationAttribute.cs +++ b/src/QBittorrent.CommandLineInterface/Attributes/EnumValidationAttribute.cs @@ -22,15 +22,15 @@ public EnumValidationAttribute(Type enumType) public bool AllowEmpty { get; set; } - protected override ValidationResult IsValid(object value, ValidationContext validationContext) + protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { - if (value == null && AllowEmpty) - return ValidationResult.Success; - - if (value is string str && - (EnumHelper.TryParse(EnumType, str, !CaseSensitive, out _) || (AllowEmpty && string.IsNullOrEmpty(str))) - ) - return ValidationResult.Success; + switch (value) + { + case null when AllowEmpty: + case string str when + Enum.TryParse(EnumType, str, !CaseSensitive, out _) || AllowEmpty && string.IsNullOrEmpty(str): + return ValidationResult.Success; + } var values = string.Join(", ", Enum.GetValues(EnumType).Cast()); if (!CaseSensitive) diff --git a/src/QBittorrent.CommandLineInterface/Attributes/IpAddressValidationAttribute.cs b/src/QBittorrent.CommandLineInterface/Attributes/IpAddressValidationAttribute.cs index d51dc60..3c871a1 100644 --- a/src/QBittorrent.CommandLineInterface/Attributes/IpAddressValidationAttribute.cs +++ b/src/QBittorrent.CommandLineInterface/Attributes/IpAddressValidationAttribute.cs @@ -1,22 +1,21 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Net; -using System.Text; namespace QBittorrent.CommandLineInterface.Attributes { public class IpAddressValidationAttribute : ValidationAttribute { - protected override ValidationResult IsValid(object value, ValidationContext validationContext) + protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { - if (value is null) - return ValidationResult.Success; + switch (value) + { + case null: + case string str when IPAddress.TryParse(str, out _): + return ValidationResult.Success; + default: + return new ValidationResult($"The value {value} is not a correct IP address."); + } - if (value is string str && IPAddress.TryParse(str, out _)) - return ValidationResult.Success; - - return new ValidationResult($"The value {value} is not a correct IP address."); } } } diff --git a/src/QBittorrent.CommandLineInterface/Attributes/IpEndpointValidationAttribute.cs b/src/QBittorrent.CommandLineInterface/Attributes/IpEndpointValidationAttribute.cs index f308c41..747a555 100644 --- a/src/QBittorrent.CommandLineInterface/Attributes/IpEndpointValidationAttribute.cs +++ b/src/QBittorrent.CommandLineInterface/Attributes/IpEndpointValidationAttribute.cs @@ -7,21 +7,23 @@ namespace QBittorrent.CommandLineInterface.Attributes { public class IpEndpointValidationAttribute : ValidationAttribute { - protected override ValidationResult IsValid(object value, ValidationContext validationContext) + protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { - if (value is null) - return ValidationResult.Success; - - if (value is string str && TryParse(str.AsSpan(), out _)) - return ValidationResult.Success; + switch (value) + { + case null: + case string str when TryParse(str.AsSpan(), out _): + return ValidationResult.Success; + default: + return new ValidationResult($"The value {value} is not a correct IP endpoint."); + } - return new ValidationResult($"The value {value} is not a correct IP endpoint."); } - private static bool TryParse(ReadOnlySpan s, out IPEndPoint result) + private static bool TryParse(ReadOnlySpan s, out IPEndPoint? result) { - int addressLength = s.Length; // If there's no port then send the entire string to the address parser - int lastColonPos = s.LastIndexOf(':'); + var addressLength = s.Length; // If there's no port then send the entire string to the address parser + var lastColonPos = s.LastIndexOf(':'); // Look to see if this is an IPv6 address with a port. if (lastColonPos > 0) @@ -31,45 +33,23 @@ private static bool TryParse(ReadOnlySpan s, out IPEndPoint result) addressLength = lastColonPos; } // Look to see if this is IPv4 with a port (IPv6 will have another colon) - else if (s.Slice(0, lastColonPos).LastIndexOf(':') == -1) + else if (s[..lastColonPos].LastIndexOf(':') == -1) { addressLength = lastColonPos; } } -#if NETFRAMEWORK - if (IPAddress.TryParse(GetString(s.Slice(0, addressLength)), out var address)) - { - uint port = 0; - if (addressLength == s.Length || - (uint.TryParse(GetString(s.Slice(addressLength + 1)), NumberStyles.None, CultureInfo.InvariantCulture, out port) && port <= IPEndPoint.MaxPort)) - - { - result = new IPEndPoint(address, (int)port); - return true; - } - } - - unsafe static string GetString(in ReadOnlySpan span) - { - fixed (char* p = span) - { - return new string(p, 0, span.Length); - } - } -#else - if (IPAddress.TryParse(s.Slice(0, addressLength), out var address)) + if (IPAddress.TryParse(s[..addressLength], out var address)) { uint port = 0; if (addressLength == s.Length || - (uint.TryParse(s.Slice(addressLength + 1), NumberStyles.None, CultureInfo.InvariantCulture, out port) && port <= IPEndPoint.MaxPort)) + uint.TryParse(s[(addressLength + 1)..], NumberStyles.None, CultureInfo.InvariantCulture, out port) && port <= IPEndPoint.MaxPort) { result = new IPEndPoint(address, (int)port); return true; } } -#endif result = null; return false; diff --git a/src/QBittorrent.CommandLineInterface/Attributes/IpNetworkValidationAttribute.cs b/src/QBittorrent.CommandLineInterface/Attributes/IpNetworkValidationAttribute.cs index 563b735..d612e0b 100644 --- a/src/QBittorrent.CommandLineInterface/Attributes/IpNetworkValidationAttribute.cs +++ b/src/QBittorrent.CommandLineInterface/Attributes/IpNetworkValidationAttribute.cs @@ -5,15 +5,17 @@ namespace QBittorrent.CommandLineInterface.Attributes { public class IpNetworkValidationAttribute : ValidationAttribute { - protected override ValidationResult IsValid(object value, ValidationContext validationContext) + protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { - if (value is null) - return ValidationResult.Success; + switch (value) + { + case null: + case string str when IPNetwork.TryParse(str, out _): + return ValidationResult.Success; + default: + return new ValidationResult($"The value {value} is not a correct IP network."); + } - if (value is string str && IPNetwork.TryParse(str, out _)) - return ValidationResult.Success; - - return new ValidationResult($"The value {value} is not a correct IP network."); } } } diff --git a/src/QBittorrent.CommandLineInterface/Attributes/ShareRatioLimitValidationAttribute.cs b/src/QBittorrent.CommandLineInterface/Attributes/ShareRatioLimitValidationAttribute.cs index c6f01d7..97da6e8 100644 --- a/src/QBittorrent.CommandLineInterface/Attributes/ShareRatioLimitValidationAttribute.cs +++ b/src/QBittorrent.CommandLineInterface/Attributes/ShareRatioLimitValidationAttribute.cs @@ -7,27 +7,24 @@ namespace QBittorrent.CommandLineInterface.Attributes { public class ShareRatioLimitValidationAttribute : ValidationAttribute { - private static readonly string[] Keywords = {"GLOBAL", "NONE"}; + private static readonly string[] Keywords = ["GLOBAL", "NONE"]; - protected override ValidationResult IsValid(object value, ValidationContext validationContext) + protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { - if (value is null) - return ValidationResult.Success; - - if (value is string str) + switch (value) { - if (Keywords.Contains(str, StringComparer.OrdinalIgnoreCase)) + case null: + case string str when Keywords.Contains(str, StringComparer.OrdinalIgnoreCase): return ValidationResult.Success; - - if (double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out _)) + case string str when double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out _): return ValidationResult.Success; - - if (double.TryParse(str, out _)) + case string str when double.TryParse(str, out _): return ValidationResult.Success; + default: + return new ValidationResult($"The value {value} is not a correct share ratio limit. " + + "The value must be either a number or one of the keywords: GLOBAL, NONE"); } - return new ValidationResult($"The value {value} is not a correct share ratio limit. " + - "The value must be either a number or one of the keywords: GLOBAL, NONE"); } } } diff --git a/src/QBittorrent.CommandLineInterface/Attributes/ShareSeedingTimeLimitValidationAttribute.cs b/src/QBittorrent.CommandLineInterface/Attributes/ShareSeedingTimeLimitValidationAttribute.cs index 17ba1cd..e6dd14f 100644 --- a/src/QBittorrent.CommandLineInterface/Attributes/ShareSeedingTimeLimitValidationAttribute.cs +++ b/src/QBittorrent.CommandLineInterface/Attributes/ShareSeedingTimeLimitValidationAttribute.cs @@ -6,24 +6,22 @@ namespace QBittorrent.CommandLineInterface.Attributes { public class ShareSeedingTimeLimitValidationAttribute : ValidationAttribute { - private static readonly string[] Keywords = {"GLOBAL", "NONE"}; + private static readonly string[] Keywords = ["GLOBAL", "NONE"]; - protected override ValidationResult IsValid(object value, ValidationContext validationContext) + protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { - if (value is null) - return ValidationResult.Success; - - if (value is string str) + switch (value) { - if (Keywords.Contains(str, StringComparer.OrdinalIgnoreCase)) + case null: + case string str when Keywords.Contains(str, StringComparer.OrdinalIgnoreCase): return ValidationResult.Success; - - if (TimeSpan.TryParse(str, out _)) + case string str when TimeSpan.TryParse(str, out _): return ValidationResult.Success; + default: + return new ValidationResult($"The value {value} is not a correct seeding time limit. " + + "The value must be either a number or one of the keywords: GLOBAL, NONE"); } - return new ValidationResult($"The value {value} is not a correct seeding time limit. " + - "The value must be either a number or one of the keywords: GLOBAL, NONE"); } } } diff --git a/src/QBittorrent.CommandLineInterface/CollectionExtensions.cs b/src/QBittorrent.CommandLineInterface/CollectionExtensions.cs deleted file mode 100644 index f5b6fd3..0000000 --- a/src/QBittorrent.CommandLineInterface/CollectionExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace QBittorrent.CommandLineInterface -{ - public static class CollectionExtensions - { - public static void Deconstruct(this KeyValuePair pair, out K key, out V value) - { - key = pair.Key; - value = pair.Value; - } - -#if NET46 - public static HashSet ToHashSet(this IEnumerable enumerable) => new HashSet(enumerable); - - public static HashSet ToHashSet(this IEnumerable enumerable, IEqualityComparer comparer) => new HashSet(enumerable, comparer); -#endif - } -} diff --git a/src/QBittorrent.CommandLineInterface/ColorSchemes/Color.cs b/src/QBittorrent.CommandLineInterface/ColorSchemes/Color.cs index 9d59403..7b4119f 100644 --- a/src/QBittorrent.CommandLineInterface/ColorSchemes/Color.cs +++ b/src/QBittorrent.CommandLineInterface/ColorSchemes/Color.cs @@ -1,11 +1,8 @@ using System; -using System.Collections.Generic; -using System.Text; -using Newtonsoft.Json.Converters; namespace QBittorrent.CommandLineInterface.ColorSchemes { - public struct Color + public readonly struct Color { private readonly ColorType _type; private readonly ConsoleColor? _consoleColor; @@ -42,7 +39,7 @@ private Color(ColorType type) _consoleColor = null; } - public Color(string value) + public Color(string? value) { switch (value) { @@ -65,18 +62,13 @@ public Color(string value) public static implicit operator ConsoleColor(Color value) { - switch (value._type) + return value._type switch { - case ColorType.Console: - // ReSharper disable once PossibleInvalidOperationException - return value._consoleColor.Value; - case ColorType.SystemBackground: - return EnumHelper.IsDefined(Console.BackgroundColor) ? Console.BackgroundColor : System.ConsoleColor.Black; - case ColorType.SystemForeground: - return EnumHelper.IsDefined(Console.ForegroundColor) ? Console.ForegroundColor : System.ConsoleColor.White; - default: - throw new ArgumentException(); - } + ColorType.Console => value._consoleColor!.Value, + ColorType.SystemBackground => EnumHelper.IsDefined(Console.BackgroundColor) ? Console.BackgroundColor : System.ConsoleColor.Black, + ColorType.SystemForeground => EnumHelper.IsDefined(Console.ForegroundColor) ? Console.ForegroundColor : System.ConsoleColor.White, + _ => throw new ArgumentOutOfRangeException() + }; } public ConsoleColor? ConsoleColor => _consoleColor; @@ -86,14 +78,15 @@ public override string ToString() switch (_type) { case ColorType.Console: - var colorName = _consoleColor.ToString(); + var colorName = _consoleColor.ToString()!; if (colorName.StartsWith("Dark")) - return "dark-" + colorName.Substring(4).ToLowerInvariant(); + return "dark-" + colorName[4..].ToLowerInvariant(); return colorName.ToLowerInvariant(); case ColorType.SystemBackground: return "system-bg"; case ColorType.SystemForeground: return "system-fg"; + case ColorType.Invalid: default: return ""; } diff --git a/src/QBittorrent.CommandLineInterface/ColorSchemes/ColorScheme.cs b/src/QBittorrent.CommandLineInterface/ColorSchemes/ColorScheme.cs index 4f44a96..c4b774f 100644 --- a/src/QBittorrent.CommandLineInterface/ColorSchemes/ColorScheme.cs +++ b/src/QBittorrent.CommandLineInterface/ColorSchemes/ColorScheme.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Reflection; -using System.Resources; -using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -18,20 +15,14 @@ public class ColorScheme private const string DarkResource = "QBittorrent.CommandLineInterface.ColorSchemes.dark.json"; private const string LightResource = "QBittorrent.CommandLineInterface.ColorSchemes.light.json"; - // ReSharper disable InconsistentNaming - private static readonly Lazy _dark; - private static readonly Lazy _light; - private static readonly Lazy _default; - // ReSharper restore InconsistentNaming - - private static ColorScheme _current; + private static ColorScheme? _current; static ColorScheme() { _dark = new Lazy(() => JsonConvert.DeserializeObject( - ReadJsonFromResource(DarkResource))); + ReadJsonFromResource(DarkResource)) ?? throw new InvalidOperationException()); _light = new Lazy(() => JsonConvert.DeserializeObject( - ReadJsonFromResource(LightResource))); + ReadJsonFromResource(LightResource)) ?? throw new InvalidOperationException()); _default = new Lazy(() => IsLight() ? _light.Value : _dark.Value); bool IsLight() @@ -73,31 +64,31 @@ public static ColorScheme Current public ColorSet Inactive { get; private set; } [JsonProperty("log")] - public IReadOnlyDictionary LogColors { get; private set; } + public IReadOnlyDictionary? LogColors { get; private set; } [JsonProperty("torrent-status")] - public IReadOnlyDictionary TorrentStateColors { get; private set; } + public IReadOnlyDictionary? TorrentStateColors { get; private set; } public static async Task FromJsonAsync(string json) { var config = JObject.Parse(json); var schema = await LoadSchemaAsync().ConfigureAwait(false); var errors = schema.Validate(config); - if (errors != null && errors.Any()) + if (errors != null && errors.Count != 0) throw new Exception("The color scheme file is invalid."); // TODO: Throw specific exception. - return config.ToObject(); + return config.ToObject() ?? throw new InvalidOperationException(); } - private static async Task LoadSchemaAsync() + private static async Task LoadSchemaAsync() { var json = ReadJsonFromResource(SchemaResource); - return await JsonSchema4.FromJsonAsync(json).ConfigureAwait(false); + return await JsonSchema.FromJsonAsync(json).ConfigureAwait(false); } private static string ReadJsonFromResource(string resourceName) { - using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName) ?? throw new InvalidOperationException()) using (var reader = new StreamReader(stream)) { var json = reader.ReadToEnd(); @@ -109,5 +100,11 @@ public override string ToString() { return JsonConvert.SerializeObject(this, Formatting.Indented); } + + // ReSharper disable InconsistentNaming + private static readonly Lazy _dark; + private static readonly Lazy _light; + private static readonly Lazy _default; + // ReSharper restore InconsistentNaming } } diff --git a/src/QBittorrent.CommandLineInterface/ColorSchemes/ColorSet.cs b/src/QBittorrent.CommandLineInterface/ColorSchemes/ColorSet.cs index 2995c86..a665bc6 100644 --- a/src/QBittorrent.CommandLineInterface/ColorSchemes/ColorSet.cs +++ b/src/QBittorrent.CommandLineInterface/ColorSchemes/ColorSet.cs @@ -25,10 +25,10 @@ public ConsoleColor GetEffectiveBackground() public ConsoleColor GetEffectiveForeground() { - ConsoleColor bg = GetEffectiveBackground(); - ConsoleColor systemFg = GetSystemForeground(); - ConsoleColor fg = Foreground ?? systemFg; - return fg != bg ? fg : (AltForeground ?? systemFg); + var bg = GetEffectiveBackground(); + var systemFg = GetSystemForeground(); + var fg = Foreground ?? systemFg; + return fg != bg ? fg : AltForeground ?? systemFg; } private ConsoleColor GetSystemBackground() diff --git a/src/QBittorrent.CommandLineInterface/ColorSchemes/ColorType.cs b/src/QBittorrent.CommandLineInterface/ColorSchemes/ColorType.cs index 7c846a5..057d27b 100644 --- a/src/QBittorrent.CommandLineInterface/ColorSchemes/ColorType.cs +++ b/src/QBittorrent.CommandLineInterface/ColorSchemes/ColorType.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace QBittorrent.CommandLineInterface.ColorSchemes +namespace QBittorrent.CommandLineInterface.ColorSchemes { public enum ColorType { diff --git a/src/QBittorrent.CommandLineInterface/ColorSchemes/dark.json b/src/QBittorrent.CommandLineInterface/ColorSchemes/dark.json index 0f9f3ee..b7afce7 100644 --- a/src/QBittorrent.CommandLineInterface/ColorSchemes/dark.json +++ b/src/QBittorrent.CommandLineInterface/ColorSchemes/dark.json @@ -1,35 +1,29 @@ { "$schema": "https://raw.githubusercontent.com/fedarovich/qbittorrent-cli/master/src/QBittorrent.CommandLineInterface/Schemas/colors-schema.json", - "normal": { "bg": "system-bg", "fg": "system-fg" }, - "strong": { "bg": "system-bg", "fg": "yellow", "fg-alt": "dark-blue" }, - "warning": { "bg": "system-bg", "fg": "yellow", "fg-alt": "dark-yellow" }, - "active": { "bg": "system-bg", "fg": "green", "fg-alt": "dark-cyan" }, - "inactive": { "bg": "system-bg", "fg": "dark-gray", "fg-alt": "gray" }, - "log": { "status-normal": { "bg": "system-bg", @@ -62,7 +56,6 @@ "fg-alt": "dark-gray" } }, - "torrent-status": { "allocating": { "bg": "system-bg", diff --git a/src/QBittorrent.CommandLineInterface/ColorSchemes/light.json b/src/QBittorrent.CommandLineInterface/ColorSchemes/light.json index 75098e2..fd24cd7 100644 --- a/src/QBittorrent.CommandLineInterface/ColorSchemes/light.json +++ b/src/QBittorrent.CommandLineInterface/ColorSchemes/light.json @@ -1,35 +1,29 @@ { "$schema": "https://raw.githubusercontent.com/fedarovich/qbittorrent-cli/master/src/QBittorrent.CommandLineInterface/Schemas/colors-schema.json", - "normal": { "bg": "system-bg", "fg": "system-fg" }, - "strong": { "bg": "system-bg", "fg": "dark-blue", "fg-alt": "yellow" }, - "warning": { "bg": "system-bg", "fg": "dark-yellow", "fg-alt": "yellow" }, - "active": { "bg": "system-bg", "fg": "dark-green", "fg-alt": "cyan" }, - "inactive": { "bg": "system-bg", "fg": "gray", "fg-alt": "dark-gray" }, - "log": { "status-normal": { "bg": "system-bg", @@ -62,7 +56,6 @@ "fg-alt": "dark-gray" } }, - "torrent-status": { "allocating": { "bg": "system-bg", diff --git a/src/QBittorrent.CommandLineInterface/Commands/AuthenticatedFormattableCommandBase.cs b/src/QBittorrent.CommandLineInterface/Commands/AuthenticatedFormattableCommandBase.cs index 146448d..63cc428 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/AuthenticatedFormattableCommandBase.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/AuthenticatedFormattableCommandBase.cs @@ -18,15 +18,24 @@ protected AuthenticatedFormattableCommandBase() _formatter = new ObjectFormatter(PrintList, FindProperty); } - protected virtual PropertyInfo FindProperty(string name) => _props.Value.FirstOrDefault(t => t.name == name).prop; + protected virtual IReadOnlyDictionary>? CustomFormatters => null; - protected virtual IReadOnlyDictionary> CustomFormatters => null; + [Option("-F|--format ", "Output format: list|csv|json|property", CommandOptionType.SingleValue)] + public string? Format { get; set; } - protected virtual void PrintList(T data) => UIHelper.PrintObject(data, CustomFormatters); + protected virtual PropertyInfo FindProperty(string name) + { + return _props.Value.FirstOrDefault(t => t.name == name).prop; + } - protected void Print(in T obj) => _formatter.PrintFormat(obj, Format); + protected virtual void PrintList(T data) + { + UIHelper.PrintObject(data, CustomFormatters); + } - [Option("-F|--format ", "Output format: list|csv|json|property", CommandOptionType.SingleValue)] - public string Format { get; set; } + protected void Print(in T obj) + { + _formatter.PrintFormat(obj, Format); + } } } diff --git a/src/QBittorrent.CommandLineInterface/Commands/CategoryCommand.cs b/src/QBittorrent.CommandLineInterface/Commands/CategoryCommand.cs index b11d9bf..f4d4e1f 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/CategoryCommand.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/CategoryCommand.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Text; using System.Threading.Tasks; using Alba.CsConsoleFormat; using McMaster.Extensions.CommandLineUtils; @@ -106,7 +104,7 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient protected override void PrintTable(IEnumerable categories) { - if (categories?.Any() == true) + if (categories.Any()) { var doc = new Document( new Grid diff --git a/src/QBittorrent.CommandLineInterface/Commands/ClientCommandBase.cs b/src/QBittorrent.CommandLineInterface/Commands/ClientCommandBase.cs index e1c58e7..5cb2c24 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/ClientCommandBase.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/ClientCommandBase.cs @@ -13,46 +13,25 @@ public abstract class ClientCommandBase { protected internal const string FormatHelpText = "\nSee https://github.com/fedarovich/qbittorrent-cli/wiki/Output-Formats for more information about output formats.\n"; - + [Option("--url ", "QBittorrent Server URL", CommandOptionType.SingleValue)] public string Url { get; set; } [Option("--username ", "User name", CommandOptionType.SingleValue)] - public string UserName { get; set; } + public string? UserName { get; set; } [Option("--password ", "User password", CommandOptionType.SingleValue)] - public string Password { get; set; } + public string? Password { get; set; } [Option("--ask-for-password", "Ask the user to enter a password in a secure way.", CommandOptionType.NoValue)] public bool AskForPassword { get; set; } - protected GeneralSettings GeneralSettings { get; } + protected GeneralSettings GeneralSettings { get; } = SettingsService.Instance.GetGeneral(); - protected NetworkSettings NetworkSettings { get; } - - protected ClientCommandBase() - { - GeneralSettings = SettingsService.Instance.GetGeneral(); - NetworkSettings = SettingsService.Instance.GetNetwork(); - } + protected NetworkSettings NetworkSettings { get; } = SettingsService.Instance.GetNetwork(); protected QBittorrentClient CreateClient() { -#if NETFRAMEWORK || NETCOREAPP2_0 - var handler = new HttpClientHandler - { - Proxy = GetProxy(), - UseDefaultCredentials = NetworkSettings.UseDefaultCredentials, - Credentials = GetCredentials(), - PreAuthenticate = true - }; - - if (NetworkSettings.IgnoreCertificateErrors) - { - handler.ServerCertificateCustomValidationCallback = (message, cert, chain, error) => true; - } - -#else var handler = new SocketsHttpHandler { Proxy = GetProxy(), @@ -64,11 +43,10 @@ protected QBittorrentClient CreateClient() { handler.SslOptions.RemoteCertificateValidationCallback = (message, cert, chain, error) => true; } -#endif return new QBittorrentClient(new Uri(Url, UriKind.Absolute), handler, true); } - private IWebProxy GetProxy() + private WebProxy? GetProxy() { if (NetworkSettings.Proxy == null) return null; @@ -76,7 +54,7 @@ private IWebProxy GetProxy() var proxy = new WebProxy { Address = NetworkSettings.Proxy.Address, - BypassProxyOnLocal = NetworkSettings.Proxy.BypassLocal, + BypassProxyOnLocal = NetworkSettings.Proxy.BypassLocal }; if (NetworkSettings.Proxy.Bypass?.Any() == true) @@ -106,12 +84,10 @@ private ICredentials GetCredentials() cache.Add(cred.Url, cred.AuthType.ToString(), cred.ToCredential()); } -#if !(NETFRAMEWORK || NETCOREAPP2_0) if (NetworkSettings.UseDefaultCredentials) { return new CredentialCacheWithDefault(cache); } -#endif return cache; } @@ -128,18 +104,11 @@ protected async Task AuthenticateAsync(QBittorrentClient client) } } - private class CredentialCacheWithDefault : ICredentials + private class CredentialCacheWithDefault(CredentialCache cache) : ICredentials { - private readonly CredentialCache _cache; - - public CredentialCacheWithDefault(CredentialCache cache) - { - _cache = cache; - } - public NetworkCredential GetCredential(Uri uri, string authType) { - return _cache.GetCredential(uri, authType) ?? CredentialCache.DefaultNetworkCredentials; + return cache.GetCredential(uri, authType) ?? CredentialCache.DefaultNetworkCredentials; } } } diff --git a/src/QBittorrent.CommandLineInterface/Commands/GlobalCommand.Limit.cs b/src/QBittorrent.CommandLineInterface/Commands/GlobalCommand.Limit.cs index 3ce7da2..02714d4 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/GlobalCommand.Limit.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/GlobalCommand.Limit.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Text; +using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using McMaster.Extensions.CommandLineUtils; using QBittorrent.Client; @@ -20,17 +17,17 @@ public class Limit : ClientRootCommandBase { protected static void PrintLimit(IConsole console, long? limit) { - if (limit == null || limit < 0) + switch (limit) { - console.WriteLineColored("n/a", ColorScheme.Current.Normal); - } - else if (limit == 0) - { - console.WriteLineColored("unlimited", ColorScheme.Current.Normal); - } - else - { - console.WriteLineColored($"{limit:N0} bytes/s", ColorScheme.Current.Normal); + case null or < 0: + console.WriteLineColored("n/a", ColorScheme.Current.Normal); + break; + case 0: + console.WriteLineColored("unlimited", ColorScheme.Current.Normal); + break; + default: + console.WriteLineColored($"{limit:N0} bytes/s", ColorScheme.Current.Normal); + break; } } diff --git a/src/QBittorrent.CommandLineInterface/Commands/InspectCommand.cs b/src/QBittorrent.CommandLineInterface/Commands/InspectCommand.cs index 2de0a51..ece82a3 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/InspectCommand.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/InspectCommand.cs @@ -13,70 +13,12 @@ namespace QBittorrent.CommandLineInterface.Commands [Subcommand(typeof(File))] public class InspectCommand { - [Command(Description = "Inspects the torrent file.")] - public class File - { - [Argument(0, "path", "The path to the torrent file.")] - [Required(AllowEmptyStrings = false)] - public string Path { get; set; } - - [Option("-s|--strict", "Enables strict parsing mode.", CommandOptionType.NoValue)] - public bool Strict { get; set; } - - public int OnExecuteAsync(CommandLineApplication app, IConsole console) - { - var cellStroke = new LineThickness(LineWidth.None, LineWidth.None); - - var torrent = ReadAndParseTorrent(); - - var document = new Document( - new Grid - { - Stroke = cellStroke, - Columns = { UIHelper.FieldsColumns }, - Children = - { - UIHelper.Row("Name", torrent.DisplayName), - UIHelper.Row("Hash", torrent.OriginalInfoHash), - UIHelper.Row("Size", $"{torrent.TotalSize:N0} bytes"), - UIHelper.Row("Created at", torrent.CreationDate), - UIHelper.Row("Created by", torrent.CreatedBy), - UIHelper.Row("Comment", torrent.Comment), - UIHelper.Row("Is Private", torrent.IsPrivate), - UIHelper.Row("Pieces", torrent.NumberOfPieces), - UIHelper.Row("Piece Size", $"{torrent.PieceSize:N0} bytes"), - UIHelper.Row("Is Private", torrent.IsPrivate), - UIHelper.Row("Magnet", torrent.GetMagnetLink()), - UIHelper.Row("File Mode", torrent.FileMode), - UIHelper.Row("Files", BuildFileTable(torrent)), - UIHelper.Row("Trackers", BuildTrackerList(torrent)), - UIHelper.Row("Extra Fields", BuildExtraFields(torrent)), - } - }).SetColors(ColorScheme.Current.Normal); - - ConsoleRenderer.RenderDocument(document); - return ExitCodes.Success; - } - - private Torrent ReadAndParseTorrent() - { - var bencodeParser = new BencodeParser(); - var torrentParser = new TorrentParser(bencodeParser, - Strict ? TorrentParserMode.Strict : TorrentParserMode.Tolerant); - using (var stream = System.IO.File.OpenRead(Path)) - { - var torrent = torrentParser.Parse(stream); - return torrent; - } - } - } - - private static Element BuildFileTable(Torrent torrent) + private static Element? BuildFileTable(Torrent torrent) { if (torrent.Files != null) { return new Stack( - $"Directory: {torrent.Files?.DirectoryName}", + $"Directory: {torrent.Files.DirectoryName}", new Grid { Stroke = new LineThickness(LineWidth.None, LineWidth.None), @@ -92,7 +34,7 @@ private static Element BuildFileTable(Torrent torrent) torrent.Files.Select(f => new[] { new Cell(f.FullPath), - new Cell(f.FileSize.ToString("N0")) { TextAlign = TextAlign.Right }, + new Cell(f.FileSize.ToString("N0")) {TextAlign = TextAlign.Right} }) } }); @@ -113,7 +55,7 @@ private static Element BuildFileTable(Torrent torrent) UIHelper.Header("Name"), UIHelper.Header("Size", TextAlign.Center), new Cell(torrent.File.FileName), - new Cell(torrent.File.FileSize.ToString("N0")) { TextAlign = TextAlign.Right }, + new Cell(torrent.File.FileSize.ToString("N0")) {TextAlign = TextAlign.Right} } }; } @@ -121,12 +63,12 @@ private static Element BuildFileTable(Torrent torrent) return null; } - private static Element BuildTrackerList(Torrent torrent) + private static List BuildTrackerList(Torrent torrent) { return new List(torrent.Trackers.Select(t => string.Join("\n", t))); } - private static Element BuildExtraFields(Torrent torrent) + private static BlockElement? BuildExtraFields(Torrent torrent) { if (torrent.ExtraFields == null || torrent.ExtraFields.Count == 0) return null; @@ -136,38 +78,33 @@ private static Element BuildExtraFields(Torrent torrent) BlockElement FormatBDictionary(BDictionary dict) { - return new Grid() + return new Grid { Stroke = new LineThickness(LineWidth.None, LineWidth.None), - Columns = { UIHelper.FieldsColumns }, + Columns = {UIHelper.FieldsColumns}, Children = { UIHelper.Header("Key"), UIHelper.Header("Value"), - dict.Select(f => new [] + dict.Select(f => new[] { new Cell(f.Key.ToString()), - new Cell(FormatBOject(f.Value)) + new Cell(FormatBObject(f.Value)) }) } }; } - Element FormatBOject(IBObject value) + Element? FormatBObject(IBObject value) { - switch (value) + return value switch { - case BList bList: - return new List(bList.Select(FormatBOject)); - case BNumber bNumber: - return new Span(bNumber.Value.ToString()); - case BDictionary bDictionary: - return FormatBDictionary(bDictionary); - case BString bString: - return new Span(bString.ToString()); - default: - return null; - } + BList bList => new List(bList.Select(FormatBObject)), + BNumber bNumber => new Span(bNumber.Value.ToString()), + BDictionary bDictionary => FormatBDictionary(bDictionary), + BString bString => new Span(bString.ToString()), + _ => null + }; } } @@ -176,5 +113,63 @@ public virtual int OnExecute(CommandLineApplication app, IConsole console) app.ShowHelp(); return ExitCodes.WrongUsage; } + + [Command(Description = "Inspects the torrent file.")] + public class File + { + [Argument(0, "path", "The path to the torrent file.")] + [Required(AllowEmptyStrings = false)] + public string Path { get; set; } + + [Option("-s|--strict", "Enables strict parsing mode.", CommandOptionType.NoValue)] + public bool Strict { get; set; } + + public int OnExecuteAsync(CommandLineApplication app, IConsole console) + { + var cellStroke = new LineThickness(LineWidth.None, LineWidth.None); + + var torrent = ReadAndParseTorrent(); + + var document = new Document( + new Grid + { + Stroke = cellStroke, + Columns = {UIHelper.FieldsColumns}, + Children = + { + UIHelper.Row("Name", torrent.DisplayName), + UIHelper.Row("Hash", torrent.OriginalInfoHash), + UIHelper.Row("Size", $"{torrent.TotalSize:N0} bytes"), + UIHelper.Row("Created at", torrent.CreationDate), + UIHelper.Row("Created by", torrent.CreatedBy), + UIHelper.Row("Comment", torrent.Comment), + UIHelper.Row("Is Private", torrent.IsPrivate), + UIHelper.Row("Pieces", torrent.NumberOfPieces), + UIHelper.Row("Piece Size", $"{torrent.PieceSize:N0} bytes"), + UIHelper.Row("Is Private", torrent.IsPrivate), + UIHelper.Row("Magnet", torrent.GetMagnetLink()), + UIHelper.Row("File Mode", torrent.FileMode), + UIHelper.Row("Files", BuildFileTable(torrent)), + UIHelper.Row("Trackers", BuildTrackerList(torrent)), + UIHelper.Row("Extra Fields", BuildExtraFields(torrent)) + } + }).SetColors(ColorScheme.Current.Normal); + + ConsoleRenderer.RenderDocument(document); + return ExitCodes.Success; + } + + private Torrent ReadAndParseTorrent() + { + var bencodeParser = new BencodeParser(); + var torrentParser = new TorrentParser(bencodeParser, + Strict ? TorrentParserMode.Strict : TorrentParserMode.Tolerant); + using (var stream = System.IO.File.OpenRead(Path)) + { + var torrent = torrentParser.Parse(stream); + return torrent; + } + } + } } } diff --git a/src/QBittorrent.CommandLineInterface/Commands/ListCommandBase.cs b/src/QBittorrent.CommandLineInterface/Commands/ListCommandBase.cs index 20ec56c..c55b3e2 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/ListCommandBase.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/ListCommandBase.cs @@ -18,9 +18,14 @@ protected ListCommandBase() [Option("-F|--format ", "Output format: table|list|csv|json", CommandOptionType.SingleValue)] public virtual string Format { get; set; } + protected virtual IReadOnlyDictionary>? ListCustomFormatters => null; + [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void Print(IEnumerable data, bool preferList = false) => _formatter.PrintFormat(data, Format, preferList); - + protected void Print(IEnumerable data, bool preferList = false) + { + _formatter.PrintFormat(data, Format, preferList); + } + protected virtual void PrintTable(IEnumerable list) { throw new Exception("Unsupported output format."); @@ -30,7 +35,5 @@ protected virtual void PrintList(IEnumerable list) { UIHelper.PrintList(list, ListCustomFormatters); } - - protected virtual IReadOnlyDictionary> ListCustomFormatters => null; } } diff --git a/src/QBittorrent.CommandLineInterface/Commands/MultiTorrentCommandBase.cs b/src/QBittorrent.CommandLineInterface/Commands/MultiTorrentCommandBase.cs index 0d5a02a..9ddbed6 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/MultiTorrentCommandBase.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/MultiTorrentCommandBase.cs @@ -18,7 +18,7 @@ public abstract class MultiTorrentCommandBase : AuthenticatedCommandBase protected virtual bool AllowAll => false; - protected bool IsAll { get; private set; } + protected bool IsAll { get; private set; } protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { @@ -26,8 +26,8 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient if (!IsAll) { - IReadOnlyList torrents = null; - for (int hashIndex = Hashes.Count - 1; hashIndex >= 0; hashIndex--) + IReadOnlyList? torrents = null; + for (var hashIndex = Hashes.Count - 1; hashIndex >= 0; hashIndex--) { var hash = Hashes[hashIndex]; if (hash.Length == 40) @@ -51,16 +51,16 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient console.WriteLineColored($"The are several torrents matching partial hash {hash}:", ColorScheme.Current.Normal); var numbers = (int)Math.Log10(matching.Count) + 1; var nameWidth = Console.BufferWidth - (numbers + 45); - for (int i = 0; i < matching.Count; i++) + for (var i = 0; i < matching.Count; i++) { var torrent = matching[i]; var name = torrent.Name.Length < nameWidth ? torrent.Name - : torrent.Name.Substring(0, nameWidth - 3) + "..."; + : torrent.Name[..(nameWidth - 3)] + "..."; console.WriteLineColored($"[{(i + 1).ToString().PadLeft(numbers)}] {torrent.Hash} {name}", ColorScheme.Current.Normal); } - int index = 0; + var index = 0; while (index <= 0 || index > matching.Count) { index = Prompt.GetInt("Please, select the required one:"); diff --git a/src/QBittorrent.CommandLineInterface/Commands/NetworkCommand.Credential.cs b/src/QBittorrent.CommandLineInterface/Commands/NetworkCommand.Credential.cs index 50663d4..869e3ce 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/NetworkCommand.Credential.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/NetworkCommand.Credential.cs @@ -31,7 +31,7 @@ public int OnExecute(CommandLineApplication app, IConsole console) new Column {Width = GridLength.Star(1), MinWidth = 20}, new Column {Width = GridLength.Auto}, new Column {Width = GridLength.Auto}, - new Column {Width = GridLength.Auto}, + new Column {Width = GridLength.Auto} }, Children = { @@ -63,17 +63,17 @@ public class Add [Option("-a|--auth-type ", "Authentication type", CommandOptionType.SingleValue)] [Required] - public NetworkSettings.AuthType? AuthType { get; set; } + public NetworkSettings.AuthType AuthType { get; set; } [Option("-u|--username ", "Username", CommandOptionType.SingleValue)] [Required] public string Username { get; set; } [Option("-p|--password ", "Password", CommandOptionType.SingleValue)] - public string Password { get; set; } + public string? Password { get; set; } [Option("-d|--domain ", "Domain", CommandOptionType.SingleValue)] - public string Domain { get; set; } + public string? Domain { get; set; } public int OnExecute(CommandLineApplication app, IConsole console) { @@ -82,10 +82,10 @@ public int OnExecute(CommandLineApplication app, IConsole console) c => AuthType == c.AuthType && Url == c.Url); if (cred == null) { - cred = new NetworkSettings.SiteCredentials() + cred = new NetworkSettings.SiteCredentials { Url = Url, - AuthType = AuthType.Value + AuthType = AuthType }; networkSettings.Credentials.Add(cred); } @@ -100,7 +100,7 @@ public int OnExecute(CommandLineApplication app, IConsole console) string GetPassword() { return console.IsInputRedirected - ? console.In.ReadLine() + ? console.In.ReadLine()! : Prompt.GetPassword("Please, enter your network password: "); } } diff --git a/src/QBittorrent.CommandLineInterface/Commands/NetworkCommand.Proxy.cs b/src/QBittorrent.CommandLineInterface/Commands/NetworkCommand.Proxy.cs index 92b2351..35c23bd 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/NetworkCommand.Proxy.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/NetworkCommand.Proxy.cs @@ -17,6 +17,47 @@ public partial class NetworkCommand [Subcommand(typeof(Reset))] public class Proxy { + + public int OnExecute(CommandLineApplication app, IConsole console) + { + var networkSettings = SettingsService.Instance.GetNetwork(); + var proxy = networkSettings.Proxy; + if (proxy == null) + { + console.WriteLineColored("", ColorScheme.Current.Inactive); + } + else + { + var doc = new Document( + new Grid + { + Stroke = UIHelper.NoneStroke, + Columns = {UIHelper.FieldsColumns}, + Children = + { + UIHelper.Row("Address", proxy.Address), + UIHelper.Row("Username", + !string.IsNullOrEmpty(proxy.Username) + ? new Span(proxy.Username).SetColors(ColorScheme.Current.Normal) + : new Span("").SetColors(ColorScheme.Current.Inactive)), + UIHelper.Row("Password", + proxy.Password != null + ? new Span("").SetColors(ColorScheme.Current.Active) + : new Span("").SetColors(ColorScheme.Current.Inactive)), + UIHelper.Row("Bypass Local", proxy.BypassLocal), + UIHelper.Row("Bypass", + proxy.Bypass?.Any() == true + ? new List(proxy.Bypass.Select(x => new Span(x))) + : new Span("").SetColors(ColorScheme.Current.Inactive)) + } + }).SetColors(ColorScheme.Current.Normal); + + ConsoleRenderer.RenderDocument(doc); + } + + return ExitCodes.Success; + } + [Command(Description = "Configures proxy to use.")] public class Set { @@ -28,10 +69,10 @@ public class Set "Proxy server user name. Pass empty string to use default credentials.", CommandOptionType.SingleValue)] [Required(AllowEmptyStrings = true)] - public string Username { get; set; } + public string? Username { get; set; } [Option("-p|--password ", "Proxy server password.", CommandOptionType.SingleValue)] - public string Password { get; set; } + public string? Password { get; set; } [Option("-l|--bypass-local", "Bypass proxy for local addresses.", CommandOptionType.NoValue)] public bool BypassLocal { get; set; } @@ -45,15 +86,15 @@ public int OnExecute(CommandLineApplication app, IConsole console) { if (!string.IsNullOrEmpty(Username)) { - Password = Password ?? GetPassword(); + Password ??= GetPassword(); } var networkSettings = SettingsService.Instance.GetNetwork(); networkSettings.Proxy = new ProxySettings { Address = Address, - Username = Username, - Password = Password, + Username = Username!, + Password = Password!, BypassLocal = BypassLocal, Bypass = Bypass }; @@ -63,7 +104,7 @@ public int OnExecute(CommandLineApplication app, IConsole console) string GetPassword() { return console.IsInputRedirected - ? console.In.ReadLine() + ? console.In.ReadLine()! : Prompt.GetPassword("Please, enter your proxy password: "); } } @@ -80,46 +121,6 @@ public int OnExecute(CommandLineApplication app, IConsole console) return ExitCodes.Success; } } - - public int OnExecute(CommandLineApplication app, IConsole console) - { - var networkSettings = SettingsService.Instance.GetNetwork(); - var proxy = networkSettings.Proxy; - if (proxy == null) - { - console.WriteLineColored("", ColorScheme.Current.Inactive); - } - else - { - var doc = new Document( - new Grid - { - Stroke = UIHelper.NoneStroke, - Columns = {UIHelper.FieldsColumns}, - Children = - { - UIHelper.Row("Address", proxy.Address), - UIHelper.Row("Username", - !string.IsNullOrEmpty(proxy.Username) - ? new Span(proxy.Username).SetColors(ColorScheme.Current.Normal) - : new Span("").SetColors(ColorScheme.Current.Inactive)), - UIHelper.Row("Password", - proxy.Password != null - ? new Span("").SetColors(ColorScheme.Current.Active) - : new Span("").SetColors(ColorScheme.Current.Inactive)), - UIHelper.Row("Bypass Local", proxy.BypassLocal), - UIHelper.Row("Bypass", - proxy.Bypass?.Any() == true - ? (Element) new List(proxy.Bypass.Select(x => new Span(x))) - : new Span("").SetColors(ColorScheme.Current.Inactive)) - } - }).SetColors(ColorScheme.Current.Normal); - - ConsoleRenderer.RenderDocument(doc); - } - - return ExitCodes.Success; - } } } } diff --git a/src/QBittorrent.CommandLineInterface/Commands/NetworkCommand.cs b/src/QBittorrent.CommandLineInterface/Commands/NetworkCommand.cs index ca5de38..e69ba5d 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/NetworkCommand.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/NetworkCommand.cs @@ -9,6 +9,13 @@ namespace QBittorrent.CommandLineInterface.Commands [Subcommand(typeof(Settings))] public partial class NetworkCommand { + + public int OnExecute(CommandLineApplication app, IConsole console) + { + app.ShowHelp(); + return ExitCodes.WrongUsage; + } + [Command(Description = "Configure network settings.")] public class Settings { @@ -28,7 +35,7 @@ public int OnExecute(CommandLineApplication app, IConsole console) { var net = SettingsService.Instance.GetNetwork(); - bool hasChanges = false; + var hasChanges = false; if (UseDefaultCredentials.HasValue) { @@ -68,12 +75,5 @@ public int OnExecute(CommandLineApplication app, IConsole console) return ExitCodes.Success; } } - - public int OnExecute(CommandLineApplication app, IConsole console) - { - app.ShowHelp(); - return ExitCodes.WrongUsage; - } } } - diff --git a/src/QBittorrent.CommandLineInterface/Commands/RssCommand.Feed.cs b/src/QBittorrent.CommandLineInterface/Commands/RssCommand.Feed.cs index 790f7e9..98aab32 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/RssCommand.Feed.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/RssCommand.Feed.cs @@ -27,7 +27,7 @@ public class List : AuthenticatedCommandBase { protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { - var root = await client.GetRssItemsAsync(false); + var root = await client.GetRssItemsAsync(); var doc = new Document(RenderItem(root, true, true)).SetColors(ColorScheme.Current.Normal); ConsoleRenderer.RenderDocument(doc); @@ -54,24 +54,22 @@ Grid RenderItem(RssItem item, bool last, bool isRoot = false) { Columns = { - new Column { Width = GridLength.Char(2) }, - new Column { Width = GridLength.Star(1) } + new Column {Width = GridLength.Char(2)}, + new Column {Width = GridLength.Star(1)} }, Children = { - new object[] { new Cell(isRoot ? "" - : item is RssFeed feed ? RenderFeed(feed) : item.Name) { Stroke = LineThickness.None } + : last ? "\u2514\u2500" : "\u251c\u2500") {Stroke = LineThickness.None}, + new Cell(isRoot + ? "oot>" + : item is RssFeed feed ? RenderFeed(feed) : item.Name) {Stroke = LineThickness.None} }, - new object[] { - new Cell(folder == null || last ? null : new Separator { Orientation = Orientation.Vertical }) { Stroke = LineThickness.None }, - new Cell(folder == null ? null : RenderFolderContent(folder)) { Stroke = LineThickness.None } + new Cell(folder == null || last ? null : new Separator {Orientation = Orientation.Vertical}) {Stroke = LineThickness.None}, + new Cell(folder == null ? null : RenderFolderContent(folder)) {Stroke = LineThickness.None} } }, Stroke = LineThickness.None @@ -94,7 +92,7 @@ public class AddFeed : AuthenticatedCommandBase public Uri FeedUrl { get; set; } [Option("-p|--path ", "Virtual path for the feed. Use backslash \\ as a separator.", CommandOptionType.SingleValue)] - public string Path { get; set; } + public string? Path { get; set; } protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { @@ -167,21 +165,21 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient var vm = new RssFeedViewModel(Path, feed); UIHelper.PrintObject(vm, - new Dictionary> + new Dictionary> { [nameof(vm.Articles)] = FormatArticles }); return ExitCodes.Success; } - private RssFeed GetFeedByPath(RssFolder folder, string path) + private RssFeed? GetFeedByPath(RssFolder? folder, string path) { - var segments = new Queue(Path.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries)); + var segments = new Queue(Path.Split(['\\'], StringSplitOptions.RemoveEmptyEntries)); while (segments.Count > 1) { var name = segments.Dequeue(); - folder = folder.Folders.SingleOrDefault(f => f.Name == name); + folder = folder?.Folders.SingleOrDefault(f => f.Name == name); if (folder == null) return null; } @@ -195,9 +193,9 @@ private RssFeed GetFeedByPath(RssFolder folder, string path) return null; } - private object FormatArticles(object obj) + private object? FormatArticles(object? obj) { - if (!(obj is IEnumerable articles)) + if (obj is not IEnumerable articles) return null; return new Alba.CsConsoleFormat.List(articles.Select(ToDocument)); diff --git a/src/QBittorrent.CommandLineInterface/Commands/RssCommand.Rule.cs b/src/QBittorrent.CommandLineInterface/Commands/RssCommand.Rule.cs index db5f241..39597fd 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/RssCommand.Rule.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/RssCommand.Rule.cs @@ -30,45 +30,41 @@ public abstract class AddSetCommandBase : AuthenticatedCommandBase [Option("-i|--contains ", "The substring that the torrent name must contain.", CommandOptionType.SingleValue)] - public string MustContain { get; set; } + public string? MustContain { get; set; } [Option("-x|--not-contains ", "The substring that the torrent name must not contain", CommandOptionType.SingleValue)] - public string MustNotContain { get; set; } + public string? MustNotContain { get; set; } [Option("-f|--episode-filter", "Episode filter definition, e.g. \"1x01-;\".", CommandOptionType.SingleValue)] - public string EpisodeFilter { get; set; } + public string? EpisodeFilter { get; set; } [Option("-E|--prev-matched-episode ", "The episode ID already matched by smart filter. Can be specified multiple times.", CommandOptionType.MultipleValue)] - public string[] PreviouslyMatchedEpisodes { get; set; } + public string[]? PreviouslyMatchedEpisodes { get; set; } [Option("-u|--feed-url ", "The feed URL the rule applied to. Can be specified multiple times.", CommandOptionType.MultipleValue)] - public Uri[] AffectedFeeds { get; set; } + public Uri[]? AffectedFeeds { get; set; } [Option("-c|--category ", "Assign category to the torrent.", CommandOptionType.SingleValue)] - public string Category { get; set; } + public string? Category { get; set; } - [Option("-s|--save-path ")] public string SavePath { get; set; } + [Option("-s|--save-path ")] + public string? SavePath { get; set; } protected bool? ConvertPauseState(RssRulePauseState? state) { - switch (state) + return state switch { - case null: - case RssRulePauseState.Auto: - return null; - case RssRulePauseState.False: - return false; - case RssRulePauseState.True: - return true; - default: - throw new ArgumentOutOfRangeException(nameof(state), state, null); - } + null or RssRulePauseState.Auto => null, + RssRulePauseState.False => false, + RssRulePauseState.True => true, + _ => throw new ArgumentOutOfRangeException(nameof(state), state, null) + }; } } @@ -190,12 +186,12 @@ void Set(string propertyName, T value) { if (value != null) { - rule.GetType().GetProperty(propertyName).SetValue(rule, value); + rule.GetType().GetProperty(propertyName)!.SetValue(rule, value); } } } } - + [Command(Description = "Renames RSS automatic downloading rule.", ExtendedHelpText = ExperimentalHelpText)] public class Rename : AuthenticatedCommandBase { @@ -231,17 +227,19 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient [Command(Description = "Shows RSS automatic downloading rule.", ExtendedHelpText = FormatHelpText + ExperimentalHelpText)] public class List : ListCommandBase { - private static readonly Dictionary> CustomFormatters; + private static readonly IReadOnlyDictionary> CustomFormatters; static List() { - CustomFormatters = new Dictionary> + CustomFormatters = new Dictionary> { [nameof(RssRuleViewModel.AffectedFeeds)] = FormatAffectedFeeds, - [nameof(RssRuleViewModel.PreviouslyMatchedEpisodes)] = FormatPreviouslyMatchedEpisodes, + [nameof(RssRuleViewModel.PreviouslyMatchedEpisodes)] = FormatPreviouslyMatchedEpisodes }; } + protected override IReadOnlyDictionary> ListCustomFormatters => CustomFormatters; + protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { var rules = await client.GetRssAutoDownloadingRulesAsync(); @@ -257,7 +255,7 @@ protected override void PrintTable(IEnumerable list) Columns = { new Column {Width = GridLength.Star(1)}, - new Column {Width = GridLength.Auto}, + new Column {Width = GridLength.Auto} }, Children = { @@ -275,23 +273,21 @@ protected override void PrintTable(IEnumerable list) ConsoleRenderer.RenderDocument(doc); } - protected override IReadOnlyDictionary> ListCustomFormatters => CustomFormatters; - - private static object FormatAffectedFeeds(object obj) + private static Alba.CsConsoleFormat.List? FormatAffectedFeeds(object? obj) { - if (!(obj is IReadOnlyList feeds)) + if (obj is not IReadOnlyList feeds) return null; - return new Alba.CsConsoleFormat.List(feeds.Where(f => f != null).Select(f => f.AbsoluteUri)) + return new Alba.CsConsoleFormat.List(from f in feeds where f is not null select f.AbsoluteUri) { IndexFormat = string.Empty }; } - private static object FormatPreviouslyMatchedEpisodes(object obj) + private static Alba.CsConsoleFormat.List? FormatPreviouslyMatchedEpisodes(object? obj) { - if (!(obj is IReadOnlyList episodes)) - return null; + if (obj is not IReadOnlyList episodes) + return null; return new Alba.CsConsoleFormat.List(episodes.Where(e => !string.IsNullOrEmpty(e))); } diff --git a/src/QBittorrent.CommandLineInterface/Commands/SearchCommand.Plugin.cs b/src/QBittorrent.CommandLineInterface/Commands/SearchCommand.Plugin.cs index 9375e13..3c6aaf9 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/SearchCommand.Plugin.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/SearchCommand.Plugin.cs @@ -78,7 +78,7 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient } } - [Command(Description = "Updates the search plugins.", + [Command(Description = "Updates the search plugins.", ExtendedHelpText = "This command will also install default search plugins if they are missing.")] public class Update : AuthenticatedCommandBase { @@ -112,7 +112,7 @@ protected override void PrintTable(IEnumerable plugins) { new Column {Width = GridLength.Auto}, new Column {Width = GridLength.Star(1)}, - new Column {Width = GridLength.Auto}, + new Column {Width = GridLength.Auto} }, Children = { @@ -161,4 +161,4 @@ protected override void PrintList(IEnumerable plugins) } } } -} \ No newline at end of file +} diff --git a/src/QBittorrent.CommandLineInterface/Commands/SearchCommand.cs b/src/QBittorrent.CommandLineInterface/Commands/SearchCommand.cs index 55a963e..f8bae98 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/SearchCommand.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/SearchCommand.cs @@ -27,10 +27,10 @@ public class Start : AuthenticatedCommandBase public string Pattern { get; set; } [Option("-c|--category ", "The category to search in. If omitted all categories will be searched.", CommandOptionType.SingleValue)] - public string Category { get; set; } + public string? Category { get; set; } [Option("-p|--plugin ", PluginsOptionDescription, CommandOptionType.MultipleValue)] - public IList Plugins { get; set; } + public IList? Plugins { get; set; } [Option("-o|--offset ", "The offset from the beginning.", CommandOptionType.SingleValue)] public int Offset { get; set; } @@ -49,19 +49,19 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient { const int resultsPerRequest = 100; var id = await client.StartSearchAsync(Pattern, - Plugins ?? new[] {"enabled"}, + Plugins ?? ["enabled"], Category ?? "all"); Console.CancelKeyPress += OnCancel; - int offset = Offset; - int remaining = Limit ?? int.MaxValue; - int limit = Math.Min(remaining, resultsPerRequest); + var offset = Offset; + var remaining = Limit ?? int.MaxValue; + var limit = Math.Min(remaining, resultsPerRequest); var pager = UsePager ? new Pager() : null; - var target = (pager != null && pager.Enabled) ? new TextRenderTarget(pager.Writer) : null; - - int index = offset + 1; + var target = pager is {Enabled: true} ? new TextRenderTarget(pager.Writer) : null; + + var index = offset + 1; try { @@ -89,7 +89,7 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient { target?.Dispose(); pager?.Dispose(); - + Console.CancelKeyPress -= OnCancel; await client.StopSearchAsync(id); await client.DeleteSearchAsync(id); @@ -103,7 +103,7 @@ void Print(SearchResult result) new Grid { Stroke = new LineThickness(LineWidth.None), - Columns = { UIHelper.FieldsColumns }, + Columns = {UIHelper.FieldsColumns}, Children = { UIHelper.Row("#", index), @@ -133,7 +133,7 @@ IEnumerable GetVerboseData() } } - async void OnCancel(object sender, ConsoleCancelEventArgs e) + async void OnCancel(object? sender, ConsoleCancelEventArgs e) { await client.StopSearchAsync(id); Console.CancelKeyPress -= OnCancel; diff --git a/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.Authentication.cs b/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.Authentication.cs index dc07239..77b3646 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.Authentication.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.Authentication.cs @@ -31,34 +31,34 @@ public class Authentication : SettingsCommand [Option("-P|--ask-server-password", "Ask for qBittorrent web interface password.", CommandOptionType.NoValue, Inherited = false)] [NoAutoSet] public bool AskForServerPassword { get; set; } - + [Option("-l|--bypass-local ", "Bypass authentication on localhost", CommandOptionType.SingleValue, Inherited = false)] public bool? BypassLocalAuthentication { get; set; } [Option("-w|--bypass-whitelist ", "Bypass authentication on whitelist", CommandOptionType.SingleValue, Inherited = false)] public bool? BypassAuthenticationSubnetWhitelistEnabled { get; set; } + protected override IReadOnlyDictionary> CustomFormatters => + new Dictionary> + { + [nameof(AuthenticationViewModel.BypassAuthenticationSubnetWhitelist)] = + value => value is IList list + ? string.Join(Environment.NewLine, list) + : string.Empty + }; + protected override Task Prepare(QBittorrentClient client, CommandLineApplication app, IConsole console) { if (AskForServerPassword) { WebUIPassword = console.IsInputRedirected - ? console.In.ReadLine() + ? console.In.ReadLine()! : Prompt.GetPassword("Please, enter your web interface password: "); } return Task.CompletedTask; } - protected override IReadOnlyDictionary> CustomFormatters => - new Dictionary> - { - [nameof(AuthenticationViewModel.BypassAuthenticationSubnetWhitelist)] = - value => (value is IList list) - ? string.Join(Environment.NewLine, list) - : string.Empty - }; - [Command(Description = "Manages authentication bypass whitelist.")] [Subcommand(typeof(List))] [Subcommand(typeof(Add))] @@ -80,12 +80,11 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient var currentNetworks = (prefs.BypassAuthenticationSubnetWhitelist ?? Enumerable.Empty()) .Select(IPNetwork.Parse) .ToHashSet(); - bool modified = false; + var modified = false; foreach (var network in Networks.Select(IPNetwork.Parse)) { - if (!currentNetworks.Contains(network)) + if (currentNetworks.Add(network)) { - currentNetworks.Add(network); modified = true; } } @@ -118,7 +117,7 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient .Select(IPNetwork.Parse) .ToHashSet(); - bool modified = false; + var modified = false; foreach (var network in Networks.Select(IPNetwork.Parse)) { modified |= currentNetworks.Remove(network); @@ -142,7 +141,7 @@ public class Clear : AuthenticatedCommandBase { protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { - var prefs = new Preferences { BypassAuthenticationSubnetWhitelist = new string[0] }; + var prefs = new Preferences {BypassAuthenticationSubnetWhitelist = Array.Empty()}; await client.SetPreferencesAsync(prefs); return ExitCodes.Success; } diff --git a/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.IpFilter.cs b/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.IpFilter.cs index d13f47a..de4e966 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.IpFilter.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.IpFilter.cs @@ -32,8 +32,8 @@ public class IpFilter : SettingsCommand [Option("-t|--filter-trackers ", "Apply filter to trackers", CommandOptionType.SingleValue, Inherited = false)] public bool? IpFilterTrackers { get; set; } - protected override IReadOnlyDictionary> CustomFormatters => - new Dictionary> + protected override IReadOnlyDictionary> CustomFormatters => + new Dictionary> { [nameof(IpFilterViewModel.BannedIpAddresses)] = value => value is IEnumerable list ? string.Join(Environment.NewLine, list) : null @@ -51,7 +51,7 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient { var prefs = await client.GetPreferencesAsync(); var banList = prefs.BannedIpAddresses ?? new List(); - bool modified = false; + var modified = false; foreach (var address in Addresses) { if (!banList.Contains(address)) @@ -84,7 +84,7 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient var prefs = await client.GetPreferencesAsync(); var banList = prefs.BannedIpAddresses ?? new List(); - bool modified = false; + var modified = false; foreach (var address in Addresses) { modified |= banList.Remove(address); @@ -92,7 +92,7 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient if (modified) { - prefs = new Preferences { BannedIpAddresses = banList }; + prefs = new Preferences {BannedIpAddresses = banList}; await client.SetPreferencesAsync(prefs); } @@ -105,7 +105,7 @@ public class Clear : AuthenticatedCommandBase { protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { - var prefs = new Preferences { BannedIpAddresses = new string[0] }; + var prefs = new Preferences {BannedIpAddresses = Array.Empty()}; await client.SetPreferencesAsync(prefs); return ExitCodes.Success; } diff --git a/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.MonitoredFolder.cs b/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.MonitoredFolder.cs index 97daae0..727abcd 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.MonitoredFolder.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.MonitoredFolder.cs @@ -47,7 +47,7 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient var prefs = await client.GetPreferencesAsync(); var dirs = prefs?.ScanDirectories ?? new Dictionary(); dirs[Folder] = saveLocation; - prefs = new Preferences { ScanDirectories = dirs }; + prefs = new Preferences {ScanDirectories = dirs}; await client.SetPreferencesAsync(prefs); return ExitCodes.Success; @@ -81,7 +81,7 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient var prefs = await client.GetPreferencesAsync(); var dirs = prefs?.ScanDirectories ?? new Dictionary(); dirs.Remove(Folder); - prefs = new Preferences { ScanDirectories = dirs }; + prefs = new Preferences {ScanDirectories = dirs}; await client.SetPreferencesAsync(prefs); return ExitCodes.Success; } @@ -92,7 +92,7 @@ public class Clear : AuthenticatedCommandBase { protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { - var prefs = new Preferences { ScanDirectories = new Dictionary() }; + var prefs = new Preferences {ScanDirectories = new Dictionary()}; await client.SetPreferencesAsync(prefs); return ExitCodes.Success; } @@ -138,19 +138,13 @@ protected override void PrintTable(IEnumerable new Cell("Monitored Folder"), + StandardSaveLocation.Default => new Cell("Default"), + null => new Cell(new Span("Custom: "), new Span(location.CustomFolder).SetColors(ColorScheme.Current.Strong)), + _ => new Cell(location.ToString()).SetColors(ColorScheme.Current.Warning) + }; } } } diff --git a/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.Tracker.cs b/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.Tracker.cs index 888a2c3..d6844ff 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.Tracker.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.Tracker.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; @@ -26,13 +27,13 @@ public class Add : AuthenticatedCommandBase [Argument(0, "URL_1 URL_2 ... URL_N", "The URLs of the trackers to be added.")] [Required] [Url] - public List Trackers { get; set; } + public List Trackers { get; set; } protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { var prefs = await client.GetPreferencesAsync(); var currentTrackers = prefs.AdditinalTrackers ?? new List(); - bool modified = false; + var modified = false; foreach (var tracker in Trackers) { if (!currentTrackers.Contains(tracker)) @@ -64,7 +65,7 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient { var prefs = await client.GetPreferencesAsync(); var currentTrackers = prefs.AdditinalTrackers ?? new List(); - bool modified = false; + var modified = false; foreach (var tracker in Trackers) { modified |= currentTrackers.Remove(tracker); @@ -72,7 +73,7 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient if (modified) { - prefs = new Preferences { AdditinalTrackers = currentTrackers }; + prefs = new Preferences {AdditinalTrackers = currentTrackers}; await client.SetPreferencesAsync(prefs); } @@ -85,7 +86,7 @@ public class Clear : AuthenticatedCommandBase { protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { - var prefs = new Preferences { AdditinalTrackers = new string[0] }; + var prefs = new Preferences {AdditinalTrackers = Array.Empty()}; await client.SetPreferencesAsync(prefs); return ExitCodes.Success; } diff --git a/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.Web.cs b/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.Web.cs index 277ecea..18e3c38 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.Web.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.Web.cs @@ -26,22 +26,22 @@ public partial class Settings [Command("web", Description = "Manages web UI and API settings.", ExtendedHelpText = ExtendedHelp)] public class WebInterface : SettingsCommand { - private string _certificate; - private string _key; + private string? _certificate; + private string? _key; [Option("-l|--lang ", "Web UI language", CommandOptionType.SingleValue)] [MinLength(2)] public string Locale { get; set; } [Option("-a|--address
", "Web interface address. Pass empty string to listen on any address.", CommandOptionType.SingleValue)] - public string WebUIAddress { get; set; } + public string? WebUIAddress { get; set; } [Option("-p|--port ", "Web interface port", CommandOptionType.SingleValue)] [Range(1, 65535)] public int? WebUIPort { get; set; } [Option("-d|--domain ", "Web interface domain. Pass empty string to listen on any domain.", CommandOptionType.SingleValue)] - public string WebUIDomain { get; set; } + public string? WebUIDomain { get; set; } [Option("-u|--upnp ", "Use UPnP/NAT-PMP", CommandOptionType.SingleValue)] public bool? WebUIUpnp { get; set; } @@ -55,7 +55,7 @@ public class WebInterface : SettingsCommand [FileExists] [NoAutoSet] [MaxApiVersion("2.3.0", "--cert option cannot be used with qBittorrent 4.2.0 or later. Use --cert-path option instead.")] - public string CertificatePath { get; set; } + public string? CertificatePath { get; set; } [Option("-k|--key ", "X509 certificate key path on the client machine. The key must be in PEM (.key) format without encryption. For qBittorrent before 4.2.", @@ -67,9 +67,9 @@ public class WebInterface : SettingsCommand [Option("-P|--key-password ", "X509 certificate key password.", CommandOptionType.SingleValue)] [NoAutoSet] - public string CertificateKeyPassword { get; set; } + public string? CertificateKeyPassword { get; set; } - [Option("-C|--cert-path ", + [Option("-C|--cert-path ", "X509 certificate path on the server machine. The certificate can be in PEM (.pem, .crt, .cer) format. For qBittorrent 4.2 or later.", CommandOptionType.SingleValue)] [MinApiVersion("2.3.0", "--cert-path option requires qBittorrent 4.2.0 or later. Use --cert option instead.")] @@ -89,8 +89,8 @@ public class WebInterface : SettingsCommand [MinApiVersion("2.2.0", "Alternative Web UI requires qBittorrent 4.1.5 or later.")] public string AlternativeWebUIPath { get; set; } - [Option("-S|--secure-cookie ", - "Set Secure attribute on cookie when using HTTPS. Requires qBittorrent 4.2.2 or later.", + [Option("-S|--secure-cookie ", + "Set Secure attribute on cookie when using HTTPS. Requires qBittorrent 4.2.2 or later.", CommandOptionType.SingleValue)] [MinApiVersion("2.4.1", "--secure-cookie option requires qBittorrent 4.2.2 or later.")] public bool? WebUISecureCookie { get; set; } @@ -113,7 +113,7 @@ public class WebInterface : SettingsCommand CommandOptionType.SingleValue)] [MinApiVersion("2.5.1", "--secure-cookie option requires qBittorrent 4.2.5 or later.")] public bool? WebUICustomHttpHeadersEnabled { get; set; } - + [Option("-H|--custom-http-header
", "Custom HTTP header for Web UI. Use a colon (:) as a separator between header name and value. " + "This option can be repeated in order to set several headers. " + @@ -122,6 +122,14 @@ public class WebInterface : SettingsCommand [MinApiVersion("2.5.1", "--secure-cookie option requires qBittorrent 4.2.5 or later.")] public IList WebUICustomHttpHeaders { get; set; } + protected override IReadOnlyDictionary> CustomFormatters => + new Dictionary> + { + [nameof(WebInterfaceViewModel.Locale)] = FormatLanguage, + [nameof(WebInterfaceViewModel.WebUISslCertificate)] = FormatCertificate, + [nameof(WebInterfaceViewModel.WebUICustomHttpHeaders)] = FormatCustomHttpHeaders + }; + protected override async Task Prepare(QBittorrentClient client, CommandLineApplication app, IConsole console) { if (WebUIAddress?.Trim() == string.Empty) @@ -147,24 +155,23 @@ protected override async Task Prepare(QBittorrentClient client, CommandLineAppli throw new InvalidOperationException("--key option must not be set if certificate file has PKCS #12 format."); (_certificate, _key) = TransformPfx(GetPasswordFinder()); break; + case CertificateFileType.None: + break; + default: + throw new ArgumentOutOfRangeException(); } - IPasswordFinder GetPasswordFinder() => CertificateKeyPassword != null - ? new PredefinedPasswordFinder(CertificateKeyPassword) - : (IPasswordFinder)new ConsolePasswordFinder(console); - } - - protected override IReadOnlyDictionary> CustomFormatters => - new Dictionary> + IPasswordFinder GetPasswordFinder() { - [nameof(WebInterfaceViewModel.Locale)] = FormatLanguage, - [nameof(WebInterfaceViewModel.WebUISslCertificate)] = FormatCertificate, - [nameof(WebInterfaceViewModel.WebUICustomHttpHeaders)] = FormatCustomHttpHeaders - }; + return CertificateKeyPassword != null + ? new PredefinedPasswordFinder(CertificateKeyPassword) + : new ConsolePasswordFinder(console); + } + } - private string FormatLanguage(object arg) + private string? FormatLanguage(object? arg) { - if (!(arg is string name)) return null; + if (arg is not string name) return null; try { @@ -177,7 +184,7 @@ private string FormatLanguage(object arg) } } - private object FormatCertificate(object arg) + private object? FormatCertificate(object? arg) { if (arg == null) return null; @@ -187,7 +194,7 @@ private object FormatCertificate(object arg) var stack = new Stack(certs.Select(cert => new Grid { Stroke = new LineThickness(LineWidth.Single), - Columns = { UIHelper.FieldsColumns }, + Columns = {UIHelper.FieldsColumns}, Children = { UIHelper.Row("Serial number", cert.SerialNumber), @@ -201,7 +208,7 @@ private object FormatCertificate(object arg) return stack; } - private object FormatCustomHttpHeaders(object arg) + private object? FormatCustomHttpHeaders(object? arg) { if (arg is not IList headers) return null; return string.Join(Environment.NewLine, headers); @@ -225,10 +232,10 @@ protected override void CustomFillPreferences(Preferences preferences) private string ReadPemCertificates() { // Validate - if (!ReadPemObjects(CertificatePath).Any()) + if (!ReadPemObjects(CertificatePath!).Any()) throw new InvalidOperationException($"The file \"{CertificatePath}\" contains no certificates or has unsupported format."); - var certificate = File.ReadAllText(CertificatePath, Encoding.ASCII); + var certificate = File.ReadAllText(CertificatePath!, Encoding.ASCII); return certificate; } @@ -245,14 +252,14 @@ private string ReadPemKeys(IPasswordFinder passwordFinder) catch (PasswordException) { var privateKeys = ReadPrivateKeys(passwordFinder).ToList(); - if (!privateKeys.Any()) + if (privateKeys.Count == 0) throw new InvalidOperationException($"The file \"{CertificateKeyPath}\" contains no private keys."); return WritePrivateKeys(privateKeys); } - - IEnumerable ReadPrivateKeys(IPasswordFinder pf) + + IEnumerable ReadPrivateKeys(IPasswordFinder? pf) { using (var input = File.OpenText(CertificateKeyPath)) { @@ -260,10 +267,15 @@ IEnumerable ReadPrivateKeys(IPasswordFinder pf) do { var obj = reader.ReadObject(); - if (obj is AsymmetricKeyParameter key && key.IsPrivate) - yield return key; - else if (obj is AsymmetricCipherKeyPair pair) - yield return pair.Private; + switch (obj) + { + case AsymmetricKeyParameter {IsPrivate: true} key: + yield return key; + break; + case AsymmetricCipherKeyPair pair: + yield return pair.Private; + break; + } } while (!input.EndOfStream); } } @@ -280,8 +292,8 @@ string WritePrivateKeys(IEnumerable keys) } } - private IEnumerable ReadPemObjects(StreamReader input, IPasswordFinder passwordFinder = null) - { + private IEnumerable ReadPemObjects(StreamReader input, IPasswordFinder? passwordFinder = null) + { var reader = new PemReader(input, passwordFinder); do { @@ -291,7 +303,7 @@ private IEnumerable ReadPemObjects(StreamReader input, IPasswordFinder pas } while (!input.EndOfStream); } - private IEnumerable ReadPemObjects(string path, IPasswordFinder passwordFinder = null) + private IEnumerable ReadPemObjects(string path, IPasswordFinder? passwordFinder = null) { using (var input = File.OpenText(path)) { @@ -307,12 +319,13 @@ private IEnumerable ReadPemObjects(string path, IPasswordFinder passwordFi var certOutput = new StringWriter(); var keyOutput = new StringWriter(); - using (var input = File.OpenRead(CertificatePath)) + using (var input = File.OpenRead(CertificatePath!)) { var certWriter = new PemWriter(certOutput); var keyWriter = new PemWriter(keyOutput); - var store = new Pkcs12Store(input, passwordFinder.GetPassword()); - foreach (string alias in store.Aliases) + var store = new Pkcs12StoreBuilder().Build(); + store.Load(input, passwordFinder.GetPassword()); + foreach (var alias in store.Aliases) { var cert = store.GetCertificate(alias); if (cert != null) @@ -337,7 +350,7 @@ private CertificateFileType GetCertificateFileType() return CertificateFileType.None; var ext = Path.GetExtension(CertificatePath).ToLowerInvariant(); - return (ext == ".pfx" || ext == ".p12") + return ext is ".pfx" or ".p12" ? CertificateFileType.Pfx : CertificateFileType.Pem; } @@ -349,24 +362,22 @@ private enum CertificateFileType Pfx } - private class PredefinedPasswordFinder : IPasswordFinder + private class PredefinedPasswordFinder(string password) : IPasswordFinder { - private readonly string _password; - - public PredefinedPasswordFinder(string password) => _password = password; - - public char[] GetPassword() => _password.ToCharArray(); + public char[] GetPassword() + { + return password.ToCharArray(); + } } - private class ConsolePasswordFinder : IPasswordFinder + private class ConsolePasswordFinder(IConsole console) : IPasswordFinder { - private readonly IConsole _console; - - public ConsolePasswordFinder(IConsole console) => _console = console; - - public char[] GetPassword() => _console.IsInputRedirected - ? _console.In.ReadLine()?.ToCharArray() - : Prompt.GetPassword("Private key password:")?.ToCharArray(); + public char[] GetPassword() + { + return console.IsInputRedirected + ? console.In.ReadLine()!.ToCharArray() + : Prompt.GetPassword("Private key password:").ToCharArray(); + } } #endregion diff --git a/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.cs b/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.cs index 591b5e7..c3d9b5a 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.Settings.cs @@ -9,7 +9,6 @@ using McMaster.Extensions.CommandLineUtils; using QBittorrent.Client; using QBittorrent.CommandLineInterface.ColorSchemes; -using QBittorrent.CommandLineInterface.Formats; using QBittorrent.CommandLineInterface.ViewModels.ServerPreferences; namespace QBittorrent.CommandLineInterface.Commands @@ -31,14 +30,10 @@ public partial class ServerCommand public partial class Settings : ClientRootCommandBase { [AttributeUsage(AttributeTargets.Property)] - private class NoAutoSetAttribute : Attribute - { - } + private class NoAutoSetAttribute : Attribute; [AttributeUsage(AttributeTargets.Property)] - private class IgnoreAttribute : Attribute - { - } + private class IgnoreAttribute : Attribute; [AttributeUsage(AttributeTargets.Property)] private class MinApiVersionAttribute : Attribute @@ -114,12 +109,12 @@ where prop.GetCustomAttribute() == null await WarnIfNotSupported(client, console, maxVersion, message, true, values); } - if (props.Any()) + if (props.Count != 0) { var prefs = new Preferences(); foreach (var prop in props.Where(p => p.autoSet)) { - typeof(Preferences).GetProperty(prop.Name).SetValue(prefs, prop.value); + typeof(Preferences).GetProperty(prop.Name)!.SetValue(prefs, prop.value); } CustomFillPreferences(prefs); @@ -138,12 +133,12 @@ protected async Task WarnIfNotSupported(QBittorrentClient client, IConsole conso ApiVersion version, string message, bool max, - params object[] properties) + params object?[]? properties) { if (properties == null || properties.All(p => p == null)) return; - if ((await client.GetApiVersionAsync() < version) ^ max) + if (await client.GetApiVersionAsync() < version ^ max) { console.WriteLineColored(message, ColorScheme.Current.Warning); } @@ -160,7 +155,7 @@ protected virtual void CustomFillPreferences(Preferences preferences) protected virtual void PrintPreferences(QBittorrentClient client, Preferences preferences) { - var vm = (T)Activator.CreateInstance(typeof(T), preferences); + var vm = (T)Activator.CreateInstance(typeof(T), preferences)!; Print(vm); } } @@ -220,7 +215,7 @@ public class Email : SettingsCommand [Option("-f|--from
", "From e-mail address. Requires qBittorrent 4.1.5 or later.", CommandOptionType.SingleValue)] [MinApiVersion("2.2.0", "\"from\" option requires qBittorrent 4.1.5 or later.")] public string MailNotificationSender { get; set; } - + [Option("-s|--smtp ", "SMTP server URL.", CommandOptionType.SingleValue)] public string MailNotificationSmtpServer { get; set; } @@ -245,7 +240,7 @@ protected override Task Prepare(QBittorrentClient client, CommandLineApplication if (AskForSmtpPassword) { MailNotificationPassword = console.IsInputRedirected - ? console.In.ReadLine() + ? console.In.ReadLine()! : Prompt.GetPassword("Please, enter your SMTP server password: "); } @@ -285,8 +280,8 @@ public class Connection : SettingsCommand [Range(-1, int.MaxValue)] public int? MaxUploadsPerTorrent { get; set; } - protected override IReadOnlyDictionary> CustomFormatters => - new Dictionary> + protected override IReadOnlyDictionary> CustomFormatters => + new Dictionary> { [nameof(ConnectionViewModel.BittorrentProtocol)] = value => Client.BittorrentProtocol.Both.Equals(value) ? "TCP and uTP" : null @@ -316,7 +311,7 @@ public class Proxy : SettingsCommand public string ProxyUsername { get; set; } [Option("-p|--proxy-password ", "Proxy password", CommandOptionType.SingleValue)] - public string ProxyPassword { get; set; } + public string? ProxyPassword { get; set; } [Option("-P|--ask-proxy-password", "Ask user to enter proxy password.", CommandOptionType.NoValue)] [NoAutoSet] @@ -342,6 +337,13 @@ public class Proxy : SettingsCommand [MinApiVersion("2.9.1", "\"proxy-general\" option requires qBittorrent 4.6.0 or later.")] public bool? ProxyMisc { get; set; } + protected override IReadOnlyDictionary> CustomFormatters => + new Dictionary> + { + [nameof(ProxyViewModel.ProxyType)] = + value => value != null ? Enum.IsDefined(typeof(ProxyType), value) ? value.ToString() : "None" : null + }; + protected override Task Prepare(QBittorrentClient client, CommandLineApplication app, IConsole console) { if (AskForProxyPassword) @@ -353,13 +355,6 @@ protected override Task Prepare(QBittorrentClient client, CommandLineApplication return Task.CompletedTask; } - - protected override IReadOnlyDictionary> CustomFormatters => - new Dictionary> - { - [nameof(ProxyViewModel.ProxyType)] = - value => value != null ? (Enum.IsDefined(typeof(ProxyType), value) ? value.ToString() : "None") : null - }; } [Command(Description = "Manages speed limits.", ExtendedHelpText = ExtendedHelp)] @@ -420,16 +415,15 @@ protected override Task Prepare(QBittorrentClient client, CommandLineApplication return base.Prepare(client, app, console); - (int? hour, int? minute) TryParseTime(string input) + (int? hour, int? minute) TryParseTime(string? input) { - const DateTimeStyles styles = + const DateTimeStyles styles = DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite | DateTimeStyles.NoCurrentDateDefault; if (input == null) return (null, null); - DateTime dt; - if (DateTime.TryParseExact(input, "t", CultureInfo.CurrentCulture, styles, out dt)) + if (DateTime.TryParseExact(input, "t", CultureInfo.CurrentCulture, styles, out var dt)) return (dt.TimeOfDay.Hours, dt.TimeOfDay.Minutes); if (DateTime.TryParseExact(input, "t", CultureInfo.InvariantCulture, styles, out dt)) return (dt.TimeOfDay.Hours, dt.TimeOfDay.Minutes); @@ -499,7 +493,7 @@ public class Seeding : SettingsCommand public bool? MaxRatioEnabled { get; set; } [Option("-r|--max-ratio ", "Maximal ratio", CommandOptionType.SingleValue)] - [Range(-1d, Double.MaxValue)] + [Range(-1d, double.MaxValue)] public double? MaxRatio { get; set; } [Option("-S|--max-seeding-time-enabled ", "Enable/disable maximal seeding time limit", CommandOptionType.SingleValue)] @@ -551,7 +545,7 @@ protected override Task Prepare(QBittorrentClient client, CommandLineApplication if (AskForDynamicDnsPassword) { DynamicDnsPassword = console.IsInputRedirected - ? console.In.ReadLine() + ? console.In.ReadLine()! : Prompt.GetPassword("Please, enter your dynamic DNS password: "); } diff --git a/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.cs b/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.cs index 9269570..5f3d94f 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/ServerCommand.cs @@ -47,8 +47,8 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient var apiVersion = await client.GetApiVersionAsync(); var timestampToDateTimeOffset = apiVersion < ApiVersion_2_8_18 - ? (Func) DateTimeOffset.FromUnixTimeMilliseconds - : (Func) DateTimeOffset.FromUnixTimeSeconds; + ? (Func)DateTimeOffset.FromUnixTimeMilliseconds + : (Func)DateTimeOffset.FromUnixTimeSeconds; var log = await client.GetLogAsync(severity, AfterId ?? -1); foreach (var entry in log) @@ -67,6 +67,9 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient case TorrentLogSeverity.Critical: console.WriteColored("[Critical]", critical.fg, critical.bg); break; + case TorrentLogSeverity.All: + default: + throw new ArgumentOutOfRangeException(); } var time = timestampToDateTimeOffset(entry.Timestamp).ToString("s").Replace("T", " "); @@ -84,8 +87,8 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient colorSet = fallback; } - var bg = colorSet?.GetEffectiveBackground() ?? fallback.GetEffectiveBackground(); - var fg = colorSet?.GetEffectiveForeground() ?? fallback.GetEffectiveForeground(); + var bg = colorSet.GetEffectiveBackground(); + var fg = colorSet.GetEffectiveForeground(); return (bg, fg); } } @@ -106,7 +109,7 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient new Grid { Stroke = UIHelper.NoneStroke, - Columns = { UIHelper.FieldsColumns }, + Columns = {UIHelper.FieldsColumns}, Children = { UIHelper.Row("QBittorrent version", qVersion), @@ -118,10 +121,10 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient UIHelper.Row("Qt version", build.QtVersion), UIHelper.Row("Boost version", build.BoostVersion), UIHelper.Row("OpenSSL version", build.OpenSslVersion), - UIHelper.Row("ZLib version", build.ZlibVersion), + UIHelper.Row("ZLib version", build.ZlibVersion) } } - ).SetColors(ColorScheme.Current.Normal); + ).SetColors(ColorScheme.Current.Normal); ConsoleRenderer.RenderDocument(doc); diff --git a/src/QBittorrent.CommandLineInterface/Commands/SettingsCommand.cs b/src/QBittorrent.CommandLineInterface/Commands/SettingsCommand.cs index afe8bc5..26b039a 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/SettingsCommand.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/SettingsCommand.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; using Alba.CsConsoleFormat; using McMaster.Extensions.CommandLineUtils; using QBittorrent.CommandLineInterface.ColorSchemes; @@ -14,12 +12,48 @@ namespace QBittorrent.CommandLineInterface.Commands [Subcommand(typeof(Reset))] public class SettingsCommand { + + public int OnExecute(CommandLineApplication app, IConsole console) + { + var settings = SettingsService.Instance.GetGeneral(); + + var doc = new Document( + new Grid + { + Stroke = UIHelper.NoneStroke, + Columns = {UIHelper.FieldsColumns}, + Children = + { + UIHelper.Row("URL", settings.Url), + UIHelper.Row("User name", + settings.Username != null + ? new Span(settings.Username).SetColors(ColorScheme.Current.Normal) + : new Span("").SetColors(ColorScheme.Current.Inactive)), + UIHelper.Row("Password", + settings.Password != null + ? new Span("").SetColors(ColorScheme.Current.Active) + : new Span("").SetColors(ColorScheme.Current.Inactive)) + } + } + ).SetColors(ColorScheme.Current.Normal); + + ConsoleRenderer.RenderDocument(doc); + return ExitCodes.Success; + } + [Command(Description = "Sets the new value for the specified setting.")] [Subcommand(typeof(Url))] [Subcommand(typeof(Username))] [Subcommand(typeof(Password))] public class Set { + + public int OnExecute(CommandLineApplication app, IConsole console) + { + app.ShowHelp(); + return ExitCodes.WrongUsage; + } + [Command(Description = "Sets the default server URL.")] public class Url { @@ -56,7 +90,7 @@ public int OnExecute(CommandLineApplication app, IConsole console) [Command(Description = "Sets the default password.", ExtendedHelpText = ExtendedHelp)] public class Password { - private const string ExtendedHelp = + private const string ExtendedHelp = "\n" + "It may be not safe to store the password this way.\n" + "This command does not accept the password to set as a parameter." + @@ -72,7 +106,7 @@ public int OnExecute(CommandLineApplication app, IConsole console) string value; if (console.IsInputRedirected) { - value = console.In.ReadLine(); + value = console.In.ReadLine()!; } else { @@ -93,12 +127,6 @@ public int OnExecute(CommandLineApplication app, IConsole console) return ExitCodes.Success; } } - - public int OnExecute(CommandLineApplication app, IConsole console) - { - app.ShowHelp(); - return ExitCodes.WrongUsage; - } } [Command(Description = "Resets the specified settings to their default values.")] @@ -108,6 +136,13 @@ public int OnExecute(CommandLineApplication app, IConsole console) [Subcommand(typeof(All))] public class Reset { + + public int OnExecute(CommandLineApplication app, IConsole console) + { + app.ShowHelp(); + return ExitCodes.WrongUsage; + } + [Command(Description = "Resets the server URL to " + GeneralSettings.DefaultUrl)] public class Url { @@ -153,40 +188,6 @@ public int OnExecute(CommandLineApplication app, IConsole console) return ExitCodes.Success; } } - - public int OnExecute(CommandLineApplication app, IConsole console) - { - app.ShowHelp(); - return ExitCodes.WrongUsage; - } - } - - public int OnExecute(CommandLineApplication app, IConsole console) - { - var settings = SettingsService.Instance.GetGeneral(); - - var doc = new Document( - new Grid - { - Stroke = UIHelper.NoneStroke, - Columns = { UIHelper.FieldsColumns }, - Children = - { - UIHelper.Row("URL", settings.Url), - UIHelper.Row("User name", - settings.Username != null - ? new Span(settings.Username).SetColors(ColorScheme.Current.Normal) - : new Span("").SetColors(ColorScheme.Current.Inactive)), - UIHelper.Row("Password", - settings.Password != null - ? new Span("").SetColors(ColorScheme.Current.Active) - : new Span("").SetColors(ColorScheme.Current.Inactive)), - } - } - ).SetColors(ColorScheme.Current.Normal); - - ConsoleRenderer.RenderDocument(doc); - return ExitCodes.Success; } } } diff --git a/src/QBittorrent.CommandLineInterface/Commands/TagCommand.cs b/src/QBittorrent.CommandLineInterface/Commands/TagCommand.cs index 486974f..2be1a67 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TagCommand.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TagCommand.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Text; using System.Threading.Tasks; using Alba.CsConsoleFormat; using McMaster.Extensions.CommandLineUtils; @@ -57,19 +56,19 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient protected override void PrintTable(IEnumerable tags) { - if (tags?.Any() == true) + if (tags.Any()) { var doc = new Document( new Grid { Columns = { - new Column {Width = GridLength.Auto}, + new Column {Width = GridLength.Auto} }, Children = { UIHelper.Header("Tags"), - tags.Select(t => new[] { new Cell(t)}) + tags.Select(t => new[] {new Cell(t)}) }, Stroke = LineThickness.Single }) diff --git a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Add.cs b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Add.cs index 70e1220..539892a 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Add.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Add.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Text; using System.Threading.Tasks; using McMaster.Extensions.CommandLineUtils; using QBittorrent.Client; @@ -67,10 +66,10 @@ public abstract class Base : AuthenticatedCommandBase [Option("-L|--content-layout", "Content layout (Original|Subfolder|NoSubfolder). Requires qBittorrent 4.3.2 or later.", CommandOptionType.SingleValue)] [EnumValidation(typeof(TorrentContentLayout), AllowEmpty = true)] - public string ContentLayout { get; set; } + public string? ContentLayout { get; set; } [Option("-t|--tag ", "The tag to be added to the torrent. Can be specified multiple times. Requires qBittorrent 4.3.4 or later.", CommandOptionType.MultipleValue)] - public IList Tags { get; set; } + public IList? Tags { get; set; } protected async Task WarnUnsupportedOptions(IQBittorrentClient client, IConsole console) { @@ -81,7 +80,7 @@ protected async Task WarnUnsupportedOptions(IQBittorrentClient client, IConsole "--automatic-torrent-management option is ignored by qBittorrent versions earlier than 4.1.5.", ColorScheme.Current.Warning); } - + if (Tags != null && apiVersion < new ApiVersion(2, 6, 2)) { console.WriteLineColored( diff --git a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.File.cs b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.File.cs index fcb70b3..1ddef7b 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.File.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.File.cs @@ -42,7 +42,7 @@ protected override void PrintTable(IEnumerable list) new Column {Width = GridLength.Auto}, new Column {Width = GridLength.Star(1)}, new Column {Width = GridLength.Auto}, - new Column {Width = GridLength.Auto}, + new Column {Width = GridLength.Auto} }, Children = { @@ -52,10 +52,10 @@ protected override void PrintTable(IEnumerable list) UIHelper.Header("Progress"), list.Select(c => new[] { - new Cell(c.Id), + new Cell(c.Id), new Cell(c.Name), new Cell(c.Size.ToString("N0")), - new Cell(c.Progress.ToString("P0")), + new Cell(c.Progress.ToString("P0")) }) }, Stroke = LineThickness.Single @@ -77,7 +77,7 @@ public class Priority : TorrentSpecificListCommandBase", "Output format: plain|table|list|csv|json", CommandOptionType.SingleValue)] - public override string Format { get; set; } + public override string? Format { get; set; } protected override async Task OnExecuteTorrentSpecificAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { @@ -92,7 +92,7 @@ protected override async Task OnExecuteTorrentSpecificAsync(QBittorrentClie { foreach (var file in Files) { - console.WriteLineColored(contents?[file]?.Priority.ToString(), ColorScheme.Current.Normal); + console.WriteLineColored(contents?[file]?.Priority.ToString()!, ColorScheme.Current.Normal); } } else @@ -124,13 +124,13 @@ public class Rename : TorrentSpecificCommandBase protected override async Task OnExecuteTorrentSpecificAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { - if ((File == null) == (OldName == null)) + if (File == null == (OldName == null)) { throw new InvalidOperationException("Either --file or --old-name option must be specified."); } var version = await client.GetApiVersionAsync(); - if (version < new ApiVersion(2, 8, 0)) + if (version < new ApiVersion(2, 8)) { await RenameFileLegacy(client); } diff --git a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Limit.cs b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Limit.cs index e0ce4df..3b4f533 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Limit.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Limit.cs @@ -16,17 +16,17 @@ public class Limit : ClientRootCommandBase { protected static void PrintLimit(IConsole console, long? limit) { - if (limit == null || limit < 0) + switch (limit) { - console.WriteLineColored("n/a", ColorScheme.Current.Normal); - } - else if (limit == 0) - { - console.WriteLineColored("unlimited", ColorScheme.Current.Normal); - } - else - { - console.WriteLineColored($"{limit:N0} bytes/s", ColorScheme.Current.Normal); + case null or < 0: + console.WriteLineColored("n/a", ColorScheme.Current.Normal); + break; + case 0: + console.WriteLineColored("unlimited", ColorScheme.Current.Normal); + break; + default: + console.WriteLineColored($"{limit:N0} bytes/s", ColorScheme.Current.Normal); + break; } } diff --git a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.List.cs b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.List.cs index d096cee..4dad111 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.List.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.List.cs @@ -59,9 +59,9 @@ from property in typeof(TorrentInfo).GetProperties() SortColumns = fields.ToDictionary(x => x.Name, x => x.json, StringComparer.InvariantCultureIgnoreCase); var regex = new Regex("[A-Z]{1}[a-z]*"); - var states = + var states = from state in Enum.GetValues(typeof(TorrentState)).Cast() - let name = string.Join("-", regex.Matches(state.ToString()).Cast().Select(m => m.Value.ToLowerInvariant())) + let name = string.Join("-", regex.Matches(state.ToString()).Select(m => m.Value.ToLowerInvariant())) select (state, name); TorrentStateColorKeys = states.ToDictionary(x => x.state, x => x.name); @@ -70,8 +70,8 @@ from state in Enum.GetValues(typeof(TorrentState)).Cast() [Option("--verbose", "Displays verbose information.", CommandOptionType.NoValue)] public bool Verbose { get; set; } - [Option("-f|--filter ", - "Filter by status: \nall|downloading|seeding|completed|paused|resumed|\nactive|inactive|errored|stalled|stalledDownloading|stalledUploading", + [Option("-f|--filter ", + "Filter by status: \nall|downloading|seeding|completed|paused|resumed|\nactive|inactive|errored|stalled|stalledDownloading|stalledUploading", CommandOptionType.SingleValue)] [EnumValidation(typeof(TorrentListFilter), AllowEmpty = true)] public string Filter { get; set; } @@ -81,7 +81,7 @@ from state in Enum.GetValues(typeof(TorrentState)).Cast() [Option("-s|--sort ", "Sort by property.", CommandOptionType.SingleValue)] [SortValidation] - public string Sort { get; set; } + public string? Sort { get; set; } [Option("-r|--reverse", "Reverse the sort order.", CommandOptionType.NoValue)] public bool Reverse { get; set; } @@ -105,7 +105,7 @@ protected override async Task OnExecuteAuthenticatedAsync(QBittorrentClient Category = Category, Filter = Enum.TryParse(Filter, true, out TorrentListFilter filter) ? filter : TorrentListFilter.All, SortBy = Sort != null - ? (SortColumns.TryGetValue(Sort, out var sort) ? sort : null) + ? SortColumns.GetValueOrDefault(Sort) : null, ReverseSort = Reverse, Limit = Limit, @@ -125,28 +125,28 @@ protected override void PrintList(IEnumerable torrents) new Grid { Stroke = new LineThickness(LineWidth.None), - Columns = { UIHelper.FieldsColumns }, + Columns = {UIHelper.FieldsColumns}, Children = - { - UIHelper.Row("Name", torrent.Name), - UIHelper.Row("State", torrent.State), - UIHelper.Row("Hash", torrent.Hash), - UIHelper.Row("Size", $"{torrent.Size:N0} bytes"), - UIHelper.Row("Progress", $"{torrent.Progress:P0}"), - UIHelper.Row("DL Speed", $"{FormatSpeed(torrent.DownloadSpeed)}"), - UIHelper.Row("UP Speed", $"{FormatSpeed(torrent.UploadSpeed)}"), - UIHelper.Row("Priority", torrent.Priority), - UIHelper.Row("Seeds", $"{torrent.ConnectedSeeds} of {torrent.TotalSeeds}"), - UIHelper.Row("Leechers", $"{torrent.ConnectedLeechers} of {torrent.TotalLeechers}"), - UIHelper.Row("Ratio", $"{torrent.Ratio:F2}"), - UIHelper.Row("ETA", FormatEta(torrent.EstimatedTime)), - UIHelper.Row("Category", torrent.Category), - UIHelper.Row("Tags", $"{string.Join(", ", torrent.Tags ?? Enumerable.Empty())}"), - UIHelper.Row("Save path", torrent.SavePath), - UIHelper.Row("Added", $"{torrent.AddedOn?.ToLocalTime():G}"), - UIHelper.Row("Completion", $"{torrent.CompletionOn?.ToLocalTime():G}"), - UIHelper.Row("Options", GetOptions(torrent)) - }, + { + UIHelper.Row("Name", torrent.Name), + UIHelper.Row("State", torrent.State), + UIHelper.Row("Hash", torrent.Hash), + UIHelper.Row("Size", $"{torrent.Size:N0} bytes"), + UIHelper.Row("Progress", $"{torrent.Progress:P0}"), + UIHelper.Row("DL Speed", $"{FormatSpeed(torrent.DownloadSpeed)}"), + UIHelper.Row("UP Speed", $"{FormatSpeed(torrent.UploadSpeed)}"), + UIHelper.Row("Priority", torrent.Priority), + UIHelper.Row("Seeds", $"{torrent.ConnectedSeeds} of {torrent.TotalSeeds}"), + UIHelper.Row("Leechers", $"{torrent.ConnectedLeechers} of {torrent.TotalLeechers}"), + UIHelper.Row("Ratio", $"{torrent.Ratio:F2}"), + UIHelper.Row("ETA", FormatEta(torrent.EstimatedTime)), + UIHelper.Row("Category", torrent.Category), + UIHelper.Row("Tags", $"{string.Join(", ", torrent.Tags ?? Enumerable.Empty())}"), + UIHelper.Row("Save path", torrent.SavePath), + UIHelper.Row("Added", $"{torrent.AddedOn?.ToLocalTime():G}"), + UIHelper.Row("Completion", $"{torrent.CompletionOn?.ToLocalTime():G}"), + UIHelper.Row("Options", GetOptions(torrent)) + }, Margin = new Thickness(0, 0, 0, 2) } ) @@ -183,7 +183,7 @@ protected override void PrintTable(IEnumerable torrents) new Column {Width = GridLength.Auto}, new Column {Width = GridLength.Auto}, new Column {Width = GridLength.Auto}, - new Column {Width = GridLength.Auto}, + new Column {Width = GridLength.Auto} }, Children = { @@ -197,7 +197,7 @@ protected override void PrintTable(IEnumerable torrents) { FormatState(t.State), new Cell(t.Name), - new Cell(t.Hash.Substring(0, 6)), + new Cell(t.Hash[..6]), new Cell(FormatSpeed(t.DownloadSpeed).PadLeft(10)), new Cell(FormatSpeed(t.UploadSpeed).PadLeft(10)), new Cell(FormatEta(t.EstimatedTime)) @@ -208,7 +208,7 @@ protected override void PrintTable(IEnumerable torrents) ).SetColors(ColorScheme.Current.Normal); ConsoleRenderer.RenderDocument(doc); - + Cell FormatState(TorrentState state) { var colorSet = GetStateColors(state); @@ -271,40 +271,30 @@ ColorSet GetStateColors(TorrentState state) return colorSet; } } - + private static string FormatSpeed(long speed) { - if (speed < 1024) - { - return $"{speed} B/s"; - } - if (speed < 1024 * 1024) + return speed switch { - return $"{speed / 1024} kB/s"; - } - if (speed < 1024 * 1024 * 1024) - { - return $"{speed / (1024 * 1024)} MB/s"; - } - return $"{speed / (1024 * 1024 * 1024)} GB/s"; + < 1024 => $"{speed} B/s", + < 1024 * 1024 => $"{speed / 1024} kB/s", + < 1024 * 1024 * 1024 => $"{speed / (1024 * 1024)} MB/s", + _ => $"{speed / (1024 * 1024 * 1024)} GB/s" + }; } private static string FormatEta(TimeSpan? eta) { - if (eta < TimeSpan.FromHours(100)) - { - var ts = eta.Value; - return $" {ts.Hours:00}.{ts.Minutes:00}.{ts.Seconds:00}"; - } - return string.Empty; + if (eta == null || eta.Value >= TimeSpan.FromHours(100)) return string.Empty; + var ts = eta.Value; + return $" {ts.Hours:00}.{ts.Minutes:00}.{ts.Seconds:00}"; } private class SortValidationAttribute : ValidationAttribute { - protected override ValidationResult IsValid(object value, ValidationContext validationContext) + protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { - var str = value as string; - if (str == null || SortColumns.ContainsKey(str)) + if (value is not string str || SortColumns.ContainsKey(str)) return ValidationResult.Success; return new ValidationResult( diff --git a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Options.cs b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Options.cs index 1f353d6..94535cf 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Options.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Options.cs @@ -44,14 +44,14 @@ protected override async Task OnExecuteTorrentSpecificAsync(QBittorrentClie new Grid { Stroke = UIHelper.NoneStroke, - Columns = { UIHelper.FieldsColumns }, + Columns = {UIHelper.FieldsColumns}, Children = { UIHelper.Row("Automatic Torrent Management", torrent.AutomaticTorrentManagement), UIHelper.Row("First/last piece prioritized", torrent.FirstLastPiecePrioritized), UIHelper.Row("Force start", torrent.ForceStart), UIHelper.Row("Sequential download", torrent.SequentialDownload), - UIHelper.Row("Super seeding", torrent.SuperSeeding), + UIHelper.Row("Super seeding", torrent.SuperSeeding) } } ).SetColors(ColorScheme.Current.Normal); @@ -124,8 +124,6 @@ async Task SetSuperSeeding() } } } - - } } } diff --git a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Peer.cs b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Peer.cs index 4837cb7..76d752e 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Peer.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Peer.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Net; -using System.Text; using System.Threading.Tasks; using Alba.CsConsoleFormat; using McMaster.Extensions.CommandLineUtils; @@ -53,23 +51,25 @@ protected override async Task OnExecuteTorrentSpecificAsync(QBittorrentClie [Command(Description = "Shows the list of torrent peers.")] public class List : TorrentSpecificListCommandBase { - private static readonly Dictionary> CustomFormatters; + private static readonly Dictionary>? CustomFormatters; static List() { - CustomFormatters = new Dictionary> + CustomFormatters = new Dictionary> { - [nameof(PeerPartialInfoViewModel.Files)] = FormatFiles, + [nameof(PeerPartialInfoViewModel.Files)] = FormatFiles }; } + protected override Dictionary>? ListCustomFormatters => CustomFormatters; + protected override async Task OnExecuteTorrentSpecificAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { var response = await client.GetPeerPartialDataAsync(Hash); if (response == null) return ExitCodes.Failure; - var peers = response.PeersChanged?.Values ?? Enumerable.Empty(); + var peers = response.PeersChanged?.Values ?? []; Print(peers.Select(p => new PeerPartialInfoViewModel(p))); @@ -91,7 +91,7 @@ protected override void PrintTable(IEnumerable peers) new Column {Width = GridLength.Auto}, new Column {Width = GridLength.Auto}, new Column {Width = GridLength.Auto}, - new Column {Width = GridLength.Auto}, + new Column {Width = GridLength.Auto} }, Children = { @@ -112,7 +112,7 @@ protected override void PrintTable(IEnumerable peers) new Cell(FormatSpeed(p.DownloadSpeed).PadLeft(10)), new Cell(FormatSpeed(p.UploadSpeed).PadLeft(10)), new Cell(FormatData(p.Downloaded).PadLeft(8)), - new Cell(FormatData(p.Uploaded).PadLeft(8)), + new Cell(FormatData(p.Uploaded).PadLeft(8)) }) }, Stroke = LineThickness.Single @@ -122,59 +122,33 @@ protected override void PrintTable(IEnumerable peers) ConsoleRenderer.RenderDocument(doc); } - protected override IReadOnlyDictionary> ListCustomFormatters => CustomFormatters; - private string FormatData(long? amount) { - if (amount == null) - { - return string.Empty; - } - - if (amount < 1024) + return amount switch { - return $"{amount} B"; - } - - if (amount < 1024 * 1024) - { - return $"{amount / 1024} kB"; - } - - if (amount < 1024 * 1024 * 1024) - { - return $"{amount / (1024 * 1024)} MB"; - } + null => string.Empty, + < 1024 => $"{amount} B", + < 1024 * 1024 => $"{amount / 1024} kB", + < 1024 * 1024 * 1024 => $"{amount / (1024 * 1024)} MB", + _ => $"{amount / (1024 * 1024 * 1024)} GB" + }; - return $"{amount / (1024 * 1024 * 1024)} GB"; } private string FormatSpeed(int? speed) { - if (speed == null) + return speed switch { - return string.Empty; - } - - if (speed < 1024) - { - return $"{speed} B/s"; - } - - if (speed < 1024 * 1024) - { - return $"{speed / 1024} kB/s"; - } - - if (speed < 1024 * 1024 * 1024) - { - return $"{speed / (1024 * 1024)} MB/s"; - } + null => string.Empty, + < 1024 => $"{speed} B/s", + < 1024 * 1024 => $"{speed / 1024} kB/s", + < 1024 * 1024 * 1024 => $"{speed / (1024 * 1024)} MB/s", + _ => $"{speed / (1024 * 1024 * 1024)} GB/s" + }; - return $"{speed / (1024 * 1024 * 1024)} GB/s"; } - private static object FormatFiles(object list) + private static string FormatFiles(object? list) { if (list is not IReadOnlyList files) return string.Empty; diff --git a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Peers.cs b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Peers.cs index b9137a6..16c3bca 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Peers.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Peers.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; +using System.Linq; using System.Net; -using System.Text; using System.Threading.Tasks; using Alba.CsConsoleFormat; using McMaster.Extensions.CommandLineUtils; @@ -24,7 +20,7 @@ protected override async Task OnExecuteTorrentSpecificAsync(QBittorrentClie if (response == null) return ExitCodes.Failure; - var peers = response.PeersChanged?.Values ?? Enumerable.Empty(); + var peers = response.PeersChanged?.Values ?? []; var doc = new Document( new Grid @@ -38,7 +34,7 @@ protected override async Task OnExecuteTorrentSpecificAsync(QBittorrentClie new Column {Width = GridLength.Auto}, new Column {Width = GridLength.Auto}, new Column {Width = GridLength.Auto}, - new Column {Width = GridLength.Auto}, + new Column {Width = GridLength.Auto} }, Children = { @@ -53,13 +49,13 @@ protected override async Task OnExecuteTorrentSpecificAsync(QBittorrentClie peers.Select(p => new[] { new Cell(p.CountryCode), - new Cell(FormatEndpoint(p.Address, p.Port)), + new Cell(FormatEndpoint(p.Address, p.Port)), new Cell(p.Client), new Cell($"{p.Progress:P0}"), new Cell(FormatSpeed(p.DownloadSpeed).PadLeft(10)), new Cell(FormatSpeed(p.UploadSpeed).PadLeft(10)), new Cell(FormatData(p.Downloaded).PadLeft(8)), - new Cell(FormatData(p.Uploaded).PadLeft(8)), + new Cell(FormatData(p.Uploaded).PadLeft(8)) }) }, Stroke = LineThickness.Single @@ -70,7 +66,7 @@ protected override async Task OnExecuteTorrentSpecificAsync(QBittorrentClie return ExitCodes.Success; - string FormatEndpoint(IPAddress address, int? port) + string? FormatEndpoint(IPAddress? address, int? port) { if (address == null || port == null) return null; @@ -80,44 +76,26 @@ string FormatEndpoint(IPAddress address, int? port) string FormatSpeed(int? speed) { - if (speed == null) - { - return string.Empty; - } - if (speed < 1024) - { - return $"{speed} B/s"; - } - if (speed < 1024 * 1024) - { - return $"{speed / 1024} kB/s"; - } - if (speed < 1024 * 1024 * 1024) + return speed switch { - return $"{speed / (1024 * 1024)} MB/s"; - } - return $"{speed / (1024 * 1024 * 1024)} GB/s"; + null => string.Empty, + < 1024 => $"{speed} B/s", + < 1024 * 1024 => $"{speed / 1024} kB/s", + < 1024 * 1024 * 1024 => $"{speed / (1024 * 1024)} MB/s", + _ => $"{speed / (1024 * 1024 * 1024)} GB/s" + }; } string FormatData(long? amount) { - if (amount == null) + return amount switch { - return string.Empty; - } - if (amount < 1024) - { - return $"{amount} B"; - } - if (amount < 1024 * 1024) - { - return $"{amount / 1024} kB"; - } - if (amount < 1024 * 1024 * 1024) - { - return $"{amount / (1024 * 1024)} MB"; - } - return $"{amount / (1024 * 1024 * 1024)} GB"; + null => string.Empty, + < 1024 => $"{amount} B", + < 1024 * 1024 => $"{amount / 1024} kB", + < 1024 * 1024 * 1024 => $"{amount / (1024 * 1024)} MB", + _ => $"{amount / (1024 * 1024 * 1024)} GB" + }; } } } diff --git a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Pieces.cs b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Pieces.cs index b1d676d..2fa0ea1 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Pieces.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Pieces.cs @@ -14,6 +14,15 @@ public partial class TorrentCommand [Command(Description = "Shows the torrent pieces' hashes and states.")] public class Pieces : TorrentSpecificCommandBase { + + public enum DisplayMode + { + Full, + Hashes, + States, + Diagram + } + [Option("-d|--display ", "Display Mode (FULL|HASHES|STATES|DIAGRAM). FULL is default", CommandOptionType.SingleValue)] [EnumValidation(typeof(DisplayMode), AllowEmpty = true)] public string Display { get; set; } @@ -37,6 +46,8 @@ protected override async Task OnExecuteTorrentSpecificAsync(QBittorrentClie case DisplayMode.Diagram: await ShowDiagram(); break; + default: + throw new ArgumentOutOfRangeException(); } async Task ShowFull() @@ -47,7 +58,7 @@ async Task ShowFull() var width = (int)Math.Log10(hashes.Count) + 1; var sequence = hashes.Zip(states, (hash, state) => (hash, state)); - int index = 0; + var index = 0; foreach (var (hash, state) in sequence) { console.WriteLineColored($"{(index++).ToString().PadLeft(width)} {hash} {state}", ColorScheme.Current.Normal); @@ -59,7 +70,7 @@ async Task ShowHashes() var hashes = await client.GetTorrentPiecesHashesAsync(Hash); var width = (int)Math.Log10(hashes.Count) + 1; - int index = 0; + var index = 0; foreach (var hash in hashes) { console.WriteLineColored($"{(index++).ToString().PadLeft(width)} {hash}", ColorScheme.Current.Normal); @@ -71,7 +82,7 @@ async Task ShowStates() var states = await client.GetTorrentPiecesStatesAsync(Hash); var width = (int)Math.Log10(states.Count) + 1; - int index = 0; + var index = 0; foreach (var state in states) { console.WriteLineColored($"{(index++).ToString().PadLeft(width)} {state}", ColorScheme.Current.Normal); @@ -94,12 +105,12 @@ async Task ShowDiagram() var states = await client.GetTorrentPiecesStatesAsync(Hash); var width = (int)Math.Log10(states.Count) + 1; - var rowWidth = (Console.BufferWidth > 100 + width) ? 100 : 50; + var rowWidth = Console.BufferWidth > 100 + width ? 100 : 50; - for (int index = 0; index < states.Count; index += rowWidth) + for (var index = 0; index < states.Count; index += rowWidth) { console.Write(index.ToString().PadLeft(width) + " "); - for (int offset = 0; offset < rowWidth && index + offset < states.Count; offset++) + for (var offset = 0; offset < rowWidth && index + offset < states.Count; offset++) { var state = (int)states[index + offset]; console.WriteColored(".", fgColors[state], bgColors[state]); @@ -110,14 +121,6 @@ async Task ShowDiagram() return ExitCodes.Success; } - - public enum DisplayMode - { - Full, - Hashes, - States, - Diagram - } } } } diff --git a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Share.cs b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Share.cs index f998e66..6955cfc 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Share.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Share.cs @@ -16,7 +16,7 @@ public partial class TorrentCommand [Command("share", "sharing", "seeding", Description = "Manages torrent sharing limits.", ExtendedHelpText = FormatHelpText)] public class Share : TorrentSpecificFormattableCommandBase { - private IReadOnlyDictionary> _customFormatters; + private Dictionary> _customFormatters; [Option("-r|--ratio-limit ", "Set the ratio limit (number|GLOBAL|NONE)", CommandOptionType.SingleValue)] [ShareRatioLimitValidation] @@ -30,6 +30,8 @@ public class Share : TorrentSpecificFormattableCommandBase> CustomFormatters => _customFormatters; + protected override async Task OnExecuteTorrentSpecificAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { var partialData = await client.GetPartialDataAsync(); @@ -74,7 +76,7 @@ await client.SetShareLimitsAsync(Hash, client.GetPreferencesAsync()); var viewModel = new TorrentShareViewModel(properties, info); - _customFormatters = new Dictionary> + _customFormatters = new Dictionary> { [nameof(viewModel.RatioLimit)] = FormatRatioLimit, [nameof(viewModel.SeedingTimeLimit)] = FormatSeedingTimeLimit, @@ -84,7 +86,7 @@ await client.SetShareLimitsAsync(Hash, return ExitCodes.Success; - object FormatRatioLimit(object arg) + object? FormatRatioLimit(object? arg) { switch (arg) { @@ -100,7 +102,7 @@ object FormatRatioLimit(object arg) } } - object FormatSeedingTimeLimit(object arg) + object? FormatSeedingTimeLimit(object? arg) { var time = arg as TimeSpan?; if (time == ShareLimits.SeedingTime.Global) @@ -112,7 +114,7 @@ object FormatSeedingTimeLimit(object arg) return time == ShareLimits.SeedingTime.Unlimited ? "None" : time?.ToString(); } - object FormatInactiveSeedingTimeLimit(object arg) + object? FormatInactiveSeedingTimeLimit(object? arg) { var time = arg as TimeSpan?; if (time == ShareLimits.SeedingTime.Global) @@ -125,8 +127,6 @@ object FormatInactiveSeedingTimeLimit(object arg) } } - protected override IReadOnlyDictionary> CustomFormatters => _customFormatters; - private double? GetRatioLimit() { if ("GLOBAL".Equals(RatioLimit, StringComparison.OrdinalIgnoreCase)) diff --git a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Tags.cs b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Tags.cs index 7b3e303..b1a9ac1 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Tags.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Tags.cs @@ -38,19 +38,19 @@ protected override async Task OnExecuteTorrentSpecificAsync(QBittorrentClie protected override void PrintTable(IEnumerable tags) { - if (tags?.Any() == true) + if (tags.Any()) { var doc = new Document( new Grid { Columns = { - new Column {Width = GridLength.Auto}, + new Column {Width = GridLength.Auto} }, Children = { UIHelper.Header("Tags"), - tags.Select(t => new[] { new Cell(t)}) + tags.Select(t => new[] {new Cell(t)}) }, Stroke = LineThickness.Single }) @@ -74,7 +74,7 @@ public class Add : TorrentSpecificCommandBase protected override bool AllowAll => true; [Argument(0, "", "Full or partial torrent hash, or keyword ALL to add tags to all torrents.")] - public override string Hash { get; set; } + public override required string Hash { get; set; } [Argument(1, "", "The tags to add.")] [Required] @@ -95,7 +95,7 @@ public class Delete : TorrentSpecificCommandBase protected override bool AllowAll => true; [Argument(0, "", "Full or partial torrent hash, or keyword ALL to remove tags from all torrents.")] - public override string Hash { get; set; } + public override required string Hash { get; set; } [Argument(1, "", "The tags to remove.")] [Required] @@ -116,7 +116,7 @@ public class Clear : TorrentSpecificCommandBase protected override bool AllowAll => true; [Argument(0, "", "Full or partial torrent hash, or keyword ALL to clear tags from all torrents.")] - public override string Hash { get; set; } + public override required string Hash { get; set; } protected override async Task OnExecuteTorrentSpecificAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { diff --git a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Tracker.cs b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Tracker.cs index 691579f..1c25fae 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Tracker.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.Tracker.cs @@ -45,7 +45,7 @@ protected override void PrintTable(IEnumerable list) new Column {Width = GridLength.Star(1)}, new Column {Width = GridLength.Auto}, new Column {Width = GridLength.Auto}, - new Column {Width = GridLength.Auto}, + new Column {Width = GridLength.Auto} }, Children = { @@ -58,7 +58,7 @@ protected override void PrintTable(IEnumerable list) new Cell(c.Url), new Cell(c.Status), new Cell(c.Seeds), - new Cell(c.Leeches), + new Cell(c.Leeches) }) }, Stroke = LineThickness.Single diff --git a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.cs b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.cs index f1700df..d3d2c64 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TorrentCommand.cs @@ -55,24 +55,24 @@ protected override void PrintTable(IEnumerable list) { Columns = { - new Column {Width = GridLength.Auto}, - new Column {Width = GridLength.Star(1)}, - new Column {Width = GridLength.Auto}, - new Column {Width = GridLength.Auto}, + new Column {Width = GridLength.Auto}, + new Column {Width = GridLength.Star(1)}, + new Column {Width = GridLength.Auto}, + new Column {Width = GridLength.Auto} }, Children = { - UIHelper.Header("Id"), - UIHelper.Header("Name"), - UIHelper.Header("Size"), - UIHelper.Header("Progress"), - list.Select(c => new[] - { - new Cell(c.Id), - new Cell(c.Name), - new Cell(c.Size.ToString("N0")), - new Cell(c.Progress.ToString("P0")), - }) + UIHelper.Header("Id"), + UIHelper.Header("Name"), + UIHelper.Header("Size"), + UIHelper.Header("Progress"), + list.Select(c => new[] + { + new Cell(c.Id), + new Cell(c.Name), + new Cell(c.Size.ToString("N0")), + new Cell(c.Progress.ToString("P0")) + }) }, Stroke = LineThickness.Single }) @@ -164,7 +164,7 @@ public class Move : MultiTorrentCommandBase protected override bool AllowAll => true; [Option("-f|--folder ", CommandOptionType.SingleValue)] - public string Folder { get; set; } + public string? Folder { get; set; } protected override Task OnExecuteAuthenticatedAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { diff --git a/src/QBittorrent.CommandLineInterface/Commands/TorrentSpecificCommandBase.cs b/src/QBittorrent.CommandLineInterface/Commands/TorrentSpecificCommandBase.cs index a55aa1b..1ec76c3 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TorrentSpecificCommandBase.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TorrentSpecificCommandBase.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using McMaster.Extensions.CommandLineUtils; using QBittorrent.Client; @@ -13,7 +14,7 @@ public abstract class TorrentSpecificCommandBase : AuthenticatedCommandBase [Argument(0, "", "Full or partial torrent hash")] [Required] [StringLength(40, MinimumLength = 1)] - public virtual string Hash { get; set; } + public virtual required string Hash { get; set; } protected virtual bool AllowAll => false; @@ -21,52 +22,52 @@ public abstract class TorrentSpecificCommandBase : AuthenticatedCommandBase protected sealed override async Task OnExecuteAuthenticatedAsync(QBittorrentClient client, CommandLineApplication app, IConsole console) { - if (Hash.Length < 40 && !(AllowAll && IsAll)) - { - var torrents = await client.GetTorrentListAsync(); - var matching = torrents - .Where(t => t.Hash.StartsWith(Hash, StringComparison.InvariantCultureIgnoreCase)) - .ToList(); + if (Hash.Length >= 40 || AllowAll && IsAll) + return await OnExecuteTorrentSpecificAsync(client, app, console); + var torrents = await client.GetTorrentListAsync(); + var matching = torrents + .Where(t => t.Hash.StartsWith(Hash, StringComparison.InvariantCultureIgnoreCase)) + .ToList(); - if (matching.Count == 0) - { + switch (matching.Count) + { + case 0: console.WriteLineColored($"No torrent matching hash {Hash} is found.", ColorScheme.Current.Warning); return ExitCodes.NotFound; - } - if (matching.Count == 1) - { + case 1: Hash = matching[0].Hash; - } - else + break; + default: { console.WriteLineColored($"The are several torrents matching partial hash {Hash}:", ColorScheme.Current.Normal); var numbers = (int)Math.Log10(matching.Count) + 1; var nameWidth = Console.BufferWidth - (numbers + 45); - for (int i = 0; i < matching.Count; i++) + for (var i = 0; i < matching.Count; i++) { var torrent = matching[i]; var name = torrent.Name.Length < nameWidth ? torrent.Name - : torrent.Name.Substring(0, nameWidth - 3) + "..."; + : torrent.Name[..(nameWidth - 3)] + "..."; console.WriteLineColored($"[{(i + 1).ToString().PadLeft(numbers)}] {torrent.Hash} {name}", ColorScheme.Current.Normal); } - int index = 0; + var index = 0; while (index <= 0 || index > matching.Count) { index = Prompt.GetInt("Please, select the required one:"); } Hash = matching[index - 1].Hash; + break; } } - + return await OnExecuteTorrentSpecificAsync(client, app, console); } - protected abstract Task OnExecuteTorrentSpecificAsync( + protected abstract Task OnExecuteTorrentSpecificAsync( QBittorrentClient client, - CommandLineApplication app, + CommandLineApplication app, IConsole console); } } diff --git a/src/QBittorrent.CommandLineInterface/Commands/TorrentSpecificFormattableCommandBase.cs b/src/QBittorrent.CommandLineInterface/Commands/TorrentSpecificFormattableCommandBase.cs index 3eecdd7..168cde7 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TorrentSpecificFormattableCommandBase.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TorrentSpecificFormattableCommandBase.cs @@ -18,15 +18,24 @@ protected TorrentSpecificFormattableCommandBase() _formatter = new ObjectFormatter(PrintList, FindProperty); } - protected virtual PropertyInfo FindProperty(string name) => _props.Value.FirstOrDefault(t => t.name == name).prop; + protected virtual IReadOnlyDictionary>? CustomFormatters => null; - protected virtual IReadOnlyDictionary> CustomFormatters => null; + [Option("-F|--format ", "Output format: list|csv|json|property", CommandOptionType.SingleValue)] + public string? Format { get; set; } - protected virtual void PrintList(T data) => UIHelper.PrintObject(data, CustomFormatters); + protected virtual PropertyInfo FindProperty(string name) + { + return _props.Value.FirstOrDefault(t => t.name == name).prop; + } - protected void Print(in T obj) => _formatter.PrintFormat(obj, Format); + protected virtual void PrintList(T data) + { + UIHelper.PrintObject(data, CustomFormatters); + } - [Option("-F|--format ", "Output format: list|csv|json|property", CommandOptionType.SingleValue)] - public string Format { get; set; } + protected void Print(in T obj) + { + _formatter.PrintFormat(obj, Format); + } } } diff --git a/src/QBittorrent.CommandLineInterface/Commands/TorrentSpecificListCommandBase.cs b/src/QBittorrent.CommandLineInterface/Commands/TorrentSpecificListCommandBase.cs index 26206d5..f730a46 100644 --- a/src/QBittorrent.CommandLineInterface/Commands/TorrentSpecificListCommandBase.cs +++ b/src/QBittorrent.CommandLineInterface/Commands/TorrentSpecificListCommandBase.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Text; using McMaster.Extensions.CommandLineUtils; using QBittorrent.CommandLineInterface.Formats; @@ -15,12 +14,17 @@ protected TorrentSpecificListCommandBase() { _formatter = new ListFormatter(PrintTable, PrintList); } - + [Option("-F|--format ", "Output format: table|list|csv|json", CommandOptionType.SingleValue)] - public virtual string Format { get; set; } + public virtual string? Format { get; set; } + + protected virtual Dictionary>? ListCustomFormatters => null; [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void Print(IEnumerable data, bool preferList = false) => _formatter.PrintFormat(data, Format, preferList); + protected void Print(IEnumerable data, bool preferList = false) + { + _formatter.PrintFormat(data, Format, preferList); + } protected virtual void PrintTable(IEnumerable list) { @@ -31,7 +35,5 @@ protected virtual void PrintList(IEnumerable list) { UIHelper.PrintList(list, ListCustomFormatters); } - - protected virtual IReadOnlyDictionary> ListCustomFormatters => null; } } diff --git a/src/QBittorrent.CommandLineInterface/ConsoleExtensions.cs b/src/QBittorrent.CommandLineInterface/ConsoleExtensions.cs index 27f7f50..39b18dd 100644 --- a/src/QBittorrent.CommandLineInterface/ConsoleExtensions.cs +++ b/src/QBittorrent.CommandLineInterface/ConsoleExtensions.cs @@ -69,7 +69,7 @@ public static IConsole WriteLineColored(this IConsole console, string text, } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IConsole WriteLineColored(this IConsole console, string text, ColorSet colorSet) + public static IConsole WriteLineColored(this IConsole console, string text, ColorSet? colorSet) { return console.WriteLineColored(text, colorSet?.GetEffectiveForeground(), colorSet?.GetEffectiveBackground()); } diff --git a/src/QBittorrent.CommandLineInterface/Converters/ColorConverter.cs b/src/QBittorrent.CommandLineInterface/Converters/ColorConverter.cs index 4fa47cb..1bc23ab 100644 --- a/src/QBittorrent.CommandLineInterface/Converters/ColorConverter.cs +++ b/src/QBittorrent.CommandLineInterface/Converters/ColorConverter.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; using Newtonsoft.Json; using QBittorrent.CommandLineInterface.ColorSchemes; @@ -8,7 +6,7 @@ namespace QBittorrent.CommandLineInterface.Converters { internal class ColorConverter : JsonConverter { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { if (value == null) { @@ -19,15 +17,15 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s writer.WriteValue(value.ToString()); } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { - if (reader.TokenType == JsonToken.Null) - return null; - - if (reader.TokenType == JsonToken.String) - return new Color((string) reader.Value); + return reader.TokenType switch + { + JsonToken.Null => null, + JsonToken.String => new Color((string?)reader.Value), + _ => throw new JsonSerializationException($"Unexpected token {reader.TokenType}.") + }; - throw new JsonSerializationException($"Unexpected token {reader.TokenType}."); } public override bool CanConvert(Type objectType) diff --git a/src/QBittorrent.CommandLineInterface/Converters/EncryptConverter.cs b/src/QBittorrent.CommandLineInterface/Converters/EncryptConverter.cs index cad204b..3f5e423 100644 --- a/src/QBittorrent.CommandLineInterface/Converters/EncryptConverter.cs +++ b/src/QBittorrent.CommandLineInterface/Converters/EncryptConverter.cs @@ -6,7 +6,7 @@ namespace QBittorrent.CommandLineInterface.Converters { public class EncryptConverter : JsonConverter { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { if (value == null) { @@ -18,19 +18,15 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s writer.WriteValue(EncryptionService.Instance.Encrypt(str)); } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { - if (reader.TokenType == JsonToken.Null) + return reader.TokenType switch { - return null; - } - - if (reader.TokenType == JsonToken.String) - { - return EncryptionService.Instance.Decrypt((string) reader.Value); - } + JsonToken.Null => (object?)null, + JsonToken.String => EncryptionService.Instance.Decrypt((string)reader.Value!), + _ => throw new JsonSerializationException($"Unexpected token {reader.TokenType}.") + }; - throw new JsonSerializationException($"Unexpected token {reader.TokenType}."); } public override bool CanConvert(Type objectType) diff --git a/src/QBittorrent.CommandLineInterface/Converters/UriConverter.cs b/src/QBittorrent.CommandLineInterface/Converters/UriConverter.cs deleted file mode 100644 index f183842..0000000 --- a/src/QBittorrent.CommandLineInterface/Converters/UriConverter.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using CsvHelper; -using CsvHelper.Configuration; -using CsvHelper.TypeConversion; - -namespace QBittorrent.CommandLineInterface.Converters -{ - internal class UriConverter : ITypeConverter - { - public static readonly UriConverter Instance = new UriConverter(); - - static UriConverter() - { - } - - private UriConverter() - { - } - - public string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) - { - return ((Uri) value).ToString(); - } - - public object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) - { - return new Uri(text, UriKind.RelativeOrAbsolute); - } - } -} diff --git a/src/QBittorrent.CommandLineInterface/EnumHelper.cs b/src/QBittorrent.CommandLineInterface/EnumHelper.cs index c64613b..f6aec28 100644 --- a/src/QBittorrent.CommandLineInterface/EnumHelper.cs +++ b/src/QBittorrent.CommandLineInterface/EnumHelper.cs @@ -1,38 +1,12 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; namespace QBittorrent.CommandLineInterface { internal static class EnumHelper { -#if !NETFRAMEWORK - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif - public static bool TryParse(Type enumType, string value, bool ignoreCase, out object result) + public static bool IsDefined(T value) { -#if !NETFRAMEWORK - return Enum.TryParse(enumType, value, ignoreCase, out result); -#else - var gerenericMethod = typeof(Enum) - .GetMethods(BindingFlags.Public | BindingFlags.Static) - .Where(m => m.Name == "TryParse" && m.IsGenericMethod) - .Single(m => m.IsGenericMethod && m.GetParameters().Length == 3); - var method = gerenericMethod.MakeGenericMethod(enumType); - object[] args = { value, ignoreCase, Enum.ToObject(enumType, 0) }; - var success = (bool) method.Invoke(null, args); - result = args[2]; - return success; -#endif - } - - public static bool IsDefined(T value) => Enum.IsDefined(typeof(T), value); - - public static IEnumerable GetValues() where T : struct, Enum - { - return Enum.GetValues(typeof(T)).Cast(); + return Enum.IsDefined(typeof(T), value!); } } } diff --git a/src/QBittorrent.CommandLineInterface/ExitCodes.cs b/src/QBittorrent.CommandLineInterface/ExitCodes.cs index f106728..c3e8636 100644 --- a/src/QBittorrent.CommandLineInterface/ExitCodes.cs +++ b/src/QBittorrent.CommandLineInterface/ExitCodes.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace QBittorrent.CommandLineInterface +namespace QBittorrent.CommandLineInterface { public static class ExitCodes { diff --git a/src/QBittorrent.CommandLineInterface/Formats/CsvFormatOptions.cs b/src/QBittorrent.CommandLineInterface/Formats/CsvFormatOptions.cs index 1b187d7..1557aba 100644 --- a/src/QBittorrent.CommandLineInterface/Formats/CsvFormatOptions.cs +++ b/src/QBittorrent.CommandLineInterface/Formats/CsvFormatOptions.cs @@ -11,18 +11,17 @@ public class CsvFormatOptions public bool Sanitize { get; set; } - public string Culture { get; set; } + public string? Culture { get; set; } - public static implicit operator Configuration(CsvFormatOptions options) + public static implicit operator CsvConfiguration(CsvFormatOptions options) { - return new Configuration + return new CsvConfiguration(string.IsNullOrWhiteSpace(options.Culture) + ? CultureInfo.InvariantCulture + : CultureInfo.GetCultureInfo(options.Culture)) { Delimiter = options.Delimiter, Quote = options.Quote, - SanitizeForInjection = options.Sanitize, - CultureInfo = string.IsNullOrWhiteSpace(options.Culture) - ? CultureInfo.InvariantCulture - : CultureInfo.GetCultureInfo(options.Culture) + InjectionOptions = options.Sanitize ? InjectionOptions.Escape : InjectionOptions.None }; } } diff --git a/src/QBittorrent.CommandLineInterface/Formats/FormatParser.cs b/src/QBittorrent.CommandLineInterface/Formats/FormatParser.cs index ddadf8a..2aea5bc 100644 --- a/src/QBittorrent.CommandLineInterface/Formats/FormatParser.cs +++ b/src/QBittorrent.CommandLineInterface/Formats/FormatParser.cs @@ -6,7 +6,7 @@ namespace QBittorrent.CommandLineInterface.Formats { public static class FormatParser { - public static (string format, IReadOnlyDictionary options) Parse(string formatString) + public static (string? format, IReadOnlyDictionary? options) Parse(string? formatString) { if (string.IsNullOrWhiteSpace(formatString)) return (null, null); @@ -14,11 +14,11 @@ public static (string format, IReadOnlyDictionary options) Parse var parts = Regex.Split(formatString, @"(? !string.IsNullOrWhiteSpace(o)).Select(ParseOption).ToDictionary(x => x.key, x => x.value)); - (string key, string value) ParseOption(string option) + (string key, string? value) ParseOption(string option) { - var optionParts = option.Split(new [] { '=' }, 2); + var optionParts = option.Split(['='], 2); return ( - optionParts[0], + optionParts[0], optionParts.ElementAtOrDefault(1) ?.Replace(@"\:", ":") .Replace(@"\t", "\t")); @@ -37,7 +37,7 @@ public static CsvFormatOptions GetCsvOptions(this IReadOnlyDictionary options, string key, bool defaultValue = false) { - return options.TryGetValue(key, out var stringValue) && bool.TryParse(stringValue, out bool result) ? result : defaultValue; + return options.TryGetValue(key, out var stringValue) && bool.TryParse(stringValue, out var result) ? result : defaultValue; } - public static char TryGetChar(this IReadOnlyDictionary options, string key, char defaultValue = default) + public static char TryGetChar(this IReadOnlyDictionary options, string key, char defaultValue = '\0') { - return options.TryGetValue(key, out var stringValue) && char.TryParse(stringValue, out char result) ? result : defaultValue; + return options.TryGetValue(key, out var stringValue) && char.TryParse(stringValue, out var result) ? result : defaultValue; } - public static string TryGetNotEmptyString(this IReadOnlyDictionary options, string key, string defaultValue) + public static string? TryGetNotEmptyString(this IReadOnlyDictionary options, string key, string? defaultValue) { return options.TryGetValue(key, out var stringValue) && !string.IsNullOrEmpty(stringValue) ? stringValue : defaultValue; } diff --git a/src/QBittorrent.CommandLineInterface/Formats/ListFormatter.cs b/src/QBittorrent.CommandLineInterface/Formats/ListFormatter.cs index a865934..7e196f3 100644 --- a/src/QBittorrent.CommandLineInterface/Formats/ListFormatter.cs +++ b/src/QBittorrent.CommandLineInterface/Formats/ListFormatter.cs @@ -1,24 +1,17 @@ using System; using System.Collections.Generic; -using System.Linq; using CsvHelper; +using CsvHelper.Configuration; using Newtonsoft.Json; -using QBittorrent.CommandLineInterface.Converters; namespace QBittorrent.CommandLineInterface.Formats { - public class ListFormatter + public class ListFormatter(Action> printTable, Action> printList) { - private readonly Action> _printTable; - private readonly Action> _printList; + private readonly Action>? _printList = printList; + private readonly Action>? _printTable = printTable; - public ListFormatter(Action> printTable, Action> printList) - { - _printTable = printTable; - _printList = printList; - } - - public void PrintFormat(IEnumerable data, string formatOptions, bool preferList = false) + public void PrintFormat(IEnumerable data, string? formatOptions, bool preferList = false) { var (format, options) = FormatParser.Parse(formatOptions); if (string.IsNullOrWhiteSpace(format)) @@ -35,10 +28,10 @@ public void PrintFormat(IEnumerable data, string formatOptions, bool preferLi _printList(data); break; case ListFormats.Json: - PrintJson(data, options.GetJsonOptions()); + PrintJson(data, options!.GetJsonOptions()); break; case ListFormats.Csv: - PrintCsv(data, options.GetCsvOptions()); + PrintCsv(data, options!.GetCsvOptions()); break; default: throw new Exception("Unsupported output format."); @@ -54,9 +47,8 @@ private void PrintJson(IEnumerable data, JsonFormatOptions options) private void PrintCsv(IEnumerable data, CsvFormatOptions options) { - using (var writer = new CsvWriter(Console.Out, options, true)) + using (var writer = new CsvWriter(Console.Out, (CsvConfiguration)options, true)) { - writer.Configuration.TypeConverterCache.AddConverter(UriConverter.Instance); writer.WriteRecords(data); } } diff --git a/src/QBittorrent.CommandLineInterface/Formats/ObjectFormats.cs b/src/QBittorrent.CommandLineInterface/Formats/ObjectFormats.cs index eab06e3..31bd39d 100644 --- a/src/QBittorrent.CommandLineInterface/Formats/ObjectFormats.cs +++ b/src/QBittorrent.CommandLineInterface/Formats/ObjectFormats.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace QBittorrent.CommandLineInterface.Formats +namespace QBittorrent.CommandLineInterface.Formats { public static class ObjectFormats { diff --git a/src/QBittorrent.CommandLineInterface/Formats/ObjectFormatter.cs b/src/QBittorrent.CommandLineInterface/Formats/ObjectFormatter.cs index cfb0f37..4868cbf 100644 --- a/src/QBittorrent.CommandLineInterface/Formats/ObjectFormatter.cs +++ b/src/QBittorrent.CommandLineInterface/Formats/ObjectFormatter.cs @@ -6,18 +6,18 @@ using System.Linq; using System.Reflection; using CsvHelper; +using CsvHelper.Configuration; using McMaster.Extensions.CommandLineUtils; using Newtonsoft.Json; -using QBittorrent.CommandLineInterface.Converters; namespace QBittorrent.CommandLineInterface.Formats { public class ObjectFormatter { + private readonly Func? _customPropertyBinder; private readonly Action _printList; - private readonly Func _customPropertyBinder; - public ObjectFormatter(Action printList = null, Func customPropertyBinder = null) + public ObjectFormatter(Action? printList = null, Func? customPropertyBinder = null) { _printList = printList ?? (obj => UIHelper.PrintObject(obj)); _customPropertyBinder = customPropertyBinder; @@ -33,7 +33,7 @@ join prop in typeof(T).GetProperties() on commandProp.Name equals prop.Name select (new CommandOption(option.Template, option.OptionType.GetValueOrDefault()).LongName, prop)); } - public void PrintFormat(in T data, string formatOptions) + public void PrintFormat(in T data, string? formatOptions) { var (format, options) = FormatParser.Parse(formatOptions); if (string.IsNullOrWhiteSpace(format)) @@ -47,13 +47,13 @@ public void PrintFormat(in T data, string formatOptions) _printList(data); break; case ObjectFormats.Json: - PrintJson(data, options.GetJsonOptions()); + PrintJson(data, options!.GetJsonOptions()); break; case ObjectFormats.Csv: - PrintCsv(data, options.GetCsvOptions()); + PrintCsv(data, options!.GetCsvOptions()); break; case ObjectFormats.Property: - PrintProperty(data, options.GetPropertyOptions()); + PrintProperty(data, options!.GetPropertyOptions()); break; } } @@ -67,10 +67,9 @@ private void PrintJson(in T data, JsonFormatOptions options) private void PrintCsv(in T data, CsvFormatOptions options) { - using (var writer = new CsvWriter(Console.Out, options, true)) + using (var writer = new CsvWriter(Console.Out, (CsvConfiguration)options, true)) { - writer.Configuration.TypeConverterCache.AddConverter(UriConverter.Instance); - writer.WriteRecords(new [] {data}); + writer.WriteRecords([data]); } } @@ -84,7 +83,7 @@ private void PrintProperty(in T data, PropertyFormatOptions options) ?? TryGetPropertyByJsonName() ?? TryGetPropertyByDisplayName() ?? _customPropertyBinder?.Invoke(options.Name) - ?? throw new Exception($"Cannot find property '{options.Name}'."); ; + ?? throw new Exception($"Cannot find property '{options.Name}'."); var value = property.GetValue(data); switch (value) @@ -107,10 +106,13 @@ private void PrintProperty(in T data, PropertyFormatOptions options) } - PropertyInfo TryGetPropertyByName() => type.GetProperty(options.Name) - ?? type.GetProperty(options.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); + PropertyInfo? TryGetPropertyByName() + { + return type.GetProperty(options.Name) + ?? type.GetProperty(options.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); + } - PropertyInfo TryGetPropertyByJsonName() + PropertyInfo? TryGetPropertyByJsonName() { var props = from p in type.GetProperties() @@ -121,7 +123,7 @@ where string.Equals(attr?.PropertyName, options.Name, StringComparison.OrdinalIg return props.FirstOrDefault(); } - PropertyInfo TryGetPropertyByDisplayName() + PropertyInfo? TryGetPropertyByDisplayName() { var props = from p in type.GetProperties() diff --git a/src/QBittorrent.CommandLineInterface/Formats/PropertyFormatOptions.cs b/src/QBittorrent.CommandLineInterface/Formats/PropertyFormatOptions.cs index bd8b40f..478e9d5 100644 --- a/src/QBittorrent.CommandLineInterface/Formats/PropertyFormatOptions.cs +++ b/src/QBittorrent.CommandLineInterface/Formats/PropertyFormatOptions.cs @@ -1,15 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace QBittorrent.CommandLineInterface.Formats +namespace QBittorrent.CommandLineInterface.Formats { public class PropertyFormatOptions { - public string Name { get; set; } + public string? Name { get; set; } - public string Format { get; set; } + public string? Format { get; set; } - public string Culture { get; set; } + public string? Culture { get; set; } } } diff --git a/src/QBittorrent.CommandLineInterface/Pager.cs b/src/QBittorrent.CommandLineInterface/Pager.cs index 89a030a..073ef4a 100644 --- a/src/QBittorrent.CommandLineInterface/Pager.cs +++ b/src/QBittorrent.CommandLineInterface/Pager.cs @@ -9,17 +9,17 @@ namespace QBittorrent.CommandLineInterface { /// - /// Process access to a console pager, which supports scrolling and search. + /// Process access to a console pager, which supports scrolling and search. /// public class Pager : IDisposable { - private string _prompt = "Use arrow keys to scroll\\. Press 'q' to exit\\."; - private readonly Lazy _less; private readonly TextWriter _fallbackWriter; + private readonly Lazy _less; private bool _disposed; + private string _prompt = @"Use arrow keys to scroll\. Press 'q' to exit\."; public Pager() - : this(PhysicalConsole.Singleton) + : this(PhysicalConsole.Singleton) { } @@ -28,18 +28,21 @@ public Pager(IConsole console) if (console == null) throw new ArgumentNullException(nameof(console)); Enabled = !console.IsOutputRedirected && PagerExists(); - _less = new Lazy(CreateWriter); + _less = new Lazy(CreateWriter); _fallbackWriter = console.Out; - bool PagerExists() => !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - || File.Exists(GetPagerPath()); + bool PagerExists() + { + return !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + || File.Exists(GetPagerPath()); + } } public bool Enabled { get; private set; } /// - /// The prompt to display at the bottom of the pager. - /// for details. + /// The prompt to display at the bottom of the pager. + /// for details. /// public string Prompt { @@ -53,12 +56,13 @@ public string Prompt } /// - /// - /// Gets an object which can be used to write text into the pager. - /// - /// - /// This fallback to if the pager is not available. - /// + /// + /// Gets an object which can be used to write text into the pager. + /// + /// + /// This fallback to if the pager is not + /// available. + /// /// public TextWriter Writer { @@ -70,6 +74,22 @@ public TextWriter Writer } } + /// This will wait until the user exits the pager. + public void Dispose() + { + if (_disposed) + return; + _disposed = true; + if (!_less.IsValueCreated) + return; + var process = _less.Value; + if (process == null) + return; + process.StandardInput.Dispose(); + process.WaitForExit(); + process.Dispose(); + } + /// This will wait until the user exits the pager. public void WaitForExit() { @@ -81,21 +101,22 @@ public void Kill() { if (!_less.IsValueCreated) return; - _less.Value.Kill(); + _less.Value?.Kill(); } - private Process CreateWriter() + private Process? CreateWriter() { if (!Enabled) return null; - List stringList = new List + var stringList = new List { "-K", "--prompt=" + Prompt }; - Process process = new Process + var process = new Process { - StartInfo = { + StartInfo = + { FileName = GetPagerPath(), Arguments = ArgumentEscaper.EscapeAndConcatenate(stringList), RedirectStandardInput = true @@ -115,27 +136,16 @@ private Process CreateWriter() } } - /// This will wait until the user exits the pager. - public void Dispose() + private string GetPagerPath() { - if (_disposed) - return; - _disposed = true; - if (!_less.IsValueCreated) - return; - Process process = _less.Value; - if (process == null) - return; - process.StandardInput.Dispose(); - process.WaitForExit(); - process.Dispose(); + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Path.Combine(GetStartupPath(), "utils", "less", "less.exe") + : "less"; } - private string GetPagerPath() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? Path.Combine(GetStartupPath(), "utils", "less", "less.exe") - : "less"; - - private string GetStartupPath() => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - + private string GetStartupPath() + { + return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new InvalidOperationException(); + } } } diff --git a/src/QBittorrent.CommandLineInterface/Program.cs b/src/QBittorrent.CommandLineInterface/Program.cs index 43b9e85..93f5336 100644 --- a/src/QBittorrent.CommandLineInterface/Program.cs +++ b/src/QBittorrent.CommandLineInterface/Program.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Reflection; using McMaster.Extensions.CommandLineUtils; using QBittorrent.Client; @@ -24,7 +23,11 @@ namespace QBittorrent.CommandLineInterface [VersionOptionFromMember(MemberName = nameof(GetVersion))] public class Program { - static int Main(string[] args) + + [Option("--print-stacktrace", "Prints exception stacktrace", CommandOptionType.NoValue, ShowInHelpText = false, Inherited = true)] + public bool PrintStackTrace { get; set; } + + private static int Main(string[] args) { var app = new CommandLineApplication(); try @@ -33,7 +36,7 @@ static int Main(string[] args) .UseDefaultConventions() .UsePagerForHelpText(false) .MakeSuggestionsInErrorMessage(); - int code = app.Execute(args); + var code = app.Execute(args); return code; } catch (ApiNotSupportedException e) @@ -56,16 +59,6 @@ static int Main(string[] args) PrintError(e); return ExitCodes.Failure; } - finally - { -#if DEBUG - if (Debugger.IsAttached) - { - Console.WriteLine("Press any key to exit..."); - Console.ReadKey(); - } -#endif - } void PrintError(Exception ex) { @@ -78,17 +71,15 @@ void PrintError(Exception ex) else { var exception = ex; - string prevMessage = null; + string? prevMessage = null; do { - if (exception.Message != prevMessage) - { - Console.Error.WriteLine(exception.Message); - prevMessage = exception.Message; - } + if (exception.Message == prevMessage) continue; + Console.Error.WriteLine(exception.Message); + prevMessage = exception.Message; } while ((exception = exception.InnerException) != null); } - + Console.ResetColor(); } @@ -102,9 +93,9 @@ void PrintApiNotSupported(ApiNotSupportedException ex) : $"A newer version of qBittorrent is required for this command.{Environment.NewLine}API {apiVersion} must be supported."); Console.ResetColor(); - string GetQBittorrentVersion() + string? GetQBittorrentVersion() { - if (apiVersion == new ApiVersion(2, 0 , 0)) + if (apiVersion == new ApiVersion(2)) return "4.1"; if (apiVersion == new ApiVersion(2, 0, 1)) return "4.1.1"; @@ -161,11 +152,8 @@ private int OnExecute(CommandLineApplication app, IConsole console) private string GetVersion() { - var attr = Assembly.GetEntryAssembly().GetCustomAttribute(); + var attr = Assembly.GetEntryAssembly()!.GetCustomAttribute()!; return attr.InformationalVersion; } - - [Option("--print-stacktrace", "Prints exception stacktrace", CommandOptionType.NoValue, ShowInHelpText = false, Inherited = true)] - public bool PrintStackTrace { get; set; } } } diff --git a/src/QBittorrent.CommandLineInterface/QBittorrent.CommandLineInterface.csproj b/src/QBittorrent.CommandLineInterface/QBittorrent.CommandLineInterface.csproj index cc3d832..252a5bc 100644 --- a/src/QBittorrent.CommandLineInterface/QBittorrent.CommandLineInterface.csproj +++ b/src/QBittorrent.CommandLineInterface/QBittorrent.CommandLineInterface.csproj @@ -1,80 +1,61 @@  - - Exe - net6;netcoreapp3.1;netcoreapp2.1;net46 - true - qbt - + + Exe + qbt + net9.0 + enable + default + + + + Pavel Fedarovich + Pavel Fedarovich + © Pavel Fedarovich, 2018-$([System.DateTime]::Now.Year) + https://github.com/fedarovich/qbittorrent-cli + https://github.com/fedarovich/qbittorrent-cli + torrent qbittorrent + https://github.com/fedarovich/qbittorrent-cli/blob/master/LICENSE + + + + https://www.myget.org/F/fedarovich/api/v3/index.json + - - latest - Pavel Fedarovich - Pavel Fedarovich - © Pavel Fedarovich, 2018-$([System.DateTime]::Now.Year) - https://github.com/fedarovich/qbittorrent-cli - https://github.com/fedarovich/qbittorrent-cli - torrent qbittorrent - https://github.com/fedarovich/qbittorrent-cli/blob/master/LICENSE - 10 - - - - https://www.myget.org/F/fedarovich/api/v3/index.json - - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - + + + + + - - - - - - - + - - + - + + + + + + + + + + + - + + + + + + + diff --git a/src/QBittorrent.CommandLineInterface/QBittorrent.CommandLineInterface.csproj.DotSettings b/src/QBittorrent.CommandLineInterface/QBittorrent.CommandLineInterface.csproj.DotSettings deleted file mode 100644 index 4887f94..0000000 --- a/src/QBittorrent.CommandLineInterface/QBittorrent.CommandLineInterface.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - CSharp100 \ No newline at end of file diff --git a/src/QBittorrent.CommandLineInterface/Schemas/colors-schema.json b/src/QBittorrent.CommandLineInterface/Schemas/colors-schema.json index bd979db..1c2371a 100644 --- a/src/QBittorrent.CommandLineInterface/Schemas/colors-schema.json +++ b/src/QBittorrent.CommandLineInterface/Schemas/colors-schema.json @@ -1,66 +1,156 @@ { "$schema": "http://json-schema.org/draft-04/schema", "title": "JSON Schema for the qBittorrent CLI color scheme.", - "type": "object", "definitions": { "color": { - "type": "string", - "enum": [ "black", "blue", "cyan", "dark-blue", "dark-cyan", "dark-gray", "dark-green", "dark-magenta", "dark-red", "dark-yellow", "gray", "green", "magenta", "red", "white", "yellow", "system-bg", "system-fg" ] + "type": "string", + "enum": [ + "black", + "blue", + "cyan", + "dark-blue", + "dark-cyan", + "dark-gray", + "dark-green", + "dark-magenta", + "dark-red", + "dark-yellow", + "gray", + "green", + "magenta", + "red", + "white", + "yellow", + "system-bg", + "system-fg" + ] }, "colors": { "type": "object", "properties": { - "bg": { "$ref": "#/definitions/color" }, - "fg": { "$ref": "#/definitions/color" }, - "fg-alt": { "$ref": "#/definitions/color" } + "bg": { + "$ref": "#/definitions/color" + }, + "fg": { + "$ref": "#/definitions/color" + }, + "fg-alt": { + "$ref": "#/definitions/color" + } } - } + } }, - "properties": { - "normal": { "$ref": "#/definitions/colors" }, - "strong": { "$ref": "#/definitions/colors" }, - "warning": { "$ref": "#/definitions/colors" }, - "active": { "$ref": "#/definitions/colors" }, - "inactive": { "$ref": "#/definitions/colors" }, + "normal": { + "$ref": "#/definitions/colors" + }, + "strong": { + "$ref": "#/definitions/colors" + }, + "warning": { + "$ref": "#/definitions/colors" + }, + "active": { + "$ref": "#/definitions/colors" + }, + "inactive": { + "$ref": "#/definitions/colors" + }, "log": { "type": "object", "properties": { - "status-normal": { "$ref": "#/definitions/colors" }, - "status-info": { "$ref": "#/definitions/colors" }, - "status-warning": { "$ref": "#/definitions/colors" }, - "status-critical": { "$ref": "#/definitions/colors" }, - "timestamp": { "$ref": "#/definitions/colors" }, - "message": { "$ref": "#/definitions/colors" } + "status-normal": { + "$ref": "#/definitions/colors" + }, + "status-info": { + "$ref": "#/definitions/colors" + }, + "status-warning": { + "$ref": "#/definitions/colors" + }, + "status-critical": { + "$ref": "#/definitions/colors" + }, + "timestamp": { + "$ref": "#/definitions/colors" + }, + "message": { + "$ref": "#/definitions/colors" + } } }, "torrent-status": { "type": "object", "properties": { - "error": { "$ref": "#/definitions/colors" }, - "download": { "$ref": "#/definitions/colors" }, - "upload": { "$ref": "#/definitions/colors" }, - "paused-download": { "$ref": "#/definitions/colors" }, - "paused-upload": { "$ref": "#/definitions/colors" }, - "queued-download": { "$ref": "#/definitions/colors" }, - "queued-upload": { "$ref": "#/definitions/colors" }, - "queued-checking": { "$ref": "#/definitions/colors" }, - "checking-download": { "$ref": "#/definitions/colors" }, - "checking-upload": { "$ref": "#/definitions/colors" }, - "checking-resume-data": { "$ref": "#/definitions/colors" }, - "stalled-download": { "$ref": "#/definitions/colors" }, - "stalled-upload": { "$ref": "#/definitions/colors" }, - "forced-download": { "$ref": "#/definitions/colors" }, - "forced-upload": { "$ref": "#/definitions/colors" }, - "fetching-metadata": { "$ref": "#/definitions/colors" }, - "missing-files": { "$ref": "#/definitions/colors" }, - "allocating": { "$ref": "#/definitions/colors" }, - "moving": { "$ref": "#/definitions/colors" }, - "unknown": { "$ref": "#/definitions/colors" } + "error": { + "$ref": "#/definitions/colors" + }, + "download": { + "$ref": "#/definitions/colors" + }, + "upload": { + "$ref": "#/definitions/colors" + }, + "paused-download": { + "$ref": "#/definitions/colors" + }, + "paused-upload": { + "$ref": "#/definitions/colors" + }, + "queued-download": { + "$ref": "#/definitions/colors" + }, + "queued-upload": { + "$ref": "#/definitions/colors" + }, + "queued-checking": { + "$ref": "#/definitions/colors" + }, + "checking-download": { + "$ref": "#/definitions/colors" + }, + "checking-upload": { + "$ref": "#/definitions/colors" + }, + "checking-resume-data": { + "$ref": "#/definitions/colors" + }, + "stalled-download": { + "$ref": "#/definitions/colors" + }, + "stalled-upload": { + "$ref": "#/definitions/colors" + }, + "forced-download": { + "$ref": "#/definitions/colors" + }, + "forced-upload": { + "$ref": "#/definitions/colors" + }, + "fetching-metadata": { + "$ref": "#/definitions/colors" + }, + "missing-files": { + "$ref": "#/definitions/colors" + }, + "allocating": { + "$ref": "#/definitions/colors" + }, + "moving": { + "$ref": "#/definitions/colors" + }, + "unknown": { + "$ref": "#/definitions/colors" + } } } }, - - "required": ["normal", "strong", "warning", "active", "inactive"] + "required": [ + "normal", + "strong", + "warning", + "active", + "inactive" + ] } diff --git a/src/QBittorrent.CommandLineInterface/Services/EncryptionService.cs b/src/QBittorrent.CommandLineInterface/Services/EncryptionService.cs index 061568e..9f654ef 100644 --- a/src/QBittorrent.CommandLineInterface/Services/EncryptionService.cs +++ b/src/QBittorrent.CommandLineInterface/Services/EncryptionService.cs @@ -4,26 +4,20 @@ namespace QBittorrent.CommandLineInterface.Services { public abstract class EncryptionService { - public static EncryptionService Instance { get; } static EncryptionService() { -#if NET6_0_OR_GREATER - Instance = OperatingSystem.IsWindows() - ? (EncryptionService) new WindowsEncryptionService() - : new UnixEncryptionService(); -#else - Instance = Environment.OSVersion.Platform == PlatformID.Win32NT - ? (EncryptionService) new WindowsEncryptionService() + Instance = OperatingSystem.IsWindows() + ? new WindowsEncryptionService() : new UnixEncryptionService(); -#endif - } private protected EncryptionService() { } + public static EncryptionService Instance { get; } + public abstract string Encrypt(string input); public abstract string Decrypt(string input); diff --git a/src/QBittorrent.CommandLineInterface/Services/GeneralSettings.cs b/src/QBittorrent.CommandLineInterface/Services/GeneralSettings.cs index 78f3fc6..ba0e8dc 100644 --- a/src/QBittorrent.CommandLineInterface/Services/GeneralSettings.cs +++ b/src/QBittorrent.CommandLineInterface/Services/GeneralSettings.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel; using System.Runtime.Serialization; -using System.Text; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using QBittorrent.CommandLineInterface.Converters; @@ -16,13 +14,13 @@ public class GeneralSettings [DefaultValue(DefaultUrl)] public string Url { get; set; } = DefaultUrl; - public string Username { get; set; } + public string? Username { get; set; } [JsonConverter(typeof(EncryptConverter))] - public string Password { get; set; } + public string? Password { get; set; } [JsonExtensionData] - public IDictionary Other { get; set; } + public IDictionary? Other { get; set; } [OnDeserialized] private void OnDeserialized(StreamingContext context) diff --git a/src/QBittorrent.CommandLineInterface/Services/NetworkSettings.cs b/src/QBittorrent.CommandLineInterface/Services/NetworkSettings.cs index d7fa550..4f37a8c 100644 --- a/src/QBittorrent.CommandLineInterface/Services/NetworkSettings.cs +++ b/src/QBittorrent.CommandLineInterface/Services/NetworkSettings.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Net; using System.Runtime.Serialization; -using System.Text; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using QBittorrent.CommandLineInterface.Converters; @@ -11,13 +10,23 @@ namespace QBittorrent.CommandLineInterface.Services { public class NetworkSettings { + + public enum AuthType + { + Basic, + Digest, + Ntlm, + Negotiate, + Kerberos + } + public bool UseDefaultCredentials { get; set; } public bool IgnoreCertificateErrors { get; set; } public IList Credentials { get; set; } = new List(); - public ProxySettings Proxy { get; set; } + public ProxySettings? Proxy { get; set; } [OnDeserialized] private void OnDeserialized(StreamingContext context) @@ -49,18 +58,12 @@ public class SiteCredentials [JsonConverter(typeof(EncryptConverter))] public string Password { get; set; } - public string Domain { get; set; } + public string? Domain { get; set; } - public NetworkCredential ToCredential() => new NetworkCredential(Username, Password, Domain ?? string.Empty); - } - - public enum AuthType - { - Basic, - Digest, - Ntlm, - Negotiate, - Kerberos + public NetworkCredential ToCredential() + { + return new NetworkCredential(Username, Password, Domain ?? string.Empty); + } } } } diff --git a/src/QBittorrent.CommandLineInterface/Services/ProxySettings.cs b/src/QBittorrent.CommandLineInterface/Services/ProxySettings.cs index 0315511..65fcf53 100644 --- a/src/QBittorrent.CommandLineInterface/Services/ProxySettings.cs +++ b/src/QBittorrent.CommandLineInterface/Services/ProxySettings.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Text; using Newtonsoft.Json; using QBittorrent.CommandLineInterface.Converters; @@ -13,11 +11,11 @@ public class ProxySettings public bool BypassLocal { get; set; } - public IList Bypass { get; set; } + public IList? Bypass { get; set; } - public string Username { get; set; } + public string? Username { get; set; } [JsonConverter(typeof(EncryptConverter))] - public string Password { get; set; } + public string? Password { get; set; } } } diff --git a/src/QBittorrent.CommandLineInterface/Services/SettingsService.cs b/src/QBittorrent.CommandLineInterface/Services/SettingsService.cs index 5dd13e0..dbf9558 100644 --- a/src/QBittorrent.CommandLineInterface/Services/SettingsService.cs +++ b/src/QBittorrent.CommandLineInterface/Services/SettingsService.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Text; using Newtonsoft.Json; @@ -8,7 +7,6 @@ namespace QBittorrent.CommandLineInterface.Services { public class SettingsService { - public static SettingsService Instance { get; } = new SettingsService(); static SettingsService() { @@ -18,6 +16,8 @@ private SettingsService() { } + public static SettingsService Instance { get; } = new SettingsService(); + public GeneralSettings GetGeneral() { var file = new FileInfo(GetGeneralSettingsPath()); @@ -30,7 +30,7 @@ public GeneralSettings GetGeneral() using (var textReader = file.OpenText()) using (var jsonReader = new JsonTextReader(textReader)) { - return serializer.Deserialize(jsonReader); + return serializer.Deserialize(jsonReader) ?? throw new InvalidOperationException(); } } @@ -48,22 +48,22 @@ public NetworkSettings GetNetwork() using (var textReader = file.OpenText()) using (var jsonReader = new JsonTextReader(textReader)) { - return serializer.Deserialize(jsonReader); + return serializer.Deserialize(jsonReader) ?? throw new InvalidOperationException(); } - ProxySettings GetLegacyProxySettings(GeneralSettings settings) + ProxySettings? GetLegacyProxySettings(GeneralSettings settings) { return settings.Other != null && settings.Other.TryGetValue("Proxy", out var jtoken) - ? jtoken.ToObject() + ? jtoken.ToObject() : null; } - NetworkSettings GetLegacyNetworkSettings(GeneralSettings settings, ProxySettings proxy) + NetworkSettings GetLegacyNetworkSettings(GeneralSettings settings, ProxySettings? proxy) { if (settings.Other == null || !settings.Other.TryGetValue("NetworkSettings", out var jtoken)) return new NetworkSettings(); - var networkSettings = jtoken.ToObject(); + var networkSettings = jtoken.ToObject() ?? throw new InvalidOperationException(); networkSettings.Proxy = proxy; return networkSettings; } @@ -78,8 +78,9 @@ public void Save(GeneralSettings generalSettings) var serializer = new JsonSerializer(); using (var stream = file.Open(FileMode.Create, FileAccess.Write)) using (var textWriter = new StreamWriter(stream, Encoding.UTF8)) - using (var jsonWriter = new JsonTextWriter(textWriter) {Formatting = Formatting.Indented} ) + using (var jsonWriter = new JsonTextWriter(textWriter)) { + jsonWriter.Formatting = Formatting.Indented; serializer.Serialize(jsonWriter, generalSettings); } } @@ -93,8 +94,9 @@ public void Save(NetworkSettings settings) var serializer = new JsonSerializer(); using (var stream = file.Open(FileMode.Create, FileAccess.Write)) using (var textWriter = new StreamWriter(stream, Encoding.UTF8)) - using (var jsonWriter = new JsonTextWriter(textWriter) { Formatting = Formatting.Indented }) + using (var jsonWriter = new JsonTextWriter(textWriter)) { + jsonWriter.Formatting = Formatting.Indented; serializer.Serialize(jsonWriter, settings); } } diff --git a/src/QBittorrent.CommandLineInterface/Services/UnixEncryptionService.cs b/src/QBittorrent.CommandLineInterface/Services/UnixEncryptionService.cs index 1e82e71..a07f804 100644 --- a/src/QBittorrent.CommandLineInterface/Services/UnixEncryptionService.cs +++ b/src/QBittorrent.CommandLineInterface/Services/UnixEncryptionService.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Security.Cryptography; using System.Text; using Mono.Unix; @@ -12,11 +10,11 @@ namespace QBittorrent.CommandLineInterface.Services internal class UnixEncryptionService : EncryptionService { private static readonly byte[] Key = - { + [ 237, 158, 211, 168, 18, 187, 41, 93, 36, 150, 14, 142, 137, 9, 29, 108, 194, 174, 191, 28, 5, 9, 127, 78, 84, 84, 6, 255, 195, 246, 124, 89, 89, 249, 104, 253, 177, 52, 111, 43, 223, 152, 114, 122, 79, 211, 28, 67, 76, 148, 161, 180, 39, 202, 153, 67, 1, 155, 183, 106, 247, 64, 220, 140 - }; + ]; public override string Encrypt(string input) { @@ -26,11 +24,11 @@ public override string Encrypt(string input) aes.Key = key; aes.Padding = PaddingMode.PKCS7; var memoryStream = new MemoryStream(); - memoryStream.Write(aes.IV, 0, aes.IV.Length); + memoryStream.Write(aes.IV); using (var cryptoStream = new CryptoStream(memoryStream, aes.CreateEncryptor(), CryptoStreamMode.Write)) { var inputBytes = Encoding.UTF8.GetBytes(input); - cryptoStream.Write(inputBytes, 0, inputBytes.Length); + cryptoStream.Write(inputBytes); } var outputBytes = memoryStream.ToArray(); @@ -45,17 +43,19 @@ public override string Decrypt(string input) { var inputStream = new MemoryStream(Convert.FromBase64String(input)); var iv = new byte[aes.IV.Length]; - inputStream.Read(iv, 0, iv.Length); + inputStream.ReadExactly(iv); aes.Key = key; aes.IV = iv; aes.Padding = PaddingMode.PKCS7; using (var outputStream = new MemoryStream()) - using (var cryptoStream = new CryptoStream(inputStream, aes.CreateDecryptor(), CryptoStreamMode.Read)) { - cryptoStream.CopyTo(outputStream); - return Encoding.UTF8.GetString(outputStream.ToArray()); + using (var cryptoStream = new CryptoStream(inputStream, aes.CreateDecryptor(), CryptoStreamMode.Read)) + { + cryptoStream.CopyTo(outputStream); + return Encoding.UTF8.GetString(outputStream.ToArray()); + } } } } @@ -115,7 +115,7 @@ private byte[] EnsureKeyFile() using (var stream = file.Open(FileMode.Open, FileAccess.Read, FilePermissions.S_IRUSR)) { var data = new byte[stream.Length]; - stream.Read(data, 0, data.Length); + stream.ReadExactly(data); return data; } } diff --git a/src/QBittorrent.CommandLineInterface/Services/WindowsEncryptionService.cs b/src/QBittorrent.CommandLineInterface/Services/WindowsEncryptionService.cs index cdbb0ed..4415ef9 100644 --- a/src/QBittorrent.CommandLineInterface/Services/WindowsEncryptionService.cs +++ b/src/QBittorrent.CommandLineInterface/Services/WindowsEncryptionService.cs @@ -1,14 +1,11 @@ using System; -using System.Collections.Generic; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; namespace QBittorrent.CommandLineInterface.Services { -#if NET6_0_OR_GREATER [SupportedOSPlatform("windows")] -#endif internal class WindowsEncryptionService : EncryptionService { private const int EntropyLength = 16; diff --git a/src/QBittorrent.CommandLineInterface/UIHelper.cs b/src/QBittorrent.CommandLineInterface/UIHelper.cs index 3a556e9..6b549cb 100644 --- a/src/QBittorrent.CommandLineInterface/UIHelper.cs +++ b/src/QBittorrent.CommandLineInterface/UIHelper.cs @@ -15,19 +15,24 @@ public static class UIHelper public static object[] FieldsColumns => // ReSharper disable once CoVariantArrayConversion - new[] - { + [ new Column {Width = GridLength.Auto}, new Column {Width = GridLength.Star(1)} - }; + ]; - public static Cell Label(string text) => new Cell(text + ":") { Color = ColorScheme.Current.Strong.Foreground, Stroke = NoneStroke }; + public static Cell Label(string text) + { + return new Cell(text + ":") {Color = ColorScheme.Current.Strong.Foreground, Stroke = NoneStroke}; + } - public static Cell Data(T data) => new Cell(data?.ToString()) { Stroke = NoneStroke, Padding = new Thickness(1, 0, 0, 0) }; + public static Cell Data(T data) + { + return new Cell(data?.ToString()) {Stroke = NoneStroke, Padding = new Thickness(1, 0, 0, 0)}; + } public static Cell Header(string text, TextAlign? textAlign = null, int? minWidth = null) { - var cell = new Cell(text) { Stroke = GridHeaderStroke }; + var cell = new Cell(text) {Stroke = GridHeaderStroke}; cell.TextAlign = textAlign ?? cell.TextAlign; cell.MinWidth = minWidth ?? cell.MinWidth; return cell; @@ -35,25 +40,22 @@ public static Cell Header(string text, TextAlign? textAlign = null, int? minWidt public static object[] Row(string label, T data) { - Cell dataCell; - switch (data) + var dataCell = data switch { - case Cell cell: - dataCell = cell; - break; - case Element element: - dataCell = new Cell(element) { Stroke = NoneStroke, Padding = new Thickness(1, 0, 0, 0) }; - break; - default: - dataCell = Data(data); - break; - } + Cell cell => cell, + Element element => new Cell(element) + { + Stroke = NoneStroke, + Padding = new Thickness(1, 0, 0, 0) + }, + _ => Data(data) + }; - return new object[] { Label(label), dataCell }; + return [Label(label), dataCell]; } public static void PrintList(IEnumerable list, - IReadOnlyDictionary> customFormatters = null) + IReadOnlyDictionary>? customFormatters = null) { var margin = new Thickness(0, 0, 0, 1); var elements = list.Select(item => ToDocument(item, customFormatters).With(x => x.Margin = margin)); @@ -62,14 +64,14 @@ public static void PrintList(IEnumerable list, } public static void PrintObject(T obj, - IReadOnlyDictionary> customFormatters = null) + IReadOnlyDictionary>? customFormatters = null) { var document = ToDocument(obj, customFormatters); ConsoleRenderer.RenderDocument(document); } public static Document ToDocument(T obj, - IReadOnlyDictionary> customFormatters = null) + IReadOnlyDictionary>? customFormatters = null) { const string defaultFormat = "{0}"; @@ -93,8 +95,8 @@ from prop in typeof(T).GetRuntimeProperties() Stroke = NoneStroke, Columns = { - new Column { Width = GridLength.Auto }, - new Column { Width = GridLength.Star(1) } + new Column {Width = GridLength.Auto}, + new Column {Width = GridLength.Star(1)} }, Children = { @@ -107,12 +109,11 @@ from prop in typeof(T).GetRuntimeProperties() IEnumerable<(string label, object value)> GetPairs() { - foreach (var property in properties) + foreach (var (label, o, format, nullString, propName) in properties) { - var label = property.name; - if (customFormatters != null && customFormatters.TryGetValue(property.propName, out var formatter)) + if (customFormatters != null && customFormatters.TryGetValue(propName, out var formatter)) { - var customValue = formatter(property.value); + var customValue = formatter(o); if (customValue != null) { yield return (label, customValue); @@ -120,9 +121,9 @@ from prop in typeof(T).GetRuntimeProperties() } } - var value = (property.value == null && property.nullString != null) - ? property.nullString - : string.Format(property.format, property.value); + var value = o == null && nullString != null + ? nullString + : string.Format(format, o); yield return (label, value); } } diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/PeerPartialInfoViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/PeerPartialInfoViewModel.cs index 629d7d4..365c314 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/PeerPartialInfoViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/PeerPartialInfoViewModel.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Net; -using System.Text; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using QBittorrent.Client; @@ -18,7 +18,7 @@ public PeerPartialInfoViewModel(PeerPartialInfo wrappedObject) } public string Endpoint => - _wrappedObject.Address != null && _wrappedObject.Port != null + _wrappedObject is {Address: not null, Port: not null} ? new IPEndPoint(_wrappedObject.Address, _wrappedObject.Port.Value).ToString() : "n/a"; @@ -61,7 +61,7 @@ public PeerPartialInfoViewModel(PeerPartialInfo wrappedObject) [Display(Name = "Country Code")] public string CountryCode => _wrappedObject.CountryCode; - [Newtonsoft.Json.JsonExtensionData] + [JsonExtensionData] public IDictionary AdditionalData => _wrappedObject.AdditionalData; } } diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/RssArticleViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/RssArticleViewModel.cs index 4aa7781..711ef03 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/RssArticleViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/RssArticleViewModel.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Text; using QBittorrent.Client; namespace QBittorrent.CommandLineInterface.ViewModels diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/RssFeedViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/RssFeedViewModel.cs index 779297b..97c66fc 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/RssFeedViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/RssFeedViewModel.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Text; using QBittorrent.Client; namespace QBittorrent.CommandLineInterface.ViewModels @@ -46,7 +45,7 @@ public RssFeedViewModel(string path, RssFeed wrappedObject) [Display(Name = "Articles")] [DisplayFormat(NullDisplayText = "n/a")] - public IEnumerable Articles => + public IEnumerable? Articles => _wrappedObject.Articles?.Select(a => new RssArticleViewModel(a)); } } diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/RssRulePauseState.cs b/src/QBittorrent.CommandLineInterface/ViewModels/RssRulePauseState.cs index 444fb4d..292df5d 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/RssRulePauseState.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/RssRulePauseState.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace QBittorrent.CommandLineInterface.ViewModels +namespace QBittorrent.CommandLineInterface.ViewModels { public enum RssRulePauseState { diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/AuthenticationViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/AuthenticationViewModel.cs index af47965..9cf47a6 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/AuthenticationViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/AuthenticationViewModel.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Text; using QBittorrent.Client; namespace QBittorrent.CommandLineInterface.ViewModels.ServerPreferences diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/AutoTorrentManagementViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/AutoTorrentManagementViewModel.cs index 938c56c..ce4f115 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/AutoTorrentManagementViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/AutoTorrentManagementViewModel.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; -using System.Text; using QBittorrent.Client; namespace QBittorrent.CommandLineInterface.ViewModels.ServerPreferences diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/ConnectionViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/ConnectionViewModel.cs index ee6e656..c699824 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/ConnectionViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/ConnectionViewModel.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Text; +using System.ComponentModel.DataAnnotations; using QBittorrent.Client; namespace QBittorrent.CommandLineInterface.ViewModels.ServerPreferences diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/DnsViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/DnsViewModel.cs index 47392df..76dacf1 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/DnsViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/DnsViewModel.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Text; +using System.ComponentModel.DataAnnotations; using QBittorrent.Client; namespace QBittorrent.CommandLineInterface.ViewModels.ServerPreferences diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/DownloadsViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/DownloadsViewModel.cs index 0e509d7..d880bf5 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/DownloadsViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/DownloadsViewModel.cs @@ -1,14 +1,13 @@ using System.ComponentModel.DataAnnotations; -using System.Diagnostics.CodeAnalysis; using QBittorrent.Client; namespace QBittorrent.CommandLineInterface.ViewModels.ServerPreferences { public readonly struct DownloadsViewModel { - private readonly Client.Preferences _wrappedObject; + private readonly Preferences _wrappedObject; - public DownloadsViewModel(Client.Preferences wrappedObject) + public DownloadsViewModel(Preferences wrappedObject) { _wrappedObject = wrappedObject; } @@ -18,7 +17,7 @@ public DownloadsViewModel(Client.Preferences wrappedObject) [Display(Name = "Incompleted file path enabled")] public bool? TempPathEnabled => _wrappedObject.TempPathEnabled; - + [Display(Name = "Incompleted file path")] public string TempPath => _wrappedObject.TempPath; diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/EmailViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/EmailViewModel.cs index b8200d7..156c497 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/EmailViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/EmailViewModel.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Text; +using System.ComponentModel.DataAnnotations; using QBittorrent.Client; namespace QBittorrent.CommandLineInterface.ViewModels.ServerPreferences diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/PrivacyViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/PrivacyViewModel.cs index b520618..c053319 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/PrivacyViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/PrivacyViewModel.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Text; +using System.ComponentModel.DataAnnotations; using QBittorrent.Client; namespace QBittorrent.CommandLineInterface.ViewModels.ServerPreferences diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/ProxyViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/ProxyViewModel.cs index a3b881d..8b854a2 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/ProxyViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/ProxyViewModel.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Text; +using System.ComponentModel.DataAnnotations; using QBittorrent.Client; namespace QBittorrent.CommandLineInterface.ViewModels.ServerPreferences diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/QueueViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/QueueViewModel.cs index 2ea7b02..7599f5a 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/QueueViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/QueueViewModel.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Text; +using System.ComponentModel.DataAnnotations; using QBittorrent.Client; namespace QBittorrent.CommandLineInterface.ViewModels.ServerPreferences diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/SpeedViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/SpeedViewModel.cs index 6577419..8422797 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/SpeedViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/SpeedViewModel.cs @@ -41,7 +41,7 @@ public SpeedViewModel(Preferences wrappedObject) [Display(Name = " On day")] public SchedulerDay? SchedulerDays => _wrappedObject.SchedulerDays; - + [Display(Name = "Apply rate limit to uTP protocol")] [SuppressMessage("ReSharper", "InconsistentNaming")] public bool? LimitUTPRate => _wrappedObject.LimitUTPRate; @@ -52,7 +52,7 @@ public SpeedViewModel(Preferences wrappedObject) [Display(Name = "Apply rate limit to peers on LAN")] [SuppressMessage("ReSharper", "InconsistentNaming")] public bool? LimitLAN => _wrappedObject.LimitLAN; - + private DateTime? ToDateTime(in int? hours, in int? minutes) { if (hours == null || minutes == null) diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/WebInterfaceViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/WebInterfaceViewModel.cs index b8efda7..a5c1ce2 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/WebInterfaceViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/ServerPreferences/WebInterfaceViewModel.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; -using System.Text; using QBittorrent.Client; namespace QBittorrent.CommandLineInterface.ViewModels.ServerPreferences @@ -44,11 +42,11 @@ public WebInterfaceViewModel(Preferences wrappedObject) [Display(Name = "SSL Certificate Path")] [DisplayFormat(NullDisplayText = "n/a")] - public string WebUISslCertificatePath => _wrappedObject.WebUISslCertificatePath?.Replace('/', Path.DirectorySeparatorChar); + public string? WebUISslCertificatePath => _wrappedObject.WebUISslCertificatePath?.Replace('/', Path.DirectorySeparatorChar); [Display(Name = "SSL Key Path")] [DisplayFormat(NullDisplayText = "n/a")] - public string WebUISslKeyPath => _wrappedObject.WebUISslKeyPath?.Replace('/', Path.DirectorySeparatorChar); + public string? WebUISslKeyPath => _wrappedObject.WebUISslKeyPath?.Replace('/', Path.DirectorySeparatorChar); [Display(Name = "Alt. Web UI")] [DisplayFormat(NullDisplayText = "n/a")] diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/TorrentContentViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/TorrentContentViewModel.cs index 5a16a91..fd496a8 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/TorrentContentViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/TorrentContentViewModel.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using QBittorrent.Client; +using Range = QBittorrent.Client.Range; namespace QBittorrent.CommandLineInterface.ViewModels { @@ -35,6 +36,6 @@ public TorrentContentViewModel(TorrentContent wrappedObject, int id) public bool IsSeeding => _wrappedObject.IsSeeding; [Display(Name = "Piece Range")] - public Client.Range PieceRange => _wrappedObject.PieceRange; + public Range PieceRange => _wrappedObject.PieceRange; } } diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/TorrentFilePriorityViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/TorrentFilePriorityViewModel.cs index 6ae6724..0bf3008 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/TorrentFilePriorityViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/TorrentFilePriorityViewModel.cs @@ -1,6 +1,6 @@ -using QBittorrent.Client; -using System; +using System; using System.ComponentModel.DataAnnotations; +using QBittorrent.Client; namespace QBittorrent.CommandLineInterface.ViewModels { diff --git a/src/QBittorrent.CommandLineInterface/ViewModels/TorrentPropertiesViewModel.cs b/src/QBittorrent.CommandLineInterface/ViewModels/TorrentPropertiesViewModel.cs index 66c1731..6b26669 100644 --- a/src/QBittorrent.CommandLineInterface/ViewModels/TorrentPropertiesViewModel.cs +++ b/src/QBittorrent.CommandLineInterface/ViewModels/TorrentPropertiesViewModel.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Text; using QBittorrent.Client; namespace QBittorrent.CommandLineInterface.ViewModels diff --git a/src/QBittorrent.CommandLineInterface/qbt-mono.sh b/src/QBittorrent.CommandLineInterface/qbt-mono.sh deleted file mode 100644 index b41f930..0000000 --- a/src/QBittorrent.CommandLineInterface/qbt-mono.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -pushd . > /dev/null -SCRIPT_PATH="${BASH_SOURCE[0]}"; -while([ -h "${SCRIPT_PATH}" ]); do - cd "`dirname "${SCRIPT_PATH}"`" - SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")"; -done -cd "`dirname "${SCRIPT_PATH}"`" > /dev/null -SCRIPT_PATH="`pwd`"; -popd > /dev/null -mono "$SCRIPT_PATH/qbt.exe" "$@" diff --git a/src/Tools/DocumentationGenerator/DocumentationGenerator.csproj b/src/Tools/DocumentationGenerator/DocumentationGenerator.csproj index 188e35b..d851d2c 100644 --- a/src/Tools/DocumentationGenerator/DocumentationGenerator.csproj +++ b/src/Tools/DocumentationGenerator/DocumentationGenerator.csproj @@ -1,22 +1,24 @@ - - Exe - netcoreapp3.1 - + + Exe + net9.0 + enable + default + - - - + + + - - - + + + - - - Always - - + + + Always + + diff --git a/src/Tools/DocumentationGenerator/MarkdownHelpTextGenerator.cs b/src/Tools/DocumentationGenerator/MarkdownHelpTextGenerator.cs index 1f2cbce..d2fcbd4 100644 --- a/src/Tools/DocumentationGenerator/MarkdownHelpTextGenerator.cs +++ b/src/Tools/DocumentationGenerator/MarkdownHelpTextGenerator.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using McMaster.Extensions.CommandLineUtils; using McMaster.Extensions.CommandLineUtils.HelpText; @@ -11,7 +10,7 @@ namespace DocumentationGenerator public class MarkdownHelpTextGenerator : IHelpTextGenerator { /// - /// Determines if commands are ordered by name in generated help text + /// Determines if commands are ordered by name in generated help text /// public bool SortCommandsByName { get; set; } = true; @@ -43,7 +42,7 @@ void RenderCommand(CommandLineApplication cmd, string prefix = "") } /// - /// Generate the first few lines of help output text + /// Generate the first few lines of help output text /// /// The app /// Help text output @@ -55,7 +54,7 @@ protected virtual void GenerateHeader( output.Write("## "); var parts = EnumerateCommandParts(application).ToList(); - for (int i = 0; i < parts.Count - 1; i++) + for (var i = 0; i < parts.Count - 1; i++) { var part = parts[i]; var link = string.Join('-', parts.Take(i + 1)); @@ -73,7 +72,7 @@ protected virtual void GenerateHeader( } /// - /// Generate detailed help information + /// Generate detailed help information /// /// The application /// Help text output @@ -86,7 +85,7 @@ protected virtual void GenerateBody( var commands = application.Commands.Where(c => c.ShowInHelpText).ToList(); var firstColumnWidth = 2 + Math.Max( - arguments.Count > 0 ? arguments.Max(a => a.Name.Length) : 0, + arguments.Count > 0 ? arguments.Max(a => a.Name!.Length) : 0, Math.Max( options.Count > 0 ? options.Max(o => Format(o).Length) : 0, commands.Count > 0 ? commands.Max(c => c.Name?.Length ?? 0) : 0)); @@ -98,7 +97,7 @@ protected virtual void GenerateBody( } /// - /// Generate the line that shows usage + /// Generate the line that shows usage /// /// The app /// Help text output @@ -146,7 +145,7 @@ protected virtual void GenerateUsage( } /// - /// Generate the lines that show information about arguments + /// Generate the lines that show information about arguments /// /// The app /// Help text output @@ -176,7 +175,7 @@ protected virtual void GenerateArguments( } /// - /// Generate the lines that show information about options + /// Generate the lines that show information about options /// /// The app /// Help text output @@ -198,7 +197,7 @@ protected virtual void GenerateOptions( foreach (var opt in visibleOptions) { - var message = $"| {Format(opt)} | {opt.Description.Replace("|", " \\| ").Replace("\n", "")} |"; + var message = $"| {Format(opt)} | {opt.Description!.Replace("|", " \\| ").Replace("\n", "")} |"; output.WriteLine(message); } output.WriteLine(); @@ -206,7 +205,7 @@ protected virtual void GenerateOptions( } /// - /// Generate the lines that show information about subcommands + /// Generate the lines that show information about subcommands /// /// The app /// Help text output @@ -250,7 +249,7 @@ protected virtual void GenerateCommands( } /// - /// Generate the last lines of help text output + /// Generate the last lines of help text output /// /// The app /// Help text output @@ -262,7 +261,7 @@ protected virtual void GenerateFooter( return; output.WriteLine("### Details"); - var paragraphs = application.ExtendedHelpText.Split(new[] {"\r\n", "\n", "\r"}, StringSplitOptions.None); + var paragraphs = application.ExtendedHelpText.Split(["\r\n", "\n", "\r"], StringSplitOptions.None); foreach (var paragraph in paragraphs) { output.Write(paragraph); @@ -271,13 +270,13 @@ protected virtual void GenerateFooter( } /// - /// Generates the template string in the format "-{Symbol}|-{Short}|--{Long} <{Value}>" for display in help text. + /// Generates the template string in the format "-{Symbol}|-{Short}|--{Long} <{Value}>" for display in help text. /// /// The template string protected virtual string Format(CommandOption option) { var value = GetValueName(); - var parts = new [] + var parts = new[] { GetOptionName("--", option.LongName), GetOptionName("-", option.ShortName), @@ -286,7 +285,7 @@ protected virtual string Format(CommandOption option) return string.Join("
", parts.Where(p => !string.IsNullOrEmpty(p))); - string GetOptionName(string prefix, string name) + string GetOptionName(string prefix, string? name) { return string.IsNullOrEmpty(name) ? string.Empty : $"`{prefix}{name}{value}`"; } @@ -300,14 +299,11 @@ string GetValueName() { return $"[`*`:<{option.ValueName}>`*`]"; } - else - { - return $" `*`<{option.ValueName}>`*` "; - } + return $" `*`<{option.ValueName}>`*` "; } } - private IEnumerable EnumerateCommandParts(CommandLineApplication command, string alias = null) + private IEnumerable EnumerateCommandParts(CommandLineApplication command, string? alias = null) { if (command.Parent != null) { @@ -317,7 +313,7 @@ private IEnumerable EnumerateCommandParts(CommandLineApplication command } } - yield return alias ?? command.Name; + yield return alias ?? command.Name!; } } } diff --git a/src/Tools/DocumentationGenerator/Program.cs b/src/Tools/DocumentationGenerator/Program.cs index 4269af2..c7071c5 100644 --- a/src/Tools/DocumentationGenerator/Program.cs +++ b/src/Tools/DocumentationGenerator/Program.cs @@ -1,44 +1,15 @@ using System; -using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; -using System.Runtime.Loader; using McMaster.Extensions.CommandLineUtils; -using McMaster.Extensions.CommandLineUtils.HelpText; namespace DocumentationGenerator { - class Program + internal class Program { - static int Main(string[] args) - { - var app = new CommandLineApplication(); - try - { - app.Conventions.UseDefaultConventions(); - int code = app.Execute(args); - return code; - } - catch (Exception e) - { - Console.WriteLine(e.Message); - return 1; - } - finally - { -#if DEBUG - if (Debugger.IsAttached) - { - Console.WriteLine("Press any key to exit..."); - Console.ReadKey(); - } -#endif - } - } [Option("-i|--input ", "Input assembly path", CommandOptionType.SingleValue)] [Required] @@ -55,16 +26,32 @@ static int Main(string[] args) public string RootCommand { get; set; } [Option("-n|--name ", "", CommandOptionType.SingleValue)] - public string RootCommandName { get; set; } + public string? RootCommandName { get; set; } + + private static int Main(string[] args) + { + var app = new CommandLineApplication(); + try + { + app.Conventions.UseDefaultConventions(); + var code = app.Execute(args); + return code; + } + catch (Exception e) + { + Console.WriteLine(e.Message); + return 1; + } + } private int OnExecute(CommandLineApplication app, IConsole console) { var assembly = Assembly.LoadFrom(InputAssembly); var rootCommandTypeName = string.IsNullOrEmpty(RootCommand) ? "Program" : RootCommand; var rootCommandType = assembly.GetExportedTypes().FirstOrDefault(t => t.FullName == rootCommandTypeName) - ?? assembly.GetExportedTypes().Single(t => t.Name == rootCommandTypeName); + ?? assembly.GetExportedTypes().Single(t => t.Name == rootCommandTypeName); var appType = typeof(CommandLineApplication<>).MakeGenericType(rootCommandType); - var root = (CommandLineApplication) Activator.CreateInstance(appType, new object[] {true}); + var root = (CommandLineApplication)Activator.CreateInstance(appType, [true])!; root.Conventions.UseDefaultConventions(); root.Name = RootCommandName ?? assembly.GetName().Name; @@ -75,7 +62,7 @@ private int OnExecute(CommandLineApplication app, IConsole console) } var helpTextGenerator = new MarkdownHelpTextGenerator(); - Generate(root, root.Name); + Generate(root, root.Name!); using (var writer = new StreamWriter(Path.Combine(OutputDir, "command-reference.md"), false)) { diff --git a/src/global.json b/src/global.json new file mode 100644 index 0000000..f4fd385 --- /dev/null +++ b/src/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "9.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file