From 2611836e5bddc6f0e87f5d5e49b0858a4e9e2ece Mon Sep 17 00:00:00 2001 From: Jdbye <jdbye3@gmail.com> Date: Sun, 9 Jun 2024 18:00:59 +0200 Subject: [PATCH 1/5] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 13ddaa0..ee58842 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Transmission-RPC-API [Official Transmission RPC specs](https://github.com/transmission/transmission/blob/master/extras/rpc-spec.txt) C# implementation of the Transmission RPC API. +Up to date with Transmission RPC specification as of Transmission 4.1.0 (rpc-version-semver 5.4.0, rpc-version: 18) | Command | Not Implemented | Implemented| | -------------------- |:-:|:-:| @@ -32,6 +33,9 @@ C# implementation of the Transmission RPC API. | queue-move-down | | x | | queue-move-bottom | | x | | free-space | | x | +| group-set | | x | +| group-get | | x | + How to use ------------- From 7feb9e7e479c73db99d49f73789098b29ea484c6 Mon Sep 17 00:00:00 2001 From: Jdbye <jdbye3@gmail.com> Date: Sun, 9 Jun 2024 18:12:56 +0200 Subject: [PATCH 2/5] Updated to latest RPC specifications as of Transmission 4.1.0 * All new methods implemented * All new fields readable * "format" optional parameter of torrent-get remains unimplemented, for now. This is not needed but provides some efficiency benefits if the "table" format type is implemented. --- .../Arguments/BandwidthGroupSettings.cs | 40 + Transmission.API.RPC/Arguments/NewTorrent.cs | 21 +- .../Arguments/PortTestProtocol.cs | 25 + .../Arguments/SessionFields.cs | 139 ++++ .../Arguments/SessionSettings.cs | 74 +- .../Arguments/TorrentFields.cs | 20 +- .../Arguments/TorrentSettings.cs | 53 +- Transmission.API.RPC/Client.Async.cs | 743 ++++++++++-------- Transmission.API.RPC/Client.cs | 90 ++- Transmission.API.RPC/Entity/BandwidthGroup.cs | 46 ++ Transmission.API.RPC/Entity/FreeSpace.cs | 33 + .../Entity/IntOrArrayConverter.cs | 80 ++ Transmission.API.RPC/Entity/NewTorrentInfo.cs | 5 + Transmission.API.RPC/Entity/SessionInfo.cs | 104 ++- Transmission.API.RPC/Entity/Statistic.cs | 20 +- Transmission.API.RPC/Entity/TorrentInfo.cs | 268 ++++--- Transmission.API.RPC/Entity/Units.cs | 6 +- Transmission.API.RPC/ITransmissionClient.cs | 47 +- .../ITransmissionClientAsync.cs | 47 +- .../Transmission.API.RPC.csproj | 2 +- 20 files changed, 1310 insertions(+), 553 deletions(-) create mode 100644 Transmission.API.RPC/Arguments/BandwidthGroupSettings.cs create mode 100644 Transmission.API.RPC/Arguments/PortTestProtocol.cs create mode 100644 Transmission.API.RPC/Arguments/SessionFields.cs create mode 100644 Transmission.API.RPC/Entity/BandwidthGroup.cs create mode 100644 Transmission.API.RPC/Entity/FreeSpace.cs create mode 100644 Transmission.API.RPC/Entity/IntOrArrayConverter.cs diff --git a/Transmission.API.RPC/Arguments/BandwidthGroupSettings.cs b/Transmission.API.RPC/Arguments/BandwidthGroupSettings.cs new file mode 100644 index 0000000..4d22b54 --- /dev/null +++ b/Transmission.API.RPC/Arguments/BandwidthGroupSettings.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Transmission.API.RPC.Common; + +namespace Transmission.API.RPC.Arguments +{ + public class BandwidthGroupSettings : ArgumentsBase + { + /// <summary> + /// Session limits are honored + /// </summary> + public bool? HonorsSessionLimits { get { return GetValue<bool?>("honorsSessionLimits"); } set { this["honorsSessionLimits"] = value; } } + + /// <summary> + /// Name of the bandwidth group + /// </summary> + public string Name { get { return GetValue<string>("name"); } set { this["name"] = value; } } + + /// <summary> + /// Max global download speed of this bandwidth group (KBps) + /// </summary> + public long? SpeedLimitDown { get { return GetValue<long?>("speed-limit-down"); } set { this["speed-limit-down"] = value; } } + + /// <summary> + /// True means enabled + /// </summary> + public bool? SpeedLimitDownEnabled { get { return GetValue<bool?>("speed-limit-down-enabled"); } set { this["speed-limit-down-enabled"] = value; } } + + /// <summary> + /// Max global upload speed of this bandwidth group (KBps) + /// </summary> + public long? SpeedLimitUp { get { return GetValue<long?>("speed-limit-up"); } set { this["speed-limit-up"] = value; } } + + /// <summary> + /// True means enabled + /// </summary> + public bool? SpeedLimitUpEnabled { get { return GetValue<bool?>("speed-limit-up-enabled"); } set { this["speed-limit-up-enabled"] = value; } } + } +} diff --git a/Transmission.API.RPC/Arguments/NewTorrent.cs b/Transmission.API.RPC/Arguments/NewTorrent.cs index f367434..f3cd710 100644 --- a/Transmission.API.RPC/Arguments/NewTorrent.cs +++ b/Transmission.API.RPC/Arguments/NewTorrent.cs @@ -28,6 +28,11 @@ public class NewTorrent : ArgumentsBase /// </summary> public string Filename { get { return GetValue<string>("filename"); } set { this["filename"] = value; } } + /// <summary> + /// Array of labels applied to the torrent + /// </summary> + public string[] Labels { get { return GetValue<string[]>("labels"); } set { this["labels"] = value; } } + /// <summary> /// base64-encoded .torrent content /// </summary> @@ -36,41 +41,41 @@ public class NewTorrent : ArgumentsBase /// <summary> /// if true, don't start the torrent /// </summary> - public bool Paused { get { return GetValue<bool>("paused"); } set { this["paused"] = value; } } + public bool? Paused { get { return GetValue<bool>("paused"); } set { this["paused"] = value; } } /// <summary> /// maximum number of peers /// </summary> - public int? PeerLimit { get { return GetValue<int?>("peer-limit"); } set { this["peer-limit"] = value; } } + public long? PeerLimit { get { return GetValue<long?>("peer-limit"); } set { this["peer-limit"] = value; } } /// <summary> /// Torrent's bandwidth priority /// </summary> - public int? BandwidthPriority { get { return GetValue<int?>("bandwidthPriority"); } set { this["bandwidthPriority"] = value; } } + public long? BandwidthPriority { get { return GetValue<long?>("bandwidthPriority"); } set { this["bandwidthPriority"] = value; } } /// <summary> /// Indices of file(s) to download /// </summary> - public int[] FilesWanted { get { return GetValue<int[]>("files-wanted"); } set { this["files-wanted"] = value; } } + public long[] FilesWanted { get { return GetValue<long[]>("files-wanted"); } set { this["files-wanted"] = value; } } /// <summary> /// Indices of file(s) to download /// </summary> - public int[] FilesUnwanted { get { return GetValue<int[]>("files-unwanted"); } set { this["files-unwanted"] = value; } } + public long[] FilesUnwanted { get { return GetValue<long[]>("files-unwanted"); } set { this["files-unwanted"] = value; } } /// <summary> /// Indices of high-priority file(s) /// </summary> - public int[] PriorityHigh { get { return GetValue<int[]>("priority-high"); } set { this["priority-high"] = value; } } + public long[] PriorityHigh { get { return GetValue<long[]>("priority-high"); } set { this["priority-high"] = value; } } /// <summary> /// Indices of low-priority file(s) /// </summary> - public int[] PriorityLow { get { return GetValue<int[]>("priority-low"); } set { this["priority-low"] = value; } } + public long[] PriorityLow { get { return GetValue<long[]>("priority-low"); } set { this["priority-low"] = value; } } /// <summary> /// Indices of normal-priority file(s) /// </summary> - public int[] PriorityNormal { get { return GetValue<int[]>("priority-normal"); } set { this["priority-normal"] = value; } } + public long[] PriorityNormal { get { return GetValue<long[]>("priority-normal"); } set { this["priority-normal"] = value; } } } } diff --git a/Transmission.API.RPC/Arguments/PortTestProtocol.cs b/Transmission.API.RPC/Arguments/PortTestProtocol.cs new file mode 100644 index 0000000..d923140 --- /dev/null +++ b/Transmission.API.RPC/Arguments/PortTestProtocol.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Transmission.API.RPC.Arguments +{ + /// <summary> + /// Which protocol was used for the port test + /// </summary> + public enum PortTestProtocol + { + /// <summary> + /// IPv4 + /// </summary> + IPv4 = 0, + /// <summary> + /// IPv6 + /// </summary> + IPV6, + /// <summary> + /// Could not be determined + /// </summary> + Unknown, + } +} diff --git a/Transmission.API.RPC/Arguments/SessionFields.cs b/Transmission.API.RPC/Arguments/SessionFields.cs new file mode 100644 index 0000000..b0855e8 --- /dev/null +++ b/Transmission.API.RPC/Arguments/SessionFields.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Transmission.API.RPC.Arguments +{ + /// <summary> + /// Session fields + /// </summary> + public sealed class SessionFields + { + private SessionFields() { } + + public const string ALT_SPEED_DOWN = "alt-speed-down"; + public const string ALT_SPEED_ENABLED = "alt-speed-enabled"; + public const string ALT_SPEED_TIME_BEGIN = "alt-speed-time-begin"; + public const string ALT_SPEED_TIME_ENABLED = "alt-speed-time-enabled"; + public const string ALT_SPEED_TIME_END = "alt-speed-time-end"; + public const string ALT_SPEED_TIME_DAY = "alt-speed-time-day"; + public const string ALT_SPEED_UP = "alt-speed-up"; + public const string BLOCKLIST_URL = "blocklist-url"; + public const string BLOCKLIST_ENABLED = "blocklist-enabled"; + public const string BLOCKLIST_SIZE = "blocklist-size"; + public const string CACHE_SIZE_MB = "cache-size-mb"; + public const string DEFAULT_TRACKERS = "default-trackers"; + public const string DHT_ENABLED = "dht-enabled"; + public const string DOWNLOAD_DIR = "download-dir"; + public const string DOWNLOAD_DIR_FREE_SPACE = "download-dir-free-space"; + public const string DOWNLOAD_QUEUE_SIZE = "download-queue-size"; + public const string DOWNLOAD_QUEUE_ENABLED = "download-queue-enabled"; + public const string ENCRYPTION = "encryption"; + public const string IDLE_SEEDING_LIMIT = "idle-seeding-limit"; + public const string IDLE_SEEDING_LIMIT_ENABLED = "idle-seeding-limit-enabled"; + public const string INCOMPLETE_DIR = "incomplete-dir"; + public const string INCOMPLETE_DIR_ENABLED = "incomplete-dir-enabled"; + public const string LPD_ENABLED = "lpd-enabled"; + public const string PEER_LIMIT_GLOBAL = "peer-limit-global"; + public const string PEER_LIMIT_PER_TORRENT = "peer-limit-per-torrent"; + public const string PEX_ENABLED = "pex-enabled"; + public const string PEER_PORT = "peer-port"; + public const string PEER_PORT_RANDOM_ON_START = "peer-port-random-on-start"; + public const string PORT_FORWARDING_ENABLED = "port-forwarding-enabled"; + public const string QUEUE_STALLED_ENABLED = "queue-stalled-enabled"; + public const string QUEUE_STALLED_MINUTES = "queue-stalled-minutes"; + public const string RENAME_PARTIAL_FILES = "rename-partial-files"; + public const string SESSION_ID = "session-id"; + public const string SCRIPT_TORRENT_ADDED_FILENAME = "script-torrent-added-filename"; + public const string SCRIPT_TORRENT_ADDED_ENABLED = "script-torrent-added-enabled"; + public const string SCRIPT_TORRENT_DONE_FILENAME = "script-torrent-done-filename"; + public const string SCRIPT_TORRENT_DONE_ENABLED = "script-torrent-done-enabled"; + public const string SCRIPT_TORRENT_DONE_SEEDING_FILENAME = "script-torrent-done-seeding-filename"; + public const string SCRIPT_TORRENT_DONE_SEEDING_ENABLED = "script-torrent-done-seeding-enabled"; + public const string SEED_RATIO_LIMIT = "seedRatioLimit"; + public const string SEED_RATIO_LIMITED = "seedRatioLimited"; + public const string SEED_QUEUE_SIZE = "seed-queue-size"; + public const string SEED_QUEUE_ENABLED = "seed-queue-enabled"; + public const string SPEED_LIMIT_DOWN = "speed-limit-down"; + public const string SPEED_LIMIT_DOWN_ENABLED = "speed-limit-down-enabled"; + public const string SPEED_LIMIT_UP = "speed-limit-up"; + public const string SPEED_LIMIT_UP_ENABLED = "speed-limit-up-enabled"; + public const string START_ADDED_TORRENTS = "start-added-torrents"; + public const string TRASH_ORIGINAL_TORRENT_FILES = "trash-original-torrent-files"; + public const string UNITS = "units"; + public const string UTP_ENABLED = "utp-enabled"; + public const string CONFIG_DIR = "config-dir"; + public const string RPC_VERSION = "rpc-version"; + public const string RPC_VERSION_MINIMUM = "rpc-version-minimum"; + public const string RPC_VERSION_SEMVER = "rpc-version-semver"; + public const string VERSION = "version"; + + public static string[] ALL_FIELDS + { + get + { + return new string[] + { + #region ALL FIELDS + ALT_SPEED_DOWN, + ALT_SPEED_ENABLED, + ALT_SPEED_TIME_BEGIN, + ALT_SPEED_TIME_ENABLED, + ALT_SPEED_TIME_END, + ALT_SPEED_TIME_DAY, + ALT_SPEED_UP, + BLOCKLIST_URL, + BLOCKLIST_ENABLED, + BLOCKLIST_SIZE, + CACHE_SIZE_MB, + DEFAULT_TRACKERS, + DHT_ENABLED, + DOWNLOAD_DIR, + DOWNLOAD_DIR_FREE_SPACE, + DOWNLOAD_QUEUE_SIZE, + DOWNLOAD_QUEUE_ENABLED, + ENCRYPTION, + IDLE_SEEDING_LIMIT, + IDLE_SEEDING_LIMIT_ENABLED, + INCOMPLETE_DIR, + INCOMPLETE_DIR_ENABLED, + LPD_ENABLED, + PEER_LIMIT_GLOBAL, + PEER_LIMIT_PER_TORRENT, + PEX_ENABLED, + PEER_PORT, + PEER_PORT_RANDOM_ON_START, + PORT_FORWARDING_ENABLED, + QUEUE_STALLED_ENABLED, + QUEUE_STALLED_MINUTES, + RENAME_PARTIAL_FILES, + SESSION_ID, + SCRIPT_TORRENT_ADDED_FILENAME, + SCRIPT_TORRENT_ADDED_ENABLED, + SCRIPT_TORRENT_DONE_FILENAME, + SCRIPT_TORRENT_DONE_ENABLED, + SCRIPT_TORRENT_DONE_SEEDING_FILENAME, + SCRIPT_TORRENT_DONE_SEEDING_ENABLED, + SEED_RATIO_LIMIT, + SEED_RATIO_LIMITED, + SEED_QUEUE_SIZE, + SEED_QUEUE_ENABLED, + SPEED_LIMIT_DOWN, + SPEED_LIMIT_DOWN_ENABLED, + SPEED_LIMIT_UP, + SPEED_LIMIT_UP_ENABLED, + START_ADDED_TORRENTS, + TRASH_ORIGINAL_TORRENT_FILES, + UNITS, + UTP_ENABLED, + CONFIG_DIR, + RPC_VERSION, + RPC_VERSION_MINIMUM, + RPC_VERSION_SEMVER, + VERSION, + #endregion + }; + } + } + } +} diff --git a/Transmission.API.RPC/Arguments/SessionSettings.cs b/Transmission.API.RPC/Arguments/SessionSettings.cs index 33bec06..d442905 100644 --- a/Transmission.API.RPC/Arguments/SessionSettings.cs +++ b/Transmission.API.RPC/Arguments/SessionSettings.cs @@ -17,7 +17,7 @@ public class SessionSettings : ArgumentsBase /// <summary> /// Max global download speed (KBps) /// </summary> - public int? AlternativeSpeedDown { get { return GetValue<int?>("alt-speed-down"); } set { this["alt-speed-down"] = value; } } + public long? AlternativeSpeedDown { get { return GetValue<long?>("alt-speed-down"); } set { this["alt-speed-down"] = value; } } /// <summary> /// True means use the alt speeds @@ -27,7 +27,7 @@ public class SessionSettings : ArgumentsBase /// <summary> /// When to turn on alt speeds (units: minutes after midnight) /// </summary> - public int? AlternativeSpeedTimeBegin { get { return GetValue<int?>("alt-speed-time-begin"); } set { this["alt-speed-time-begin"] = value; } } + public long? AlternativeSpeedTimeBegin { get { return GetValue<long?>("alt-speed-time-begin"); } set { this["alt-speed-time-begin"] = value; } } /// <summary> /// True means the scheduled on/off times are used @@ -37,22 +37,22 @@ public class SessionSettings : ArgumentsBase /// <summary> /// When to turn off alt speeds /// </summary> - public int? AlternativeSpeedTimeEnd { get { return GetValue<int?>("alt-speed-time-end"); } set { this["alt-speed-time-end"] = value; } } + public long? AlternativeSpeedTimeEnd { get { return GetValue<long?>("alt-speed-time-end"); } set { this["alt-speed-time-end"] = value; } } /// <summary> /// What day(s) to turn on alt speeds /// </summary> - public int? AlternativeSpeedTimeDay { get { return GetValue<int?>("alt-speed-time-day"); } set { this["alt-speed-time-day"] = value; } } + public long? AlternativeSpeedTimeDay { get { return GetValue<long?>("alt-speed-time-day"); } set { this["alt-speed-time-day"] = value; } } /// <summary> /// Max global upload speed (KBps) /// </summary> - public int? AlternativeSpeedUp { get { return GetValue<int?>("alt-speed-up"); } set { this["alt-speed-up"] = value; } } + public long? AlternativeSpeedUp { get { return GetValue<long?>("alt-speed-up"); } set { this["alt-speed-up"] = value; } } /// <summary> /// Location of the blocklist to use for "blocklist-update" /// </summary> - public string BlocklistURL { get { return GetValue<string>("blocklist-url"); } set { this["blocklist-url"] = value; } } + public string BlocklistUrl { get { return GetValue<string>("blocklist-url"); } set { this["blocklist-url"] = value; } } /// <summary> /// True means enabled @@ -62,17 +62,22 @@ public class SessionSettings : ArgumentsBase /// <summary> /// Maximum size of the disk cache (MB) /// </summary> - public int? CacheSizeMB { get { return GetValue<int?>("cache-size-mb"); } set { this["cache-size-mb"] = value; } } + public long? CacheSizeMb { get { return GetValue<long?>("cache-size-mb"); } set { this["cache-size-mb"] = value; } } /// <summary> - /// Default path to download torrents + /// Announce URLs, one per line, and a blank line between tiers + /// </summary> + public string DefaultTrackers { get { return GetValue<string>("default-trackers"); } set { this["default-trackers"] = value; } } + + /// <summary> + /// Default path to download torrents to /// </summary> public string DownloadDirectory { get { return GetValue<string>("download-dir"); } set { this["download-dir"] = value; } } /// <summary> /// Max number of torrents to download at once (see download-queue-enabled) /// </summary> - public int? DownloadQueueSize { get { return GetValue<int?>("download-queue-size"); } set { this["download-queue-size"] = value; } } + public long? DownloadQueueSize { get { return GetValue<long?>("download-queue-size"); } set { this["download-queue-size"] = value; } } /// <summary> /// If true, limit how many torrents can be downloaded at once @@ -82,7 +87,7 @@ public class SessionSettings : ArgumentsBase /// <summary> /// True means allow dht in public torrents /// </summary> - public bool? DHTEnabled { get { return GetValue<bool?>("dht-enabled"); } set { this["dht-enabled"] = value; } } + public bool? DhtEnabled { get { return GetValue<bool?>("dht-enabled"); } set { this["dht-enabled"] = value; } } /// <summary> /// "required", "preferred", "tolerated" @@ -90,9 +95,9 @@ public class SessionSettings : ArgumentsBase public string Encryption { get { return GetValue<string>("encryption"); } set { this["encryption"] = value; } } /// <summary> - /// Torrents we're seeding will be stopped if they're idle for this long + /// Torrents we're seeding will be stopped if they're idle for this long? /// </summary> - public int? IdleSeedingLimit { get { return GetValue<int?>("idle-seeding-limit"); } set { this["idle-seeding-limit"] = value; } } + public long? IdleSeedingLimit { get { return GetValue<long?>("idle-seeding-limit"); } set { this["idle-seeding-limit"] = value; } } /// <summary> /// True if the seeding inactivity limit is honored by default @@ -112,17 +117,17 @@ public class SessionSettings : ArgumentsBase /// <summary> /// True means allow Local Peer Discovery in public torrents /// </summary> - public bool? LPDEnabled { get { return GetValue<bool?>("lpd-enabled"); } set { this["lpd-enabled"] = value; } } + public bool? LpdEnabled { get { return GetValue<bool?>("lpd-enabled"); } set { this["lpd-enabled"] = value; } } /// <summary> /// Maximum global number of peers /// </summary> - public int? PeerLimitGlobal { get { return GetValue<int?>("peer-limit-global"); } set { this["peer-limit-global"] = value; } } + public long? PeerLimitGlobal { get { return GetValue<long?>("peer-limit-global"); } set { this["peer-limit-global"] = value; } } /// <summary> /// Maximum global number of peers /// </summary> - public int? PeerLimitPerTorrent { get { return GetValue<int?>("peer-limit-per-torrent"); } set { this["peer-limit-per-torrent"] = value; } } + public long? PeerLimitPerTorrent { get { return GetValue<long?>("peer-limit-per-torrent"); } set { this["peer-limit-per-torrent"] = value; } } /// <summary> /// True means allow pex in public torrents @@ -132,7 +137,7 @@ public class SessionSettings : ArgumentsBase /// <summary> /// Port number /// </summary> - public int? PeerPort { get { return GetValue<int?>("peer-port"); } set { this["peer-port"] = value; } } + public long? PeerPort { get { return GetValue<long?>("peer-port"); } set { this["peer-port"] = value; } } /// <summary> /// True means pick a random peer port on launch @@ -152,13 +157,23 @@ public class SessionSettings : ArgumentsBase /// <summary> /// Torrents that are idle for N minuets aren't counted toward seed-queue-size or download-queue-size /// </summary> - public int? QueueStalledMinutes { get { return GetValue<int?>("queue-stalled-minutes"); } set { this["queue-stalled-minutes"] = value; } } + public long? QueueStalledMinutes { get { return GetValue<long?>("queue-stalled-minutes"); } set { this["queue-stalled-minutes"] = value; } } /// <summary> /// True means append ".part" to incomplete files /// </summary> public bool? RenamePartialFiles { get { return GetValue<bool?>("rename-partial-files"); } set { this["rename-partial-files"] = value; } } + /// <summary> + /// Filename of the script to run + /// </summary> + public string ScriptTorrentAddedFilename { get { return GetValue<string>("script-torrent-added-filename"); } set { this["script-torrent-added-filename"] = value; } } + + /// <summary> + /// Whether or not to call the "added" script + /// </summary> + public bool? ScriptTorrentAddedEnabled { get { return GetValue<bool?>("script-torrent-added-enabled"); } set { this["script-torrent-added-enabled"] = value; } } + /// <summary> /// Filename of the script to run /// </summary> @@ -169,10 +184,20 @@ public class SessionSettings : ArgumentsBase /// </summary> public bool? ScriptTorrentDoneEnabled { get { return GetValue<bool?>("script-torrent-done-enabled"); } set { this["script-torrent-done-enabled"] = value; } } + /// <summary> + /// Filename of the script to run + /// </summary> + public string ScriptTorrentDoneSeedingFilename { get { return GetValue<string>("script-torrent-done-seeding-filename"); } set { this["script-torrent-done-seeding-filename"] = value; } } + + /// <summary> + /// Whether or not to call the "done seeding" script + /// </summary> + public bool? ScriptTorrentDoneSeedingEnabled { get { return GetValue<bool?>("script-torrent-done-seeding-enabled"); } set { this["script-torrent-done-seeding-enabled"] = value; } } + /// <summary> /// The default seed ratio for torrents to use /// </summary> - public double? SeedRatioLimit { get { return GetValue<int?>("seedRatioLimit"); } set { this["seedRatioLimit"] = value; } } + public double? SeedRatioLimit { get { return GetValue<long?>("seedRatioLimit"); } set { this["seedRatioLimit"] = value; } } /// <summary> /// True if seedRatioLimit is honored by default @@ -182,7 +207,7 @@ public class SessionSettings : ArgumentsBase /// <summary> /// Max number of torrents to uploaded at once (see seed-queue-enabled) /// </summary> - public int? SeedQueueSize { get { return GetValue<int?>("seed-queue-size"); } set { this["seed-queue-size"] = value; } } + public long? SeedQueueSize { get { return GetValue<long?>("seed-queue-size"); } set { this["seed-queue-size"] = value; } } /// <summary> /// If true, limit how many torrents can be uploaded at once @@ -192,7 +217,7 @@ public class SessionSettings : ArgumentsBase /// <summary> /// Max global download speed (KBps) /// </summary> - public int? SpeedLimitDown { get { return GetValue<int?>("speed-limit-down"); } set { this["speed-limit-down"] = value; } } + public long? SpeedLimitDown { get { return GetValue<long?>("speed-limit-down"); } set { this["speed-limit-down"] = value; } } /// <summary> /// True means enabled @@ -202,7 +227,7 @@ public class SessionSettings : ArgumentsBase /// <summary> /// max global upload speed (KBps) /// </summary> - public int? SpeedLimitUp { get { return GetValue<int?>("speed-limit-up"); } set { this["speed-limit-up"] = value; } } + public long? SpeedLimitUp { get { return GetValue<long?>("speed-limit-up"); } set { this["speed-limit-up"] = value; } } /// <summary> /// True means enabled @@ -219,15 +244,10 @@ public class SessionSettings : ArgumentsBase /// </summary> public bool? TrashOriginalTorrentFiles { get { return GetValue<bool?>("trash-original-torrent-files"); } set { this["trash-original-torrent-files"] = value; } } - /// <summary> - /// Units - /// </summary> - public Units Units { get { return GetValue<Units>("units"); } set { this["units"] = value; } } - /// <summary> /// True means allow utp /// </summary> - public bool? UtpEnabled { get { return GetValue<bool?> ("utp-enabled"); } set { this["utp-enabled"] = value; } } + public bool? UtpEnabled { get { return GetValue<bool?>("utp-enabled"); } set { this["utp-enabled"] = value; } } } } diff --git a/Transmission.API.RPC/Arguments/TorrentFields.cs b/Transmission.API.RPC/Arguments/TorrentFields.cs index dd69edd..a04c856 100644 --- a/Transmission.API.RPC/Arguments/TorrentFields.cs +++ b/Transmission.API.RPC/Arguments/TorrentFields.cs @@ -23,6 +23,11 @@ private TorrentFields() { } /// </summary> public const string ADDED_DATE = "addedDate"; + /// <summary> + /// availability + /// </summary> + public const string AVAILABILITY = "availability"; + /// <summary> /// bandwidthPriority /// </summary> @@ -82,7 +87,7 @@ private TorrentFields() { } /// editDate /// </summary> public const string EDIT_DATE = "editDate"; - + /// <summary> /// error /// </summary> @@ -118,6 +123,11 @@ private TorrentFields() { } /// </summary> public const string FILE_STATS = "fileStats"; + /// <summary> + /// group + /// </summary> + public const string GROUP = "group"; + /// <summary> /// hashString /// </summary> @@ -308,6 +318,11 @@ private TorrentFields() { } /// </summary> public const string SEED_RATIO_MODE = "seedRatioMode"; + /// <summary> + /// sequentialDownload + /// </summary> + public const string SEQUENTIAL_DOWNLOAD = "sequentialDownload"; + /// <summary> /// sizeWhenDone /// </summary> @@ -395,6 +410,7 @@ public static string[] ALL_FIELDS #region ALL FIELDS ACTIVITY_DATE, ADDED_DATE, + AVAILABILITY, BANDWIDTH_PRIORITY, COMMENT, CORRUPT_EVER, @@ -414,6 +430,7 @@ public static string[] ALL_FIELDS FILE_COUNT, FILES, FILE_STATS, + GROUP, HASH_STRING, HAVE_UNCHECKED, HAVE_VALID, @@ -452,6 +469,7 @@ public static string[] ALL_FIELDS SEED_IDLE_MODE, SEED_RATIO_LIMIT, SEED_RATIO_MODE, + SEQUENTIAL_DOWNLOAD, SIZE_WHEN_DONE, START_DATE, STATUS, diff --git a/Transmission.API.RPC/Arguments/TorrentSettings.cs b/Transmission.API.RPC/Arguments/TorrentSettings.cs index 5b1c8a3..b0e1276 100644 --- a/Transmission.API.RPC/Arguments/TorrentSettings.cs +++ b/Transmission.API.RPC/Arguments/TorrentSettings.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -16,18 +17,23 @@ public class TorrentSettings : ArgumentsBase /// <summary> /// This torrent's bandwidth tr_priority_t /// </summary> - public int? BandwidthPriority { get { return GetValue<int?>("bandwidthPriority"); } set { this["bandwidthPriority"] = value; } } + public long? BandwidthPriority { get { return GetValue<long?>("bandwidthPriority"); } set { this["bandwidthPriority"] = value; } } /// <summary> /// Maximum download speed (KBps) /// </summary> - public int? DownloadLimit { get { return GetValue<int?>("downloadLimit"); } set { this["downloadLimit"] = value; } } + public long? DownloadLimit { get { return GetValue<long?>("downloadLimit"); } set { this["downloadLimit"] = value; } } /// <summary> /// Download limit is honored /// </summary> public bool? DownloadLimited { get { return GetValue<bool?>("downloadLimited"); } set { this["downloadLimited"] = value; } } + /// <summary> + /// The name of this torrent's bandwidth group + /// </summary> + public string Group { get { return GetValue<string>("group"); } set { this["group"] = value; } } + /// <summary> /// Session upload limits are honored /// </summary> @@ -36,7 +42,12 @@ public class TorrentSettings : ArgumentsBase /// <summary> /// Torrent id array /// </summary> - public object[] IDs { get { return GetValue<object[]>("ids"); } set { this["ids"] = value; } } + public object[] Ids { get { return GetValue<object[]>("ids"); } set { this["ids"] = value; } } + + /// <summary> + /// Array of strings containing the labels assigned to the torrent + /// </summary> + public string[] Labels { get { return GetValue<string[]>("labels"); } set { this["labels"] = value; } } /// <summary> /// New location of the torrent's content @@ -46,22 +57,22 @@ public class TorrentSettings : ArgumentsBase /// <summary> /// Maximum number of peers /// </summary> - public int? PeerLimit { get { return GetValue<int?>("peer-limit"); } set { this["peer-limit"] = value; } } + public long? PeerLimit { get { return GetValue<long?>("peer-limit"); } set { this["peer-limit"] = value; } } /// <summary> /// Position of this torrent in its queue [0...n) /// </summary> - public int? QueuePosition { get { return GetValue<int?>("queuePosition"); } set { this["queuePosition"] = value; } } + public long? QueuePosition { get { return GetValue<long?>("queuePosition"); } set { this["queuePosition"] = value; } } /// <summary> /// Torrent-level number of minutes of seeding inactivity /// </summary> - public int? SeedIdleLimit { get { return GetValue<int?>("seedIdleLimit"); } set { this["seedIdleLimit"] = value; } } + public long? SeedIdleLimit { get { return GetValue<long?>("seedIdleLimit"); } set { this["seedIdleLimit"] = value; } } /// <summary> - /// Which seeding inactivity to use + /// Which seeding inactivity mode to use. 0=Global 1=Single 2=Unlimited /// </summary> - public int? SeedIdleMode { get { return GetValue<int?>("seedIdleMode"); } set { this["seedIdleMode"] = value; } } + public long? SeedIdleMode { get { return GetValue<long?>("seedIdleMode"); } set { this["seedIdleMode"] = value; } } /// <summary> /// Torrent-level seeding ratio @@ -69,14 +80,19 @@ public class TorrentSettings : ArgumentsBase public double? SeedRatioLimit { get { return GetValue<double?>("seedRatioLimit"); } set { this["seedRatioLimit"] = value; } } /// <summary> - /// Which ratio to use. + /// Which ratio mode to use. 0=Global 1=Single 2=Unlimited + /// </summary> + public long? SeedRatioMode { get { return GetValue<long?>("seedRatioMode"); } set { this["seedRatioMode"] = value; } } + + /// <summary> + /// Whether to download the torrent sequentially /// </summary> - public int? SeedRatioMode { get { return GetValue<int?>("seedRatioMode"); } set { this["seedRatioMode"] = value; } } + public bool? SequentialDownload { get { return GetValue<bool?>("sequentialDownload"); } set { this["sequentialDownload"] = value; } } /// <summary> /// Maximum upload speed (KBps) /// </summary> - public int? UploadLimit { get { return GetValue<int?>("uploadLimit"); } set { this["uploadLimit"] = value; } } + public long? UploadLimit { get { return GetValue<long?>("uploadLimit"); } set { this["uploadLimit"] = value; } } /// <summary> /// Upload limit is honored @@ -93,12 +109,18 @@ public class TorrentSettings : ArgumentsBase /// Ids of trackers to remove /// </summary> [Obsolete("TrackerRemove is obsolete since Transmission 4.0.0, use TrackerList instead.")] - public int[] TrackerRemove { get { return GetValue<int[]>("trackerRemove"); } set { this["trackerRemove"] = value; } } + public long?[] TrackerRemove { get { return GetValue<long?[]>("trackerRemove"); } set { this["trackerRemove"] = value; } } + + /// <summary> + /// Pairs of IDs of announce URLs to replace along with their new value + /// </summary> + [Obsolete("TrackerReplace is obsolete since Transmission 4.0.0, use TrackerList instead.")] + public KeyValuePair<int, string>?[] TrackerReplace { get { return GetValue<KeyValuePair<int, string>?[]>("trackerReplace"); } set { this["trackerReplace"] = value; } } /// <summary> /// String of announce URLs, one per line, with a blank line between tiers /// </summary> - public string[] TrackerList { get { return GetValue<string[]>("trackerList"); } set { this["trackerAdd"] = value; } } + public string[] TrackerList { get { return GetValue<string[]>("trackerList"); } set { this["trackerList"] = value; } } /// <summary> /// Files wanted @@ -124,10 +146,5 @@ public class TorrentSettings : ArgumentsBase /// Normal priority files /// </summary> public string[] PriorityNormal { get { return GetValue<string[]>("priority-normal"); } set { this["priority-normal"] = value; } } - - //TODO: Add and test - //"trackerReplace" | array pairs of <trackerId/new announce URLs> - //public [] trackerReplace; - } } diff --git a/Transmission.API.RPC/Client.Async.cs b/Transmission.API.RPC/Client.Async.cs index f1c96c8..14aba55 100644 --- a/Transmission.API.RPC/Client.Async.cs +++ b/Transmission.API.RPC/Client.Async.cs @@ -11,119 +11,142 @@ using Newtonsoft.Json.Linq; using Transmission.API.RPC.Common; using Transmission.API.RPC.Arguments; +using System.Runtime; namespace Transmission.API.RPC { - public partial class Client - { - #region Session methods - - /// <summary> - /// Close current session (API: session-close) - /// </summary> - public async Task CloseSessionAsync() - { - var request = new TransmissionRequest("session-close"); - var response = await SendRequestAsync(request); - } - - /// <summary> - /// Set information to current session (API: session-set) - /// </summary> - /// <param name="settings">New session settings</param> - public async Task SetSessionSettingsAsync(SessionSettings settings) - { - var request = new TransmissionRequest("session-set", settings); - var response = await SendRequestAsync(request); - } - - /// <summary> - /// Get session stat - /// </summary> - /// <returns>Session stat</returns> - public async Task<Statistic> GetSessionStatisticAsync() - { - var request = new TransmissionRequest("session-stats"); - var response = await SendRequestAsync(request); - var result = response.Deserialize<Statistic>(); - return result; - } + public partial class Client + { + #region Session methods + + /// <summary> + /// Close current session (API: session-close) + /// </summary> + public async Task CloseSessionAsync() + { + var request = new TransmissionRequest("session-close"); + var response = await SendRequestAsync(request); + } + + /// <summary> + /// Set information to current session (API: session-set) + /// </summary> + /// <param name="settings">New session settings</param> + public async Task SetSessionSettingsAsync(SessionSettings settings) + { + var request = new TransmissionRequest("session-set", settings); + var response = await SendRequestAsync(request); + } + + /// <summary> + /// Get session stat + /// </summary> + /// <returns>Session stat</returns> + public async Task<Statistic> GetSessionStatisticAsync() + { + var request = new TransmissionRequest("session-stats"); + var response = await SendRequestAsync(request); + var result = response.Deserialize<Statistic>(); + return result; + } /// <summary> /// Get information of current session (API: session-get) /// </summary> /// <returns>Session information</returns> - //TODO: support optional "fields" argument public async Task<SessionInfo> GetSessionInformationAsync() - { - var request = new TransmissionRequest("session-get"); - var response = await SendRequestAsync(request); - var result = response.Deserialize<SessionInfo>(); - return result; - } + { + var request = new TransmissionRequest("session-get"); + var response = await SendRequestAsync(request); + var result = response.Deserialize<SessionInfo>(); + return result; + } + + + + /// <summary> + /// Get information of current session (API: session-get) + /// </summary> + /// <param name="fields">Optional fields of session information</param> + /// <returns>Session information</returns> + public async Task<SessionInfo> GetSessionInformationAsync(string[] fields) + { + var arguments = new Dictionary<string, object>(); + arguments.Add("fields", fields); - #endregion + var request = new TransmissionRequest("session-get", arguments); + var response = await SendRequestAsync(request); + var result = response.Deserialize<SessionInfo>(); + return result; + } + #endregion - #region Torrents methods + #region Torrents methods - /// <summary> - /// Add torrent (API: torrent-add) - /// </summary> - /// <returns>Torrent info (ID, Name and HashString)</returns> - public async Task<NewTorrentInfo> TorrentAddAsync(NewTorrent torrent) - { - if (String.IsNullOrWhiteSpace(torrent.Metainfo) && String.IsNullOrWhiteSpace(torrent.Filename)) - throw new Exception("Either \"filename\" or \"metainfo\" must be included."); + /// <summary> + /// Add torrent (API: torrent-add) + /// </summary> + /// <returns>Torrent info (ID, Name and HashString)</returns> + public async Task<NewTorrentInfo> TorrentAddAsync(NewTorrent torrent) + { + if (String.IsNullOrWhiteSpace(torrent.Metainfo) && String.IsNullOrWhiteSpace(torrent.Filename)) + throw new Exception("Either \"filename\" or \"metainfo\" must be included."); - var request = new TransmissionRequest("torrent-add", torrent); - var response = await SendRequestAsync(request); - var jObject = response.Deserialize<JObject>(); + var request = new TransmissionRequest("torrent-add", torrent); + var response = await SendRequestAsync(request); + var jObject = response.Deserialize<JObject>(); - if (jObject == null || jObject.First == null) - return null; + if (jObject == null || jObject.First == null) + return null; - NewTorrentInfo result = null; - JToken value = null; + NewTorrentInfo result = null; + JToken value = null; - if (jObject.TryGetValue("torrent-duplicate", out value)) - result = JsonConvert.DeserializeObject<NewTorrentInfo>(value.ToString()); - else if (jObject.TryGetValue("torrent-added", out value)) - result = JsonConvert.DeserializeObject<NewTorrentInfo>(value.ToString()); + if (jObject.TryGetValue("torrent-duplicate", out value)) + { + result = JsonConvert.DeserializeObject<NewTorrentInfo>(value.ToString()); + result.Duplicate = true; + } + else if (jObject.TryGetValue("torrent-added", out value)) + { + result = JsonConvert.DeserializeObject<NewTorrentInfo>(value.ToString()); + result.Duplicate = false; + } - return result; - } + return result; + } /// <summary> /// Set torrent params (API: torrent-set) /// </summary> /// <param name="settings">Torrent settings</param> public async Task TorrentSetAsync(TorrentSettings settings) - { - var request = new TransmissionRequest("torrent-set", settings); - var response = await SendRequestAsync(request); - } + { + var request = new TransmissionRequest("torrent-set", settings); + var response = await SendRequestAsync(request); + } - /// <summary> - /// Get fields of torrents from ids (API: torrent-get) - /// </summary> - /// <param name="fields">Fields of torrents</param> - /// <param name="ids">IDs of torrents (null or empty for get all torrents)</param> - /// <returns>Torrents info</returns> - public async Task<TransmissionTorrents> TorrentGetAsync(string[] fields, params int[] ids) - { - var arguments = new Dictionary<string, object>(); - arguments.Add("fields", fields); + /// <summary> + /// Get fields of torrents from ids (API: torrent-get) + /// </summary> + /// <param name="fields">Fields of torrents</param> + /// <param name="ids">IDs of torrents (null or empty for get all torrents)</param> + /// <returns>Torrents info</returns> + public async Task<TransmissionTorrents> TorrentGetAsync(string[] fields, params int[] ids) + { + var arguments = new Dictionary<string, object>(); + arguments.Add("fields", fields); - if (ids != null && ids.Length > 0) - arguments.Add("ids", ids); + if (ids != null && ids.Length > 0) + arguments.Add("ids", ids); - var request = new TransmissionRequest("torrent-get", arguments); + var request = new TransmissionRequest("torrent-get", arguments); - var response = await SendRequestAsync(request); - var result = response.Deserialize<TransmissionTorrents>(); + var response = await SendRequestAsync(request); + var json = response.ToJson - return result; - } + return result; + } /// <summary> /// Remove torrents @@ -131,247 +154,315 @@ public async Task<TransmissionTorrents> TorrentGetAsync(string[] fields, params /// <param name="ids">Torrents id</param> /// <param name="deleteData">Remove data</param> public async Task TorrentRemoveAsync(int[] ids, bool deleteData = false) - { - var arguments = new Dictionary<string, object>(); - - arguments.Add("ids", ids); - arguments.Add("delete-local-data", deleteData); - - var request = new TransmissionRequest("torrent-remove", arguments); - var response = await SendRequestAsync(request); - } - - #region Torrent Start - - /// <summary> - /// Start torrents (API: torrent-start) - /// </summary> - /// <param name="ids">A list of torrent id numbers, sha1 hash strings, or both</param> - public async Task TorrentStartAsync(object[] ids) - { - var request = new TransmissionRequest("torrent-start", new Dictionary<string, object> { { "ids", ids } }); - var response = await SendRequestAsync(request); - } - - /// <summary> - /// Start recently active torrents (API: torrent-start) - /// </summary> - public async Task TorrentStartAsync() - { - var request = new TransmissionRequest("torrent-start", new Dictionary<string, object> { { "ids", "recently-active" } }); - var response = await SendRequestAsync(request); - } - - #endregion - - #region Torrent Start Now - - /// <summary> - /// Start now torrents (API: torrent-start-now) - /// </summary> - /// <param name="ids">A list of torrent id numbers, sha1 hash strings, or both</param> - public async Task TorrentStartNowAsync(object[] ids) - { - var request = new TransmissionRequest("torrent-start-now", new Dictionary<string, object> { { "ids", ids } }); - var response = await SendRequestAsync(request); - } - - /// <summary> - /// Start now recently active torrents (API: torrent-start-now) - /// </summary> - public async Task TorrentStartNowAsync() - { - var request = new TransmissionRequest("torrent-start-now", new Dictionary<string, object> { { "ids", "recently-active" } }); - var response = await SendRequestAsync(request); - } - - #endregion - - #region Torrent Stop - - /// <summary> - /// Stop torrents (API: torrent-stop) - /// </summary> - /// <param name="ids">A list of torrent id numbers, sha1 hash strings, or both</param> - public async Task TorrentStopAsync(object[] ids) - { - var request = new TransmissionRequest("torrent-stop", new Dictionary<string, object> { { "ids", ids } }); - var response = await SendRequestAsync(request); - } - - /// <summary> - /// Stop recently active torrents (API: torrent-stop) - /// </summary> - public async Task TorrentStopAsync() - { - var request = new TransmissionRequest("torrent-stop", new Dictionary<string, object> { { "ids", "recently-active" } }); - var response = await SendRequestAsync(request); - } - - #endregion - - #region Torrent Verify - - /// <summary> - /// Verify torrents (API: torrent-verify) - /// </summary> - /// <param name="ids">A list of torrent id numbers, sha1 hash strings, or both</param> - public async Task TorrentVerifyAsync(object[] ids) - { - var request = new TransmissionRequest("torrent-verify", new Dictionary<string, object> { { "ids", ids } }); - var response = await SendRequestAsync(request); - } - - /// <summary> - /// Verify recently active torrents (API: torrent-verify) - /// </summary> - public async Task TorrentVerifyAsync() - { - var request = new TransmissionRequest("torrent-verify", new Dictionary<string, object> { { "ids", "recently-active" } }); - var response = await SendRequestAsync(request); - } - #endregion - - /// <summary> - /// Move torrents in queue on top (API: queue-move-top) - /// </summary> - /// <param name="ids">Torrents id</param> - public async Task TorrentQueueMoveTopAsync(int[] ids) - { - var request = new TransmissionRequest("queue-move-top", new Dictionary<string, object> { { "ids", ids } }); - var response = await SendRequestAsync(request); - } - - /// <summary> - /// Move up torrents in queue (API: queue-move-up) - /// </summary> - /// <param name="ids"></param> - public async Task TorrentQueueMoveUpAsync(int[] ids) - { - var request = new TransmissionRequest("queue-move-up", new Dictionary<string, object> { { "ids", ids } }); - var response = await SendRequestAsync(request); - } - - /// <summary> - /// Move down torrents in queue (API: queue-move-down) - /// </summary> - /// <param name="ids"></param> - public async Task TorrentQueueMoveDownAsync(int[] ids) - { - var request = new TransmissionRequest("queue-move-down", new Dictionary<string, object> { { "ids", ids } }); - var response = await SendRequestAsync(request); - } - - /// <summary> - /// Move torrents to bottom in queue (API: queue-move-bottom) - /// </summary> - /// <param name="ids"></param> - public async Task TorrentQueueMoveBottomAsync(int[] ids) - { - var request = new TransmissionRequest("queue-move-bottom", new Dictionary<string, object> { { "ids", ids } }); - var response = await SendRequestAsync(request); - } - - /// <summary> - /// Set new location for torrents files (API: torrent-set-location) - /// </summary> - /// <param name="ids">Torrent ids</param> - /// <param name="location">The new torrent location</param> - /// <param name="move">Move from previous location</param> - public async Task TorrentSetLocationAsync(int[] ids, string location, bool move) - { - var arguments = new Dictionary<string, object>(); - arguments.Add("ids", ids); - arguments.Add("location", location); - arguments.Add("move", move); - - var request = new TransmissionRequest("torrent-set-location", arguments); - var response = await SendRequestAsync(request); - } - - /// <summary> - /// Rename a file or directory in a torrent (API: torrent-rename-path) - /// </summary> - /// <param name="id">The torrent whose path will be renamed</param> - /// <param name="path">The path to the file or folder that will be renamed</param> - /// <param name="name">The file or folder's new name</param> - public async Task<RenameTorrentInfo> TorrentRenamePathAsync(int id, string path, string name) - { - var arguments = new Dictionary<string, object>(); - arguments.Add("ids", new int[] { id }); - arguments.Add("path", path); - arguments.Add("name", name); - - var request = new TransmissionRequest("torrent-rename-path", arguments); - var response = await SendRequestAsync(request); - - var result = response.Deserialize<RenameTorrentInfo>(); - - return result; - } - - //method name not recognized - ///// <summary> - ///// Reannounce torrent (API: torrent-reannounce) - ///// </summary> - ///// <param name="ids"></param> - //public void ReannounceTorrents(object[] ids) - //{ - // var arguments = new Dictionary<string, object>(); - // arguments.Add("ids", ids); - - // var request = new TransmissionRequest("torrent-reannounce", arguments); - // var response = SendRequest(request); - //} - - #endregion - - #region System - - /// <summary> - /// See if your incoming peer port is accessible from the outside world (API: port-test) - /// </summary> - /// <returns>Accessible state</returns> - public async Task<bool> PortTestAsync() - { - var request = new TransmissionRequest("port-test"); - var response = await SendRequestAsync(request); - - var data = response.Deserialize<JObject>(); - var result = (bool)data.GetValue("port-is-open"); - return result; - } - - /// <summary> - /// Update blocklist (API: blocklist-update) - /// </summary> - /// <returns>Blocklist size</returns> - public async Task<int> BlocklistUpdateAsync() - { - var request = new TransmissionRequest("blocklist-update"); - var response = await SendRequestAsync(request); - - var data = response.Deserialize<JObject>(); - var result = (int)data.GetValue("blocklist-size"); - return result; - } - - /// <summary> - /// Get free space is available in a client-specified folder. - /// </summary> - /// <param name="path">The directory to query</param> - public async Task<long> FreeSpaceAsync(string path) - { - var arguments = new Dictionary<string, object>(); - arguments.Add("path", path); - - var request = new TransmissionRequest("free-space", arguments); - var response = await SendRequestAsync(request); - - var data = response.Deserialize<JObject>(); - var result = (long)data.GetValue("size-bytes"); - return result; - } + { + var arguments = new Dictionary<string, object>(); + + arguments.Add("ids", ids); + arguments.Add("delete-local-data", deleteData); + + var request = new TransmissionRequest("torrent-remove", arguments); + var response = await SendRequestAsync(request); + } + + #region Torrent Start + + /// <summary> + /// Start torrents (API: torrent-start) + /// </summary> + /// <param name="ids">A list of torrent id numbers, sha1 hash strings, or both</param> + public async Task TorrentStartAsync(object[] ids) + { + var request = new TransmissionRequest("torrent-start", new Dictionary<string, object> { { "ids", ids } }); + var response = await SendRequestAsync(request); + } + + /// <summary> + /// Start recently active torrents (API: torrent-start) + /// </summary> + public async Task TorrentStartAsync() + { + var request = new TransmissionRequest("torrent-start", new Dictionary<string, object> { { "ids", "recently-active" } }); + var response = await SendRequestAsync(request); + } + + #endregion + + #region Torrent Start Now + + /// <summary> + /// Start now torrents (API: torrent-start-now) + /// </summary> + /// <param name="ids">A list of torrent id numbers, sha1 hash strings, or both</param> + public async Task TorrentStartNowAsync(object[] ids) + { + var request = new TransmissionRequest("torrent-start-now", new Dictionary<string, object> { { "ids", ids } }); + var response = await SendRequestAsync(request); + } + + /// <summary> + /// Start now recently active torrents (API: torrent-start-now) + /// </summary> + public async Task TorrentStartNowAsync() + { + var request = new TransmissionRequest("torrent-start-now", new Dictionary<string, object> { { "ids", "recently-active" } }); + var response = await SendRequestAsync(request); + } + + #endregion + + #region Torrent Stop + + /// <summary> + /// Stop torrents (API: torrent-stop) + /// </summary> + /// <param name="ids">A list of torrent id numbers, sha1 hash strings, or both</param> + public async Task TorrentStopAsync(object[] ids) + { + var request = new TransmissionRequest("torrent-stop", new Dictionary<string, object> { { "ids", ids } }); + var response = await SendRequestAsync(request); + } + + /// <summary> + /// Stop recently active torrents (API: torrent-stop) + /// </summary> + public async Task TorrentStopAsync() + { + var request = new TransmissionRequest("torrent-stop", new Dictionary<string, object> { { "ids", "recently-active" } }); + var response = await SendRequestAsync(request); + } + + #endregion + + #region Torrent Verify + + /// <summary> + /// Verify torrents (API: torrent-verify) + /// </summary> + /// <param name="ids">A list of torrent id numbers, sha1 hash strings, or both</param> + public async Task TorrentVerifyAsync(object[] ids) + { + var request = new TransmissionRequest("torrent-verify", new Dictionary<string, object> { { "ids", ids } }); + var response = await SendRequestAsync(request); + } + + /// <summary> + /// Verify recently active torrents (API: torrent-verify) + /// </summary> + public async Task TorrentVerifyAsync() + { + var request = new TransmissionRequest("torrent-verify", new Dictionary<string, object> { { "ids", "recently-active" } }); + var response = await SendRequestAsync(request); + } + + #endregion + + #region Torrent Reannounce + + /// <summary> + /// Reannounce torrents (API: torrent-reannounce) + /// </summary> + /// <param name="ids">A list of torrent id numbers, sha1 hash strings, or both</param> + public async Task TorrentReannounceAsync(object[] ids) + { + var request = new TransmissionRequest("torrent-reannounce", new Dictionary<string, object> { { "ids", ids } }); + var response = await SendRequestAsync(request); + } + + /// <summary> + /// Reannounce recently active torrents (API: torrent-reannounce) + /// </summary> + public async Task TorrentReannounceAsync() + { + var request = new TransmissionRequest("torrent-reannounce", new Dictionary<string, object> { { "ids", "recently-active" } }); + var response = await SendRequestAsync(request); + } + + #endregion + + #region Torrent Queue + + /// <summary> + /// Move torrents in queue on top (API: queue-move-top) + /// </summary> + /// <param name="ids">Torrents id</param> + public async Task TorrentQueueMoveTopAsync(int[] ids) + { + var request = new TransmissionRequest("queue-move-top", new Dictionary<string, object> { { "ids", ids } }); + var response = await SendRequestAsync(request); + } + + /// <summary> + /// Move up torrents in queue (API: queue-move-up) + /// </summary> + /// <param name="ids"></param> + public async Task TorrentQueueMoveUpAsync(int[] ids) + { + var request = new TransmissionRequest("queue-move-up", new Dictionary<string, object> { { "ids", ids } }); + var response = await SendRequestAsync(request); + } + + /// <summary> + /// Move down torrents in queue (API: queue-move-down) + /// </summary> + /// <param name="ids"></param> + public async Task TorrentQueueMoveDownAsync(int[] ids) + { + var request = new TransmissionRequest("queue-move-down", new Dictionary<string, object> { { "ids", ids } }); + var response = await SendRequestAsync(request); + } + + /// <summary> + /// Move torrents to bottom in queue (API: queue-move-bottom) + /// </summary> + /// <param name="ids"></param> + public async Task TorrentQueueMoveBottomAsync(int[] ids) + { + var request = new TransmissionRequest("queue-move-bottom", new Dictionary<string, object> { { "ids", ids } }); + var response = await SendRequestAsync(request); + } + + #endregion + + /// <summary> + /// Set new location for torrents files (API: torrent-set-location) + /// </summary> + /// <param name="ids">Torrent ids</param> + /// <param name="location">The new torrent location</param> + /// <param name="move">Move from previous location</param> + public async Task TorrentSetLocationAsync(int[] ids, string location, bool move) + { + var arguments = new Dictionary<string, object>(); + arguments.Add("ids", ids); + arguments.Add("location", location); + arguments.Add("move", move); + + var request = new TransmissionRequest("torrent-set-location", arguments); + var response = await SendRequestAsync(request); + } + + /// <summary> + /// Rename a file or directory in a torrent (API: torrent-rename-path) + /// </summary> + /// <param name="id">The torrent whose path will be renamed</param> + /// <param name="path">The path to the file or folder that will be renamed</param> + /// <param name="name">The file or folder's new name</param> + public async Task<RenameTorrentInfo> TorrentRenamePathAsync(int id, string path, string name) + { + var arguments = new Dictionary<string, object>(); + arguments.Add("ids", new int[] { id }); + arguments.Add("path", path); + arguments.Add("name", name); + + var request = new TransmissionRequest("torrent-rename-path", arguments); + var response = await SendRequestAsync(request); + + var result = response.Deserialize<RenameTorrentInfo>(); + + return result; + } + + #endregion + + #region Bandwidth Groups + + /// <summary> + /// Get bandwidth groups (API: group-get) + /// </summary> + /// <returns></returns> + public async Task<BandwidthGroup[]> BandwidthGroupGetAsync() + { + var request = new TransmissionRequest("group-get"); + + var response = await SendRequestAsync(request); + var result = response.Deserialize<BandwidthGroup[]>(); + + return result; + } + + /// <summary> + /// Get bandwidth groups (API: group-get) + /// </summary> + /// <param name="groups">Optional names of groups to get</param> + /// <returns></returns> + public async Task<BandwidthGroup[]> BandwidthGroupGetAsync(string[] groups) + { + var arguments = new Dictionary<string, object>(); + arguments.Add("group", groups); + + var request = new TransmissionRequest("group-get", arguments); + + var response = await SendRequestAsync(request); + var result = response.Deserialize<BandwidthGroup[]>(); + + return result; + } + + /// <summary> + /// Set bandwidth groups (API: group-set) + /// </summary> + /// <param name="group">A bandwidth group to set</param> + public async Task BandwidthGroupSetAsync(BandwidthGroupSettings group) + { + var request = new TransmissionRequest("group-set", group); + var response = await SendRequestAsync(request); + } + + #endregion + + #region System + + /// <summary> + /// See if your incoming peer port is accessible from the outside world (API: port-test) + /// </summary> + /// <returns>A Tuple with a boolean of whether the port test succeeded, and a PortTestProtocol enum of which protocol was used for the test</returns> + public async Task<Tuple<bool, PortTestProtocol>> PortTestAsync() + { + var request = new TransmissionRequest("port-test"); + var response = await SendRequestAsync(request); + + var data = response.Deserialize<JObject>(); + var result = (bool)data.GetValue("port-is-open"); + PortTestProtocol protocol = PortTestProtocol.Unknown; + if (data.TryGetValue("ipProtocol", out var protocolValue)) + { + switch ((string)protocolValue) + { + case "ipv4": protocol = PortTestProtocol.IPv4; break; + case "ipv6": protocol = PortTestProtocol.IPV6; break; + } + } + return new Tuple<bool, PortTestProtocol>(result, protocol); + } + + /// <summary> + /// Update blocklist (API: blocklist-update) + /// </summary> + /// <returns>Blocklist size</returns> + public async Task<int> BlocklistUpdateAsync() + { + var request = new TransmissionRequest("blocklist-update"); + var response = await SendRequestAsync(request); + + var data = response.Deserialize<JObject>(); + var result = (int)data.GetValue("blocklist-size"); + return result; + } + + /// <summary> + /// Get free space is available in a client-specified folder. + /// </summary> + /// <param name="path">The directory to query</param> + public async Task<FreeSpace> FreeSpaceAsync(string path) + { + var arguments = new Dictionary<string, object>(); + arguments.Add("path", path); + + var request = new TransmissionRequest("free-space", arguments); + var response = await SendRequestAsync(request); + + var data = response.Deserialize<FreeSpace>(); + return data; + } #endregion diff --git a/Transmission.API.RPC/Client.cs b/Transmission.API.RPC/Client.cs index fa2f484..46d2b2f 100644 --- a/Transmission.API.RPC/Client.cs +++ b/Transmission.API.RPC/Client.cs @@ -2,6 +2,8 @@ using System.Text; using Transmission.API.RPC.Entity; using Transmission.API.RPC.Arguments; +using System.IO; +using System.Xml.Linq; namespace Transmission.API.RPC { @@ -108,6 +110,18 @@ public SessionInfo GetSessionInformation() return task.Result; } + /// <summary> + /// Get information of current session (API: session-get) + /// </summary> + /// <param name="fields">Optional fields of session information</param> + /// <returns>Session information</returns> + public SessionInfo GetSessionInformation(string[] fields) + { + var task = GetSessionInformationAsync(fields); + task.WaitAndUnwrapException(); + return task.Result; + } + #endregion #region Torrents methods @@ -214,6 +228,7 @@ public void TorrentStop() #endregion #region Torrent Verify + /// <summary> /// Verify torrents (API: torrent-verify) /// </summary> @@ -230,8 +245,32 @@ public void TorrentVerify() { TorrentVerifyAsync().WaitAndUnwrapException(); } + + #endregion + + #region Torrent Reannounce + + /// <summary> + /// Reannounce torrents (API: torrent-reannounce) + /// </summary> + /// <param name="ids">A list of torrent id numbers, sha1 hash strings, or both</param> + public void TorrentReannounce(object[] ids) + { + TorrentReannounceAsync(ids).WaitAndUnwrapException(); + } + + /// <summary> + /// Reannounce recently active torrents (API: torrent-reannounce) + /// </summary> + public void TorrentReannounce() + { + TorrentReannounceAsync().WaitAndUnwrapException(); + } + #endregion + #region Torrent Queue + /// <summary> /// Move torrents in queue on top (API: queue-move-top) /// </summary> @@ -268,6 +307,8 @@ public void TorrentQueueMoveBottom(int[] ids) TorrentQueueMoveBottomAsync(ids).WaitAndUnwrapException(); } + #endregion + /// <summary> /// Set new location for torrents files (API: torrent-set-location) /// </summary> @@ -292,15 +333,41 @@ public RenameTorrentInfo TorrentRenamePath(int id, string path, string name) return task.Result; } - //method name not recognized - ///// <summary> - ///// Reannounce torrent (API: torrent-reannounce) - ///// </summary> - ///// <param name="ids"></param> - //public void ReannounceTorrents(object[] ids) - //{ - // ReannounceTorrentsAsync(ids).WaitAndUnwrapException(); - //} + #endregion + + #region Bandwidth Groups + + /// <summary> + /// Get bandwidth groups (API: group-get) + /// </summary> + /// <returns></returns> + public BandwidthGroup[] BandwidthGroupGet() + { + var task = BandwidthGroupGetAsync(); + task.WaitAndUnwrapException(); + return task.Result; + } + + /// <summary> + /// Get bandwidth groups (API: group-get) + /// </summary> + /// <param name="groups">Optional names of groups to get</param> + /// <returns></returns> + public BandwidthGroup[] BandwidthGroupGet(string[] groups) + { + var task = BandwidthGroupGetAsync(groups); + task.WaitAndUnwrapException(); + return task.Result; + } + + /// <summary> + /// Set bandwidth groups (API: group-set) + /// </summary> + /// <param name="group">A bandwidth group to set</param> + public void BandwidthGroupSet(BandwidthGroupSettings group) + { + BandwidthGroupGetAsync().WaitAndUnwrapException(); + } #endregion @@ -309,7 +376,7 @@ public RenameTorrentInfo TorrentRenamePath(int id, string path, string name) /// See if your incoming peer port is accessible from the outside world (API: port-test) /// </summary> /// <returns>Accessible state</returns> - public bool PortTest() + public Tuple<bool, PortTestProtocol> PortTest() { var task = PortTestAsync(); task.WaitAndUnwrapException(); @@ -331,12 +398,13 @@ public int BlocklistUpdate() /// Get free space is available in a client-specified folder. /// </summary> /// <param name="path">The directory to query</param> - public long FreeSpace(string path) + public FreeSpace FreeSpace(string path) { var task = FreeSpaceAsync(path); task.WaitAndUnwrapException(); return task.Result; } + #endregion } } diff --git a/Transmission.API.RPC/Entity/BandwidthGroup.cs b/Transmission.API.RPC/Entity/BandwidthGroup.cs new file mode 100644 index 0000000..9d6df63 --- /dev/null +++ b/Transmission.API.RPC/Entity/BandwidthGroup.cs @@ -0,0 +1,46 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Transmission.API.RPC.Entity +{ + public class BandwidthGroup + { + /// <summary> + /// Session limits are honored + /// </summary> + [JsonProperty("honorsSessionLimits")] + public bool? HonorsSessionLimits { get; set; } + + /// <summary> + /// Name of the bandwidth group + /// </summary> + [JsonProperty("name")] + public string Name { get; set; } + + /// <summary> + /// Max global download speed of this bandwidth group (KBps) + /// </summary> + [JsonProperty("speed-limit-down")] + public long? SpeedLimitDown { get; set; } + + /// <summary> + /// True means enabled + /// </summary> + [JsonProperty("speed-limit-down-enabled")] + public bool? SpeedLimitDownEnabled { get; set; } + + /// <summary> + /// Max global upload speed of this bandwidth group (KBps) + /// </summary> + [JsonProperty("speed-limit-up")] + public long? SpeedLimitUp { get; set; } + + /// <summary> + /// True means enabled + /// </summary> + [JsonProperty("speed-limit-up-enabled")] + public bool? SpeedLimitUpEnabled { get; set; } + } +} diff --git a/Transmission.API.RPC/Entity/FreeSpace.cs b/Transmission.API.RPC/Entity/FreeSpace.cs new file mode 100644 index 0000000..71b80b2 --- /dev/null +++ b/Transmission.API.RPC/Entity/FreeSpace.cs @@ -0,0 +1,33 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Transmission.API.RPC.Entity +{ + /// <summary> + /// FreeSpace + /// </summary> + public class FreeSpace + { + /// <summary> + /// Path of the queried directory + /// </summary> + [JsonProperty("path")] + public string Path { get; set; } + + /// <summary> + /// The size, in bytes, of the free space in that directory + /// </summary> + [JsonProperty("size-bytes")] + public long? SizeBytes { get; set; } + + /// <summary> + /// the total capacity, in bytes, of that directory + /// </summary> + [JsonProperty("total_size")] + public long? TotalSize { get; set; } + } +} diff --git a/Transmission.API.RPC/Entity/IntOrArrayConverter.cs b/Transmission.API.RPC/Entity/IntOrArrayConverter.cs new file mode 100644 index 0000000..64b3b8b --- /dev/null +++ b/Transmission.API.RPC/Entity/IntOrArrayConverter.cs @@ -0,0 +1,80 @@ +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JDRemote.Backends.Transmission +{ + /// <summary> + /// JSON type converter for type long[] or long or int[] or int + /// This is needed because the Availability field was introduced in Transmission 4.0.0 + /// On earlier versions, the field defaults to "0" and decoding to an array fails. + /// </summary> + public class IntOrArrayConverter : JsonConverter + { + /// <summary> + /// Returns true whether the object is of type long[] or long or int[] or int + /// </summary> + /// <param name="objectType"></param> + /// <returns></returns> + public override bool CanConvert(Type objectType) + { + return objectType == typeof(long[]) || objectType == typeof(long) || objectType == typeof(int[]) || objectType == typeof(int) || objectType == typeof(long?[]) || objectType == typeof(long?) || objectType == typeof(int?[]) || objectType == typeof(int?); + } + + /// <summary> + /// Read long[] or single long (not array) from json + /// </summary> + /// <param name="reader"></param> + /// <param name="objectType"></param> + /// <param name="existingValue"></param> + /// <param name="serializer"></param> + /// <returns></returns> + /// <exception cref="JsonSerializationException"></exception> + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + JToken token = JToken.Load(reader); + + if (token.Type == JTokenType.Integer) + { + // Return an array with one element if it's a single integer + if (objectType == typeof(long[])) return new long[] { (long)token }; + if (objectType == typeof(long?[])) return new long?[] { (long?)token }; + } + else if (token.Type == JTokenType.Array) + { + // Return the integer array if it's a JSON array + if (objectType == typeof(long[])) return token.ToObject<long[]>(); + if (objectType == typeof(long?[])) return token.ToObject<long?[]>(); + } + + throw new JsonSerializationException("Unexpected token type"); + } + + /// <summary> + /// Write long[] to json + /// </summary> + /// <param name="writer"></param> + /// <param name="value"></param> + /// <param name="serializer"></param> + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value != null) + { + long?[] longArray = (long?[])value; + if (longArray.Length == 1) + { + writer.WriteValue(longArray[0]); + } + else + { + JArray array = new JArray(longArray); + array.WriteTo(writer); + } + } + } + } +} diff --git a/Transmission.API.RPC/Entity/NewTorrentInfo.cs b/Transmission.API.RPC/Entity/NewTorrentInfo.cs index ee5ac79..d3e82be 100644 --- a/Transmission.API.RPC/Entity/NewTorrentInfo.cs +++ b/Transmission.API.RPC/Entity/NewTorrentInfo.cs @@ -30,5 +30,10 @@ public class NewTorrentInfo [JsonProperty("hashString")] public string HashString { get; set; } + /// <summary> + /// Whether the torrent is a duplicate of an existing torrent (add failed) + /// </summary> + public bool Duplicate { get; set; } + } } diff --git a/Transmission.API.RPC/Entity/SessionInfo.cs b/Transmission.API.RPC/Entity/SessionInfo.cs index f5137d4..2b4fba3 100644 --- a/Transmission.API.RPC/Entity/SessionInfo.cs +++ b/Transmission.API.RPC/Entity/SessionInfo.cs @@ -18,7 +18,7 @@ public class SessionInfo /// Max global download speed (KBps) /// </summary> [JsonProperty("alt-speed-down")] - public int? AlternativeSpeedDown { get; set; } + public long? AlternativeSpeedDown { get; set; } /// <summary> /// True means use the alt speeds @@ -30,7 +30,7 @@ public class SessionInfo /// When to turn on alt speeds (units: minutes after midnight) /// </summary> [JsonProperty("alt-speed-time-begin")] - public int? AlternativeSpeedTimeBegin { get; set; } + public long? AlternativeSpeedTimeBegin { get; set; } /// <summary> /// True means the scheduled on/off times are used @@ -42,25 +42,25 @@ public class SessionInfo /// When to turn off alt speeds /// </summary> [JsonProperty("alt-speed-time-end")] - public int? AlternativeSpeedTimeEnd { get; set; } + public long? AlternativeSpeedTimeEnd { get; set; } /// <summary> /// What day(s) to turn on alt speeds /// </summary> [JsonProperty("alt-speed-time-day")] - public int? AlternativeSpeedTimeDay { get; set; } + public long? AlternativeSpeedTimeDay { get; set; } /// <summary> /// Max global upload speed (KBps) /// </summary> [JsonProperty("alt-speed-up")] - public int? AlternativeSpeedUp { get; set; } + public long? AlternativeSpeedUp { get; set; } /// <summary> /// Location of the blocklist to use for "blocklist-update" /// </summary> [JsonProperty("blocklist-url")] - public string BlocklistURL { get; set; } + public string BlocklistUrl { get; set; } /// <summary> /// True means enabled @@ -68,11 +68,29 @@ public class SessionInfo [JsonProperty("blocklist-enabled")] public bool? BlocklistEnabled { get; set; } + /// <summary> + /// Number of rules in the blocklist + /// </summary> + [JsonProperty("blocklist-size")] + public long? BlocklistSize { get; set; } + /// <summary> /// Maximum size of the disk cache (MB) /// </summary> [JsonProperty("cache-size-mb")] - public int? CacheSizeMB { get; set; } + public long? CacheSizeMb { get; set; } + + /// <summary> + /// Default announce URLs, one per line, and a blank line between tiers + /// </summary> + [JsonProperty("default-trackers")] + public string DefaultTrackers { get; set; } + + /// <summary> + /// Allow DHT in public torrents + /// </summary> + [JsonProperty("dht-enabled")] + public bool? DhtEnabled { get; set; } /// <summary> /// Default path to download torrents @@ -91,7 +109,7 @@ public class SessionInfo /// Max number of torrents to download at once (see download-queue-enabled) /// </summary> [JsonProperty("download-queue-size")] - public int? DownloadQueueSize { get; set; } + public long? DownloadQueueSize { get; set; } /// <summary> /// If true, limit how many torrents can be downloaded at once @@ -99,12 +117,6 @@ public class SessionInfo [JsonProperty("download-queue-enabled")] public bool? DownloadQueueEnabled { get; set; } - /// <summary> - /// True means allow dht in public torrents - /// </summary> - [JsonProperty("dht-enabled")] - public bool? DHTEnabled { get; set; } - /// <summary> /// "required", "preferred", "tolerated" /// </summary> @@ -115,7 +127,7 @@ public class SessionInfo /// Torrents we're seeding will be stopped if they're idle for this long /// </summary> [JsonProperty("idle-seeding-limit")] - public int? IdleSeedingLimit { get; set; } + public long? IdleSeedingLimit { get; set; } /// <summary> /// True if the seeding inactivity limit is honored by default @@ -139,19 +151,19 @@ public class SessionInfo /// True means allow Local Peer Discovery in public torrents /// </summary> [JsonProperty("lpd-enabled")] - public bool? LPDEnabled { get; set; } + public bool? LpdEnabled { get; set; } /// <summary> /// Maximum global number of peers /// </summary> [JsonProperty("peer-limit-global")] - public int? PeerLimitGlobal { get; set; } + public long? PeerLimitGlobal { get; set; } /// <summary> /// Maximum global number of peers /// </summary> [JsonProperty("peer-limit-per-torrent")] - public int? PeerLimitPerTorrent { get; set; } + public long? PeerLimitPerTorrent { get; set; } /// <summary> /// True means allow pex in public torrents @@ -163,7 +175,7 @@ public class SessionInfo /// Port number /// </summary> [JsonProperty("peer-port")] - public int? PeerPort { get; set; } + public long? PeerPort { get; set; } /// <summary> /// True means pick a random peer port on launch @@ -187,19 +199,31 @@ public class SessionInfo /// Torrents that are idle for N minuets aren't counted toward seed-queue-size or download-queue-size /// </summary> [JsonProperty("queue-stalled-minutes")] - public int? QueueStalledMinutes { get; set; } + public long? QueueStalledMinutes { get; set; } /// <summary> /// True means append ".part" to incomplete files /// </summary> [JsonProperty("rename-partial-files")] public bool? RenamePartialFiles { get; set; } - + /// <summary> /// Session ID /// </summary> [JsonProperty("session-id")] - public string SessionID { get; set; } + public string SessionId { get; set; } + + /// <summary> + /// Filename of the script to run + /// </summary> + [JsonProperty("script-torrent-added-filename")] + public string ScriptTorrentAddedFilename { get; set; } + + /// <summary> + /// Whether or not to call the "added" script + /// </summary> + [JsonProperty("script-torrent-added-enabled")] + public bool? ScriptTorrentAddedEnabled { get; set; } /// <summary> /// Filename of the script to run @@ -213,6 +237,18 @@ public class SessionInfo [JsonProperty("script-torrent-done-enabled")] public bool? ScriptTorrentDoneEnabled { get; set; } + /// <summary> + /// Filename of the script to run + /// </summary> + [JsonProperty("script-torrent-done-seeding-filename")] + public string ScriptTorrentDoneSeedingFilename { get; set; } + + /// <summary> + /// Whether or not to call the "done seeding" script + /// </summary> + [JsonProperty("script-torrent-done-seeding-enabled")] + public bool? ScriptTorrentDoneSeedingEnabled { get; set; } + /// <summary> /// The default seed ratio for torrents to use /// </summary> @@ -229,7 +265,7 @@ public class SessionInfo /// Max number of torrents to uploaded at once (see seed-queue-enabled) /// </summary> [JsonProperty("seed-queue-size")] - public int? SeedQueueSize { get; set; } + public long? SeedQueueSize { get; set; } /// <summary> /// If true, limit how many torrents can be uploaded at once @@ -241,7 +277,7 @@ public class SessionInfo /// Max global download speed (KBps) /// </summary> [JsonProperty("speed-limit-down")] - public int? SpeedLimitDown { get; set; } + public long? SpeedLimitDown { get; set; } /// <summary> /// True means enabled @@ -253,7 +289,7 @@ public class SessionInfo /// max global upload speed (KBps) /// </summary> [JsonProperty("speed-limit-up")] - public int? SpeedLimitUp { get; set; } + public long? SpeedLimitUp { get; set; } /// <summary> /// True means enabled @@ -285,12 +321,6 @@ public class SessionInfo [JsonProperty("utp-enabled")] public bool? UtpEnabled { get; set; } - /// <summary> - /// Number of rules in the blocklist - /// </summary> - [JsonProperty("blocklist-size")] - public int BlocklistSize{ get; set; } - /// <summary> /// Location of transmission's configuration directory /// </summary> @@ -301,16 +331,22 @@ public class SessionInfo /// The current RPC API version /// </summary> [JsonProperty("rpc-version")] - public int RpcVersion{ get; set; } + public long? RpcVersion{ get; set; } /// <summary> /// The minimum RPC API version supported /// </summary> [JsonProperty("rpc-version-minimum")] - public int RpcVersionMinimum{ get; set; } + public long? RpcVersionMinimum { get; set; } + + /// <summary> + /// Current RPC API version in a semver-compatible string + /// </summary> + [JsonProperty("rpc-version-semver")] + public string RpcVersionSemver { get; set; } /// <summary> - /// Long version string "$version ($revision)" + /// long? version string "$version ($revision)" /// </summary> [JsonProperty("version")] public string Version{ get; set; } diff --git a/Transmission.API.RPC/Entity/Statistic.cs b/Transmission.API.RPC/Entity/Statistic.cs index 22668bf..d54e36b 100644 --- a/Transmission.API.RPC/Entity/Statistic.cs +++ b/Transmission.API.RPC/Entity/Statistic.cs @@ -16,31 +16,31 @@ public class Statistic /// Active torrent count /// </summary> [JsonProperty("activeTorrentCount")] - public int ActiveTorrentCount { get; set; } + public long? ActiveTorrentCount { get; set; } /// <summary> /// Download speed /// </summary> [JsonProperty("downloadSpeed")] - public int downloadSpeed{ get; set; } + public long? downloadSpeed{ get; set; } /// <summary> /// Paused torrent count /// </summary> [JsonProperty("pausedTorrentCount")] - public int pausedTorrentCount{ get; set; } + public long? pausedTorrentCount{ get; set; } /// <summary> /// Torrent count /// </summary> [JsonProperty("torrentCount")] - public int torrentCount{ get; set; } + public long? torrentCount{ get; set; } /// <summary> /// Upload speed /// </summary> [JsonProperty("uploadSpeed")] - public int uploadSpeed{ get; set; } + public long? uploadSpeed{ get; set; } /// <summary> /// Cumulative stats @@ -64,30 +64,30 @@ public class CommonStatistic /// Uploaded bytes /// </summary> [JsonProperty("uploadedBytes")] - public double uploadedBytes{ get; set; } + public long? UploadedBytes{ get; set; } /// <summary> /// Downloaded bytes /// </summary> [JsonProperty("downloadedBytes")] - public double DownloadedBytes{ get; set; } + public long? DownloadedBytes{ get; set; } /// <summary> /// Files added /// </summary> [JsonProperty("filesAdded")] - public int FilesAdded{ get; set; } + public long? FilesAdded{ get; set; } /// <summary> /// Session count /// </summary> [JsonProperty("SessionCount")] - public int SessionCount{ get; set; } + public long? SessionCount{ get; set; } /// <summary> /// Seconds active /// </summary> [JsonProperty("SecondsActive")] - public int SecondsActive{ get; set; } + public long? SecondsActive{ get; set; } } } diff --git a/Transmission.API.RPC/Entity/TorrentInfo.cs b/Transmission.API.RPC/Entity/TorrentInfo.cs index 3a8cfef..d7ac0e2 100644 --- a/Transmission.API.RPC/Entity/TorrentInfo.cs +++ b/Transmission.API.RPC/Entity/TorrentInfo.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using JDRemote.Backends.Transmission; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -16,25 +17,32 @@ public class TorrentInfo /// The torrent's unique Id. /// </summary> [JsonProperty("id")] - public int ID { get; set; } + public long? Id { get; set; } /// <summary> /// Activity date /// </summary> [JsonProperty("activityDate")] - public long ActivityDate { get; set; } + public long? ActivityDate { get; set; } /// <summary> /// Added date /// </summary> [JsonProperty("addedDate")] - public long AddedDate { get; set; } + public long? AddedDate { get; set; } + + /// <summary> + /// Availability + /// </summary> + [JsonProperty("availability")] + [JsonConverter(typeof(IntOrArrayConverter))] // Without this converter, Transmission < 4.0.0 leads to an error. + public long?[] Availability { get; set; } /// <summary> /// Torrents bandwidth priority /// </summary> [JsonProperty("bandwidthPriority")] - public int BandwidthPriority { get; set; } + public long? BandwidthPriority { get; set; } /// <summary> /// Comment @@ -46,7 +54,7 @@ public class TorrentInfo /// Corrupt ever /// </summary> [JsonProperty("corruptEver")] - public int CorruptEver { get; set; } + public long? CorruptEver { get; set; } /// <summary> /// Creator @@ -58,19 +66,19 @@ public class TorrentInfo /// Date created /// </summary> [JsonProperty("dateCreated")] - public long DateCreated { get; set; } + public long? DateCreated { get; set; } /// <summary> /// Desired available /// </summary> [JsonProperty("desiredAvailable")] - public long DesiredAvailable { get; set; } + public long? DesiredAvailable { get; set; } /// <summary> /// Done date /// </summary> [JsonProperty("doneDate")] - public long DoneDate { get; set; } + public long? DoneDate { get; set; } /// <summary> /// Download directory @@ -82,31 +90,31 @@ public class TorrentInfo /// Downloaded ever /// </summary> [JsonProperty("downloadedEver")] - public string DownloadedEver { get; set; } + public long? DownloadedEver { get; set; } /// <summary> /// Download limit /// </summary> [JsonProperty("downloadLimit")] - public string DownloadLimit { get; set; } + public long? DownloadLimit { get; set; } /// <summary> /// Download limited /// </summary> [JsonProperty("downloadLimited")] - public string DownloadLimited { get; set; } + public bool? DownloadLimited { get; set; } /// <summary> /// Edit date /// </summary> [JsonProperty("editDate")] - public long EditDate { get; set; } + public long? EditDate { get; set; } /// <summary> /// Error /// </summary> [JsonProperty("error")] - public int Error { get; set; } + public long? Error { get; set; } /// <summary> /// Error string @@ -118,19 +126,19 @@ public class TorrentInfo /// ETA /// </summary> [JsonProperty("eta")] - public int ETA { get; set; } + public long? Eta { get; set; } /// <summary> /// ETA idle /// </summary> [JsonProperty("etaIdle")] - public int ETAIdle { get; set; } + public long? EtaIdle { get; set; } /// <summary> /// File count /// </summary> [JsonProperty("file-count")] - public int FileCount { get; set; } + public long? FileCount { get; set; } /// <summary> /// Files @@ -144,6 +152,12 @@ public class TorrentInfo [JsonProperty("fileStats")] public TransmissionTorrentFileStats[] FileStats { get; set; } + /// <summary> + /// Group + /// </summary> + [JsonProperty("group")] + public string Group { get; set; } + /// <summary> /// Hash string /// </summary> @@ -154,37 +168,37 @@ public class TorrentInfo /// Have unchecked /// </summary> [JsonProperty("haveUnchecked")] - public int HaveUnchecked { get; set; } + public long? HaveUnchecked { get; set; } /// <summary> /// Have valid /// </summary> [JsonProperty("haveValid")] - public long HaveValid { get; set; } + public long? HaveValid { get; set; } /// <summary> /// Honors session limits /// </summary> [JsonProperty("honorsSessionLimits")] - public bool HonorsSessionLimits { get; set; } + public bool? HonorsSessionLimits { get; set; } /// <summary> /// Is finished /// </summary> [JsonProperty("isFinished")] - public bool IsFinished { get; set; } + public bool? IsFinished { get; set; } /// <summary> /// Is private /// </summary> [JsonProperty("isPrivate")] - public bool IsPrivate { get; set; } + public bool? IsPrivate { get; set; } /// <summary> /// Is stalled /// </summary> [JsonProperty("isStalled")] - public bool IsStalled { get; set; } + public bool? IsStalled { get; set; } /// <summary> /// Labels @@ -196,7 +210,7 @@ public class TorrentInfo /// Left until done /// </summary> [JsonProperty("leftUntilDone")] - public long LeftUntilDone { get; set; } + public long? LeftUntilDone { get; set; } /// <summary> /// Magnet link @@ -208,19 +222,19 @@ public class TorrentInfo /// Manual announce time /// </summary> [JsonProperty("manualAnnounceTime")] - public int ManualAnnounceTime { get; set; } + public long? ManualAnnounceTime { get; set; } /// <summary> /// Max connected peers /// </summary> [JsonProperty("maxConnectedPeers")] - public int MaxConnectedPeers { get; set; } + public long? MaxConnectedPeers { get; set; } /// <summary> /// Metadata percent complete /// </summary> [JsonProperty("metadataPercentComplete")] - public double MetadataPercentComplete { get; set; } + public double? MetadataPercentComplete { get; set; } /// <summary> /// Name @@ -232,7 +246,7 @@ public class TorrentInfo /// Peer limit /// </summary> [JsonProperty("peer-limit")] - public int PeerLimit { get; set; } + public long? PeerLimit { get; set; } /// <summary> /// Peers @@ -244,7 +258,7 @@ public class TorrentInfo /// Peers connected /// </summary> [JsonProperty("peersConnected")] - public int PeersConnected { get; set; } + public long? PeersConnected { get; set; } /// <summary> /// Peers from @@ -256,25 +270,25 @@ public class TorrentInfo /// Peers getting from us /// </summary> [JsonProperty("peersGettingFromUs")] - public int PeersGettingFromUs { get; set; } + public long? PeersGettingFromUs { get; set; } /// <summary> /// Peers sending to us /// </summary> [JsonProperty("peersSendingToUs")] - public int PeersSendingToUs { get; set; } + public long? PeersSendingToUs { get; set; } /// <summary> /// Percent complete /// </summary> [JsonProperty("percentComplete")] - public double PercentComplete { get; set; } + public double? PercentComplete { get; set; } /// <summary> /// Percent done /// </summary> [JsonProperty("percentDone")] - public double PercentDone { get; set; } + public double? PercentDone { get; set; } /// <summary> /// Pieces @@ -286,19 +300,19 @@ public class TorrentInfo /// Piece count /// </summary> [JsonProperty("pieceCount")] - public int PieceCount { get; set; } + public long? PieceCount { get; set; } /// <summary> /// Piece size /// </summary> [JsonProperty("pieceSize")] - public long PieceSize { get; set; } + public long? PieceSize { get; set; } /// <summary> /// Priorities /// </summary> [JsonProperty("priorities")] - public int[] Priorities { get; set; } + public long?[] Priorities { get; set; } /// <summary> /// Primary mime type @@ -310,79 +324,85 @@ public class TorrentInfo /// Queue position /// </summary> [JsonProperty("queuePosition")] - public int QueuePosition { get; set; } + public long? QueuePosition { get; set; } /// <summary> /// Rate download /// </summary> [JsonProperty("rateDownload")] - public int RateDownload { get; set; } + public long? RateDownload { get; set; } /// <summary> /// Rate upload /// </summary> [JsonProperty("rateUpload")] - public int RateUpload { get; set; } + public long? RateUpload { get; set; } /// <summary> /// Recheck progress /// </summary> [JsonProperty("recheckProgress")] - public double RecheckProgress { get; set; } + public double? RecheckProgress { get; set; } /// <summary> /// Seconds downloading /// </summary> [JsonProperty("secondsDownloading")] - public int SecondsDownloading { get; set; } + public long? SecondsDownloading { get; set; } /// <summary> /// Seconds seeding /// </summary> [JsonProperty("secondsSeeding")] - public int SecondsSeeding { get; set; } + public long? SecondsSeeding { get; set; } /// <summary> /// Seed idle limit /// </summary> [JsonProperty("seedIdleLimit")] - public int SeedIdleLimit { get; set; } + public long? SeedIdleLimit { get; set; } /// <summary> /// Seed idle mode /// </summary> [JsonProperty("seedIdleMode")] - public int SeedIdleMode { get; set; } + public long? SeedIdleMode { get; set; } /// <summary> /// Seed ratio limit /// </summary> [JsonProperty("seedRatioLimit")] - public double SeedRatioLimit { get; set; } + public double? SeedRatioLimit { get; set; } /// <summary> /// Seed ratio mode /// </summary> [JsonProperty("seedRatioMode")] - public int SeedRatioMode { get; set; } + public long? SeedRatioMode { get; set; } + + /// <summary> + /// Sequential Download + /// </summary> + [JsonProperty("sequentialDownload")] + public bool? SequentialDownload { get; set; } /// <summary> /// Size when done /// </summary> [JsonProperty("sizeWhenDone")] - public long SizeWhenDone { get; set; } + public long? SizeWhenDone { get; set; } /// <summary> /// Start date /// </summary> [JsonProperty("startDate")] - public long StartDate { get; set; } + public long? StartDate { get; set; } /// <summary> /// Status /// </summary> [JsonProperty("status")] - public int Status { get; set; } + public long? Status { get; set; } /// <summary> /// Trackers @@ -408,7 +428,7 @@ public class TorrentInfo /// Total size /// </summary> [JsonProperty("totalSize")] - public long TotalSize { get; set; } + public long? TotalSize { get; set; } /// <summary> /// Torrent file @@ -420,31 +440,31 @@ public class TorrentInfo /// Uploaded ever /// </summary> [JsonProperty("uploadedEver")] - public long UploadedEver { get; set; } + public long? UploadedEver { get; set; } /// <summary> /// Upload limit /// </summary> [JsonProperty("uploadLimit")] - public int UploadLimit { get; set; } + public long? UploadLimit { get; set; } /// <summary> /// Upload limited /// </summary> [JsonProperty("uploadLimited")] - public bool UploadLimited { get; set; } + public bool? UploadLimited { get; set; } /// <summary> /// Upload ratio /// </summary> [JsonProperty("uploadRatio")] - public double uploadRatio { get; set; } + public double? uploadRatio { get; set; } /// <summary> /// Wanted /// </summary> [JsonProperty("wanted")] - public bool[] Wanted { get; set; } + public bool?[] Wanted { get; set; } /// <summary> /// Web seeds @@ -456,7 +476,7 @@ public class TorrentInfo /// Web seeds sending to us /// </summary> [JsonProperty("webseedsSendingToUs")] - public int WebseedsSendingToUs { get; set; } + public long? WebseedsSendingToUs { get; set; } } /// <summary> @@ -468,19 +488,31 @@ public class TransmissionTorrentFiles /// Bytes completed /// </summary> [JsonProperty("bytesCompleted")] - public double BytesCompleted{ get; set; } + public long? BytesCompleted{ get; set; } /// <summary> /// Length /// </summary> [JsonProperty("length")] - public double Length{ get; set; } + public long? Length{ get; set; } /// <summary> /// Name /// </summary> [JsonProperty("name")] public string Name{ get; set; } + + /// <summary> + /// First piece index of file + /// </summary> + [JsonProperty("beginPiece")] + public long? BeginPiece { get; set; } + + /// <summary> + /// Last piece index of file + /// </summary> + [JsonProperty("endPiece")] + public long? EndPiece { get; set; } } /// <summary> @@ -492,19 +524,19 @@ public class TransmissionTorrentFileStats /// Bytes completed /// </summary> [JsonProperty("bytesCompleted")] - public double BytesCompleted{ get; set; } + public long? BytesCompleted{ get; set; } /// <summary> /// Wanted /// </summary> [JsonProperty("wanted")] - public bool Wanted{ get; set; } + public bool? Wanted{ get; set; } /// <summary> /// Priority /// </summary> [JsonProperty("priority")] - public int Priority{ get; set; } + public long? Priority{ get; set; } } /// <summary> @@ -528,13 +560,13 @@ public class TransmissionTorrentPeers /// Client is choked /// </summary> [JsonProperty("clientIsChoked")] - public bool ClientIsChoked{ get; set; } + public bool? ClientIsChoked{ get; set; } /// <summary> - /// Client is interested + /// Client is Interested /// </summary> [JsonProperty("clientIsInterested")] - public bool ClientIsInterested{ get; set; } + public bool? ClientIsInterested{ get; set; } /// <summary> /// Flag string @@ -546,61 +578,67 @@ public class TransmissionTorrentPeers /// Is downloading from /// </summary> [JsonProperty("isDownloadingFrom")] - public bool IsDownloadingFrom{ get; set; } + public bool? IsDownloadingFrom{ get; set; } /// <summary> /// Is encrypted /// </summary> [JsonProperty("isEncrypted")] - public bool IsEncrypted{ get; set; } + public bool? IsEncrypted { get; set; } + + /// <summary> + /// Is incoming + /// </summary> + [JsonProperty("isIncoming")] + public bool? IsIncoming { get; set; } /// <summary> /// Is uploading to /// </summary> [JsonProperty("isUploadingTo")] - public bool IsUploadingTo{ get; set; } + public bool? IsUploadingTo{ get; set; } /// <summary> /// Is UTP /// </summary> [JsonProperty("isUTP")] - public bool IsUTP{ get; set; } + public bool? IsUtp{ get; set; } /// <summary> /// Peer is choked /// </summary> [JsonProperty("peerIsChoked")] - public bool PeerIsChoked{ get; set; } + public bool? PeerIsChoked{ get; set; } /// <summary> - /// Peer is interested + /// Peer is Interested /// </summary> [JsonProperty("peerIsInterested")] - public bool PeerIsInterested{ get; set; } + public bool? PeerIsInterested{ get; set; } /// <summary> /// Port /// </summary> [JsonProperty("port")] - public int Port{ get; set; } + public long? Port{ get; set; } /// <summary> /// Progress /// </summary> [JsonProperty("progress")] - public double Progress{ get; set; } + public double? Progress{ get; set; } /// <summary> /// Rate to client /// </summary> [JsonProperty("rateToClient")] - public int RateToClient{ get; set; } + public long? RateToClient{ get; set; } /// <summary> /// Rate to peer /// </summary> [JsonProperty("rateToPeer")] - public int RateToPeer{ get; set; } + public long? RateToPeer{ get; set; } } /// <summary> @@ -608,41 +646,47 @@ public class TransmissionTorrentPeers /// </summary> public class TransmissionTorrentPeersFrom { + /// <summary> + /// From cache + /// </summary> + [JsonProperty("fromCache")] + public long? FromCache { get; set; } + /// <summary> /// From DHT /// </summary> [JsonProperty("fromDht")] - public int FromDHT{ get; set; } + public long? FromDht { get; set; } /// <summary> /// From incoming /// </summary> [JsonProperty("fromIncoming")] - public int FromIncoming{ get; set; } + public long? FromIncoming{ get; set; } /// <summary> /// From LPD /// </summary> [JsonProperty("fromLpd")] - public int FromLPD{ get; set; } + public long? FromLpd{ get; set; } /// <summary> /// From LTEP /// </summary> [JsonProperty("fromLtep")] - public int FromLTEP{ get; set; } + public long? FromLtep{ get; set; } /// <summary> /// From PEX /// </summary> [JsonProperty("fromPex")] - public int FromPEX{ get; set; } + public long? FromPex{ get; set; } /// <summary> /// From tracker /// </summary> [JsonProperty("fromTracker")] - public int FromTracker{ get; set; } + public long? FromTracker{ get; set; } } /// <summary> @@ -654,13 +698,13 @@ public class TransmissionTorrentTrackers /// Announce /// </summary> [JsonProperty("announce")] - public string announce{ get; set; } + public string Announce{ get; set; } /// <summary> /// Id /// </summary> [JsonProperty("id")] - public int ID{ get; set; } + public long? Id{ get; set; } /// <summary> /// Scrape @@ -668,11 +712,17 @@ public class TransmissionTorrentTrackers [JsonProperty("scrape")] public string Scrape{ get; set; } + /// <summary> + /// Site name + /// </summary> + [JsonProperty("sitename")] + public string SiteName { get; set; } + /// <summary> /// Tier /// </summary> [JsonProperty("tier")] - public int Tier{ get; set; } + public long? Tier{ get; set; } } /// <summary> @@ -684,31 +734,31 @@ public class TransmissionTorrentTrackerStats /// Announce /// </summary> [JsonProperty("announce")] - public string announce{ get; set; } + public string Announce{ get; set; } /// <summary> /// Announce state /// </summary> [JsonProperty("announceState")] - public int AnnounceState{ get; set; } + public long? AnnounceState{ get; set; } /// <summary> /// Download count /// </summary> [JsonProperty("downloadCount")] - public int DownloadCount{ get; set; } + public long? DownloadCount{ get; set; } /// <summary> /// Has announced /// </summary> [JsonProperty("hasAnnounced")] - public bool HasAnnounced{ get; set; } + public bool? HasAnnounced{ get; set; } /// <summary> /// Has scraped /// </summary> [JsonProperty("hasScraped")] - public bool HasScraped{ get; set; } + public bool? HasScraped{ get; set; } /// <summary> /// Host @@ -720,19 +770,19 @@ public class TransmissionTorrentTrackerStats /// Is backup /// </summary> [JsonProperty("isBackup")] - public bool IsBackup{ get; set; } + public bool? IsBackup{ get; set; } /// <summary> /// Last announce peer count /// </summary> [JsonProperty("lastAnnouncePeerCount")] - public int LastAnnouncePeerCount{ get; set; } + public long? LastAnnouncePeerCount{ get; set; } /// <summary> /// Id /// </summary> [JsonProperty("id")] - public int ID{ get; set; } + public long? Id{ get; set; } /// <summary> /// Last announce result @@ -744,13 +794,13 @@ public class TransmissionTorrentTrackerStats /// Last announce succeeded /// </summary> [JsonProperty("lastAnnounceSucceeded")] - public bool LastAnnounceSucceeded{ get; set; } + public bool? LastAnnounceSucceeded{ get; set; } /// <summary> /// Last announce start time /// </summary> [JsonProperty("lastAnnounceStartTime")] - public int LastAnnounceStartTime{ get; set; } + public long? LastAnnounceStartTime{ get; set; } /// <summary> /// Last scrape result @@ -762,37 +812,37 @@ public class TransmissionTorrentTrackerStats /// Last announce timed out /// </summary> [JsonProperty("lastAnnounceTimedOut")] - public bool LastAnnounceTimedOut{ get; set; } + public bool? LastAnnounceTimedOut{ get; set; } /// <summary> /// Last announce time /// </summary> [JsonProperty("lastAnnounceTime")] - public int LastAnnounceTime{ get; set; } + public long? LastAnnounceTime{ get; set; } /// <summary> /// Last scrape scceeded /// </summary> [JsonProperty("lastScrapeSucceeded")] - public bool LastScrapeSucceeded{ get; set; } + public bool? LastScrapeSucceeded{ get; set; } /// <summary> /// Last scrape start time /// </summary> [JsonProperty("lastScrapeStartTime")] - public int LastScrapeStartTime{ get; set; } + public long? LastScrapeStartTime{ get; set; } /// <summary> /// Last scrape timed out /// </summary> [JsonProperty("lastScrapeTimedOut")] - public bool LastScrapeTimedOut{ get; set; } + public bool? LastScrapeTimedOut{ get; set; } /// <summary> /// Last scrape time /// </summary> [JsonProperty("lastScrapeTime")] - public int LastScrapeTime{ get; set; } + public long? LastScrapeTime{ get; set; } /// <summary> /// Scrape @@ -804,37 +854,43 @@ public class TransmissionTorrentTrackerStats /// Tier /// </summary> [JsonProperty("tier")] - public int Tier{ get; set; } + public long? Tier{ get; set; } /// <summary> /// Leecher count /// </summary> [JsonProperty("leecherCount")] - public int LeecherCount{ get; set; } + public long? LeecherCount{ get; set; } /// <summary> /// Next announce time /// </summary> [JsonProperty("nextAnnounceTime")] - public int NextAnnounceTime{ get; set; } + public long? NextAnnounceTime{ get; set; } /// <summary> /// Next scrape time /// </summary> [JsonProperty("nextScrapeTime")] - public int NextScrapeTime{ get; set; } + public long? NextScrapeTime{ get; set; } /// <summary> /// Scrape state /// </summary> [JsonProperty("scrapeState")] - public int ScrapeState{ get; set; } + public long? ScrapeState{ get; set; } /// <summary> /// Seeder count /// </summary> [JsonProperty("seederCount")] - public int SeederCount{ get; set; } + public long? SeederCount{ get; set; } + + /// <summary> + /// Site name + /// </summary> + [JsonProperty("sitename")] + public string SiteName { get; set; } } /// <summary> diff --git a/Transmission.API.RPC/Entity/Units.cs b/Transmission.API.RPC/Entity/Units.cs index 50d402c..18aa269 100644 --- a/Transmission.API.RPC/Entity/Units.cs +++ b/Transmission.API.RPC/Entity/Units.cs @@ -22,7 +22,7 @@ public class Units /// Speed bytes /// </summary> [JsonProperty("speed-bytes")] - public int? SpeedBytes { get; set; } + public long? SpeedBytes { get; set; } /// <summary> /// Size units @@ -34,7 +34,7 @@ public class Units /// Size bytes /// </summary> [JsonProperty("size-bytes")] - public int? SizeBytes { get; set; } + public long? SizeBytes { get; set; } /// <summary> /// Memory units @@ -46,6 +46,6 @@ public class Units /// Memory bytes /// </summary> [JsonProperty("memory-bytes")] - public int? MemoryBytes { get; set; } + public long? MemoryBytes { get; set; } } } diff --git a/Transmission.API.RPC/ITransmissionClient.cs b/Transmission.API.RPC/ITransmissionClient.cs index b978ca0..fa0dbbc 100644 --- a/Transmission.API.RPC/ITransmissionClient.cs +++ b/Transmission.API.RPC/ITransmissionClient.cs @@ -1,4 +1,6 @@ -using Transmission.API.RPC.Arguments; +using System.Threading.Tasks; +using System; +using Transmission.API.RPC.Arguments; using Transmission.API.RPC.Entity; namespace Transmission.API.RPC @@ -38,7 +40,7 @@ public interface ITransmissionClient /// Get free space is available in a client-specified folder. /// </summary> /// <param name="path">The directory to query</param> - long FreeSpace(string path); + FreeSpace FreeSpace(string path); /// <summary> /// Get information of current session (API: session-get) @@ -46,6 +48,13 @@ public interface ITransmissionClient /// <returns>Session information</returns> SessionInfo GetSessionInformation(); + /// <summary> + /// Get information of current session (API: session-get) + /// </summary> + /// <param name="fields">Optional fields of session information</param> + /// <returns>Session information</returns> + SessionInfo GetSessionInformation(string[] fields); + /// <summary> /// Get session stat /// </summary> @@ -55,8 +64,8 @@ public interface ITransmissionClient /// <summary> /// See if your incoming peer port is accessible from the outside world (API: port-test) /// </summary> - /// <returns>Accessible state</returns> - bool PortTest(); + /// <returns>A Tuple with a boolean of whether the port test succeeded, and a PortTestProtocol enum of which protocol was used for the test</returns> + Tuple<bool, PortTestProtocol> PortTest(); /// <summary> /// Set information to current session (API: session-set) @@ -174,5 +183,35 @@ public interface ITransmissionClient /// </summary> /// <param name="ids">A list of torrent id numbers, sha1 hash strings, or both</param> void TorrentVerify(object[] ids); + + /// <summary> + /// Reannounce recently active torrents (API: torrent-reannounce) + /// </summary> + void TorrentReannounce(); + + /// <summary> + /// Reannounce torrents (API: torrent-reannounce) + /// </summary> + /// <param name="ids">A list of torrent id numbers, sha1 hash strings, or both</param> + void TorrentReannounce(object[] ids); + + /// <summary> + /// Get bandwidth groups (API: group-get) + /// </summary> + /// <returns></returns> + BandwidthGroup[] BandwidthGroupGet(); + + /// <summary> + /// Get bandwidth groups (API: group-get) + /// </summary> + /// <param name="groups">Optional names of groups to get</param> + /// <returns></returns> + BandwidthGroup[] BandwidthGroupGet(string[] groups); + + /// <summary> + /// Set bandwidth groups (API: group-set) + /// </summary> + /// <param name="group">A bandwidth group to set</param> + void BandwidthGroupSet(BandwidthGroupSettings group); } } diff --git a/Transmission.API.RPC/ITransmissionClientAsync.cs b/Transmission.API.RPC/ITransmissionClientAsync.cs index dbf91c6..a341752 100644 --- a/Transmission.API.RPC/ITransmissionClientAsync.cs +++ b/Transmission.API.RPC/ITransmissionClientAsync.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Transmission.API.RPC.Arguments; using Transmission.API.RPC.Entity; @@ -25,14 +26,22 @@ public interface ITransmissionClientAsync /// Get free space is available in a client-specified folder. /// </summary> /// <param name="path">The directory to query</param> - Task<long> FreeSpaceAsync(string path); + Task<FreeSpace> FreeSpaceAsync(string path); /// <summary> /// Get information of current session (API: session-get) /// </summary> + /// <param name="fields">Fields of session information</param> /// <returns>Session information</returns> Task<SessionInfo> GetSessionInformationAsync(); + /// <summary> + /// Get information of current session (API: session-get) + /// </summary> + /// <param name="fields">Optional fields of session information</param> + /// <returns>Session information</returns> + Task<SessionInfo> GetSessionInformationAsync(string[] fields); + /// <summary> /// Get session stat /// </summary> @@ -42,8 +51,8 @@ public interface ITransmissionClientAsync /// <summary> /// See if your incoming peer port is accessible from the outside world (API: port-test) /// </summary> - /// <returns>Accessible state</returns> - Task<bool> PortTestAsync(); + /// <returns>A Tuple with a boolean of whether the port test succeeded, and a PortTestProtocol enum of which protocol was used for the test</returns> + Task<Tuple<bool, PortTestProtocol>> PortTestAsync(); /// <summary> /// Set information to current session (API: session-set) @@ -161,5 +170,35 @@ public interface ITransmissionClientAsync /// </summary> /// <param name="ids">A list of torrent id numbers, sha1 hash strings, or both</param> Task TorrentVerifyAsync(object[] ids); + + /// <summary> + /// Reannounce recently active torrents (API: torrent-reannounce) + /// </summary> + Task TorrentReannounceAsync(); + + /// <summary> + /// Reannounce torrents (API: torrent-reannounce) + /// </summary> + /// <param name="ids">A list of torrent id numbers, sha1 hash strings, or both</param> + Task TorrentReannounceAsync(object[] ids); + + /// <summary> + /// Get bandwidth groups (API: group-get) + /// </summary> + /// <returns></returns> + Task<BandwidthGroup[]> BandwidthGroupGetAsync(); + + /// <summary> + /// Get bandwidth groups (API: group-get) + /// </summary> + /// <param name="groups">Optional names of groups to get</param> + /// <returns></returns> + Task<BandwidthGroup[]> BandwidthGroupGetAsync(string[] groups); + + /// <summary> + /// Set bandwidth groups (API: group-set) + /// </summary> + /// <param name="group">A bandwidth group to set</param> + Task BandwidthGroupSetAsync(BandwidthGroupSettings group); } } diff --git a/Transmission.API.RPC/Transmission.API.RPC.csproj b/Transmission.API.RPC/Transmission.API.RPC.csproj index f020772..680474b 100644 --- a/Transmission.API.RPC/Transmission.API.RPC.csproj +++ b/Transmission.API.RPC/Transmission.API.RPC.csproj @@ -25,7 +25,7 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> + <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="System.Net.Http" Version="4.3.4" /> <PackageReference Include="System.Net.Requests" Version="4.3.0" /> </ItemGroup> From e1900ca6e7f67335bec49d1bb8e7ab5050556475 Mon Sep 17 00:00:00 2001 From: Jdbye <jdbye3@gmail.com> Date: Sun, 9 Jun 2024 22:51:23 +0200 Subject: [PATCH 3/5] Corrected "removed" field of torrent-get, renamed some functions for clarity, added TorrentGetRecentlyActive Also added missing summaries to get rid of those pesky warnings from Visual Studio --- .../Arguments/BandwidthGroupSettings.cs | 3 + .../Arguments/SessionFields.cs | 174 +++++++++++++++++- Transmission.API.RPC/Client.Async.cs | 31 +++- Transmission.API.RPC/Client.cs | 32 +++- Transmission.API.RPC/Entity/BandwidthGroup.cs | 3 + Transmission.API.RPC/Entity/TorrentInfo.cs | 2 +- Transmission.API.RPC/ITransmissionClient.cs | 17 +- .../ITransmissionClientAsync.cs | 18 +- 8 files changed, 250 insertions(+), 30 deletions(-) diff --git a/Transmission.API.RPC/Arguments/BandwidthGroupSettings.cs b/Transmission.API.RPC/Arguments/BandwidthGroupSettings.cs index 4d22b54..b7096f4 100644 --- a/Transmission.API.RPC/Arguments/BandwidthGroupSettings.cs +++ b/Transmission.API.RPC/Arguments/BandwidthGroupSettings.cs @@ -5,6 +5,9 @@ namespace Transmission.API.RPC.Arguments { + /// <summary> + /// Bandwidth group settings for editing bandwidth groups + /// </summary> public class BandwidthGroupSettings : ArgumentsBase { /// <summary> diff --git a/Transmission.API.RPC/Arguments/SessionFields.cs b/Transmission.API.RPC/Arguments/SessionFields.cs index b0855e8..5f095b5 100644 --- a/Transmission.API.RPC/Arguments/SessionFields.cs +++ b/Transmission.API.RPC/Arguments/SessionFields.cs @@ -10,64 +10,234 @@ namespace Transmission.API.RPC.Arguments public sealed class SessionFields { private SessionFields() { } - + /// <summary> + /// alt-speed-down + /// </summary> public const string ALT_SPEED_DOWN = "alt-speed-down"; + /// <summary> + /// alt-speed-enabled + /// </summary> public const string ALT_SPEED_ENABLED = "alt-speed-enabled"; + /// <summary> + /// alt-speed-time-begin + /// </summary> public const string ALT_SPEED_TIME_BEGIN = "alt-speed-time-begin"; + /// <summary> + /// alt-speed-time-enabled + /// </summary> public const string ALT_SPEED_TIME_ENABLED = "alt-speed-time-enabled"; + /// <summary> + /// alt-speed-time-end + /// </summary> public const string ALT_SPEED_TIME_END = "alt-speed-time-end"; + /// <summary> + /// alt-speed-time-day + /// </summary> public const string ALT_SPEED_TIME_DAY = "alt-speed-time-day"; + /// <summary> + /// alt-speed-up + /// </summary> public const string ALT_SPEED_UP = "alt-speed-up"; + /// <summary> + /// blocklist-url + /// </summary> public const string BLOCKLIST_URL = "blocklist-url"; + /// <summary> + /// blocklist-enabled + /// </summary> public const string BLOCKLIST_ENABLED = "blocklist-enabled"; + /// <summary> + /// blocklist-size + /// </summary> public const string BLOCKLIST_SIZE = "blocklist-size"; + /// <summary> + /// cache-size-mb + /// </summary> public const string CACHE_SIZE_MB = "cache-size-mb"; + /// <summary> + /// default-trackers + /// </summary> public const string DEFAULT_TRACKERS = "default-trackers"; + /// <summary> + /// dht-enabled + /// </summary> public const string DHT_ENABLED = "dht-enabled"; + /// <summary> + /// download-dir + /// </summary> public const string DOWNLOAD_DIR = "download-dir"; + /// <summary> + /// download-dir-free-space + /// </summary> public const string DOWNLOAD_DIR_FREE_SPACE = "download-dir-free-space"; + /// <summary> + /// download-queue-size + /// </summary> public const string DOWNLOAD_QUEUE_SIZE = "download-queue-size"; + /// <summary> + /// download-queue-enabled + /// </summary> public const string DOWNLOAD_QUEUE_ENABLED = "download-queue-enabled"; + /// <summary> + /// encryption + /// </summary> public const string ENCRYPTION = "encryption"; + /// <summary> + /// idle-seeding-limit + /// </summary> public const string IDLE_SEEDING_LIMIT = "idle-seeding-limit"; + /// <summary> + /// idle-seeding-limit-enabled + /// </summary> public const string IDLE_SEEDING_LIMIT_ENABLED = "idle-seeding-limit-enabled"; + /// <summary> + /// incomplete-dir + /// </summary> public const string INCOMPLETE_DIR = "incomplete-dir"; + /// <summary> + /// incomplete-dir-enabled + /// </summary> public const string INCOMPLETE_DIR_ENABLED = "incomplete-dir-enabled"; + /// <summary> + /// lpd-enabled + /// </summary> public const string LPD_ENABLED = "lpd-enabled"; + /// <summary> + /// peer-limit-global + /// </summary> public const string PEER_LIMIT_GLOBAL = "peer-limit-global"; + /// <summary> + /// peer-limit-per-torrent + /// </summary> public const string PEER_LIMIT_PER_TORRENT = "peer-limit-per-torrent"; + /// <summary> + /// pex-enabled + /// </summary> public const string PEX_ENABLED = "pex-enabled"; + /// <summary> + /// peer-port + /// </summary> public const string PEER_PORT = "peer-port"; + /// <summary> + /// peer-port-random-on-start + /// </summary> public const string PEER_PORT_RANDOM_ON_START = "peer-port-random-on-start"; + /// <summary> + /// port-forwarding-enabled + /// </summary> public const string PORT_FORWARDING_ENABLED = "port-forwarding-enabled"; + /// <summary> + /// queue-stalled-enabled + /// </summary> public const string QUEUE_STALLED_ENABLED = "queue-stalled-enabled"; + /// <summary> + /// queue-stalled-minutes + /// </summary> public const string QUEUE_STALLED_MINUTES = "queue-stalled-minutes"; + /// <summary> + /// rename-partial-files + /// </summary> public const string RENAME_PARTIAL_FILES = "rename-partial-files"; + /// <summary> + /// session-id + /// </summary> public const string SESSION_ID = "session-id"; + /// <summary> + /// script-torrent-added-filename + /// </summary> public const string SCRIPT_TORRENT_ADDED_FILENAME = "script-torrent-added-filename"; + /// <summary> + /// script-torrent-added-enabled + /// </summary> public const string SCRIPT_TORRENT_ADDED_ENABLED = "script-torrent-added-enabled"; + /// <summary> + /// script-torrent-done-filename + /// </summary> public const string SCRIPT_TORRENT_DONE_FILENAME = "script-torrent-done-filename"; + /// <summary> + /// script-torrent-done-enabled + /// </summary> public const string SCRIPT_TORRENT_DONE_ENABLED = "script-torrent-done-enabled"; + /// <summary> + /// script-torrent-done-seeding-filename + /// </summary> public const string SCRIPT_TORRENT_DONE_SEEDING_FILENAME = "script-torrent-done-seeding-filename"; + /// <summary> + /// script-torrent-done-seeding-enabled + /// </summary> public const string SCRIPT_TORRENT_DONE_SEEDING_ENABLED = "script-torrent-done-seeding-enabled"; + /// <summary> + /// seedRatioLimit + /// </summary> public const string SEED_RATIO_LIMIT = "seedRatioLimit"; + /// <summary> + /// seedRatioLimited + /// </summary> public const string SEED_RATIO_LIMITED = "seedRatioLimited"; + /// <summary> + /// seed-queue-size + /// </summary> public const string SEED_QUEUE_SIZE = "seed-queue-size"; + /// <summary> + /// seed-queue-enabled + /// </summary> public const string SEED_QUEUE_ENABLED = "seed-queue-enabled"; + /// <summary> + /// speed-limit-down + /// </summary> public const string SPEED_LIMIT_DOWN = "speed-limit-down"; + /// <summary> + /// speed-limit-down-enabled + /// </summary> public const string SPEED_LIMIT_DOWN_ENABLED = "speed-limit-down-enabled"; + /// <summary> + /// speed-limit-up + /// </summary> public const string SPEED_LIMIT_UP = "speed-limit-up"; + /// <summary> + /// speed-limit-up-enabled + /// </summary> public const string SPEED_LIMIT_UP_ENABLED = "speed-limit-up-enabled"; + /// <summary> + /// start-added-torrents + /// </summary> public const string START_ADDED_TORRENTS = "start-added-torrents"; + /// <summary> + /// trash-original-torrent-files + /// </summary> public const string TRASH_ORIGINAL_TORRENT_FILES = "trash-original-torrent-files"; + /// <summary> + /// units + /// </summary> public const string UNITS = "units"; + /// <summary> + /// utp-enabled + /// </summary> public const string UTP_ENABLED = "utp-enabled"; + /// <summary> + /// config-dir + /// </summary> public const string CONFIG_DIR = "config-dir"; + /// <summary> + /// rpc-version + /// </summary> public const string RPC_VERSION = "rpc-version"; + /// <summary> + /// rpc-version-minimum + /// </summary> public const string RPC_VERSION_MINIMUM = "rpc-version-minimum"; + /// <summary> + /// rpc-version-semver + /// </summary> public const string RPC_VERSION_SEMVER = "rpc-version-semver"; + /// <summary> + /// version + /// </summary> public const string VERSION = "version"; + /// <summary> + /// All fields + /// </summary> public static string[] ALL_FIELDS { get @@ -89,7 +259,7 @@ public static string[] ALL_FIELDS DEFAULT_TRACKERS, DHT_ENABLED, DOWNLOAD_DIR, - DOWNLOAD_DIR_FREE_SPACE, + //DOWNLOAD_DIR_FREE_SPACE, // Deprecated DOWNLOAD_QUEUE_SIZE, DOWNLOAD_QUEUE_ENABLED, ENCRYPTION, diff --git a/Transmission.API.RPC/Client.Async.cs b/Transmission.API.RPC/Client.Async.cs index 14aba55..fbcbd3c 100644 --- a/Transmission.API.RPC/Client.Async.cs +++ b/Transmission.API.RPC/Client.Async.cs @@ -126,6 +126,25 @@ public async Task TorrentSetAsync(TorrentSettings settings) var response = await SendRequestAsync(request); } + /// <summary> + /// Get fields of recently active torrents (API: torrent-get) + /// </summary> + /// <param name="fields">Fields of torrents</param> + /// <returns>Torrents info</returns> + public async Task<TransmissionTorrents> TorrentGetRecentlyActiveAsync(string[] fields) + { + var arguments = new Dictionary<string, object>(); + arguments.Add("fields", fields); + arguments.Add("ids", "recently-active"); + + var request = new TransmissionRequest("torrent-get", arguments); + + var response = await SendRequestAsync(request); + var result = response.Deserialize<TransmissionTorrents>(); + + return result; + } + /// <summary> /// Get fields of torrents from ids (API: torrent-get) /// </summary> @@ -143,7 +162,7 @@ public async Task<TransmissionTorrents> TorrentGetAsync(string[] fields, params var request = new TransmissionRequest("torrent-get", arguments); var response = await SendRequestAsync(request); - var json = response.ToJson + var result = response.Deserialize<TransmissionTorrents>(); return result; } @@ -179,7 +198,7 @@ public async Task TorrentStartAsync(object[] ids) /// <summary> /// Start recently active torrents (API: torrent-start) /// </summary> - public async Task TorrentStartAsync() + public async Task TorrentStartRecentlyActiveAsync() { var request = new TransmissionRequest("torrent-start", new Dictionary<string, object> { { "ids", "recently-active" } }); var response = await SendRequestAsync(request); @@ -202,7 +221,7 @@ public async Task TorrentStartNowAsync(object[] ids) /// <summary> /// Start now recently active torrents (API: torrent-start-now) /// </summary> - public async Task TorrentStartNowAsync() + public async Task TorrentStartNowRecentlyActiveAsync() { var request = new TransmissionRequest("torrent-start-now", new Dictionary<string, object> { { "ids", "recently-active" } }); var response = await SendRequestAsync(request); @@ -225,7 +244,7 @@ public async Task TorrentStopAsync(object[] ids) /// <summary> /// Stop recently active torrents (API: torrent-stop) /// </summary> - public async Task TorrentStopAsync() + public async Task TorrentStopRecentlyActiveAsync() { var request = new TransmissionRequest("torrent-stop", new Dictionary<string, object> { { "ids", "recently-active" } }); var response = await SendRequestAsync(request); @@ -248,7 +267,7 @@ public async Task TorrentVerifyAsync(object[] ids) /// <summary> /// Verify recently active torrents (API: torrent-verify) /// </summary> - public async Task TorrentVerifyAsync() + public async Task TorrentVerifyRecentlyActiveAsync() { var request = new TransmissionRequest("torrent-verify", new Dictionary<string, object> { { "ids", "recently-active" } }); var response = await SendRequestAsync(request); @@ -271,7 +290,7 @@ public async Task TorrentReannounceAsync(object[] ids) /// <summary> /// Reannounce recently active torrents (API: torrent-reannounce) /// </summary> - public async Task TorrentReannounceAsync() + public async Task TorrentReannounceRecentlyActiveAsync() { var request = new TransmissionRequest("torrent-reannounce", new Dictionary<string, object> { { "ids", "recently-active" } }); var response = await SendRequestAsync(request); diff --git a/Transmission.API.RPC/Client.cs b/Transmission.API.RPC/Client.cs index 46d2b2f..abb142d 100644 --- a/Transmission.API.RPC/Client.cs +++ b/Transmission.API.RPC/Client.cs @@ -146,6 +146,18 @@ public void TorrentSet(TorrentSettings settings) TorrentSetAsync(settings).WaitAndUnwrapException(); } + /// <summary> + /// Get fields of recently active torrents (API: torrent-get) + /// </summary> + /// <param name="fields">Fields of torrents</param> + /// <returns>Torrents info</returns> + public TransmissionTorrents TorrentGetRecentlyActive(string[] fields) + { + var task = TorrentGetRecentlyActiveAsync(fields); + task.WaitAndUnwrapException(); + return task.Result; + } + /// <summary> /// Get fields of torrents from ids (API: torrent-get) /// </summary> @@ -182,9 +194,9 @@ public void TorrentStart(object[] ids) /// <summary> /// Start recently active torrents (API: torrent-start) /// </summary> - public void TorrentStart() + public void TorrentStartRecentlyActive() { - TorrentStartAsync().WaitAndUnwrapException(); + TorrentStartRecentlyActiveAsync().WaitAndUnwrapException(); } #endregion @@ -202,9 +214,9 @@ public void TorrentStartNow(object[] ids) /// <summary> /// Start now recently active torrents (API: torrent-start-now) /// </summary> - public void TorrentStartNow() + public void TorrentStartNowRecentlyActive() { - TorrentStartNowAsync().WaitAndUnwrapException(); + TorrentStartNowRecentlyActiveAsync().WaitAndUnwrapException(); } #endregion @@ -221,9 +233,9 @@ public void TorrentStop(object[] ids) /// <summary> /// Stop recently active torrents (API: torrent-stop) /// </summary> - public void TorrentStop() + public void TorrentStopRecentlyActive() { - TorrentStopAsync().WaitAndUnwrapException(); + TorrentStopRecentlyActiveAsync().WaitAndUnwrapException(); } #endregion @@ -241,9 +253,9 @@ public void TorrentVerify(object[] ids) /// <summary> /// Verify recently active torrents (API: torrent-verify) /// </summary> - public void TorrentVerify() + public void TorrentVerifyRecentlyActive() { - TorrentVerifyAsync().WaitAndUnwrapException(); + TorrentVerifyRecentlyActiveAsync().WaitAndUnwrapException(); } #endregion @@ -262,9 +274,9 @@ public void TorrentReannounce(object[] ids) /// <summary> /// Reannounce recently active torrents (API: torrent-reannounce) /// </summary> - public void TorrentReannounce() + public void TorrentReannounceRecentlyActive() { - TorrentReannounceAsync().WaitAndUnwrapException(); + TorrentReannounceRecentlyActiveAsync().WaitAndUnwrapException(); } #endregion diff --git a/Transmission.API.RPC/Entity/BandwidthGroup.cs b/Transmission.API.RPC/Entity/BandwidthGroup.cs index 9d6df63..df48db6 100644 --- a/Transmission.API.RPC/Entity/BandwidthGroup.cs +++ b/Transmission.API.RPC/Entity/BandwidthGroup.cs @@ -5,6 +5,9 @@ namespace Transmission.API.RPC.Entity { + /// <summary> + /// Contains settings for a bandwidth group + /// </summary> public class BandwidthGroup { /// <summary> diff --git a/Transmission.API.RPC/Entity/TorrentInfo.cs b/Transmission.API.RPC/Entity/TorrentInfo.cs index d7ac0e2..d18ee1e 100644 --- a/Transmission.API.RPC/Entity/TorrentInfo.cs +++ b/Transmission.API.RPC/Entity/TorrentInfo.cs @@ -908,6 +908,6 @@ public class TransmissionTorrents /// Array of torrent-id numbers of recently-removed torrents /// </summary> [JsonProperty("removed")] - public TorrentInfo[] Removed{ get; set; } + public long?[] Removed{ get; set; } } } diff --git a/Transmission.API.RPC/ITransmissionClient.cs b/Transmission.API.RPC/ITransmissionClient.cs index fa0dbbc..911dc62 100644 --- a/Transmission.API.RPC/ITransmissionClient.cs +++ b/Transmission.API.RPC/ITransmissionClient.cs @@ -79,6 +79,13 @@ public interface ITransmissionClient /// <returns>Torrent info (ID, Name and HashString)</returns> NewTorrentInfo TorrentAdd(NewTorrent torrent); + /// <summary> + /// Get fields of recently active torrents (API: torrent-get) + /// </summary> + /// <param name="fields">Fields of torrents</param> + /// <returns>Torrents info</returns> + TransmissionTorrents TorrentGetRecentlyActive(string[] fields); + /// <summary> /// Get fields of torrents from ids (API: torrent-get) /// </summary> @@ -143,7 +150,7 @@ public interface ITransmissionClient /// <summary> /// Start recently active torrents (API: torrent-start) /// </summary> - void TorrentStart(); + void TorrentStartRecentlyActive(); /// <summary> /// Start torrents (API: torrent-start) @@ -154,7 +161,7 @@ public interface ITransmissionClient /// <summary> /// Start now recently active torrents (API: torrent-start-now) /// </summary> - void TorrentStartNow(); + void TorrentStartNowRecentlyActive(); /// <summary> /// Start now torrents (API: torrent-start-now) @@ -165,7 +172,7 @@ public interface ITransmissionClient /// <summary> /// Stop recently active torrents (API: torrent-stop) /// </summary> - void TorrentStop(); + void TorrentStopRecentlyActive(); /// <summary> /// Stop torrents (API: torrent-stop) @@ -176,7 +183,7 @@ public interface ITransmissionClient /// <summary> /// Verify recently active torrents (API: torrent-verify) /// </summary> - void TorrentVerify(); + void TorrentVerifyRecentlyActive(); /// <summary> /// Verify torrents (API: torrent-verify) @@ -187,7 +194,7 @@ public interface ITransmissionClient /// <summary> /// Reannounce recently active torrents (API: torrent-reannounce) /// </summary> - void TorrentReannounce(); + void TorrentReannounceRecentlyActive(); /// <summary> /// Reannounce torrents (API: torrent-reannounce) diff --git a/Transmission.API.RPC/ITransmissionClientAsync.cs b/Transmission.API.RPC/ITransmissionClientAsync.cs index a341752..192bba7 100644 --- a/Transmission.API.RPC/ITransmissionClientAsync.cs +++ b/Transmission.API.RPC/ITransmissionClientAsync.cs @@ -31,7 +31,6 @@ public interface ITransmissionClientAsync /// <summary> /// Get information of current session (API: session-get) /// </summary> - /// <param name="fields">Fields of session information</param> /// <returns>Session information</returns> Task<SessionInfo> GetSessionInformationAsync(); @@ -66,6 +65,13 @@ public interface ITransmissionClientAsync /// <returns>Torrent info (ID, Name and HashString)</returns> Task<NewTorrentInfo> TorrentAddAsync(NewTorrent torrent); + /// <summary> + /// Get fields of recently active torrents (API: torrent-get) + /// </summary> + /// <param name="fields">Fields of torrents</param> + /// <returns>Torrents info</returns> + Task<TransmissionTorrents> TorrentGetRecentlyActiveAsync(string[] fields); + /// <summary> /// Get fields of torrents from ids (API: torrent-get) /// </summary> @@ -130,7 +136,7 @@ public interface ITransmissionClientAsync /// <summary> /// Start recently active torrents (API: torrent-start) /// </summary> - Task TorrentStartAsync(); + Task TorrentStartRecentlyActiveAsync(); /// <summary> /// Start torrents (API: torrent-start) @@ -141,7 +147,7 @@ public interface ITransmissionClientAsync /// <summary> /// Start now recently active torrents (API: torrent-start-now) /// </summary> - Task TorrentStartNowAsync(); + Task TorrentStartNowRecentlyActiveAsync(); /// <summary> /// Start now torrents (API: torrent-start-now) @@ -152,7 +158,7 @@ public interface ITransmissionClientAsync /// <summary> /// Stop recently active torrents (API: torrent-stop) /// </summary> - Task TorrentStopAsync(); + Task TorrentStopRecentlyActiveAsync(); /// <summary> /// Stop torrents (API: torrent-stop) @@ -163,7 +169,7 @@ public interface ITransmissionClientAsync /// <summary> /// Verify recently active torrents (API: torrent-verify) /// </summary> - Task TorrentVerifyAsync(); + Task TorrentVerifyRecentlyActiveAsync(); /// <summary> /// Verify torrents (API: torrent-verify) @@ -174,7 +180,7 @@ public interface ITransmissionClientAsync /// <summary> /// Reannounce recently active torrents (API: torrent-reannounce) /// </summary> - Task TorrentReannounceAsync(); + Task TorrentReannounceRecentlyActiveAsync(); /// <summary> /// Reannounce torrents (API: torrent-reannounce) From f4e5e04794377635aefa019825bb65301b855876 Mon Sep 17 00:00:00 2001 From: Jdbye <jdbye3@gmail.com> Date: Mon, 10 Jun 2024 02:44:29 +0200 Subject: [PATCH 4/5] More performance. * Significantly more performant (around 20% when fetching a large amount of torrents with torrent-get) * torrent-get now supports the table format. This by itself only provides a small speed improvement. * RPC requests no longer need to deserialize->serialize->deserialize the JSON to arrive at the final result. Once is enough. --- Transmission.API.RPC.Test/MethodsTest.cs | 20 +-- .../Transmission.API.RPC.Test.csproj | 2 +- .../Arguments/BandwidthGroupSettings.cs | 10 +- Transmission.API.RPC/Arguments/NewTorrent.cs | 6 +- .../Arguments/SessionSettings.cs | 80 +++++----- .../Arguments/TorrentFields.cs | 2 +- .../Arguments/TorrentSettings.cs | 34 ++--- Transmission.API.RPC/Client.Async.cs | 126 +++++++++------- Transmission.API.RPC/Client.cs | 30 ++-- .../Common/CommunicateBase.cs | 46 ------ .../Common/TransmissionRequest.cs | 25 ++- .../Common/TransmissionResponse.cs | 49 +++++- Transmission.API.RPC/Entity/BandwidthGroup.cs | 5 +- Transmission.API.RPC/Entity/FreeSpace.cs | 5 +- .../Entity/IntOrArrayConverter.cs | 11 +- Transmission.API.RPC/Entity/NewTorrentInfo.cs | 11 +- .../Entity/RenameTorrentInfo.cs | 9 +- Transmission.API.RPC/Entity/SessionInfo.cs | 31 ++-- Transmission.API.RPC/Entity/Statistic.cs | 7 +- Transmission.API.RPC/Entity/TorrentInfo.cs | 85 +++++------ .../Entity/TorrentInfoConverter.cs | 142 ++++++++++++++++++ Transmission.API.RPC/Entity/Units.cs | 9 +- Transmission.API.RPC/Exceptions.cs | 16 ++ Transmission.API.RPC/ITransmissionClient.cs | 43 +++--- .../ITransmissionClientAsync.cs | 41 ++--- .../Transmission.API.RPC.csproj | 2 +- 26 files changed, 529 insertions(+), 318 deletions(-) delete mode 100644 Transmission.API.RPC/Common/CommunicateBase.cs create mode 100644 Transmission.API.RPC/Entity/TorrentInfoConverter.cs create mode 100644 Transmission.API.RPC/Exceptions.cs diff --git a/Transmission.API.RPC.Test/MethodsTest.cs b/Transmission.API.RPC.Test/MethodsTest.cs index ccf2cee..5f000d1 100644 --- a/Transmission.API.RPC.Test/MethodsTest.cs +++ b/Transmission.API.RPC.Test/MethodsTest.cs @@ -44,7 +44,7 @@ public void AddTorrent_Test() var newTorrentInfo = client.TorrentAdd(torrent); Assert.IsNotNull(newTorrentInfo); - Assert.IsTrue(newTorrentInfo.ID != 0); + Assert.IsTrue(newTorrentInfo.Id != 0); } [TestMethod] @@ -59,7 +59,7 @@ public void AddTorrent_Magnet_Test() var newTorrentInfo = client.TorrentAdd(torrent); Assert.IsNotNull(newTorrentInfo); - Assert.IsTrue(newTorrentInfo.ID != 0); + Assert.IsTrue(newTorrentInfo.Id != 0); } [TestMethod] @@ -84,13 +84,13 @@ public void SetTorrentSettings_Test() var trackerCount = torrentInfo.Trackers.Length; TorrentSettings settings = new TorrentSettings() { - IDs = new object[] { torrentInfo.HashString }, - TrackerRemove = new int[] { trackerInfo.ID } + Ids = new object[] { torrentInfo.HashString }, + TrackerRemove = new long[] { trackerInfo.Id } }; client.TorrentSet(settings); - torrentsInfo = client.TorrentGet(TorrentFields.ALL_FIELDS, torrentInfo.ID); + torrentsInfo = client.TorrentGet(TorrentFields.ALL_FIELDS, false, torrentInfo.Id); torrentInfo = torrentsInfo.Torrents.FirstOrDefault(); Assert.IsFalse(trackerCount == torrentInfo.Trackers.Length); @@ -103,10 +103,10 @@ public void RenamePathTorrent_Test() var torrentInfo = torrentsInfo.Torrents.FirstOrDefault(); Assert.IsNotNull(torrentInfo, "Torrent not found"); - var result = client.TorrentRenamePath(torrentInfo.ID, torrentInfo.Files[0].Name, "test_" + torrentInfo.Files[0].Name); + var result = client.TorrentRenamePath(torrentInfo.Id, torrentInfo.Files[0].Name, "test_" + torrentInfo.Files[0].Name); Assert.IsNotNull(result, "Torrent not found"); - Assert.IsTrue(result.ID != 0); + Assert.IsTrue(result.Id != 0); } [TestMethod] @@ -116,11 +116,11 @@ public void RemoveTorrent_Test() var torrentInfo = torrentsInfo.Torrents.FirstOrDefault(); Assert.IsNotNull(torrentInfo, "Torrent not found"); - client.TorrentRemove(new int[] { torrentInfo.ID }); + client.TorrentRemove(new long[] { torrentInfo.Id }); torrentsInfo = client.TorrentGet(TorrentFields.ALL_FIELDS); - Assert.IsFalse(torrentsInfo.Torrents.Any(t => t.ID == torrentInfo.ID)); + Assert.IsFalse(torrentsInfo.Torrents.Any(t => t.Id == torrentInfo.Id)); } #endregion @@ -142,7 +142,7 @@ public void ChangeSessionTest() var sessionInformation = client.GetSessionInformation(); //Save old speed limit up - var oldSpeedLimit = sessionInformation.SpeedLimitUp; + var oldSpeedLimit = (long)sessionInformation.SpeedLimitUp; //Set new session settings client.SetSessionSettings(new SessionSettings() { SpeedLimitUp = 100 }); diff --git a/Transmission.API.RPC.Test/Transmission.API.RPC.Test.csproj b/Transmission.API.RPC.Test/Transmission.API.RPC.Test.csproj index 818fab3..cb17e6e 100644 --- a/Transmission.API.RPC.Test/Transmission.API.RPC.Test.csproj +++ b/Transmission.API.RPC.Test/Transmission.API.RPC.Test.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>netcoreapp2.0</TargetFramework> + <TargetFramework>netcoreapp3.1</TargetFramework> </PropertyGroup> <ItemGroup> diff --git a/Transmission.API.RPC/Arguments/BandwidthGroupSettings.cs b/Transmission.API.RPC/Arguments/BandwidthGroupSettings.cs index b7096f4..8bbabc1 100644 --- a/Transmission.API.RPC/Arguments/BandwidthGroupSettings.cs +++ b/Transmission.API.RPC/Arguments/BandwidthGroupSettings.cs @@ -13,7 +13,7 @@ public class BandwidthGroupSettings : ArgumentsBase /// <summary> /// Session limits are honored /// </summary> - public bool? HonorsSessionLimits { get { return GetValue<bool?>("honorsSessionLimits"); } set { this["honorsSessionLimits"] = value; } } + public bool HonorsSessionLimits { get { return GetValue<bool>("honorsSessionLimits"); } set { this["honorsSessionLimits"] = value; } } /// <summary> /// Name of the bandwidth group @@ -23,21 +23,21 @@ public class BandwidthGroupSettings : ArgumentsBase /// <summary> /// Max global download speed of this bandwidth group (KBps) /// </summary> - public long? SpeedLimitDown { get { return GetValue<long?>("speed-limit-down"); } set { this["speed-limit-down"] = value; } } + public long SpeedLimitDown { get { return GetValue<long>("speed-limit-down"); } set { this["speed-limit-down"] = value; } } /// <summary> /// True means enabled /// </summary> - public bool? SpeedLimitDownEnabled { get { return GetValue<bool?>("speed-limit-down-enabled"); } set { this["speed-limit-down-enabled"] = value; } } + public bool SpeedLimitDownEnabled { get { return GetValue<bool>("speed-limit-down-enabled"); } set { this["speed-limit-down-enabled"] = value; } } /// <summary> /// Max global upload speed of this bandwidth group (KBps) /// </summary> - public long? SpeedLimitUp { get { return GetValue<long?>("speed-limit-up"); } set { this["speed-limit-up"] = value; } } + public long SpeedLimitUp { get { return GetValue<long>("speed-limit-up"); } set { this["speed-limit-up"] = value; } } /// <summary> /// True means enabled /// </summary> - public bool? SpeedLimitUpEnabled { get { return GetValue<bool?>("speed-limit-up-enabled"); } set { this["speed-limit-up-enabled"] = value; } } + public bool SpeedLimitUpEnabled { get { return GetValue<bool>("speed-limit-up-enabled"); } set { this["speed-limit-up-enabled"] = value; } } } } diff --git a/Transmission.API.RPC/Arguments/NewTorrent.cs b/Transmission.API.RPC/Arguments/NewTorrent.cs index f3cd710..0ebc44f 100644 --- a/Transmission.API.RPC/Arguments/NewTorrent.cs +++ b/Transmission.API.RPC/Arguments/NewTorrent.cs @@ -41,17 +41,17 @@ public class NewTorrent : ArgumentsBase /// <summary> /// if true, don't start the torrent /// </summary> - public bool? Paused { get { return GetValue<bool>("paused"); } set { this["paused"] = value; } } + public bool Paused { get { return GetValue<bool>("paused"); } set { this["paused"] = value; } } /// <summary> /// maximum number of peers /// </summary> - public long? PeerLimit { get { return GetValue<long?>("peer-limit"); } set { this["peer-limit"] = value; } } + public long PeerLimit { get { return GetValue<long>("peer-limit"); } set { this["peer-limit"] = value; } } /// <summary> /// Torrent's bandwidth priority /// </summary> - public long? BandwidthPriority { get { return GetValue<long?>("bandwidthPriority"); } set { this["bandwidthPriority"] = value; } } + public long BandwidthPriority { get { return GetValue<long>("bandwidthPriority"); } set { this["bandwidthPriority"] = value; } } /// <summary> /// Indices of file(s) to download diff --git a/Transmission.API.RPC/Arguments/SessionSettings.cs b/Transmission.API.RPC/Arguments/SessionSettings.cs index d442905..318ccf1 100644 --- a/Transmission.API.RPC/Arguments/SessionSettings.cs +++ b/Transmission.API.RPC/Arguments/SessionSettings.cs @@ -17,37 +17,37 @@ public class SessionSettings : ArgumentsBase /// <summary> /// Max global download speed (KBps) /// </summary> - public long? AlternativeSpeedDown { get { return GetValue<long?>("alt-speed-down"); } set { this["alt-speed-down"] = value; } } + public long AlternativeSpeedDown { get { return GetValue<long>("alt-speed-down"); } set { this["alt-speed-down"] = value; } } /// <summary> /// True means use the alt speeds /// </summary> - public bool? AlternativeSpeedEnabled { get { return GetValue<bool?>("alt-speed-enabled"); } set { this["alt-speed-enabled"] = value; } } + public bool AlternativeSpeedEnabled { get { return GetValue<bool>("alt-speed-enabled"); } set { this["alt-speed-enabled"] = value; } } /// <summary> /// When to turn on alt speeds (units: minutes after midnight) /// </summary> - public long? AlternativeSpeedTimeBegin { get { return GetValue<long?>("alt-speed-time-begin"); } set { this["alt-speed-time-begin"] = value; } } + public long AlternativeSpeedTimeBegin { get { return GetValue<long>("alt-speed-time-begin"); } set { this["alt-speed-time-begin"] = value; } } /// <summary> /// True means the scheduled on/off times are used /// </summary> - public bool? AlternativeSpeedTimeEnabled { get { return GetValue<bool?>("alt-speed-time-enabled"); } set { this["bandwidthPriority"] = value; } } + public bool AlternativeSpeedTimeEnabled { get { return GetValue<bool>("alt-speed-time-enabled"); } set { this["bandwidthPriority"] = value; } } /// <summary> /// When to turn off alt speeds /// </summary> - public long? AlternativeSpeedTimeEnd { get { return GetValue<long?>("alt-speed-time-end"); } set { this["alt-speed-time-end"] = value; } } + public long AlternativeSpeedTimeEnd { get { return GetValue<long>("alt-speed-time-end"); } set { this["alt-speed-time-end"] = value; } } /// <summary> /// What day(s) to turn on alt speeds /// </summary> - public long? AlternativeSpeedTimeDay { get { return GetValue<long?>("alt-speed-time-day"); } set { this["alt-speed-time-day"] = value; } } + public long AlternativeSpeedTimeDay { get { return GetValue<long>("alt-speed-time-day"); } set { this["alt-speed-time-day"] = value; } } /// <summary> /// Max global upload speed (KBps) /// </summary> - public long? AlternativeSpeedUp { get { return GetValue<long?>("alt-speed-up"); } set { this["alt-speed-up"] = value; } } + public long AlternativeSpeedUp { get { return GetValue<long>("alt-speed-up"); } set { this["alt-speed-up"] = value; } } /// <summary> /// Location of the blocklist to use for "blocklist-update" @@ -57,12 +57,12 @@ public class SessionSettings : ArgumentsBase /// <summary> /// True means enabled /// </summary> - public bool? BlocklistEnabled { get { return GetValue<bool?>("blocklist-enabled"); } set { this["blocklist-enabled"] = value; } } + public bool BlocklistEnabled { get { return GetValue<bool>("blocklist-enabled"); } set { this["blocklist-enabled"] = value; } } /// <summary> /// Maximum size of the disk cache (MB) /// </summary> - public long? CacheSizeMb { get { return GetValue<long?>("cache-size-mb"); } set { this["cache-size-mb"] = value; } } + public long CacheSizeMb { get { return GetValue<long>("cache-size-mb"); } set { this["cache-size-mb"] = value; } } /// <summary> /// Announce URLs, one per line, and a blank line between tiers @@ -77,17 +77,17 @@ public class SessionSettings : ArgumentsBase /// <summary> /// Max number of torrents to download at once (see download-queue-enabled) /// </summary> - public long? DownloadQueueSize { get { return GetValue<long?>("download-queue-size"); } set { this["download-queue-size"] = value; } } + public long DownloadQueueSize { get { return GetValue<long>("download-queue-size"); } set { this["download-queue-size"] = value; } } /// <summary> /// If true, limit how many torrents can be downloaded at once /// </summary> - public bool? DownloadQueueEnabled { get { return GetValue<bool?>("download-queue-enabled"); } set { this["download-queue-enabled"] = value; } } + public bool DownloadQueueEnabled { get { return GetValue<bool>("download-queue-enabled"); } set { this["download-queue-enabled"] = value; } } /// <summary> /// True means allow dht in public torrents /// </summary> - public bool? DhtEnabled { get { return GetValue<bool?>("dht-enabled"); } set { this["dht-enabled"] = value; } } + public bool DhtEnabled { get { return GetValue<bool>("dht-enabled"); } set { this["dht-enabled"] = value; } } /// <summary> /// "required", "preferred", "tolerated" @@ -95,14 +95,14 @@ public class SessionSettings : ArgumentsBase public string Encryption { get { return GetValue<string>("encryption"); } set { this["encryption"] = value; } } /// <summary> - /// Torrents we're seeding will be stopped if they're idle for this long? + /// Torrents we're seeding will be stopped if they're idle for this long /// </summary> - public long? IdleSeedingLimit { get { return GetValue<long?>("idle-seeding-limit"); } set { this["idle-seeding-limit"] = value; } } + public long IdleSeedingLimit { get { return GetValue<long>("idle-seeding-limit"); } set { this["idle-seeding-limit"] = value; } } /// <summary> /// True if the seeding inactivity limit is honored by default /// </summary> - public bool? IdleSeedingLimitEnabled { get { return GetValue<bool?>("idle-seeding-limit-enabled"); } set { this["idle-seeding-limit-enabled"] = value; } } + public bool IdleSeedingLimitEnabled { get { return GetValue<bool>("idle-seeding-limit-enabled"); } set { this["idle-seeding-limit-enabled"] = value; } } /// <summary> /// Path for incomplete torrents, when enabled @@ -112,57 +112,57 @@ public class SessionSettings : ArgumentsBase /// <summary> /// True means keep torrents in incomplete-dir until done /// </summary> - public bool? IncompleteDirectoryEnabled { get { return GetValue<bool?>("incomplete-dir-enabled"); } set { this["incomplete-dir-enabled"] = value; } } + public bool IncompleteDirectoryEnabled { get { return GetValue<bool>("incomplete-dir-enabled"); } set { this["incomplete-dir-enabled"] = value; } } /// <summary> /// True means allow Local Peer Discovery in public torrents /// </summary> - public bool? LpdEnabled { get { return GetValue<bool?>("lpd-enabled"); } set { this["lpd-enabled"] = value; } } + public bool LpdEnabled { get { return GetValue<bool>("lpd-enabled"); } set { this["lpd-enabled"] = value; } } /// <summary> /// Maximum global number of peers /// </summary> - public long? PeerLimitGlobal { get { return GetValue<long?>("peer-limit-global"); } set { this["peer-limit-global"] = value; } } + public long PeerLimitGlobal { get { return GetValue<long>("peer-limit-global"); } set { this["peer-limit-global"] = value; } } /// <summary> /// Maximum global number of peers /// </summary> - public long? PeerLimitPerTorrent { get { return GetValue<long?>("peer-limit-per-torrent"); } set { this["peer-limit-per-torrent"] = value; } } + public long PeerLimitPerTorrent { get { return GetValue<long>("peer-limit-per-torrent"); } set { this["peer-limit-per-torrent"] = value; } } /// <summary> /// True means allow pex in public torrents /// </summary> - public bool? PexEnabled { get { return GetValue<bool?>("pex-enabled"); } set { this["pex-enabled"] = value; } } + public bool PexEnabled { get { return GetValue<bool>("pex-enabled"); } set { this["pex-enabled"] = value; } } /// <summary> /// Port number /// </summary> - public long? PeerPort { get { return GetValue<long?>("peer-port"); } set { this["peer-port"] = value; } } + public long PeerPort { get { return GetValue<long>("peer-port"); } set { this["peer-port"] = value; } } /// <summary> /// True means pick a random peer port on launch /// </summary> - public bool? PeerPortRandomOnStart { get { return GetValue<bool?>("peer-port-random-on-start"); } set { this["peer-port-random-on-start"] = value; } } + public bool PeerPortRandomOnStart { get { return GetValue<bool>("peer-port-random-on-start"); } set { this["peer-port-random-on-start"] = value; } } /// <summary> /// true means enabled /// </summary> - public bool? PortForwardingEnabled { get { return GetValue<bool?>("port-forwarding-enabled"); } set { this["port-forwarding-enabled"] = value; } } + public bool PortForwardingEnabled { get { return GetValue<bool>("port-forwarding-enabled"); } set { this["port-forwarding-enabled"] = value; } } /// <summary> /// Whether or not to consider idle torrents as stalled /// </summary> - public bool? QueueStalledEnabled { get { return GetValue<bool?>("queue-stalled-enabled"); } set { this["queue-stalled-enabled"] = value; } } + public bool QueueStalledEnabled { get { return GetValue<bool>("queue-stalled-enabled"); } set { this["queue-stalled-enabled"] = value; } } /// <summary> /// Torrents that are idle for N minuets aren't counted toward seed-queue-size or download-queue-size /// </summary> - public long? QueueStalledMinutes { get { return GetValue<long?>("queue-stalled-minutes"); } set { this["queue-stalled-minutes"] = value; } } + public long QueueStalledMinutes { get { return GetValue<long>("queue-stalled-minutes"); } set { this["queue-stalled-minutes"] = value; } } /// <summary> /// True means append ".part" to incomplete files /// </summary> - public bool? RenamePartialFiles { get { return GetValue<bool?>("rename-partial-files"); } set { this["rename-partial-files"] = value; } } + public bool RenamePartialFiles { get { return GetValue<bool>("rename-partial-files"); } set { this["rename-partial-files"] = value; } } /// <summary> /// Filename of the script to run @@ -172,7 +172,7 @@ public class SessionSettings : ArgumentsBase /// <summary> /// Whether or not to call the "added" script /// </summary> - public bool? ScriptTorrentAddedEnabled { get { return GetValue<bool?>("script-torrent-added-enabled"); } set { this["script-torrent-added-enabled"] = value; } } + public bool ScriptTorrentAddedEnabled { get { return GetValue<bool>("script-torrent-added-enabled"); } set { this["script-torrent-added-enabled"] = value; } } /// <summary> /// Filename of the script to run @@ -182,7 +182,7 @@ public class SessionSettings : ArgumentsBase /// <summary> /// Whether or not to call the "done" script /// </summary> - public bool? ScriptTorrentDoneEnabled { get { return GetValue<bool?>("script-torrent-done-enabled"); } set { this["script-torrent-done-enabled"] = value; } } + public bool ScriptTorrentDoneEnabled { get { return GetValue<bool>("script-torrent-done-enabled"); } set { this["script-torrent-done-enabled"] = value; } } /// <summary> /// Filename of the script to run @@ -192,62 +192,62 @@ public class SessionSettings : ArgumentsBase /// <summary> /// Whether or not to call the "done seeding" script /// </summary> - public bool? ScriptTorrentDoneSeedingEnabled { get { return GetValue<bool?>("script-torrent-done-seeding-enabled"); } set { this["script-torrent-done-seeding-enabled"] = value; } } + public bool ScriptTorrentDoneSeedingEnabled { get { return GetValue<bool>("script-torrent-done-seeding-enabled"); } set { this["script-torrent-done-seeding-enabled"] = value; } } /// <summary> /// The default seed ratio for torrents to use /// </summary> - public double? SeedRatioLimit { get { return GetValue<long?>("seedRatioLimit"); } set { this["seedRatioLimit"] = value; } } + public double SeedRatioLimit { get { return GetValue<long>("seedRatioLimit"); } set { this["seedRatioLimit"] = value; } } /// <summary> /// True if seedRatioLimit is honored by default /// </summary> - public bool? SeedRatioLimited { get { return GetValue<bool?>("seedRatioLimited"); } set { this["seedRatioLimited"] = value; } } + public bool SeedRatioLimited { get { return GetValue<bool>("seedRatioLimited"); } set { this["seedRatioLimited"] = value; } } /// <summary> /// Max number of torrents to uploaded at once (see seed-queue-enabled) /// </summary> - public long? SeedQueueSize { get { return GetValue<long?>("seed-queue-size"); } set { this["seed-queue-size"] = value; } } + public long SeedQueueSize { get { return GetValue<long>("seed-queue-size"); } set { this["seed-queue-size"] = value; } } /// <summary> /// If true, limit how many torrents can be uploaded at once /// </summary> - public bool? SeedQueueEnabled { get { return GetValue<bool?>("seed-queue-enabled"); } set { this["seed-queue-enabled"] = value; } } + public bool SeedQueueEnabled { get { return GetValue<bool>("seed-queue-enabled"); } set { this["seed-queue-enabled"] = value; } } /// <summary> /// Max global download speed (KBps) /// </summary> - public long? SpeedLimitDown { get { return GetValue<long?>("speed-limit-down"); } set { this["speed-limit-down"] = value; } } + public long SpeedLimitDown { get { return GetValue<long>("speed-limit-down"); } set { this["speed-limit-down"] = value; } } /// <summary> /// True means enabled /// </summary> - public bool? SpeedLimitDownEnabled { get { return GetValue<bool?>("speed-limit-down-enabled"); } set { this["speed-limit-down-enabled"] = value; } } + public bool SpeedLimitDownEnabled { get { return GetValue<bool>("speed-limit-down-enabled"); } set { this["speed-limit-down-enabled"] = value; } } /// <summary> /// max global upload speed (KBps) /// </summary> - public long? SpeedLimitUp { get { return GetValue<long?>("speed-limit-up"); } set { this["speed-limit-up"] = value; } } + public long SpeedLimitUp { get { return GetValue<long>("speed-limit-up"); } set { this["speed-limit-up"] = value; } } /// <summary> /// True means enabled /// </summary> - public bool? SpeedLimitUpEnabled { get { return GetValue<bool?>("speed-limit-up-enabled"); } set { this["speed-limit-up-enabled"] = value; } } + public bool SpeedLimitUpEnabled { get { return GetValue<bool>("speed-limit-up-enabled"); } set { this["speed-limit-up-enabled"] = value; } } /// <summary> /// True means added torrents will be started right away /// </summary> - public bool? StartAddedTorrents { get { return GetValue<bool?>("start-added-torrents"); } set { this["start-added-torrents"] = value; } } + public bool StartAddedTorrents { get { return GetValue<bool>("start-added-torrents"); } set { this["start-added-torrents"] = value; } } /// <summary> /// True means the .torrent file of added torrents will be deleted /// </summary> - public bool? TrashOriginalTorrentFiles { get { return GetValue<bool?>("trash-original-torrent-files"); } set { this["trash-original-torrent-files"] = value; } } + public bool TrashOriginalTorrentFiles { get { return GetValue<bool>("trash-original-torrent-files"); } set { this["trash-original-torrent-files"] = value; } } /// <summary> /// True means allow utp /// </summary> - public bool? UtpEnabled { get { return GetValue<bool?>("utp-enabled"); } set { this["utp-enabled"] = value; } } + public bool UtpEnabled { get { return GetValue<bool>("utp-enabled"); } set { this["utp-enabled"] = value; } } } } diff --git a/Transmission.API.RPC/Arguments/TorrentFields.cs b/Transmission.API.RPC/Arguments/TorrentFields.cs index a04c856..cbc2be9 100644 --- a/Transmission.API.RPC/Arguments/TorrentFields.cs +++ b/Transmission.API.RPC/Arguments/TorrentFields.cs @@ -405,7 +405,7 @@ public static string[] ALL_FIELDS { get { - return new string[] + return new string[] { #region ALL FIELDS ACTIVITY_DATE, diff --git a/Transmission.API.RPC/Arguments/TorrentSettings.cs b/Transmission.API.RPC/Arguments/TorrentSettings.cs index b0e1276..3a2ec1f 100644 --- a/Transmission.API.RPC/Arguments/TorrentSettings.cs +++ b/Transmission.API.RPC/Arguments/TorrentSettings.cs @@ -17,17 +17,17 @@ public class TorrentSettings : ArgumentsBase /// <summary> /// This torrent's bandwidth tr_priority_t /// </summary> - public long? BandwidthPriority { get { return GetValue<long?>("bandwidthPriority"); } set { this["bandwidthPriority"] = value; } } + public long BandwidthPriority { get { return GetValue<long>("bandwidthPriority"); } set { this["bandwidthPriority"] = value; } } /// <summary> /// Maximum download speed (KBps) /// </summary> - public long? DownloadLimit { get { return GetValue<long?>("downloadLimit"); } set { this["downloadLimit"] = value; } } + public long DownloadLimit { get { return GetValue<long>("downloadLimit"); } set { this["downloadLimit"] = value; } } /// <summary> /// Download limit is honored /// </summary> - public bool? DownloadLimited { get { return GetValue<bool?>("downloadLimited"); } set { this["downloadLimited"] = value; } } + public bool DownloadLimited { get { return GetValue<bool>("downloadLimited"); } set { this["downloadLimited"] = value; } } /// <summary> /// The name of this torrent's bandwidth group @@ -37,7 +37,7 @@ public class TorrentSettings : ArgumentsBase /// <summary> /// Session upload limits are honored /// </summary> - public bool? HonorsSessionLimits { get { return GetValue<bool?>("honorsSessionLimits"); } set { this["honorsSessionLimits"] = value; } } + public bool HonorsSessionLimits { get { return GetValue<bool>("honorsSessionLimits"); } set { this["honorsSessionLimits"] = value; } } /// <summary> /// Torrent id array @@ -57,50 +57,50 @@ public class TorrentSettings : ArgumentsBase /// <summary> /// Maximum number of peers /// </summary> - public long? PeerLimit { get { return GetValue<long?>("peer-limit"); } set { this["peer-limit"] = value; } } + public long PeerLimit { get { return GetValue<long>("peer-limit"); } set { this["peer-limit"] = value; } } /// <summary> /// Position of this torrent in its queue [0...n) /// </summary> - public long? QueuePosition { get { return GetValue<long?>("queuePosition"); } set { this["queuePosition"] = value; } } + public long QueuePosition { get { return GetValue<long>("queuePosition"); } set { this["queuePosition"] = value; } } /// <summary> /// Torrent-level number of minutes of seeding inactivity /// </summary> - public long? SeedIdleLimit { get { return GetValue<long?>("seedIdleLimit"); } set { this["seedIdleLimit"] = value; } } + public long SeedIdleLimit { get { return GetValue<long>("seedIdleLimit"); } set { this["seedIdleLimit"] = value; } } /// <summary> /// Which seeding inactivity mode to use. 0=Global 1=Single 2=Unlimited /// </summary> - public long? SeedIdleMode { get { return GetValue<long?>("seedIdleMode"); } set { this["seedIdleMode"] = value; } } + public long SeedIdleMode { get { return GetValue<long>("seedIdleMode"); } set { this["seedIdleMode"] = value; } } /// <summary> /// Torrent-level seeding ratio /// </summary> - public double? SeedRatioLimit { get { return GetValue<double?>("seedRatioLimit"); } set { this["seedRatioLimit"] = value; } } + public double SeedRatioLimit { get { return GetValue<double>("seedRatioLimit"); } set { this["seedRatioLimit"] = value; } } /// <summary> /// Which ratio mode to use. 0=Global 1=Single 2=Unlimited /// </summary> - public long? SeedRatioMode { get { return GetValue<long?>("seedRatioMode"); } set { this["seedRatioMode"] = value; } } + public long SeedRatioMode { get { return GetValue<long>("seedRatioMode"); } set { this["seedRatioMode"] = value; } } /// <summary> /// Whether to download the torrent sequentially /// </summary> - public bool? SequentialDownload { get { return GetValue<bool?>("sequentialDownload"); } set { this["sequentialDownload"] = value; } } + public bool SequentialDownload { get { return GetValue<bool>("sequentialDownload"); } set { this["sequentialDownload"] = value; } } /// <summary> /// Maximum upload speed (KBps) /// </summary> - public long? UploadLimit { get { return GetValue<long?>("uploadLimit"); } set { this["uploadLimit"] = value; } } + public long UploadLimit { get { return GetValue<long>("uploadLimit"); } set { this["uploadLimit"] = value; } } /// <summary> /// Upload limit is honored /// </summary> - public bool? UploadLimited { get { return GetValue<bool?>("uploadLimited"); } set { this["uploadLimited"] = value; } } + public bool UploadLimited { get { return GetValue<bool>("uploadLimited"); } set { this["uploadLimited"] = value; } } /// <summary> - /// Strings of announce URLs to add + /// strings of announce URLs to add /// </summary> [Obsolete("TrackerAdd is obsolete since Transmission 4.0.0, use TrackerList instead.")] public string[] TrackerAdd { get { return GetValue<string[]>("trackerAdd"); } set { this["trackerAdd"] = value; } } @@ -109,16 +109,16 @@ public class TorrentSettings : ArgumentsBase /// Ids of trackers to remove /// </summary> [Obsolete("TrackerRemove is obsolete since Transmission 4.0.0, use TrackerList instead.")] - public long?[] TrackerRemove { get { return GetValue<long?[]>("trackerRemove"); } set { this["trackerRemove"] = value; } } + public long[] TrackerRemove { get { return GetValue<long[]>("trackerRemove"); } set { this["trackerRemove"] = value; } } /// <summary> /// Pairs of IDs of announce URLs to replace along with their new value /// </summary> [Obsolete("TrackerReplace is obsolete since Transmission 4.0.0, use TrackerList instead.")] - public KeyValuePair<int, string>?[] TrackerReplace { get { return GetValue<KeyValuePair<int, string>?[]>("trackerReplace"); } set { this["trackerReplace"] = value; } } + public KeyValuePair<int, string>[] TrackerReplace { get { return GetValue<KeyValuePair<int, string>[]>("trackerReplace"); } set { this["trackerReplace"] = value; } } /// <summary> - /// String of announce URLs, one per line, with a blank line between tiers + /// string of announce URLs, one per line, with a blank line between tiers /// </summary> public string[] TrackerList { get { return GetValue<string[]>("trackerList"); } set { this["trackerList"] = value; } } diff --git a/Transmission.API.RPC/Client.Async.cs b/Transmission.API.RPC/Client.Async.cs index fbcbd3c..318c5b4 100644 --- a/Transmission.API.RPC/Client.Async.cs +++ b/Transmission.API.RPC/Client.Async.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -42,11 +43,11 @@ public async Task SetSessionSettingsAsync(SessionSettings settings) /// Get session stat /// </summary> /// <returns>Session stat</returns> - public async Task<Statistic> GetSessionStatisticAsync() + public async Task<Statistic?> GetSessionStatisticAsync() { var request = new TransmissionRequest("session-stats"); var response = await SendRequestAsync(request); - var result = response.Deserialize<Statistic>(); + var result = response?.Arguments.ToObject<Statistic>(); return result; } @@ -54,11 +55,11 @@ public async Task<Statistic> GetSessionStatisticAsync() /// Get information of current session (API: session-get) /// </summary> /// <returns>Session information</returns> - public async Task<SessionInfo> GetSessionInformationAsync() + public async Task<SessionInfo?> GetSessionInformationAsync() { var request = new TransmissionRequest("session-get"); var response = await SendRequestAsync(request); - var result = response.Deserialize<SessionInfo>(); + var result = response?.Arguments.ToObject<SessionInfo>(); return result; } @@ -69,14 +70,14 @@ public async Task<SessionInfo> GetSessionInformationAsync() /// </summary> /// <param name="fields">Optional fields of session information</param> /// <returns>Session information</returns> - public async Task<SessionInfo> GetSessionInformationAsync(string[] fields) + public async Task<SessionInfo?> GetSessionInformationAsync(string[] fields) { var arguments = new Dictionary<string, object>(); arguments.Add("fields", fields); var request = new TransmissionRequest("session-get", arguments); var response = await SendRequestAsync(request); - var result = response.Deserialize<SessionInfo>(); + var result = response?.Arguments.ToObject<SessionInfo>(); return result; } #endregion @@ -87,30 +88,30 @@ public async Task<SessionInfo> GetSessionInformationAsync(string[] fields) /// Add torrent (API: torrent-add) /// </summary> /// <returns>Torrent info (ID, Name and HashString)</returns> - public async Task<NewTorrentInfo> TorrentAddAsync(NewTorrent torrent) + public async Task<NewTorrentInfo?> TorrentAddAsync(NewTorrent torrent) { if (String.IsNullOrWhiteSpace(torrent.Metainfo) && String.IsNullOrWhiteSpace(torrent.Filename)) - throw new Exception("Either \"filename\" or \"metainfo\" must be included."); + throw new ArgumentException("Either \"filename\" or \"metainfo\" must be included."); var request = new TransmissionRequest("torrent-add", torrent); var response = await SendRequestAsync(request); - var jObject = response.Deserialize<JObject>(); + var jObject = response?.Arguments; if (jObject == null || jObject.First == null) return null; - NewTorrentInfo result = null; - JToken value = null; + NewTorrentInfo? result = null; + JToken? value = null; if (jObject.TryGetValue("torrent-duplicate", out value)) { result = JsonConvert.DeserializeObject<NewTorrentInfo>(value.ToString()); - result.Duplicate = true; + if (result != null) result.Duplicate = true; } else if (jObject.TryGetValue("torrent-added", out value)) { result = JsonConvert.DeserializeObject<NewTorrentInfo>(value.ToString()); - result.Duplicate = false; + if (result != null) result.Duplicate = false; } return result; @@ -130,41 +131,57 @@ public async Task TorrentSetAsync(TorrentSettings settings) /// Get fields of recently active torrents (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> + /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <returns>Torrents info</returns> - public async Task<TransmissionTorrents> TorrentGetRecentlyActiveAsync(string[] fields) + public async Task<TransmissionTorrents?> TorrentGetRecentlyActiveAsync(string[] fields, bool asObjects = false) { var arguments = new Dictionary<string, object>(); arguments.Add("fields", fields); arguments.Add("ids", "recently-active"); - var request = new TransmissionRequest("torrent-get", arguments); + if (asObjects) arguments.Add("format", "objects"); + else arguments.Add("format", "table"); + var request = new TransmissionRequest("torrent-get", arguments); var response = await SendRequestAsync(request); - var result = response.Deserialize<TransmissionTorrents>(); - return result; + if (response == null) return null; + + TransmissionTorrents? torrents; + if (asObjects) torrents = response.Arguments.ToObject<TransmissionTorrents>(); + else torrents = TorrentInfoConverter.ConvertFromJObject(response.Arguments); + + return torrents; } /// <summary> /// Get fields of torrents from ids (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> + /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <param name="ids">IDs of torrents (null or empty for get all torrents)</param> /// <returns>Torrents info</returns> - public async Task<TransmissionTorrents> TorrentGetAsync(string[] fields, params int[] ids) + public async Task<TransmissionTorrents?> TorrentGetAsync(string[] fields, bool asObjects = false, params long[] ids) { var arguments = new Dictionary<string, object>(); arguments.Add("fields", fields); + if (asObjects) arguments.Add("format", "objects"); + else arguments.Add("format", "table"); + if (ids != null && ids.Length > 0) arguments.Add("ids", ids); var request = new TransmissionRequest("torrent-get", arguments); - var response = await SendRequestAsync(request); - var result = response.Deserialize<TransmissionTorrents>(); - return result; + if (response == null) return null; + + TransmissionTorrents? torrents; + if (asObjects) torrents = response?.Arguments.ToObject<TransmissionTorrents>(); + else torrents = TorrentInfoConverter.ConvertFromJObject(response.Arguments); + + return torrents; } /// <summary> @@ -172,7 +189,7 @@ public async Task<TransmissionTorrents> TorrentGetAsync(string[] fields, params /// </summary> /// <param name="ids">Torrents id</param> /// <param name="deleteData">Remove data</param> - public async Task TorrentRemoveAsync(int[] ids, bool deleteData = false) + public async Task TorrentRemoveAsync(long[] ids, bool deleteData = false) { var arguments = new Dictionary<string, object>(); @@ -304,7 +321,7 @@ public async Task TorrentReannounceRecentlyActiveAsync() /// Move torrents in queue on top (API: queue-move-top) /// </summary> /// <param name="ids">Torrents id</param> - public async Task TorrentQueueMoveTopAsync(int[] ids) + public async Task TorrentQueueMoveTopAsync(long[] ids) { var request = new TransmissionRequest("queue-move-top", new Dictionary<string, object> { { "ids", ids } }); var response = await SendRequestAsync(request); @@ -314,7 +331,7 @@ public async Task TorrentQueueMoveTopAsync(int[] ids) /// Move up torrents in queue (API: queue-move-up) /// </summary> /// <param name="ids"></param> - public async Task TorrentQueueMoveUpAsync(int[] ids) + public async Task TorrentQueueMoveUpAsync(long[] ids) { var request = new TransmissionRequest("queue-move-up", new Dictionary<string, object> { { "ids", ids } }); var response = await SendRequestAsync(request); @@ -324,7 +341,7 @@ public async Task TorrentQueueMoveUpAsync(int[] ids) /// Move down torrents in queue (API: queue-move-down) /// </summary> /// <param name="ids"></param> - public async Task TorrentQueueMoveDownAsync(int[] ids) + public async Task TorrentQueueMoveDownAsync(long[] ids) { var request = new TransmissionRequest("queue-move-down", new Dictionary<string, object> { { "ids", ids } }); var response = await SendRequestAsync(request); @@ -334,7 +351,7 @@ public async Task TorrentQueueMoveDownAsync(int[] ids) /// Move torrents to bottom in queue (API: queue-move-bottom) /// </summary> /// <param name="ids"></param> - public async Task TorrentQueueMoveBottomAsync(int[] ids) + public async Task TorrentQueueMoveBottomAsync(long[] ids) { var request = new TransmissionRequest("queue-move-bottom", new Dictionary<string, object> { { "ids", ids } }); var response = await SendRequestAsync(request); @@ -348,7 +365,7 @@ public async Task TorrentQueueMoveBottomAsync(int[] ids) /// <param name="ids">Torrent ids</param> /// <param name="location">The new torrent location</param> /// <param name="move">Move from previous location</param> - public async Task TorrentSetLocationAsync(int[] ids, string location, bool move) + public async Task TorrentSetLocationAsync(long[] ids, string location, bool move) { var arguments = new Dictionary<string, object>(); arguments.Add("ids", ids); @@ -365,17 +382,17 @@ public async Task TorrentSetLocationAsync(int[] ids, string location, bool move) /// <param name="id">The torrent whose path will be renamed</param> /// <param name="path">The path to the file or folder that will be renamed</param> /// <param name="name">The file or folder's new name</param> - public async Task<RenameTorrentInfo> TorrentRenamePathAsync(int id, string path, string name) + public async Task<RenameTorrentInfo?> TorrentRenamePathAsync(long id, string path, string name) { var arguments = new Dictionary<string, object>(); - arguments.Add("ids", new int[] { id }); + arguments.Add("ids", new long[] { id }); arguments.Add("path", path); arguments.Add("name", name); var request = new TransmissionRequest("torrent-rename-path", arguments); var response = await SendRequestAsync(request); - var result = response.Deserialize<RenameTorrentInfo>(); + var result = response?.Arguments.ToObject<RenameTorrentInfo>(); return result; } @@ -388,12 +405,12 @@ public async Task<RenameTorrentInfo> TorrentRenamePathAsync(int id, string path, /// Get bandwidth groups (API: group-get) /// </summary> /// <returns></returns> - public async Task<BandwidthGroup[]> BandwidthGroupGetAsync() + public async Task<BandwidthGroup[]?> BandwidthGroupGetAsync() { var request = new TransmissionRequest("group-get"); var response = await SendRequestAsync(request); - var result = response.Deserialize<BandwidthGroup[]>(); + var result = response?.Arguments.ToObject<BandwidthGroup[]>(); return result; } @@ -403,7 +420,7 @@ public async Task<BandwidthGroup[]> BandwidthGroupGetAsync() /// </summary> /// <param name="groups">Optional names of groups to get</param> /// <returns></returns> - public async Task<BandwidthGroup[]> BandwidthGroupGetAsync(string[] groups) + public async Task<BandwidthGroup[]?> BandwidthGroupGetAsync(string[] groups) { var arguments = new Dictionary<string, object>(); arguments.Add("group", groups); @@ -411,7 +428,7 @@ public async Task<BandwidthGroup[]> BandwidthGroupGetAsync(string[] groups) var request = new TransmissionRequest("group-get", arguments); var response = await SendRequestAsync(request); - var result = response.Deserialize<BandwidthGroup[]>(); + var result = response?.Arguments.ToObject<BandwidthGroup[]>(); return result; } @@ -434,36 +451,39 @@ public async Task BandwidthGroupSetAsync(BandwidthGroupSettings group) /// See if your incoming peer port is accessible from the outside world (API: port-test) /// </summary> /// <returns>A Tuple with a boolean of whether the port test succeeded, and a PortTestProtocol enum of which protocol was used for the test</returns> - public async Task<Tuple<bool, PortTestProtocol>> PortTestAsync() + public async Task<Tuple<bool?, PortTestProtocol>> PortTestAsync() { var request = new TransmissionRequest("port-test"); var response = await SendRequestAsync(request); - var data = response.Deserialize<JObject>(); - var result = (bool)data.GetValue("port-is-open"); + var data = response?.Arguments; + var result = (bool?)data?.GetValue("port-is-open"); PortTestProtocol protocol = PortTestProtocol.Unknown; - if (data.TryGetValue("ipProtocol", out var protocolValue)) + if (data?.TryGetValue("ipProtocol", out var protocolValue) == true) { - switch ((string)protocolValue) + if (protocolValue != null) { - case "ipv4": protocol = PortTestProtocol.IPv4; break; - case "ipv6": protocol = PortTestProtocol.IPV6; break; + switch ((string?)protocolValue) + { + case "ipv4": protocol = PortTestProtocol.IPv4; break; + case "ipv6": protocol = PortTestProtocol.IPV6; break; + } } } - return new Tuple<bool, PortTestProtocol>(result, protocol); + return new Tuple<bool?, PortTestProtocol>(result, protocol); } /// <summary> /// Update blocklist (API: blocklist-update) /// </summary> /// <returns>Blocklist size</returns> - public async Task<int> BlocklistUpdateAsync() + public async Task<long?> BlocklistUpdateAsync() { var request = new TransmissionRequest("blocklist-update"); var response = await SendRequestAsync(request); - var data = response.Deserialize<JObject>(); - var result = (int)data.GetValue("blocklist-size"); + var data = response?.Arguments; + var result = (long?)data?.GetValue("blocklist-size"); return result; } @@ -471,7 +491,7 @@ public async Task<int> BlocklistUpdateAsync() /// Get free space is available in a client-specified folder. /// </summary> /// <param name="path">The directory to query</param> - public async Task<FreeSpace> FreeSpaceAsync(string path) + public async Task<FreeSpace?> FreeSpaceAsync(string path) { var arguments = new Dictionary<string, object>(); arguments.Add("path", path); @@ -479,15 +499,15 @@ public async Task<FreeSpace> FreeSpaceAsync(string path) var request = new TransmissionRequest("free-space", arguments); var response = await SendRequestAsync(request); - var data = response.Deserialize<FreeSpace>(); + var data = response?.Arguments.ToObject<FreeSpace>(); return data; } #endregion - private async Task<TransmissionResponse> SendRequestAsync(TransmissionRequest request) + private async Task<TransmissionResponse?> SendRequestAsync(TransmissionRequest request) { - TransmissionResponse result = new TransmissionResponse(); + TransmissionResponse? result = null; request.Tag = ++CurrentTag; @@ -508,10 +528,10 @@ private async Task<TransmissionResponse> SendRequestAsync(TransmissionRequest re if (httpResponse.IsSuccessStatusCode) { var responseString = await httpResponse.Content.ReadAsStringAsync(); - result = JsonConvert.DeserializeObject<TransmissionResponse>(responseString); + result = new TransmissionResponse(responseString); if (result.Result != "success") - throw new Exception(result.Result); + throw new RequestFailedException(result.Result); } else if (httpResponse.StatusCode == HttpStatusCode.Conflict) { @@ -521,7 +541,7 @@ private async Task<TransmissionResponse> SendRequestAsync(TransmissionRequest re if (httpResponse.Headers.TryGetValues("X-Transmission-Session-Id", out var values)) SessionID = values.First(); else - throw new Exception("Session ID Error"); + throw new SessionIdException(); result = await SendRequestAsync(request); } diff --git a/Transmission.API.RPC/Client.cs b/Transmission.API.RPC/Client.cs index abb142d..3a765e3 100644 --- a/Transmission.API.RPC/Client.cs +++ b/Transmission.API.RPC/Client.cs @@ -36,7 +36,7 @@ public string SessionID /// <summary> /// Current Tag /// </summary> - public int CurrentTag + public long CurrentTag { get; private set; @@ -150,10 +150,11 @@ public void TorrentSet(TorrentSettings settings) /// Get fields of recently active torrents (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> + /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <returns>Torrents info</returns> - public TransmissionTorrents TorrentGetRecentlyActive(string[] fields) + public TransmissionTorrents TorrentGetRecentlyActive(string[] fields, bool asObjects = false) { - var task = TorrentGetRecentlyActiveAsync(fields); + var task = TorrentGetRecentlyActiveAsync(fields, asObjects); task.WaitAndUnwrapException(); return task.Result; } @@ -162,11 +163,12 @@ public TransmissionTorrents TorrentGetRecentlyActive(string[] fields) /// Get fields of torrents from ids (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> + /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <param name="ids">IDs of torrents (null or empty for get all torrents)</param> /// <returns>Torrents info</returns> - public TransmissionTorrents TorrentGet(string[] fields, params int[] ids) + public TransmissionTorrents TorrentGet(string[] fields, bool asObjects = false, params long[] ids) { - var task = TorrentGetAsync(fields, ids); + var task = TorrentGetAsync(fields, asObjects, ids); task.WaitAndUnwrapException(); return task.Result; } @@ -176,7 +178,7 @@ public TransmissionTorrents TorrentGet(string[] fields, params int[] ids) /// </summary> /// <param name="ids">Torrents id</param> /// <param name="deleteData">Remove data</param> - public void TorrentRemove(int[] ids, bool deleteData = false) + public void TorrentRemove(long[] ids, bool deleteData = false) { TorrentRemoveAsync(ids, deleteData).WaitAndUnwrapException(); } @@ -287,7 +289,7 @@ public void TorrentReannounceRecentlyActive() /// Move torrents in queue on top (API: queue-move-top) /// </summary> /// <param name="ids">Torrents id</param> - public void TorrentQueueMoveTop(int[] ids) + public void TorrentQueueMoveTop(long[] ids) { TorrentQueueMoveTopAsync(ids).WaitAndUnwrapException(); } @@ -296,7 +298,7 @@ public void TorrentQueueMoveTop(int[] ids) /// Move up torrents in queue (API: queue-move-up) /// </summary> /// <param name="ids"></param> - public void TorrentQueueMoveUp(int[] ids) + public void TorrentQueueMoveUp(long[] ids) { TorrentQueueMoveUpAsync(ids).WaitAndUnwrapException(); } @@ -305,7 +307,7 @@ public void TorrentQueueMoveUp(int[] ids) /// Move down torrents in queue (API: queue-move-down) /// </summary> /// <param name="ids"></param> - public void TorrentQueueMoveDown(int[] ids) + public void TorrentQueueMoveDown(long[] ids) { TorrentQueueMoveDownAsync(ids).WaitAndUnwrapException(); } @@ -314,7 +316,7 @@ public void TorrentQueueMoveDown(int[] ids) /// Move torrents to bottom in queue (API: queue-move-bottom) /// </summary> /// <param name="ids"></param> - public void TorrentQueueMoveBottom(int[] ids) + public void TorrentQueueMoveBottom(long[] ids) { TorrentQueueMoveBottomAsync(ids).WaitAndUnwrapException(); } @@ -327,7 +329,7 @@ public void TorrentQueueMoveBottom(int[] ids) /// <param name="ids">Torrent ids</param> /// <param name="location">The new torrent location</param> /// <param name="move">Move from previous location</param> - public void TorrentSetLocation(int[] ids, string location, bool move) + public void TorrentSetLocation(long[] ids, string location, bool move) { TorrentSetLocationAsync(ids, location, move).WaitAndUnwrapException(); } @@ -338,7 +340,7 @@ public void TorrentSetLocation(int[] ids, string location, bool move) /// <param name="id">The torrent whose path will be renamed</param> /// <param name="path">The path to the file or folder that will be renamed</param> /// <param name="name">The file or folder's new name</param> - public RenameTorrentInfo TorrentRenamePath(int id, string path, string name) + public RenameTorrentInfo TorrentRenamePath(long id, string path, string name) { var task = TorrentRenamePathAsync(id, path, name); task.WaitAndUnwrapException(); @@ -388,7 +390,7 @@ public void BandwidthGroupSet(BandwidthGroupSettings group) /// See if your incoming peer port is accessible from the outside world (API: port-test) /// </summary> /// <returns>Accessible state</returns> - public Tuple<bool, PortTestProtocol> PortTest() + public Tuple<bool?, PortTestProtocol> PortTest() { var task = PortTestAsync(); task.WaitAndUnwrapException(); @@ -399,7 +401,7 @@ public Tuple<bool, PortTestProtocol> PortTest() /// Update blocklist (API: blocklist-update) /// </summary> /// <returns>Blocklist size</returns> - public int BlocklistUpdate() + public long? BlocklistUpdate() { var task = BlocklistUpdateAsync(); task.WaitAndUnwrapException(); diff --git a/Transmission.API.RPC/Common/CommunicateBase.cs b/Transmission.API.RPC/Common/CommunicateBase.cs deleted file mode 100644 index ed999fa..0000000 --- a/Transmission.API.RPC/Common/CommunicateBase.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Transmission.API.RPC.Common -{ - /// <summary> - /// Base class for request/response - /// </summary> - public abstract class CommunicateBase - { - /// <summary> - /// Data - /// </summary> - [JsonProperty("arguments")] - public Dictionary<string, object> Arguments; - - /// <summary> - /// Number (id) - /// </summary> - [JsonProperty("tag")] - public int Tag; - - /// <summary> - /// Convert to JSON string - /// </summary> - /// <returns></returns> - public virtual string ToJson() - { - return JsonConvert.SerializeObject(this, Formatting.Indented); - } - - /// <summary> - /// Deserialize to class - /// </summary> - /// <returns></returns> - public T Deserialize<T>() - { - var argumentsString = JsonConvert.SerializeObject(this.Arguments); - return JsonConvert.DeserializeObject<T>(argumentsString); - } - } -} diff --git a/Transmission.API.RPC/Common/TransmissionRequest.cs b/Transmission.API.RPC/Common/TransmissionRequest.cs index 6396a8a..597a52c 100644 --- a/Transmission.API.RPC/Common/TransmissionRequest.cs +++ b/Transmission.API.RPC/Common/TransmissionRequest.cs @@ -10,7 +10,7 @@ namespace Transmission.API.RPC.Common /// <summary> /// Transmission request /// </summary> - public class TransmissionRequest : CommunicateBase + public class TransmissionRequest { /// <summary> /// Name of the method to invoke @@ -18,6 +18,18 @@ public class TransmissionRequest : CommunicateBase [JsonProperty("method")] public string Method; + /// <summary> + /// Data + /// </summary> + [JsonProperty("arguments")] + public Dictionary<string, object> Arguments; + + /// <summary> + /// Number (id) + /// </summary> + [JsonProperty("tag")] + public long Tag; + /// <summary> /// Initialize request /// </summary> @@ -48,5 +60,14 @@ public TransmissionRequest(string method, Dictionary<string, object> arguments) this.Method = method; this.Arguments = arguments; } - } + + /// <summary> + /// Convert to JSON string + /// </summary> + /// <returns></returns> + public virtual string ToJson() + { + return JsonConvert.SerializeObject(this, Formatting.Indented); + } + } } diff --git a/Transmission.API.RPC/Common/TransmissionResponse.cs b/Transmission.API.RPC/Common/TransmissionResponse.cs index 9fde714..d3c29b7 100644 --- a/Transmission.API.RPC/Common/TransmissionResponse.cs +++ b/Transmission.API.RPC/Common/TransmissionResponse.cs @@ -1,6 +1,8 @@ using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -10,12 +12,51 @@ namespace Transmission.API.RPC.Common /// <summary> /// Transmission response /// </summary> - public class TransmissionResponse : CommunicateBase + public class TransmissionResponse { /// <summary> /// Contains "success" on success, or an error string on failure. /// </summary> - [JsonProperty("result")] - public string Result; - } + public string Result { get; } + + /// <summary> + /// Uniquely identifies which request this is a response to + /// </summary> + public int? Tag; + + /// <summary> + /// Data + /// </summary> + public JObject Arguments { get; } + + public TransmissionResponse(string json) + { + using var stringReader = new StringReader(json); + using var jsonReader = new JsonTextReader(stringReader); + + while (jsonReader.Read()) + { + if (jsonReader.TokenType == JsonToken.PropertyName) + { + if (jsonReader.Value.ToString() == "result") + { + jsonReader.Read(); + Result = jsonReader.Value.ToString(); + } + + else if (jsonReader.Value.ToString() == "tag") + { + jsonReader.Read(); + Tag = jsonReader.Value != null ? (int?)Convert.ToInt32(jsonReader.Value) : null; + } + + else if (jsonReader.Value.ToString() == "arguments") + { + jsonReader.Read(); + Arguments = JObject.Load(jsonReader); + } + } + } + } + } } diff --git a/Transmission.API.RPC/Entity/BandwidthGroup.cs b/Transmission.API.RPC/Entity/BandwidthGroup.cs index df48db6..3ad96de 100644 --- a/Transmission.API.RPC/Entity/BandwidthGroup.cs +++ b/Transmission.API.RPC/Entity/BandwidthGroup.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +#nullable enable +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Text; @@ -20,7 +21,7 @@ public class BandwidthGroup /// Name of the bandwidth group /// </summary> [JsonProperty("name")] - public string Name { get; set; } + public string? Name { get; set; } /// <summary> /// Max global download speed of this bandwidth group (KBps) diff --git a/Transmission.API.RPC/Entity/FreeSpace.cs b/Transmission.API.RPC/Entity/FreeSpace.cs index 71b80b2..5addb32 100644 --- a/Transmission.API.RPC/Entity/FreeSpace.cs +++ b/Transmission.API.RPC/Entity/FreeSpace.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +#nullable enable +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -16,7 +17,7 @@ public class FreeSpace /// Path of the queried directory /// </summary> [JsonProperty("path")] - public string Path { get; set; } + public string? Path { get; set; } /// <summary> /// The size, in bytes, of the free space in that directory diff --git a/Transmission.API.RPC/Entity/IntOrArrayConverter.cs b/Transmission.API.RPC/Entity/IntOrArrayConverter.cs index 64b3b8b..9175648 100644 --- a/Transmission.API.RPC/Entity/IntOrArrayConverter.cs +++ b/Transmission.API.RPC/Entity/IntOrArrayConverter.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +#nullable enable +using Newtonsoft.Json.Linq; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -22,7 +23,7 @@ public class IntOrArrayConverter : JsonConverter /// <returns></returns> public override bool CanConvert(Type objectType) { - return objectType == typeof(long[]) || objectType == typeof(long) || objectType == typeof(int[]) || objectType == typeof(int) || objectType == typeof(long?[]) || objectType == typeof(long?) || objectType == typeof(int?[]) || objectType == typeof(int?); + return objectType == typeof(long?[]) || objectType == typeof(long[]) || objectType == typeof(int?[]) || objectType == typeof(int[]) || objectType == typeof(long?) || objectType == typeof(long) || objectType == typeof(int?) || objectType == typeof(int); } /// <summary> @@ -34,7 +35,7 @@ public override bool CanConvert(Type objectType) /// <param name="serializer"></param> /// <returns></returns> /// <exception cref="JsonSerializationException"></exception> - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { JToken token = JToken.Load(reader); @@ -60,11 +61,11 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist /// <param name="writer"></param> /// <param name="value"></param> /// <param name="serializer"></param> - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { if (value != null) { - long?[] longArray = (long?[])value; + long[] longArray = (long[])value; if (longArray.Length == 1) { writer.WriteValue(longArray[0]); diff --git a/Transmission.API.RPC/Entity/NewTorrentInfo.cs b/Transmission.API.RPC/Entity/NewTorrentInfo.cs index d3e82be..9be24bf 100644 --- a/Transmission.API.RPC/Entity/NewTorrentInfo.cs +++ b/Transmission.API.RPC/Entity/NewTorrentInfo.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +#nullable enable +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -16,24 +17,24 @@ public class NewTorrentInfo /// Torrent ID /// </summary> [JsonProperty("id")] - public int ID { get; set; } + public int Id { get; set; } /// <summary> /// Torrent name /// </summary> [JsonProperty("name")] - public string Name { get; set; } + public string? Name { get; set; } /// <summary> /// Torrent Hash /// </summary> [JsonProperty("hashString")] - public string HashString { get; set; } + public string? HashString { get; set; } /// <summary> /// Whether the torrent is a duplicate of an existing torrent (add failed) /// </summary> - public bool Duplicate { get; set; } + public bool? Duplicate { get; set; } } } diff --git a/Transmission.API.RPC/Entity/RenameTorrentInfo.cs b/Transmission.API.RPC/Entity/RenameTorrentInfo.cs index 7365f4e..e26ecf8 100644 --- a/Transmission.API.RPC/Entity/RenameTorrentInfo.cs +++ b/Transmission.API.RPC/Entity/RenameTorrentInfo.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +#nullable enable +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -16,18 +17,18 @@ public class RenameTorrentInfo /// The torrent's unique Id. /// </summary> [JsonProperty("id")] - public int ID { get; set; } + public int Id { get; set; } /// <summary> /// File path. /// </summary> [JsonProperty("path")] - public string Path { get; set; } + public string? Path { get; set; } /// <summary> /// File name. /// </summary> [JsonProperty("name")] - public string Name { get; set; } + public string? Name { get; set; } } } diff --git a/Transmission.API.RPC/Entity/SessionInfo.cs b/Transmission.API.RPC/Entity/SessionInfo.cs index 2b4fba3..c75eb04 100644 --- a/Transmission.API.RPC/Entity/SessionInfo.cs +++ b/Transmission.API.RPC/Entity/SessionInfo.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +#nullable enable +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -60,7 +61,7 @@ public class SessionInfo /// Location of the blocklist to use for "blocklist-update" /// </summary> [JsonProperty("blocklist-url")] - public string BlocklistUrl { get; set; } + public string? BlocklistUrl { get; set; } /// <summary> /// True means enabled @@ -84,7 +85,7 @@ public class SessionInfo /// Default announce URLs, one per line, and a blank line between tiers /// </summary> [JsonProperty("default-trackers")] - public string DefaultTrackers { get; set; } + public string? DefaultTrackers { get; set; } /// <summary> /// Allow DHT in public torrents @@ -96,14 +97,14 @@ public class SessionInfo /// Default path to download torrents /// </summary> [JsonProperty("download-dir")] - public string DownloadDirectory { get; set; } + public string? DownloadDirectory { get; set; } /// <summary> /// Free space in download dir /// </summary> [JsonProperty("download-dir-free-space")] [Obsolete("Obsolete since Transmission 4.0.0. Use the free-space method instead.")] - public string DownloadDirectoryFreeSpace { get; set; } + public string? DownloadDirectoryFreeSpace { get; set; } /// <summary> /// Max number of torrents to download at once (see download-queue-enabled) @@ -121,7 +122,7 @@ public class SessionInfo /// "required", "preferred", "tolerated" /// </summary> [JsonProperty("encryption")] - public string Encryption { get; set; } + public string? Encryption { get; set; } /// <summary> /// Torrents we're seeding will be stopped if they're idle for this long @@ -139,7 +140,7 @@ public class SessionInfo /// Path for incomplete torrents, when enabled /// </summary> [JsonProperty("incomplete-dir")] - public string IncompleteDirectory { get; set; } + public string? IncompleteDirectory { get; set; } /// <summary> /// True means keep torrents in incomplete-dir until done @@ -211,13 +212,13 @@ public class SessionInfo /// Session ID /// </summary> [JsonProperty("session-id")] - public string SessionId { get; set; } + public string? SessionId { get; set; } /// <summary> /// Filename of the script to run /// </summary> [JsonProperty("script-torrent-added-filename")] - public string ScriptTorrentAddedFilename { get; set; } + public string? ScriptTorrentAddedFilename { get; set; } /// <summary> /// Whether or not to call the "added" script @@ -229,7 +230,7 @@ public class SessionInfo /// Filename of the script to run /// </summary> [JsonProperty("script-torrent-done-filename")] - public string ScriptTorrentDoneFilename { get; set; } + public string? ScriptTorrentDoneFilename { get; set; } /// <summary> /// Whether or not to call the "done" script @@ -241,7 +242,7 @@ public class SessionInfo /// Filename of the script to run /// </summary> [JsonProperty("script-torrent-done-seeding-filename")] - public string ScriptTorrentDoneSeedingFilename { get; set; } + public string? ScriptTorrentDoneSeedingFilename { get; set; } /// <summary> /// Whether or not to call the "done seeding" script @@ -313,7 +314,7 @@ public class SessionInfo /// Units /// </summary> [JsonProperty("units")] - public Units Units { get; set; } + public Units? Units { get; set; } /// <summary> /// True means allow utp @@ -325,7 +326,7 @@ public class SessionInfo /// Location of transmission's configuration directory /// </summary> [JsonProperty("config-dir")] - public string ConfigDirectory{ get; set; } + public string? ConfigDirectory{ get; set; } /// <summary> /// The current RPC API version @@ -343,12 +344,12 @@ public class SessionInfo /// Current RPC API version in a semver-compatible string /// </summary> [JsonProperty("rpc-version-semver")] - public string RpcVersionSemver { get; set; } + public string? RpcVersionSemver { get; set; } /// <summary> /// long? version string "$version ($revision)" /// </summary> [JsonProperty("version")] - public string Version{ get; set; } + public string? Version{ get; set; } } } diff --git a/Transmission.API.RPC/Entity/Statistic.cs b/Transmission.API.RPC/Entity/Statistic.cs index d54e36b..b63ca66 100644 --- a/Transmission.API.RPC/Entity/Statistic.cs +++ b/Transmission.API.RPC/Entity/Statistic.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +#nullable enable +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -46,13 +47,13 @@ public class Statistic /// Cumulative stats /// </summary> [JsonProperty("cumulative-stats")] - public CommonStatistic CumulativeStats { get; set; } + public CommonStatistic? CumulativeStats { get; set; } /// <summary> /// Current stats /// </summary> [JsonProperty("current-stats")] - public CommonStatistic CurrentStats { get; set; } + public CommonStatistic? CurrentStats { get; set; } } /// <summary> diff --git a/Transmission.API.RPC/Entity/TorrentInfo.cs b/Transmission.API.RPC/Entity/TorrentInfo.cs index d18ee1e..4907b25 100644 --- a/Transmission.API.RPC/Entity/TorrentInfo.cs +++ b/Transmission.API.RPC/Entity/TorrentInfo.cs @@ -1,4 +1,5 @@ -using JDRemote.Backends.Transmission; +#nullable enable +using JDRemote.Backends.Transmission; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -17,7 +18,7 @@ public class TorrentInfo /// The torrent's unique Id. /// </summary> [JsonProperty("id")] - public long? Id { get; set; } + public long Id { get; set; } /// <summary> /// Activity date @@ -36,7 +37,7 @@ public class TorrentInfo /// </summary> [JsonProperty("availability")] [JsonConverter(typeof(IntOrArrayConverter))] // Without this converter, Transmission < 4.0.0 leads to an error. - public long?[] Availability { get; set; } + public long[]? Availability { get; set; } /// <summary> /// Torrents bandwidth priority @@ -48,7 +49,7 @@ public class TorrentInfo /// Comment /// </summary> [JsonProperty("comment")] - public string Comment { get; set; } + public string? Comment { get; set; } /// <summary> /// Corrupt ever @@ -60,7 +61,7 @@ public class TorrentInfo /// Creator /// </summary> [JsonProperty("creator")] - public string Creator { get; set; } + public string? Creator { get; set; } /// <summary> /// Date created @@ -84,7 +85,7 @@ public class TorrentInfo /// Download directory /// </summary> [JsonProperty("downloadDir")] - public string DownloadDir { get; set; } + public string? DownloadDir { get; set; } /// <summary> /// Downloaded ever @@ -120,7 +121,7 @@ public class TorrentInfo /// Error string /// </summary> [JsonProperty("errorString")] - public string ErrorString { get; set; } + public string? ErrorString { get; set; } /// <summary> /// ETA @@ -144,25 +145,25 @@ public class TorrentInfo /// Files /// </summary> [JsonProperty("files")] - public TransmissionTorrentFiles[] Files { get; set; } + public TransmissionTorrentFiles[]? Files { get; set; } /// <summary> /// File stats /// </summary> [JsonProperty("fileStats")] - public TransmissionTorrentFileStats[] FileStats { get; set; } + public TransmissionTorrentFileStats[]? FileStats { get; set; } /// <summary> /// Group /// </summary> [JsonProperty("group")] - public string Group { get; set; } + public string? Group { get; set; } /// <summary> /// Hash string /// </summary> [JsonProperty("hashString")] - public string HashString { get; set; } + public string? HashString { get; set; } /// <summary> /// Have unchecked @@ -204,7 +205,7 @@ public class TorrentInfo /// Labels /// </summary> [JsonProperty("labels")] - public string[] Labels { get; set; } + public string[]? Labels { get; set; } /// <summary> /// Left until done @@ -216,7 +217,7 @@ public class TorrentInfo /// Magnet link /// </summary> [JsonProperty("magnetLink")] - public string MagnetLink { get; set; } + public string? MagnetLink { get; set; } /// <summary> /// Manual announce time @@ -240,7 +241,7 @@ public class TorrentInfo /// Name /// </summary> [JsonProperty("name")] - public string Name { get; set; } + public string? Name { get; set; } /// <summary> /// Peer limit @@ -252,7 +253,7 @@ public class TorrentInfo /// Peers /// </summary> [JsonProperty("peers")] - public TransmissionTorrentPeers[] Peers { get; set; } + public TransmissionTorrentPeers[]? Peers { get; set; } /// <summary> /// Peers connected @@ -264,7 +265,7 @@ public class TorrentInfo /// Peers from /// </summary> [JsonProperty("peersFrom")] - public TransmissionTorrentPeersFrom PeersFrom { get; set; } + public TransmissionTorrentPeersFrom? PeersFrom { get; set; } /// <summary> /// Peers getting from us @@ -294,7 +295,7 @@ public class TorrentInfo /// Pieces /// </summary> [JsonProperty("pieces")] - public string Pieces { get; set; } + public string? Pieces { get; set; } /// <summary> /// Piece count @@ -312,13 +313,13 @@ public class TorrentInfo /// Priorities /// </summary> [JsonProperty("priorities")] - public long?[] Priorities { get; set; } + public long[]? Priorities { get; set; } /// <summary> /// Primary mime type /// </summary> [JsonProperty("primary-mime-type")] - public string PrimaryMimeType { get; set; } + public string? PrimaryMimeType { get; set; } /// <summary> /// Queue position @@ -408,7 +409,7 @@ public class TorrentInfo /// Trackers /// </summary> [JsonProperty("trackers")] - public TransmissionTorrentTrackers[] Trackers { get; set; } + public TransmissionTorrentTrackers[]? Trackers { get; set; } /// <summary> /// Tracker list: @@ -416,13 +417,13 @@ public class TorrentInfo /// line between tiers /// </summary> [JsonProperty("trackerList")] - public string TrackerList { get; set; } + public string? TrackerList { get; set; } /// <summary> /// Tracker stats /// </summary> [JsonProperty("trackerStats")] - public TransmissionTorrentTrackerStats[] TrackerStats { get; set; } + public TransmissionTorrentTrackerStats[]? TrackerStats { get; set; } /// <summary> /// Total size @@ -434,7 +435,7 @@ public class TorrentInfo /// Torrent file /// </summary> [JsonProperty("torrentFile")] - public string TorrentFile { get; set; } + public string? TorrentFile { get; set; } /// <summary> /// Uploaded ever @@ -464,13 +465,13 @@ public class TorrentInfo /// Wanted /// </summary> [JsonProperty("wanted")] - public bool?[] Wanted { get; set; } + public bool[]? Wanted { get; set; } /// <summary> /// Web seeds /// </summary> [JsonProperty("webseeds")] - public string[] Webseeds { get; set; } + public string[]? Webseeds { get; set; } /// <summary> /// Web seeds sending to us @@ -500,7 +501,7 @@ public class TransmissionTorrentFiles /// Name /// </summary> [JsonProperty("name")] - public string Name{ get; set; } + public string? Name{ get; set; } /// <summary> /// First piece index of file @@ -548,13 +549,13 @@ public class TransmissionTorrentPeers /// Address /// </summary> [JsonProperty("address")] - public string Address{ get; set; } + public string? Address{ get; set; } /// <summary> /// Client name /// </summary> [JsonProperty("clientName")] - public string ClientName{ get; set; } + public string? ClientName{ get; set; } /// <summary> /// Client is choked @@ -572,7 +573,7 @@ public class TransmissionTorrentPeers /// Flag string /// </summary> [JsonProperty("flagStr")] - public string FlagStr{ get; set; } + public string? FlagStr{ get; set; } /// <summary> /// Is downloading from @@ -698,25 +699,25 @@ public class TransmissionTorrentTrackers /// Announce /// </summary> [JsonProperty("announce")] - public string Announce{ get; set; } + public string? Announce{ get; set; } /// <summary> /// Id /// </summary> [JsonProperty("id")] - public long? Id{ get; set; } + public long Id{ get; set; } /// <summary> /// Scrape /// </summary> [JsonProperty("scrape")] - public string Scrape{ get; set; } + public string? Scrape{ get; set; } /// <summary> /// Site name /// </summary> [JsonProperty("sitename")] - public string SiteName { get; set; } + public string? SiteName { get; set; } /// <summary> /// Tier @@ -734,7 +735,7 @@ public class TransmissionTorrentTrackerStats /// Announce /// </summary> [JsonProperty("announce")] - public string Announce{ get; set; } + public string? Announce{ get; set; } /// <summary> /// Announce state @@ -764,7 +765,7 @@ public class TransmissionTorrentTrackerStats /// Host /// </summary> [JsonProperty("host")] - public string Host{ get; set; } + public string? Host{ get; set; } /// <summary> /// Is backup @@ -788,7 +789,7 @@ public class TransmissionTorrentTrackerStats /// Last announce result /// </summary> [JsonProperty("lastAnnounceResult")] - public string LastAnnounceResult{ get; set; } + public string? LastAnnounceResult{ get; set; } /// <summary> /// Last announce succeeded @@ -806,7 +807,7 @@ public class TransmissionTorrentTrackerStats /// Last scrape result /// </summary> [JsonProperty("lastScrapeResult")] - public string LastScrapeResult{ get; set; } + public string? LastScrapeResult{ get; set; } /// <summary> /// Last announce timed out @@ -848,7 +849,7 @@ public class TransmissionTorrentTrackerStats /// Scrape /// </summary> [JsonProperty("scrape")] - public string Scrape{ get; set; } + public string? Scrape{ get; set; } /// <summary> /// Tier @@ -890,11 +891,11 @@ public class TransmissionTorrentTrackerStats /// Site name /// </summary> [JsonProperty("sitename")] - public string SiteName { get; set; } + public string? SiteName { get; set; } } /// <summary> - /// Contains arrays of torrents and removed torrents + /// Contains arrays of torrents, and removed torrents in an integer array /// </summary> public class TransmissionTorrents { @@ -902,12 +903,12 @@ public class TransmissionTorrents /// Array of torrents /// </summary> [JsonProperty("torrents")] - public TorrentInfo[] Torrents{ get; set; } + public TorrentInfo[]? Torrents{ get; set; } /// <summary> /// Array of torrent-id numbers of recently-removed torrents /// </summary> [JsonProperty("removed")] - public long?[] Removed{ get; set; } + public long[]? Removed{ get; set; } } } diff --git a/Transmission.API.RPC/Entity/TorrentInfoConverter.cs b/Transmission.API.RPC/Entity/TorrentInfoConverter.cs new file mode 100644 index 0000000..de89d33 --- /dev/null +++ b/Transmission.API.RPC/Entity/TorrentInfoConverter.cs @@ -0,0 +1,142 @@ +#nullable enable +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Transmission.API.RPC.Entity +{ + // Credits to ChatGPT + /// <summary> + /// Converts a table (array of arrays) result from torrent-get into TransmissionTorrents in a reasonably performant way + /// </summary> + public static class TorrentInfoConverter + { + private readonly static Dictionary<string, PropertyInfo> _propertyMap = CreatePropertyMap(typeof(TorrentInfo)); + + // Method to create the property map for TorrentInfo + private static Dictionary<string, PropertyInfo> CreatePropertyMap(Type type) + { + return type.GetProperties() + .Where(prop => prop.GetCustomAttributes(typeof(JsonPropertyAttribute), false).Any()) + .ToDictionary( + prop => prop.GetCustomAttributes(typeof(JsonPropertyAttribute), false) + .Cast<JsonPropertyAttribute>() + .First() + .PropertyName!, + prop => prop); + } + + public static TransmissionTorrents ConvertFromJObject(JObject jObject) + { + // Extract and convert the "torrents" field + var torrentsArray = jObject["torrents"] as JArray; + if (torrentsArray == null || torrentsArray.Count == 0) + { + return new TransmissionTorrents + { + Torrents = Array.Empty<TorrentInfo>(), + Removed = GetRemoved(jObject) + }; + } + + var fieldNames = torrentsArray[0].ToObject<string[]>(); + var torrents = new TorrentInfo[torrentsArray.Count - 1]; + + for (int i = 1; i < torrentsArray.Count; i++) + { + var row = torrentsArray[i].ToObject<object[]>(); + if (fieldNames != null & row != null) torrents[i - 1] = CreateTorrentInfo(fieldNames!, row!); + } + + return new TransmissionTorrents + { + Torrents = torrents, + Removed = GetRemoved(jObject) + }; + } + + private static TorrentInfo CreateTorrentInfo(string[] fieldNames, object[] row) + { + var torrentInfo = new TorrentInfo(); + + for (int j = 0; j < fieldNames.Length; j++) + { + var fieldName = fieldNames[j]; + var value = row[j]; + + if (_propertyMap.TryGetValue(fieldName, out var property)) + { + try + { + var convertedValue = ConvertValue(value, property.PropertyType); + property.SetValue(torrentInfo, convertedValue); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to set property '{property.Name}': {ex.Message}"); + } + } + } + + return torrentInfo; + } + + private static object? ConvertValue(object value, Type targetType) + { + if (value == null) + { + return null; + } + + // Handle specific types directly for efficiency + if (targetType == typeof(long?)) return value is long l ? (long?)l : null; + if (targetType == typeof(double?)) return value is double d ? (double?)d : null; + if (targetType == typeof(bool?)) return value is bool b ? (bool?)b : null; + if (targetType == typeof(string)) return value?.ToString(); + + if (targetType.IsArray) + { + var elementType = targetType.GetElementType(); + var valueArray = value as JArray; + if (valueArray != null) + { + var convertedArray = Array.CreateInstance(elementType, valueArray.Count); + + for (int i = 0; i < valueArray.Count; i++) + { + convertedArray.SetValue(ConvertValue(valueArray[i], elementType), i); + } + return convertedArray; + } + else return null; + } + + // Handle complex nested objects + if (targetType.IsClass && targetType != typeof(string)) + { + var nestedJObject = value as JObject; + if (nestedJObject != null) + { + return nestedJObject.ToObject(targetType); + } + } + + return Convert.ChangeType(value, targetType); + } + + private static long[]? GetRemoved(JObject jObject) + { + var removedArray = jObject["removed"] as JArray; + if (removedArray == null) + { + return null; + } + + return removedArray.ToObject<long[]>(); + } + } +} diff --git a/Transmission.API.RPC/Entity/Units.cs b/Transmission.API.RPC/Entity/Units.cs index 18aa269..faf6766 100644 --- a/Transmission.API.RPC/Entity/Units.cs +++ b/Transmission.API.RPC/Entity/Units.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +#nullable enable +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -16,7 +17,7 @@ public class Units /// Speed units /// </summary> [JsonProperty("speed-units")] - public string[] SpeedUnits { get; set; } + public string[]? SpeedUnits { get; set; } /// <summary> /// Speed bytes @@ -28,7 +29,7 @@ public class Units /// Size units /// </summary> [JsonProperty("size-units")] - public string[] SizeUnits { get; set; } + public string[]? SizeUnits { get; set; } /// <summary> /// Size bytes @@ -40,7 +41,7 @@ public class Units /// Memory units /// </summary> [JsonProperty("memory-units")] - public string[] MemoryUnits { get; set; } + public string[]? MemoryUnits { get; set; } /// <summary> /// Memory bytes diff --git a/Transmission.API.RPC/Exceptions.cs b/Transmission.API.RPC/Exceptions.cs new file mode 100644 index 0000000..b8ce25e --- /dev/null +++ b/Transmission.API.RPC/Exceptions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Transmission.API.RPC +{ + public class RequestFailedException : Exception + { + public RequestFailedException(string description) : base(description) { } + } + + public class SessionIdException : Exception + { + public SessionIdException() : base("Session ID Error") { } + } +} diff --git a/Transmission.API.RPC/ITransmissionClient.cs b/Transmission.API.RPC/ITransmissionClient.cs index 911dc62..b32be98 100644 --- a/Transmission.API.RPC/ITransmissionClient.cs +++ b/Transmission.API.RPC/ITransmissionClient.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +#nullable enable +using System.Threading.Tasks; using System; using Transmission.API.RPC.Arguments; using Transmission.API.RPC.Entity; @@ -13,7 +14,7 @@ public interface ITransmissionClient /// <summary> /// Current tag /// </summary> - int CurrentTag { get; } + long CurrentTag { get; } /// <summary> /// Session ID @@ -29,7 +30,7 @@ public interface ITransmissionClient /// Update blocklist (API: blocklist-update) /// </summary> /// <returns>Blocklist size</returns> - int BlocklistUpdate(); + long? BlocklistUpdate(); /// <summary> /// Close current session (API: session-close) @@ -40,32 +41,32 @@ public interface ITransmissionClient /// Get free space is available in a client-specified folder. /// </summary> /// <param name="path">The directory to query</param> - FreeSpace FreeSpace(string path); + FreeSpace? FreeSpace(string path); /// <summary> /// Get information of current session (API: session-get) /// </summary> /// <returns>Session information</returns> - SessionInfo GetSessionInformation(); + SessionInfo? GetSessionInformation(); /// <summary> /// Get information of current session (API: session-get) /// </summary> /// <param name="fields">Optional fields of session information</param> /// <returns>Session information</returns> - SessionInfo GetSessionInformation(string[] fields); + SessionInfo? GetSessionInformation(string[] fields); /// <summary> /// Get session stat /// </summary> /// <returns>Session stat</returns> - Statistic GetSessionStatistic(); + Statistic? GetSessionStatistic(); /// <summary> /// See if your incoming peer port is accessible from the outside world (API: port-test) /// </summary> /// <returns>A Tuple with a boolean of whether the port test succeeded, and a PortTestProtocol enum of which protocol was used for the test</returns> - Tuple<bool, PortTestProtocol> PortTest(); + Tuple<bool?, PortTestProtocol> PortTest(); /// <summary> /// Set information to current session (API: session-set) @@ -77,53 +78,55 @@ public interface ITransmissionClient /// Add torrent (API: torrent-add) /// </summary> /// <returns>Torrent info (ID, Name and HashString)</returns> - NewTorrentInfo TorrentAdd(NewTorrent torrent); + NewTorrentInfo? TorrentAdd(NewTorrent torrent); /// <summary> /// Get fields of recently active torrents (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> + /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <returns>Torrents info</returns> - TransmissionTorrents TorrentGetRecentlyActive(string[] fields); + TransmissionTorrents? TorrentGetRecentlyActive(string[] fields, bool asObjects = false); /// <summary> /// Get fields of torrents from ids (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> + /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <param name="ids">IDs of torrents (null or empty for get all torrents)</param> /// <returns>Torrents info</returns> - TransmissionTorrents TorrentGet(string[] fields, params int[] ids); + TransmissionTorrents? TorrentGet(string[] fields, bool asObjects = false, params long[] ids); /// <summary> /// Move torrents to bottom in queue (API: queue-move-bottom) /// </summary> /// <param name="ids"></param> - void TorrentQueueMoveBottom(int[] ids); + void TorrentQueueMoveBottom(long[] ids); /// <summary> /// Move down torrents in queue (API: queue-move-down) /// </summary> /// <param name="ids"></param> - void TorrentQueueMoveDown(int[] ids); + void TorrentQueueMoveDown(long[] ids); /// <summary> /// Move torrents in queue on top (API: queue-move-top) /// </summary> /// <param name="ids">Torrents id</param> - void TorrentQueueMoveTop(int[] ids); + void TorrentQueueMoveTop(long[] ids); /// <summary> /// Move up torrents in queue (API: queue-move-up) /// </summary> /// <param name="ids"></param> - void TorrentQueueMoveUp(int[] ids); + void TorrentQueueMoveUp(long[] ids); /// <summary> /// Remove torrents (API: torrent-remove) /// </summary> /// <param name="ids">Torrents id</param> /// <param name="deleteData">Remove local data</param> - void TorrentRemove(int[] ids, bool deleteData = false); + void TorrentRemove(long[] ids, bool deleteData = false); /// <summary> /// Rename a file or directory in a torrent (API: torrent-rename-path) @@ -131,7 +134,7 @@ public interface ITransmissionClient /// <param name="id">The torrent whose path will be renamed</param> /// <param name="path">The path to the file or folder that will be renamed</param> /// <param name="name">The file or folder's new name</param> - RenameTorrentInfo TorrentRenamePath(int id, string path, string name); + RenameTorrentInfo? TorrentRenamePath(long id, string path, string name); /// <summary> /// Set torrent params (API: torrent-set) @@ -145,7 +148,7 @@ public interface ITransmissionClient /// <param name="ids">Torrent ids</param> /// <param name="location">The new torrent location</param> /// <param name="move">Move from previous location</param> - void TorrentSetLocation(int[] ids, string location, bool move); + void TorrentSetLocation(long[] ids, string location, bool move); /// <summary> /// Start recently active torrents (API: torrent-start) @@ -206,14 +209,14 @@ public interface ITransmissionClient /// Get bandwidth groups (API: group-get) /// </summary> /// <returns></returns> - BandwidthGroup[] BandwidthGroupGet(); + BandwidthGroup[]? BandwidthGroupGet(); /// <summary> /// Get bandwidth groups (API: group-get) /// </summary> /// <param name="groups">Optional names of groups to get</param> /// <returns></returns> - BandwidthGroup[] BandwidthGroupGet(string[] groups); + BandwidthGroup[]? BandwidthGroupGet(string[] groups); /// <summary> /// Set bandwidth groups (API: group-set) diff --git a/Transmission.API.RPC/ITransmissionClientAsync.cs b/Transmission.API.RPC/ITransmissionClientAsync.cs index 192bba7..e0f0cda 100644 --- a/Transmission.API.RPC/ITransmissionClientAsync.cs +++ b/Transmission.API.RPC/ITransmissionClientAsync.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Threading.Tasks; using Transmission.API.RPC.Arguments; using Transmission.API.RPC.Entity; @@ -15,7 +16,7 @@ public interface ITransmissionClientAsync /// Update blocklist (API: blocklist-update) /// </summary> /// <returns>Blocklist size</returns> - Task<int> BlocklistUpdateAsync(); + Task<long?> BlocklistUpdateAsync(); /// <summary> /// Close current session (API: session-close) @@ -26,32 +27,32 @@ public interface ITransmissionClientAsync /// Get free space is available in a client-specified folder. /// </summary> /// <param name="path">The directory to query</param> - Task<FreeSpace> FreeSpaceAsync(string path); + Task<FreeSpace?> FreeSpaceAsync(string path); /// <summary> /// Get information of current session (API: session-get) /// </summary> /// <returns>Session information</returns> - Task<SessionInfo> GetSessionInformationAsync(); + Task<SessionInfo?> GetSessionInformationAsync(); /// <summary> /// Get information of current session (API: session-get) /// </summary> /// <param name="fields">Optional fields of session information</param> /// <returns>Session information</returns> - Task<SessionInfo> GetSessionInformationAsync(string[] fields); + Task<SessionInfo?> GetSessionInformationAsync(string[] fields); /// <summary> /// Get session stat /// </summary> /// <returns>Session stat</returns> - Task<Statistic> GetSessionStatisticAsync(); + Task<Statistic?> GetSessionStatisticAsync(); /// <summary> /// See if your incoming peer port is accessible from the outside world (API: port-test) /// </summary> /// <returns>A Tuple with a boolean of whether the port test succeeded, and a PortTestProtocol enum of which protocol was used for the test</returns> - Task<Tuple<bool, PortTestProtocol>> PortTestAsync(); + Task<Tuple<bool?, PortTestProtocol>> PortTestAsync(); /// <summary> /// Set information to current session (API: session-set) @@ -63,53 +64,55 @@ public interface ITransmissionClientAsync /// Add torrent (API: torrent-add) /// </summary> /// <returns>Torrent info (ID, Name and HashString)</returns> - Task<NewTorrentInfo> TorrentAddAsync(NewTorrent torrent); + Task<NewTorrentInfo?> TorrentAddAsync(NewTorrent torrent); /// <summary> /// Get fields of recently active torrents (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> + /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <returns>Torrents info</returns> - Task<TransmissionTorrents> TorrentGetRecentlyActiveAsync(string[] fields); + Task<TransmissionTorrents?> TorrentGetRecentlyActiveAsync(string[] fields, bool asObjects = false); /// <summary> /// Get fields of torrents from ids (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> + /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <param name="ids">IDs of torrents (null or empty for get all torrents)</param> /// <returns>Torrents info</returns> - Task<TransmissionTorrents> TorrentGetAsync(string[] fields, params int[] ids); + Task<TransmissionTorrents?> TorrentGetAsync(string[] fields, bool asObjects = false, params long[] ids); /// <summary> /// Move torrents to bottom in queue (API: queue-move-bottom) /// </summary> /// <param name="ids"></param> - Task TorrentQueueMoveBottomAsync(int[] ids); + Task TorrentQueueMoveBottomAsync(long[] ids); /// <summary> /// Move down torrents in queue (API: queue-move-down) /// </summary> /// <param name="ids"></param> - Task TorrentQueueMoveDownAsync(int[] ids); + Task TorrentQueueMoveDownAsync(long[] ids); /// <summary> /// Move torrents in queue on top (API: queue-move-top) /// </summary> /// <param name="ids">Torrents id</param> - Task TorrentQueueMoveTopAsync(int[] ids); + Task TorrentQueueMoveTopAsync(long[] ids); /// <summary> /// Move up torrents in queue (API: queue-move-up) /// </summary> /// <param name="ids"></param> - Task TorrentQueueMoveUpAsync(int[] ids); + Task TorrentQueueMoveUpAsync(long[] ids); /// <summary> /// Remove torrents /// </summary> /// <param name="ids">Torrents id</param> /// <param name="deleteData">Remove local data</param> - Task TorrentRemoveAsync(int[] ids, bool deleteData = false); + Task TorrentRemoveAsync(long[] ids, bool deleteData = false); /// <summary> /// Rename a file or directory in a torrent (API: torrent-rename-path) @@ -117,7 +120,7 @@ public interface ITransmissionClientAsync /// <param name="id">The torrent whose path will be renamed</param> /// <param name="path">The path to the file or folder that will be renamed</param> /// <param name="name">The file or folder's new name</param> - Task<RenameTorrentInfo> TorrentRenamePathAsync(int id, string path, string name); + Task<RenameTorrentInfo?> TorrentRenamePathAsync(long id, string path, string name); /// <summary> /// Set torrent params (API: torrent-set) @@ -131,7 +134,7 @@ public interface ITransmissionClientAsync /// <param name="ids">Torrent ids</param> /// <param name="location">The new torrent location</param> /// <param name="move">Move from previous location</param> - Task TorrentSetLocationAsync(int[] ids, string location, bool move); + Task TorrentSetLocationAsync(long[] ids, string location, bool move); /// <summary> /// Start recently active torrents (API: torrent-start) @@ -192,14 +195,14 @@ public interface ITransmissionClientAsync /// Get bandwidth groups (API: group-get) /// </summary> /// <returns></returns> - Task<BandwidthGroup[]> BandwidthGroupGetAsync(); + Task<BandwidthGroup[]?> BandwidthGroupGetAsync(); /// <summary> /// Get bandwidth groups (API: group-get) /// </summary> /// <param name="groups">Optional names of groups to get</param> /// <returns></returns> - Task<BandwidthGroup[]> BandwidthGroupGetAsync(string[] groups); + Task<BandwidthGroup[]?> BandwidthGroupGetAsync(string[] groups); /// <summary> /// Set bandwidth groups (API: group-set) diff --git a/Transmission.API.RPC/Transmission.API.RPC.csproj b/Transmission.API.RPC/Transmission.API.RPC.csproj index 680474b..9dca373 100644 --- a/Transmission.API.RPC/Transmission.API.RPC.csproj +++ b/Transmission.API.RPC/Transmission.API.RPC.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFrameworks>netstandard2.0</TargetFrameworks> + <TargetFrameworks>netstandard2.1</TargetFrameworks> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <ApplicationIcon /> <OutputType>Library</OutputType> From a876f71ad60748bbd063ff78dfe9e8ff5c82e523 Mon Sep 17 00:00:00 2001 From: Jdbye <jdbye3@gmail.com> Date: Mon, 10 Jun 2024 04:44:52 +0200 Subject: [PATCH 5/5] Removed table format support as it was not working correctly It didn't read all the fields correctly and there was very minimal improvement in performance anyway. --- Transmission.API.RPC.Test/MethodsTest.cs | 2 +- Transmission.API.RPC/Client.Async.cs | 20 +-- Transmission.API.RPC/Client.cs | 10 +- .../Common/TransmissionResponse.cs | 2 +- .../Entity/TorrentInfoConverter.cs | 142 ------------------ Transmission.API.RPC/ITransmissionClient.cs | 6 +- .../ITransmissionClientAsync.cs | 6 +- 7 files changed, 14 insertions(+), 174 deletions(-) delete mode 100644 Transmission.API.RPC/Entity/TorrentInfoConverter.cs diff --git a/Transmission.API.RPC.Test/MethodsTest.cs b/Transmission.API.RPC.Test/MethodsTest.cs index 5f000d1..8a1e342 100644 --- a/Transmission.API.RPC.Test/MethodsTest.cs +++ b/Transmission.API.RPC.Test/MethodsTest.cs @@ -90,7 +90,7 @@ public void SetTorrentSettings_Test() client.TorrentSet(settings); - torrentsInfo = client.TorrentGet(TorrentFields.ALL_FIELDS, false, torrentInfo.Id); + torrentsInfo = client.TorrentGet(TorrentFields.ALL_FIELDS, torrentInfo.Id); torrentInfo = torrentsInfo.Torrents.FirstOrDefault(); Assert.IsFalse(trackerCount == torrentInfo.Trackers.Length); diff --git a/Transmission.API.RPC/Client.Async.cs b/Transmission.API.RPC/Client.Async.cs index 318c5b4..17a2d40 100644 --- a/Transmission.API.RPC/Client.Async.cs +++ b/Transmission.API.RPC/Client.Async.cs @@ -131,25 +131,19 @@ public async Task TorrentSetAsync(TorrentSettings settings) /// Get fields of recently active torrents (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> - /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <returns>Torrents info</returns> - public async Task<TransmissionTorrents?> TorrentGetRecentlyActiveAsync(string[] fields, bool asObjects = false) + public async Task<TransmissionTorrents?> TorrentGetRecentlyActiveAsync(string[] fields) { var arguments = new Dictionary<string, object>(); arguments.Add("fields", fields); arguments.Add("ids", "recently-active"); - if (asObjects) arguments.Add("format", "objects"); - else arguments.Add("format", "table"); - var request = new TransmissionRequest("torrent-get", arguments); var response = await SendRequestAsync(request); if (response == null) return null; - TransmissionTorrents? torrents; - if (asObjects) torrents = response.Arguments.ToObject<TransmissionTorrents>(); - else torrents = TorrentInfoConverter.ConvertFromJObject(response.Arguments); + TransmissionTorrents? torrents = response.Arguments.ToObject<TransmissionTorrents>(); return torrents; } @@ -158,17 +152,13 @@ public async Task TorrentSetAsync(TorrentSettings settings) /// Get fields of torrents from ids (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> - /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <param name="ids">IDs of torrents (null or empty for get all torrents)</param> /// <returns>Torrents info</returns> - public async Task<TransmissionTorrents?> TorrentGetAsync(string[] fields, bool asObjects = false, params long[] ids) + public async Task<TransmissionTorrents?> TorrentGetAsync(string[] fields, params long[] ids) { var arguments = new Dictionary<string, object>(); arguments.Add("fields", fields); - if (asObjects) arguments.Add("format", "objects"); - else arguments.Add("format", "table"); - if (ids != null && ids.Length > 0) arguments.Add("ids", ids); @@ -177,9 +167,7 @@ public async Task TorrentSetAsync(TorrentSettings settings) if (response == null) return null; - TransmissionTorrents? torrents; - if (asObjects) torrents = response?.Arguments.ToObject<TransmissionTorrents>(); - else torrents = TorrentInfoConverter.ConvertFromJObject(response.Arguments); + TransmissionTorrents? torrents = response?.Arguments.ToObject<TransmissionTorrents>(); return torrents; } diff --git a/Transmission.API.RPC/Client.cs b/Transmission.API.RPC/Client.cs index 3a765e3..dbe8018 100644 --- a/Transmission.API.RPC/Client.cs +++ b/Transmission.API.RPC/Client.cs @@ -150,11 +150,10 @@ public void TorrentSet(TorrentSettings settings) /// Get fields of recently active torrents (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> - /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <returns>Torrents info</returns> - public TransmissionTorrents TorrentGetRecentlyActive(string[] fields, bool asObjects = false) + public TransmissionTorrents TorrentGetRecentlyActive(string[] fields) { - var task = TorrentGetRecentlyActiveAsync(fields, asObjects); + var task = TorrentGetRecentlyActiveAsync(fields); task.WaitAndUnwrapException(); return task.Result; } @@ -163,12 +162,11 @@ public TransmissionTorrents TorrentGetRecentlyActive(string[] fields, bool asObj /// Get fields of torrents from ids (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> - /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <param name="ids">IDs of torrents (null or empty for get all torrents)</param> /// <returns>Torrents info</returns> - public TransmissionTorrents TorrentGet(string[] fields, bool asObjects = false, params long[] ids) + public TransmissionTorrents TorrentGet(string[] fields, params long[] ids) { - var task = TorrentGetAsync(fields, asObjects, ids); + var task = TorrentGetAsync(fields, ids); task.WaitAndUnwrapException(); return task.Result; } diff --git a/Transmission.API.RPC/Common/TransmissionResponse.cs b/Transmission.API.RPC/Common/TransmissionResponse.cs index d3c29b7..1fbe997 100644 --- a/Transmission.API.RPC/Common/TransmissionResponse.cs +++ b/Transmission.API.RPC/Common/TransmissionResponse.cs @@ -22,7 +22,7 @@ public class TransmissionResponse /// <summary> /// Uniquely identifies which request this is a response to /// </summary> - public int? Tag; + public int? Tag { get; } /// <summary> /// Data diff --git a/Transmission.API.RPC/Entity/TorrentInfoConverter.cs b/Transmission.API.RPC/Entity/TorrentInfoConverter.cs deleted file mode 100644 index de89d33..0000000 --- a/Transmission.API.RPC/Entity/TorrentInfoConverter.cs +++ /dev/null @@ -1,142 +0,0 @@ -#nullable enable -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; - -namespace Transmission.API.RPC.Entity -{ - // Credits to ChatGPT - /// <summary> - /// Converts a table (array of arrays) result from torrent-get into TransmissionTorrents in a reasonably performant way - /// </summary> - public static class TorrentInfoConverter - { - private readonly static Dictionary<string, PropertyInfo> _propertyMap = CreatePropertyMap(typeof(TorrentInfo)); - - // Method to create the property map for TorrentInfo - private static Dictionary<string, PropertyInfo> CreatePropertyMap(Type type) - { - return type.GetProperties() - .Where(prop => prop.GetCustomAttributes(typeof(JsonPropertyAttribute), false).Any()) - .ToDictionary( - prop => prop.GetCustomAttributes(typeof(JsonPropertyAttribute), false) - .Cast<JsonPropertyAttribute>() - .First() - .PropertyName!, - prop => prop); - } - - public static TransmissionTorrents ConvertFromJObject(JObject jObject) - { - // Extract and convert the "torrents" field - var torrentsArray = jObject["torrents"] as JArray; - if (torrentsArray == null || torrentsArray.Count == 0) - { - return new TransmissionTorrents - { - Torrents = Array.Empty<TorrentInfo>(), - Removed = GetRemoved(jObject) - }; - } - - var fieldNames = torrentsArray[0].ToObject<string[]>(); - var torrents = new TorrentInfo[torrentsArray.Count - 1]; - - for (int i = 1; i < torrentsArray.Count; i++) - { - var row = torrentsArray[i].ToObject<object[]>(); - if (fieldNames != null & row != null) torrents[i - 1] = CreateTorrentInfo(fieldNames!, row!); - } - - return new TransmissionTorrents - { - Torrents = torrents, - Removed = GetRemoved(jObject) - }; - } - - private static TorrentInfo CreateTorrentInfo(string[] fieldNames, object[] row) - { - var torrentInfo = new TorrentInfo(); - - for (int j = 0; j < fieldNames.Length; j++) - { - var fieldName = fieldNames[j]; - var value = row[j]; - - if (_propertyMap.TryGetValue(fieldName, out var property)) - { - try - { - var convertedValue = ConvertValue(value, property.PropertyType); - property.SetValue(torrentInfo, convertedValue); - } - catch (Exception ex) - { - Console.WriteLine($"Failed to set property '{property.Name}': {ex.Message}"); - } - } - } - - return torrentInfo; - } - - private static object? ConvertValue(object value, Type targetType) - { - if (value == null) - { - return null; - } - - // Handle specific types directly for efficiency - if (targetType == typeof(long?)) return value is long l ? (long?)l : null; - if (targetType == typeof(double?)) return value is double d ? (double?)d : null; - if (targetType == typeof(bool?)) return value is bool b ? (bool?)b : null; - if (targetType == typeof(string)) return value?.ToString(); - - if (targetType.IsArray) - { - var elementType = targetType.GetElementType(); - var valueArray = value as JArray; - if (valueArray != null) - { - var convertedArray = Array.CreateInstance(elementType, valueArray.Count); - - for (int i = 0; i < valueArray.Count; i++) - { - convertedArray.SetValue(ConvertValue(valueArray[i], elementType), i); - } - return convertedArray; - } - else return null; - } - - // Handle complex nested objects - if (targetType.IsClass && targetType != typeof(string)) - { - var nestedJObject = value as JObject; - if (nestedJObject != null) - { - return nestedJObject.ToObject(targetType); - } - } - - return Convert.ChangeType(value, targetType); - } - - private static long[]? GetRemoved(JObject jObject) - { - var removedArray = jObject["removed"] as JArray; - if (removedArray == null) - { - return null; - } - - return removedArray.ToObject<long[]>(); - } - } -} diff --git a/Transmission.API.RPC/ITransmissionClient.cs b/Transmission.API.RPC/ITransmissionClient.cs index b32be98..ef46539 100644 --- a/Transmission.API.RPC/ITransmissionClient.cs +++ b/Transmission.API.RPC/ITransmissionClient.cs @@ -84,18 +84,16 @@ public interface ITransmissionClient /// Get fields of recently active torrents (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> - /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <returns>Torrents info</returns> - TransmissionTorrents? TorrentGetRecentlyActive(string[] fields, bool asObjects = false); + TransmissionTorrents? TorrentGetRecentlyActive(string[] fields); /// <summary> /// Get fields of torrents from ids (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> - /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <param name="ids">IDs of torrents (null or empty for get all torrents)</param> /// <returns>Torrents info</returns> - TransmissionTorrents? TorrentGet(string[] fields, bool asObjects = false, params long[] ids); + TransmissionTorrents? TorrentGet(string[] fields, params long[] ids); /// <summary> /// Move torrents to bottom in queue (API: queue-move-bottom) diff --git a/Transmission.API.RPC/ITransmissionClientAsync.cs b/Transmission.API.RPC/ITransmissionClientAsync.cs index e0f0cda..6b51880 100644 --- a/Transmission.API.RPC/ITransmissionClientAsync.cs +++ b/Transmission.API.RPC/ITransmissionClientAsync.cs @@ -70,18 +70,16 @@ public interface ITransmissionClientAsync /// Get fields of recently active torrents (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> - /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <returns>Torrents info</returns> - Task<TransmissionTorrents?> TorrentGetRecentlyActiveAsync(string[] fields, bool asObjects = false); + Task<TransmissionTorrents?> TorrentGetRecentlyActiveAsync(string[] fields); /// <summary> /// Get fields of torrents from ids (API: torrent-get) /// </summary> /// <param name="fields">Fields of torrents</param> - /// <param name="asObjects">Whether to request the json as objects. Recommended to leave this set to false to use tables, which is slightly more performant.</param> /// <param name="ids">IDs of torrents (null or empty for get all torrents)</param> /// <returns>Torrents info</returns> - Task<TransmissionTorrents?> TorrentGetAsync(string[] fields, bool asObjects = false, params long[] ids); + Task<TransmissionTorrents?> TorrentGetAsync(string[] fields, params long[] ids); /// <summary> /// Move torrents to bottom in queue (API: queue-move-bottom)