mirror of
https://github.com/transmission/transmission.git
synced 2026-04-02 00:27:38 +01:00
feat: new JSON-RPC 2.0 RPC API (#7269)
* feat: add enum for JSON-RPC error codes * feat: new `tr_rpc_request_exec()` overload that accepts string * feat: add JSON-RPC parse error handling * feat: add logic for branching to JSON-RPC or legacy API * feat: error codes for existing errors strings * refactor: async handlers now take the done cb as parameter * feat: support non-batch JSON-RPC requests * feat: support batch JSON-RPC requests * refactor: move JSON-RPC error codes to header * test: new tests for JSON-RPC * refactor(webui): use jsonrpc api * docs: update docs for jsonrpc * fix: clang-tidy warning * perf: avoid copying callback in batch mode * code review: don't commit to dropping old RPC * chore: fix shadowed variable warnings
This commit is contained in:
176
docs/rpc-spec.md
176
docs/rpc-spec.md
@@ -1,8 +1,16 @@
|
||||
> [!IMPORTANT]
|
||||
> Transmisson 4.1.0 (`rpc-version` 18) added support for the JSON-RPC 2.0 protocol.
|
||||
>
|
||||
> The old bespoke RPC protocol is still supported in Transmission 4 but is deprecated and will be removed in the future. People using the old protocol should update their code!
|
||||
>
|
||||
> For documentation of the old RPC protocol, please consult documentation from previous versions.
|
||||
> https://github.com/transmission/transmission/blob/4.0.6/docs/rpc-spec.md
|
||||
|
||||
# Transmission's RPC specification
|
||||
This document describes a protocol for interacting with Transmission sessions remotely.
|
||||
|
||||
### 1.1 Terminology
|
||||
The [JSON](https://www.json.org/) terminology in [RFC 4627](https://datatracker.ietf.org/doc/html/rfc4627) is used.
|
||||
The [JSON](https://www.json.org/) terminology in [RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259) is used.
|
||||
RPC requests and responses are formatted in JSON.
|
||||
|
||||
### 1.2 Tools
|
||||
@@ -24,48 +32,61 @@ Some people outside of the Transmission project have written libraries that wrap
|
||||
|
||||
|
||||
## 2 Message format
|
||||
Messages are formatted as objects. There are two types: requests (described in [section 2.1](#21-requests)) and responses (described in [section 2.2](#22-responses)).
|
||||
Transmission follows the [JSON-RPC 2.0](https://www.jsonrpc.org/specification) specification and supports the entirety of it,
|
||||
except that parameters by-position is not supported, meaning the request parameters must be an Object.
|
||||
|
||||
All text **must** be UTF-8 encoded.
|
||||
|
||||
### 2.1 Requests
|
||||
Requests support three keys:
|
||||
|
||||
1. A required `method` string telling the name of the method to invoke
|
||||
2. An optional `arguments` object of key/value pairs. The keys allowed are defined by the `method`.
|
||||
3. An optional `tag` number used by clients to track responses. If provided by a request, the response MUST include the same tag.
|
||||
Response parameters are returned in the `result` Object.
|
||||
|
||||
#### Example request
|
||||
```json
|
||||
{
|
||||
"arguments": {
|
||||
"fields": [
|
||||
"version"
|
||||
]
|
||||
"jsonrpc": "2.0",
|
||||
"params": {
|
||||
"fields": [ "version" ]
|
||||
},
|
||||
"method": "session-get",
|
||||
"tag": 912313
|
||||
"id": 912313
|
||||
}
|
||||
```
|
||||
|
||||
#### Example response
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"version": "4.1.0-dev (ae226418eb)"
|
||||
},
|
||||
"id": 912313
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Responses
|
||||
Responses to a request will include:
|
||||
### 2.1 Error data
|
||||
|
||||
1. A required `result` string whose value MUST be `success` on success, or an error string on failure.
|
||||
2. An optional `arguments` object of key/value pairs. Its keys contents are defined by the `method` and `arguments` of the original request.
|
||||
3. An optional `tag` number as described in 2.1.
|
||||
JSON-RPC 2.0 allows for additional information about an error be included in the `data` key of the Error object in an implementation-defined format.
|
||||
|
||||
In Transmission, this key is an Object that includes:
|
||||
|
||||
1. An optional `errorString` string that provides additional information that is not included in the `message` key of the Error object.
|
||||
2. An optional `result` Object that contains additional keys defined by the method.
|
||||
|
||||
```json
|
||||
{
|
||||
"arguments": {
|
||||
"version": "2.93 (3c5870d4f5)"
|
||||
"jsonrpc": "2.0",
|
||||
"error": {
|
||||
"code": 7,
|
||||
"message": "HTTP error from backend service",
|
||||
"data": {
|
||||
"errorString": "Couldn't test port: No Response (0)",
|
||||
"result": {
|
||||
"ipProtocol": "ipv6"
|
||||
}
|
||||
}
|
||||
},
|
||||
"result": "success",
|
||||
"tag": 912313
|
||||
"id": 912313
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 Transport mechanism
|
||||
### 2.2 Transport mechanism
|
||||
HTTP POSTing a JSON-encoded request is the preferred way of communicating
|
||||
with a Transmission RPC server. The current Transmission implementation
|
||||
has the default URL as `http://host:9091/transmission/rpc`. Clients
|
||||
@@ -73,7 +94,10 @@ may use this as a default, but should allow the URL to be reconfigured,
|
||||
since the port and path may be changed to allow mapping and/or multiple
|
||||
daemons to run on a single server.
|
||||
|
||||
#### 2.3.1 CSRF protection
|
||||
The RPC server will normally return HTTP 200 regardless of whether the
|
||||
request succeeded. For JSON-RPC 2.0 notifications, HTTP 204 will be returned.
|
||||
|
||||
#### 2.2.1 CSRF protection
|
||||
Most Transmission RPC servers require a `X-Transmission-Session-Id`
|
||||
header to be sent with requests, to prevent CSRF attacks.
|
||||
|
||||
@@ -85,7 +109,7 @@ right `X-Transmission-Session-Id` in its own headers.
|
||||
So, the correct way to handle a 409 response is to update your
|
||||
`X-Transmission-Session-Id` and to resend the previous request.
|
||||
|
||||
#### 2.3.2 DNS rebinding protection
|
||||
#### 2.2.2 DNS rebinding protection
|
||||
Additional check is being made on each RPC request to make sure that the
|
||||
client sending the request does so using one of the allowed hostnames by
|
||||
which RPC server is meant to be available.
|
||||
@@ -99,7 +123,7 @@ addresses are always implicitly allowed.
|
||||
For more information on configuration, see settings.json documentation for
|
||||
`rpc-host-whitelist-enabled` and `rpc-host-whitelist` keys.
|
||||
|
||||
#### 2.3.3 Authentication
|
||||
#### 2.2.3 Authentication
|
||||
Enabling authentication is an optional security feature that can be enabled
|
||||
on Transmission RPC servers. Authentication occurs by method of HTTP Basic
|
||||
Access Authentication.
|
||||
@@ -120,8 +144,8 @@ username and password (respectively), separated by a colon.
|
||||
| `torrent-verify` | tr_torrentVerify | verify torrent
|
||||
| `torrent-reannounce` | tr_torrentManualUpdate | re-announce to trackers now
|
||||
|
||||
Request arguments: `ids`, which specifies which torrents to use.
|
||||
All torrents are used if the `ids` argument is omitted.
|
||||
Request parameters: `ids`, which specifies which torrents to use.
|
||||
All torrents are used if the `ids` parameter is omitted.
|
||||
|
||||
`ids` should be one of the following:
|
||||
|
||||
@@ -132,12 +156,12 @@ All torrents are used if the `ids` argument is omitted.
|
||||
Note that integer torrent ids are not stable across Transmission daemon
|
||||
restarts. Use torrent hashes if you need stable ids.
|
||||
|
||||
Response arguments: none
|
||||
Response parameters: none
|
||||
|
||||
### 3.2 Torrent mutator: `torrent-set`
|
||||
Method name: `torrent-set`
|
||||
|
||||
Request arguments:
|
||||
Request parameters:
|
||||
|
||||
| Key | Value Type | Value Description
|
||||
|:--|:--|:--
|
||||
@@ -173,20 +197,20 @@ Just as an empty `ids` value is shorthand for "all ids", using an empty array
|
||||
for `files-wanted`, `files-unwanted`, `priority-high`, `priority-low`, or
|
||||
`priority-normal` is shorthand for saying "all files".
|
||||
|
||||
Response arguments: none
|
||||
Response parameters: none
|
||||
|
||||
### 3.3 Torrent accessor: `torrent-get`
|
||||
Method name: `torrent-get`.
|
||||
|
||||
Request arguments:
|
||||
Request parameters:
|
||||
|
||||
1. An optional `ids` array as described in 3.1.
|
||||
2. A required `fields` array of keys. (see list below)
|
||||
3. An optional `format` string specifying how to format the
|
||||
`torrents` response field. Allowed values are `objects`
|
||||
(default) and `table`. (see "Response arguments" below)
|
||||
(default) and `table`. (see "Response parameters" below)
|
||||
|
||||
Response arguments:
|
||||
Response parameters:
|
||||
|
||||
1. A `torrents` array.
|
||||
|
||||
@@ -426,12 +450,13 @@ Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"arguments": {
|
||||
"jsonrpc": "2.0",
|
||||
"params": {
|
||||
"fields": [ "id", "name", "totalSize" ],
|
||||
"ids": [ 7, 10 ]
|
||||
},
|
||||
"method": "torrent-get",
|
||||
"tag": 39693
|
||||
"id": 39693
|
||||
}
|
||||
```
|
||||
|
||||
@@ -439,7 +464,8 @@ Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"arguments": {
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"torrents": [
|
||||
{
|
||||
"id": 10,
|
||||
@@ -453,15 +479,14 @@ Response:
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": "success",
|
||||
"tag": 39693
|
||||
"id": 39693
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Adding a torrent
|
||||
Method name: `torrent-add`
|
||||
|
||||
Request arguments:
|
||||
Request parameters:
|
||||
|
||||
| Key | Value Type | Description
|
||||
|:--|:--|:--
|
||||
@@ -481,11 +506,11 @@ Request arguments:
|
||||
| `sequential_download` | boolean | download torrent pieces sequentially
|
||||
| `sequential_download_from_piece` | number | download from a specific piece when sequential download is enabled
|
||||
|
||||
Either `filename` **or** `metainfo` **must** be included. All other arguments are optional.
|
||||
Either `filename` **or** `metainfo` **must** be included. All other parameters are optional.
|
||||
|
||||
The format of the `cookies` should be `NAME=CONTENTS`, where `NAME` is the cookie name and `CONTENTS` is what the cookie should contain. Set multiple cookies like this: `name1=content1; name2=content2;` etc. See [libcurl documentation](http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCOOKIE) for more information.
|
||||
|
||||
Response arguments:
|
||||
Response parameters:
|
||||
|
||||
* On success, a `torrent-added` object in the form of one of 3.3's torrent objects with the fields for `id`, `name`, and `hashString`.
|
||||
|
||||
@@ -499,12 +524,12 @@ Method name: `torrent-remove`
|
||||
| `ids` | array | torrent list, as described in 3.1
|
||||
| `delete-local-data` | boolean | delete local data. (default: false)
|
||||
|
||||
Response arguments: none
|
||||
Response parameters: none
|
||||
|
||||
### 3.6 Moving a torrent
|
||||
Method name: `torrent-set-location`
|
||||
|
||||
Request arguments:
|
||||
Request parameters:
|
||||
|
||||
| Key | Value Type | Description
|
||||
|:--|:--|:--
|
||||
@@ -512,7 +537,7 @@ Request arguments:
|
||||
| `location` | string | the new torrent location
|
||||
| `move` | boolean | if true, move from previous location. otherwise, search "location" for files (default: false)
|
||||
|
||||
Response arguments: none
|
||||
Response parameters: none
|
||||
|
||||
### 3.7 Renaming a torrent's path
|
||||
Method name: `torrent-rename-path`
|
||||
@@ -522,7 +547,7 @@ documentation of `tr_torrentRenamePath()`. In particular, note that if this
|
||||
call succeeds you'll want to update the torrent's `files` and `name` field
|
||||
with `torrent-get`.
|
||||
|
||||
Request arguments:
|
||||
Request parameters:
|
||||
|
||||
| Key | Value Type | Description
|
||||
|:--|:--|:--
|
||||
@@ -530,10 +555,10 @@ Request arguments:
|
||||
| `path` | string | the path to the file or folder that will be renamed
|
||||
| `name` | string | the file or folder's new name
|
||||
|
||||
Response arguments: `path`, `name`, and `id`, holding the torrent ID integer
|
||||
Response parameters: `path`, `name`, and `id`, holding the torrent ID integer
|
||||
|
||||
## 4 Session requests
|
||||
### 4.1 Session arguments
|
||||
### 4.1 Session parameters
|
||||
| Key | Value Type | Description
|
||||
|:--|:--|:--
|
||||
| `alt-speed-down` | number | max global download speed (kB/s)
|
||||
@@ -619,7 +644,7 @@ to be common behavior.
|
||||
#### 4.1.1 Mutators
|
||||
Method name: `session-set`
|
||||
|
||||
Request arguments: the mutable properties from 4.1's arguments, i.e. all of them
|
||||
Request parameters: the mutable properties from 4.1's parameters, i.e. all of them
|
||||
except:
|
||||
|
||||
* `blocklist-size`
|
||||
@@ -631,22 +656,22 @@ except:
|
||||
* `units`
|
||||
* `version`
|
||||
|
||||
Response arguments: none
|
||||
Response parameters: none
|
||||
|
||||
#### 4.1.2 Accessors
|
||||
Method name: `session-get`
|
||||
|
||||
Request arguments: an optional `fields` array of keys (see 4.1)
|
||||
Request parameters: an optional `fields` array of keys (see 4.1)
|
||||
|
||||
Response arguments: key/value pairs matching the request's `fields`
|
||||
argument if present, or all supported fields (see 4.1) otherwise.
|
||||
Response parameters: key/value pairs matching the request's `fields`
|
||||
parameter if present, or all supported fields (see 4.1) otherwise.
|
||||
|
||||
### 4.2 Session statistics
|
||||
Method name: `session-stats`
|
||||
|
||||
Request arguments: none
|
||||
Request parameters: none
|
||||
|
||||
Response arguments:
|
||||
Response parameters:
|
||||
|
||||
| Key | Value Type | Description
|
||||
|:--|:--|:--
|
||||
@@ -671,9 +696,9 @@ A stats object contains:
|
||||
### 4.3 Blocklist
|
||||
Method name: `blocklist-update`
|
||||
|
||||
Request arguments: none
|
||||
Request parameters: none
|
||||
|
||||
Response arguments: a number `blocklist-size`
|
||||
Response parameters: a number `blocklist-size`
|
||||
|
||||
### 4.4 Port checking
|
||||
This method tests to see if your incoming peer port is accessible
|
||||
@@ -681,14 +706,14 @@ from the outside world.
|
||||
|
||||
Method name: `port-test`
|
||||
|
||||
Request arguments: an optional argument `ip_protocol`.
|
||||
Request parameters: an optional parameter `ip_protocol`.
|
||||
`ip_protocol` is a string specifying the IP protocol version to be used for the port test.
|
||||
Set to `ipv4` to check IPv4, or set to `ipv6` to check IPv6.
|
||||
For backwards compatibility, it is allowed to omit this argument to get the behaviour before Transmission `4.1.0`,
|
||||
For backwards compatibility, it is allowed to omit this parameter to get the behaviour before Transmission `4.1.0`,
|
||||
which is to check whichever IP protocol the OS happened to use to connect to our port test service,
|
||||
frankly not very useful.
|
||||
|
||||
Response arguments:
|
||||
Response parameters:
|
||||
|
||||
| Key | Value Type | Description
|
||||
| :-- | :-- | :--
|
||||
@@ -696,13 +721,13 @@ Response arguments:
|
||||
| `ip_protocol` | string | `ipv4` if the test was carried out on IPv4, `ipv6` if the test was carried out on IPv6, unset if it cannot be determined
|
||||
|
||||
### 4.5 Session shutdown
|
||||
This method tells the transmission session to shut down.
|
||||
This method tells the Transmission session to shut down.
|
||||
|
||||
Method name: `session-close`
|
||||
|
||||
Request arguments: none
|
||||
Request parameters: none
|
||||
|
||||
Response arguments: none
|
||||
Response parameters: none
|
||||
|
||||
### 4.6 Queue movement requests
|
||||
| Method name | transmission.h source
|
||||
@@ -712,13 +737,13 @@ Response arguments: none
|
||||
| `queue-move-down` | tr_torrentQueueMoveDown()
|
||||
| `queue-move-bottom` | tr_torrentQueueMoveBottom()
|
||||
|
||||
Request arguments:
|
||||
Request parameters:
|
||||
|
||||
| Key | Value Type | Description
|
||||
|:--|:--|:--
|
||||
| `ids` | array | torrent list, as described in 3.1.
|
||||
|
||||
Response arguments: none
|
||||
Response parameters: none
|
||||
|
||||
### 4.7 Free space
|
||||
This method tests how much free space is available in a
|
||||
@@ -726,17 +751,17 @@ client-specified folder.
|
||||
|
||||
Method name: `free-space`
|
||||
|
||||
Request arguments:
|
||||
Request parameters:
|
||||
|
||||
| Key | Value type | Description
|
||||
|:--|:--|:--
|
||||
| `path` | string | the directory to query
|
||||
|
||||
Response arguments:
|
||||
Response parameters:
|
||||
|
||||
| Key | Value type | Description
|
||||
|:--|:--|:--
|
||||
| `path` | string | same as the Request argument
|
||||
| `path` | string | same as the Request parameter
|
||||
| `size-bytes` | number | the size, in bytes, of the free space in that directory
|
||||
| `total_size` | number | the total capacity, in bytes, of that directory
|
||||
|
||||
@@ -755,17 +780,17 @@ Request parameters:
|
||||
| `speed-limit-up-enabled` | boolean | true means enabled
|
||||
| `speed-limit-up` | number | max global upload speed (kB/s)
|
||||
|
||||
Response arguments: none
|
||||
Response parameters: none
|
||||
|
||||
#### 4.8.2 Bandwidth group accessor: `group-get`
|
||||
Method name: `group-get`
|
||||
|
||||
Request arguments: An optional argument `group`.
|
||||
Request parameters: An optional parameter `group`.
|
||||
`group` is either a string naming the bandwidth group,
|
||||
or a list of such strings.
|
||||
If `group` is omitted, all bandwidth groups are used.
|
||||
|
||||
Response arguments:
|
||||
Response parameters:
|
||||
|
||||
| Key | Value type | Description
|
||||
|:--|:--|:--
|
||||
@@ -1037,7 +1062,10 @@ Transmission 4.0.0 (`rpc-version-semver` 5.3.0, `rpc-version`: 17)
|
||||
| `group-get` | new method
|
||||
| `torrent-get` | :warning: old arg `wanted` was implemented as an array of `0` or `1` in Transmission 3.00 and older, despite being documented as an array of booleans. Transmission 4.0.0 and 4.0.1 "fixed" this by returning an array of booleans; but in practical terms, this change caused an unannounced breaking change for any 3rd party code that expected `0` or `1`. For this reason, 4.0.2 restored the 3.00 behavior and updated this spec to match the code.
|
||||
|
||||
Transmission 4.1.0 (`rpc-version-semver` 5.4.0, `rpc-version`: 18)
|
||||
Transmission 4.1.0 (`rpc-version-semver` 6.0.0, `rpc-version`: 18)
|
||||
|
||||
:bomb: switch to the JSON-RPC 2.0 protocol
|
||||
|
||||
| Method | Description
|
||||
|:---|:---
|
||||
| `session-get` | new arg `sequential_download`
|
||||
|
||||
@@ -65,6 +65,7 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
|
||||
"clientIsChoked"sv,
|
||||
"clientIsInterested"sv,
|
||||
"clientName"sv,
|
||||
"code"sv,
|
||||
"comment"sv,
|
||||
"compact-view"sv,
|
||||
"complete"sv,
|
||||
@@ -77,6 +78,7 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
|
||||
"creator"sv,
|
||||
"cumulative-stats"sv,
|
||||
"current-stats"sv,
|
||||
"data"sv,
|
||||
"date"sv,
|
||||
"dateCreated"sv,
|
||||
"default-trackers"sv,
|
||||
@@ -170,6 +172,7 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
|
||||
"isStalled"sv,
|
||||
"isUTP"sv,
|
||||
"isUploadingTo"sv,
|
||||
"jsonrpc"sv,
|
||||
"labels"sv,
|
||||
"lastAnnouncePeerCount"sv,
|
||||
"lastAnnounceResult"sv,
|
||||
@@ -200,6 +203,7 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
|
||||
"maxConnectedPeers"sv,
|
||||
"memory-bytes"sv,
|
||||
"memory-units"sv,
|
||||
"message"sv,
|
||||
"message-level"sv,
|
||||
"metadataPercentComplete"sv,
|
||||
"metadata_size"sv,
|
||||
@@ -215,6 +219,7 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
|
||||
"nodes6"sv,
|
||||
"open-dialog-dir"sv,
|
||||
"p"sv,
|
||||
"params"sv,
|
||||
"path"sv,
|
||||
"paused"sv,
|
||||
"pausedTorrentCount"sv,
|
||||
|
||||
@@ -67,6 +67,7 @@ enum // NOLINT(performance-enum-size)
|
||||
TR_KEY_clientIsChoked,
|
||||
TR_KEY_clientIsInterested,
|
||||
TR_KEY_clientName,
|
||||
TR_KEY_code,
|
||||
TR_KEY_comment,
|
||||
TR_KEY_compact_view,
|
||||
TR_KEY_complete,
|
||||
@@ -79,6 +80,7 @@ enum // NOLINT(performance-enum-size)
|
||||
TR_KEY_creator,
|
||||
TR_KEY_cumulative_stats,
|
||||
TR_KEY_current_stats,
|
||||
TR_KEY_data,
|
||||
TR_KEY_date,
|
||||
TR_KEY_dateCreated,
|
||||
TR_KEY_default_trackers,
|
||||
@@ -172,6 +174,7 @@ enum // NOLINT(performance-enum-size)
|
||||
TR_KEY_isStalled,
|
||||
TR_KEY_isUTP,
|
||||
TR_KEY_isUploadingTo,
|
||||
TR_KEY_jsonrpc,
|
||||
TR_KEY_labels,
|
||||
TR_KEY_lastAnnouncePeerCount,
|
||||
TR_KEY_lastAnnounceResult,
|
||||
@@ -202,6 +205,7 @@ enum // NOLINT(performance-enum-size)
|
||||
TR_KEY_maxConnectedPeers,
|
||||
TR_KEY_memory_bytes,
|
||||
TR_KEY_memory_units,
|
||||
TR_KEY_message,
|
||||
TR_KEY_message_level,
|
||||
TR_KEY_metadataPercentComplete,
|
||||
TR_KEY_metadata_size,
|
||||
@@ -217,6 +221,7 @@ enum // NOLINT(performance-enum-size)
|
||||
TR_KEY_nodes6,
|
||||
TR_KEY_open_dialog_dir,
|
||||
TR_KEY_p,
|
||||
TR_KEY_params,
|
||||
TR_KEY_path,
|
||||
TR_KEY_paused,
|
||||
TR_KEY_pausedTorrentCount,
|
||||
|
||||
@@ -356,20 +356,23 @@ void handle_web_client(struct evhttp_request* req, tr_rpc_server const* server)
|
||||
|
||||
void handle_rpc_from_json(struct evhttp_request* req, tr_rpc_server* server, std::string_view json)
|
||||
{
|
||||
if (auto otop = tr_variant_serde::json().inplace().parse(json); otop)
|
||||
{
|
||||
tr_rpc_request_exec(
|
||||
server->session,
|
||||
*otop,
|
||||
[req, server](tr_session* /*session*/, tr_variant&& content)
|
||||
tr_rpc_request_exec(
|
||||
server->session,
|
||||
json,
|
||||
[req, server](tr_session* /*session*/, tr_variant&& content)
|
||||
{
|
||||
if (!content.has_value())
|
||||
{
|
||||
auto* const output_headers = evhttp_request_get_output_headers(req);
|
||||
auto* const response = make_response(req, server, tr_variant_serde::json().compact().to_string(content));
|
||||
evhttp_add_header(output_headers, "Content-Type", "application/json; charset=UTF-8");
|
||||
evhttp_send_reply(req, HTTP_OK, "OK", response);
|
||||
evbuffer_free(response);
|
||||
});
|
||||
}
|
||||
evhttp_send_reply(req, HTTP_NOCONTENT, "OK", nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* const output_headers = evhttp_request_get_output_headers(req);
|
||||
auto* const response = make_response(req, server, tr_variant_serde::json().compact().to_string(content));
|
||||
evhttp_add_header(output_headers, "Content-Type", "application/json; charset=UTF-8");
|
||||
evhttp_send_reply(req, HTTP_OK, "OK", response);
|
||||
evbuffer_free(response);
|
||||
});
|
||||
}
|
||||
|
||||
void handle_rpc(struct evhttp_request* req, tr_rpc_server* server)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,35 @@
|
||||
struct tr_session;
|
||||
struct tr_variant;
|
||||
|
||||
namespace JsonRpc
|
||||
{
|
||||
auto constexpr Version = std::string_view{ "2.0" };
|
||||
|
||||
namespace Error
|
||||
{
|
||||
enum Code : int16_t
|
||||
{
|
||||
PARSE_ERROR = -32700,
|
||||
INVALID_REQUEST = -32600,
|
||||
METHOD_NOT_FOUND = -32601,
|
||||
INVALID_PARAMS = -32602,
|
||||
INTERNAL_ERROR = -32603,
|
||||
SUCCESS = 0,
|
||||
SET_ANNOUNCE_LIST,
|
||||
INVALID_TRACKER_LIST,
|
||||
PATH_NOT_ABSOLUTE,
|
||||
UNRECOGNIZED_INFO,
|
||||
SYSTEM_ERROR,
|
||||
FILE_IDX_OOR,
|
||||
PIECE_IDX_OOR,
|
||||
HTTP_ERROR,
|
||||
CORRUPT_TORRENT
|
||||
};
|
||||
}
|
||||
} // namespace JsonRpc
|
||||
|
||||
using tr_rpc_response_func = std::function<void(tr_session* session, tr_variant&& response)>;
|
||||
|
||||
void tr_rpc_request_exec(tr_session* session, tr_variant const& request, tr_rpc_response_func&& callback = {});
|
||||
|
||||
void tr_rpc_request_exec(tr_session* session, std::string_view request, tr_rpc_response_func&& callback = {});
|
||||
|
||||
@@ -2438,7 +2438,7 @@ void renameTorrentFileString(tr_torrent* tor, std::string_view oldpath, std::str
|
||||
void tr_torrent::rename_path_in_session_thread(
|
||||
std::string_view const oldpath,
|
||||
std::string_view const newname,
|
||||
tr_torrent_rename_done_func const callback,
|
||||
tr_torrent_rename_done_func const& callback,
|
||||
void* const callback_user_data)
|
||||
{
|
||||
using namespace rename_helpers;
|
||||
@@ -2482,19 +2482,19 @@ void tr_torrent::rename_path_in_session_thread(
|
||||
{
|
||||
auto const szold = tr_pathbuf{ oldpath };
|
||||
auto const sznew = tr_pathbuf{ newname };
|
||||
(*callback)(this, szold.c_str(), sznew.c_str(), error, callback_user_data);
|
||||
callback(this, szold.c_str(), sznew.c_str(), error, callback_user_data);
|
||||
}
|
||||
}
|
||||
|
||||
void tr_torrent::rename_path(
|
||||
std::string_view oldpath,
|
||||
std::string_view newname,
|
||||
tr_torrent_rename_done_func callback,
|
||||
tr_torrent_rename_done_func&& callback,
|
||||
void* callback_user_data)
|
||||
{
|
||||
this->session->run_in_session_thread(
|
||||
[this, oldpath = std::string(oldpath), newname = std::string(newname), callback, callback_user_data]()
|
||||
{ rename_path_in_session_thread(oldpath, newname, callback, callback_user_data); });
|
||||
[this, oldpath = std::string(oldpath), newname = std::string(newname), cb = std::move(callback), callback_user_data]()
|
||||
{ rename_path_in_session_thread(oldpath, newname, std::move(cb), callback_user_data); });
|
||||
}
|
||||
|
||||
void tr_torrentRenamePath(
|
||||
@@ -2507,7 +2507,7 @@ void tr_torrentRenamePath(
|
||||
oldpath = oldpath != nullptr ? oldpath : "";
|
||||
newname = newname != nullptr ? newname : "";
|
||||
|
||||
tor->rename_path(oldpath, newname, callback, callback_user_data);
|
||||
tor->rename_path(oldpath, newname, std::move(callback), callback_user_data);
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
@@ -183,7 +183,7 @@ struct tr_torrent
|
||||
void rename_path(
|
||||
std::string_view oldpath,
|
||||
std::string_view newname,
|
||||
tr_torrent_rename_done_func callback,
|
||||
tr_torrent_rename_done_func&& callback,
|
||||
void* callback_user_data);
|
||||
|
||||
// these functions should become private when possible,
|
||||
@@ -1323,7 +1323,7 @@ private:
|
||||
void rename_path_in_session_thread(
|
||||
std::string_view oldpath,
|
||||
std::string_view newname,
|
||||
tr_torrent_rename_done_func callback,
|
||||
tr_torrent_rename_done_func const& callback,
|
||||
void* callback_user_data);
|
||||
|
||||
void start_in_session_thread();
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <time.h> // time_t
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#else
|
||||
@@ -853,12 +854,8 @@ void tr_torrentStart(tr_torrent* torrent);
|
||||
/** @brief Stop (pause) a torrent */
|
||||
void tr_torrentStop(tr_torrent* torrent);
|
||||
|
||||
using tr_torrent_rename_done_func = void (*)( //
|
||||
tr_torrent* torrent,
|
||||
char const* oldpath,
|
||||
char const* newname,
|
||||
int error,
|
||||
void* user_data);
|
||||
using tr_torrent_rename_done_func = std::function<
|
||||
void(tr_torrent* torrent, char const* oldpath, char const* newname, int error, void* user_data)>;
|
||||
|
||||
/**
|
||||
* @brief Rename a file or directory in a torrent.
|
||||
|
||||
@@ -30,7 +30,195 @@ namespace libtransmission::test
|
||||
|
||||
using RpcTest = SessionTest;
|
||||
|
||||
TEST_F(RpcTest, tagSync)
|
||||
TEST_F(RpcTest, EmptyRequest)
|
||||
{
|
||||
static auto constexpr Request = ""sv;
|
||||
|
||||
auto response = tr_variant{};
|
||||
tr_rpc_request_exec(
|
||||
session_,
|
||||
Request,
|
||||
[&response](tr_session* /*session*/, tr_variant&& resp) { response = std::move(resp); });
|
||||
|
||||
auto const* const response_map = response.get_if<tr_variant::Map>();
|
||||
ASSERT_NE(response_map, nullptr);
|
||||
auto const* const result = response_map->find_if<tr_variant::Map>(TR_KEY_result);
|
||||
EXPECT_EQ(result, nullptr);
|
||||
auto const* const error = response_map->find_if<tr_variant::Map>(TR_KEY_error);
|
||||
ASSERT_NE(error, nullptr);
|
||||
auto const error_code = error->value_if<int64_t>(TR_KEY_code);
|
||||
ASSERT_TRUE(error_code);
|
||||
EXPECT_EQ(*error_code, -32700); // don't use constants here in case they are wrong
|
||||
auto const error_message = error->value_if<std::string_view>(TR_KEY_message);
|
||||
ASSERT_TRUE(error_message);
|
||||
EXPECT_EQ(*error_message, "Parse error"sv);
|
||||
auto const id = response_map->value_if<std::nullptr_t>(TR_KEY_id);
|
||||
EXPECT_TRUE(id);
|
||||
}
|
||||
|
||||
TEST_F(RpcTest, NotArrayOrObject)
|
||||
{
|
||||
auto requests = std::vector<tr_variant>{};
|
||||
requests.emplace_back(12345);
|
||||
requests.emplace_back(0.5);
|
||||
requests.emplace_back("12345"sv);
|
||||
requests.emplace_back(nullptr);
|
||||
requests.emplace_back(true);
|
||||
|
||||
for (auto const& req : requests)
|
||||
{
|
||||
auto response = tr_variant{};
|
||||
tr_rpc_request_exec(
|
||||
session_,
|
||||
req,
|
||||
[&response](tr_session* /*session*/, tr_variant&& resp) { response = std::move(resp); });
|
||||
|
||||
auto const* const response_map = response.get_if<tr_variant::Map>();
|
||||
ASSERT_NE(response_map, nullptr);
|
||||
auto const result = response_map->find(TR_KEY_result);
|
||||
EXPECT_EQ(result, std::end(*response_map));
|
||||
auto const* const error = response_map->find_if<tr_variant::Map>(TR_KEY_error);
|
||||
ASSERT_NE(error, nullptr);
|
||||
auto const error_code = error->value_if<int64_t>(TR_KEY_code);
|
||||
ASSERT_TRUE(error_code);
|
||||
EXPECT_EQ(*error_code, -32600); // don't use constants here in case they are wrong
|
||||
auto const error_message = error->value_if<std::string_view>(TR_KEY_message);
|
||||
ASSERT_TRUE(error_message);
|
||||
EXPECT_EQ(*error_message, "Invalid Request"sv);
|
||||
auto const error_data = error->find_if<tr_variant::Map>(TR_KEY_data);
|
||||
ASSERT_NE(error_data, nullptr);
|
||||
auto const error_string = error_data->value_if<std::string_view>(TR_KEY_errorString);
|
||||
ASSERT_TRUE(error_string);
|
||||
EXPECT_EQ(*error_string, "request must be an Array or Object"sv);
|
||||
auto const id = response_map->value_if<std::nullptr_t>(TR_KEY_id);
|
||||
EXPECT_TRUE(id);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RpcTest, JsonRpcWrongVersion)
|
||||
{
|
||||
auto request_map = tr_variant::Map{ 3U };
|
||||
request_map.try_emplace(TR_KEY_jsonrpc, "1.0");
|
||||
request_map.try_emplace(TR_KEY_method, "session_stats");
|
||||
request_map.try_emplace(TR_KEY_id, 12345);
|
||||
|
||||
auto response = tr_variant{};
|
||||
tr_rpc_request_exec(
|
||||
session_,
|
||||
std::move(request_map),
|
||||
[&response](tr_session* /*session*/, tr_variant&& resp) { response = std::move(resp); });
|
||||
|
||||
auto const* const response_map = response.get_if<tr_variant::Map>();
|
||||
ASSERT_NE(response_map, nullptr);
|
||||
auto const result = response_map->find(TR_KEY_result);
|
||||
EXPECT_EQ(result, std::end(*response_map));
|
||||
auto const* const error = response_map->find_if<tr_variant::Map>(TR_KEY_error);
|
||||
ASSERT_NE(error, nullptr);
|
||||
auto const error_code = error->value_if<int64_t>(TR_KEY_code);
|
||||
ASSERT_TRUE(error_code);
|
||||
EXPECT_EQ(*error_code, -32600); // don't use constants here in case they are wrong
|
||||
auto const error_message = error->value_if<std::string_view>(TR_KEY_message);
|
||||
ASSERT_TRUE(error_message);
|
||||
EXPECT_EQ(*error_message, "Invalid Request"sv);
|
||||
auto const error_data = error->find_if<tr_variant::Map>(TR_KEY_data);
|
||||
ASSERT_NE(error_data, nullptr);
|
||||
auto const error_string = error_data->value_if<std::string_view>(TR_KEY_errorString);
|
||||
ASSERT_TRUE(error_string);
|
||||
EXPECT_EQ(*error_string, "JSON-RPC version is not 2.0"sv);
|
||||
auto const id = response_map->value_if<std::nullptr_t>(TR_KEY_id);
|
||||
EXPECT_TRUE(id);
|
||||
}
|
||||
|
||||
TEST_F(RpcTest, idSync)
|
||||
{
|
||||
auto ids = std::vector<tr_variant>{};
|
||||
ids.emplace_back(12345);
|
||||
ids.emplace_back(0.5);
|
||||
ids.emplace_back("12345"sv);
|
||||
ids.emplace_back(nullptr);
|
||||
|
||||
for (auto const& request_id : ids)
|
||||
{
|
||||
auto request_map = tr_variant::Map{ 3U };
|
||||
request_map.try_emplace(TR_KEY_jsonrpc, JsonRpc::Version);
|
||||
request_map.try_emplace(TR_KEY_method, "session-stats");
|
||||
request_map[TR_KEY_id].merge(request_id); // copy
|
||||
|
||||
auto response = tr_variant{};
|
||||
tr_rpc_request_exec(
|
||||
session_,
|
||||
std::move(request_map),
|
||||
[&response](tr_session* /*session*/, tr_variant&& resp) { response = std::move(resp); });
|
||||
|
||||
auto const* const response_map = response.get_if<tr_variant::Map>();
|
||||
ASSERT_NE(response_map, nullptr);
|
||||
auto const* const result = response_map->find_if<tr_variant::Map>(TR_KEY_result);
|
||||
EXPECT_NE(result, nullptr);
|
||||
auto const error = response_map->find(TR_KEY_error);
|
||||
EXPECT_EQ(error, std::end(*response_map));
|
||||
switch (request_id.index())
|
||||
{
|
||||
case tr_variant::IntIndex:
|
||||
EXPECT_EQ(request_id.value_if<int64_t>(), response_map->value_if<int64_t>(TR_KEY_id));
|
||||
break;
|
||||
case tr_variant::DoubleIndex:
|
||||
EXPECT_EQ(request_id.value_if<double>(), response_map->value_if<double>(TR_KEY_id));
|
||||
break;
|
||||
case tr_variant::StringIndex:
|
||||
EXPECT_EQ(request_id.value_if<std::string_view>(), response_map->value_if<std::string_view>(TR_KEY_id));
|
||||
break;
|
||||
case tr_variant::NullIndex:
|
||||
EXPECT_EQ(request_id.value_if<std::nullptr_t>(), response_map->value_if<std::nullptr_t>(TR_KEY_id));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RpcTest, idWrongType)
|
||||
{
|
||||
auto ids = std::vector<tr_variant>{};
|
||||
ids.emplace_back(tr_variant::Map{});
|
||||
ids.emplace_back(tr_variant::Vector{});
|
||||
ids.emplace_back(true);
|
||||
|
||||
for (auto const& request_id : ids)
|
||||
{
|
||||
auto request_map = tr_variant::Map{ 3U };
|
||||
request_map.try_emplace(TR_KEY_jsonrpc, JsonRpc::Version);
|
||||
request_map.try_emplace(TR_KEY_method, "session_stats");
|
||||
request_map[TR_KEY_id].merge(request_id); // copy
|
||||
|
||||
auto response = tr_variant{};
|
||||
tr_rpc_request_exec(
|
||||
session_,
|
||||
std::move(request_map),
|
||||
[&response](tr_session* /*session*/, tr_variant&& resp) { response = std::move(resp); });
|
||||
|
||||
auto const* const response_map = response.get_if<tr_variant::Map>();
|
||||
ASSERT_NE(response_map, nullptr);
|
||||
auto const result = response_map->find(TR_KEY_result);
|
||||
EXPECT_EQ(result, std::end(*response_map));
|
||||
auto const error = response_map->find_if<tr_variant::Map>(TR_KEY_error);
|
||||
ASSERT_NE(error, nullptr);
|
||||
auto const error_code = error->value_if<int64_t>(TR_KEY_code);
|
||||
ASSERT_TRUE(error_code);
|
||||
EXPECT_EQ(*error_code, -32600); // don't use constants here in case they are wrong
|
||||
auto const error_message = error->value_if<std::string_view>(TR_KEY_message);
|
||||
ASSERT_TRUE(error_message);
|
||||
EXPECT_EQ(*error_message, "Invalid Request"sv);
|
||||
auto const error_data = error->find_if<tr_variant::Map>(TR_KEY_data);
|
||||
ASSERT_NE(error_data, nullptr);
|
||||
auto const error_string = error_data->value_if<std::string_view>(TR_KEY_errorString);
|
||||
ASSERT_TRUE(error_string);
|
||||
EXPECT_EQ(*error_string, "id type must be String, Number, or Null"sv);
|
||||
auto const id = response_map->value_if<std::nullptr_t>(TR_KEY_id);
|
||||
EXPECT_TRUE(id);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RpcTest, tagSyncLegacy)
|
||||
{
|
||||
auto request_map = tr_variant::Map{ 2U };
|
||||
request_map.try_emplace(TR_KEY_method, "session-stats");
|
||||
@@ -39,7 +227,7 @@ TEST_F(RpcTest, tagSync)
|
||||
auto response = tr_variant{};
|
||||
tr_rpc_request_exec(
|
||||
session_,
|
||||
tr_variant{ std::move(request_map) },
|
||||
std::move(request_map),
|
||||
[&response](tr_session* /*session*/, tr_variant&& resp) { response = std::move(resp); });
|
||||
|
||||
auto const* const response_map = response.get_if<tr_variant::Map>();
|
||||
@@ -52,7 +240,67 @@ TEST_F(RpcTest, tagSync)
|
||||
EXPECT_EQ(*tag, 12345);
|
||||
}
|
||||
|
||||
TEST_F(RpcTest, tagAsync)
|
||||
TEST_F(RpcTest, idAsync)
|
||||
{
|
||||
auto ids = std::vector<tr_variant>{};
|
||||
ids.emplace_back(12345);
|
||||
ids.emplace_back(0.5);
|
||||
ids.emplace_back("12345"sv);
|
||||
ids.emplace_back(nullptr);
|
||||
|
||||
for (auto const& request_id : ids)
|
||||
{
|
||||
auto* tor = zeroTorrentInit(ZeroTorrentState::Complete);
|
||||
EXPECT_NE(nullptr, tor);
|
||||
|
||||
auto request_map = tr_variant::Map{ 3U };
|
||||
request_map.try_emplace(TR_KEY_jsonrpc, JsonRpc::Version);
|
||||
request_map.try_emplace(TR_KEY_method, "torrent-rename-path");
|
||||
request_map[TR_KEY_id].merge(request_id); // copy
|
||||
|
||||
auto params_map = tr_variant::Map{ 2U };
|
||||
params_map.try_emplace(TR_KEY_path, "files-filled-with-zeroes/512");
|
||||
params_map.try_emplace(TR_KEY_name, "512_test");
|
||||
request_map.try_emplace(TR_KEY_params, std::move(params_map));
|
||||
|
||||
auto promise = std::promise<tr_variant>{};
|
||||
auto future = promise.get_future();
|
||||
tr_rpc_request_exec(
|
||||
session_,
|
||||
std::move(request_map),
|
||||
[&promise](tr_session* /*session*/, tr_variant&& resp) { promise.set_value(std::move(resp)); });
|
||||
auto const response = future.get();
|
||||
|
||||
auto const* const response_map = response.get_if<tr_variant::Map>();
|
||||
ASSERT_NE(response_map, nullptr);
|
||||
auto const result = response_map->find_if<tr_variant::Map>(TR_KEY_result);
|
||||
EXPECT_NE(result, nullptr);
|
||||
auto const error = response_map->find(TR_KEY_error);
|
||||
EXPECT_EQ(error, std::end(*response_map));
|
||||
switch (request_id.index())
|
||||
{
|
||||
case tr_variant::IntIndex:
|
||||
EXPECT_EQ(request_id.value_if<int64_t>(), response_map->value_if<int64_t>(TR_KEY_id));
|
||||
break;
|
||||
case tr_variant::DoubleIndex:
|
||||
EXPECT_EQ(request_id.value_if<double>(), response_map->value_if<double>(TR_KEY_id));
|
||||
break;
|
||||
case tr_variant::StringIndex:
|
||||
EXPECT_EQ(request_id.value_if<std::string_view>(), response_map->value_if<std::string_view>(TR_KEY_id));
|
||||
break;
|
||||
case tr_variant::NullIndex:
|
||||
EXPECT_EQ(request_id.value_if<std::nullptr_t>(), response_map->value_if<std::nullptr_t>(TR_KEY_id));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// cleanup
|
||||
tr_torrentRemove(tor, false, nullptr, nullptr, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RpcTest, tagAsyncLegacy)
|
||||
{
|
||||
auto* tor = zeroTorrentInit(ZeroTorrentState::Complete);
|
||||
EXPECT_NE(nullptr, tor);
|
||||
@@ -70,7 +318,7 @@ TEST_F(RpcTest, tagAsync)
|
||||
auto future = promise.get_future();
|
||||
tr_rpc_request_exec(
|
||||
session_,
|
||||
tr_variant{ std::move(request_map) },
|
||||
std::move(request_map),
|
||||
[&promise](tr_session* /*session*/, tr_variant&& resp) { promise.set_value(std::move(resp)); });
|
||||
auto const response = future.get();
|
||||
|
||||
@@ -87,7 +335,83 @@ TEST_F(RpcTest, tagAsync)
|
||||
tr_torrentRemove(tor, false, nullptr, nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(RpcTest, NotificationSync)
|
||||
{
|
||||
auto request_map = tr_variant::Map{ 2U };
|
||||
request_map.try_emplace(TR_KEY_jsonrpc, JsonRpc::Version);
|
||||
request_map.try_emplace(TR_KEY_method, "session_stats");
|
||||
|
||||
auto response = tr_variant{};
|
||||
tr_rpc_request_exec(
|
||||
session_,
|
||||
std::move(request_map),
|
||||
[&response](tr_session* /*session*/, tr_variant&& resp) { response = std::move(resp); });
|
||||
|
||||
EXPECT_FALSE(response.has_value());
|
||||
}
|
||||
|
||||
TEST_F(RpcTest, NotificationAsync)
|
||||
{
|
||||
auto* tor = zeroTorrentInit(ZeroTorrentState::Complete);
|
||||
EXPECT_NE(nullptr, tor);
|
||||
|
||||
auto request_map = tr_variant::Map{ 2U };
|
||||
request_map.try_emplace(TR_KEY_jsonrpc, JsonRpc::Version);
|
||||
request_map.try_emplace(TR_KEY_method, "torrent_rename_path");
|
||||
|
||||
auto params_map = tr_variant::Map{ 2U };
|
||||
params_map.try_emplace(TR_KEY_path, "files-filled-with-zeroes/512");
|
||||
params_map.try_emplace(TR_KEY_name, "512_test");
|
||||
request_map.try_emplace(TR_KEY_params, std::move(params_map));
|
||||
|
||||
auto promise = std::promise<tr_variant>{};
|
||||
auto future = promise.get_future();
|
||||
tr_rpc_request_exec(
|
||||
session_,
|
||||
std::move(request_map),
|
||||
[&promise](tr_session* /*session*/, tr_variant&& resp) { promise.set_value(std::move(resp)); });
|
||||
auto const response = future.get();
|
||||
|
||||
EXPECT_FALSE(response.has_value());
|
||||
|
||||
// cleanup
|
||||
tr_torrentRemove(tor, false, nullptr, nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(RpcTest, tagNoHandler)
|
||||
{
|
||||
auto request_map = tr_variant::Map{ 3U };
|
||||
request_map.try_emplace(TR_KEY_jsonrpc, JsonRpc::Version);
|
||||
request_map.try_emplace(TR_KEY_method, "sdgdhsgg");
|
||||
request_map.try_emplace(TR_KEY_id, 12345);
|
||||
|
||||
auto response = tr_variant{};
|
||||
tr_rpc_request_exec(
|
||||
session_,
|
||||
std::move(request_map),
|
||||
[&response](tr_session* /*session*/, tr_variant&& resp) { response = std::move(resp); });
|
||||
|
||||
auto const* const response_map = response.get_if<tr_variant::Map>();
|
||||
ASSERT_NE(response_map, nullptr);
|
||||
auto const jsonrpc = response_map->value_if<std::string_view>(TR_KEY_jsonrpc);
|
||||
ASSERT_TRUE(jsonrpc);
|
||||
EXPECT_EQ(*jsonrpc, JsonRpc::Version);
|
||||
auto const result = response_map->find_if<tr_variant::Map>(TR_KEY_result);
|
||||
EXPECT_EQ(result, nullptr);
|
||||
auto const error = response_map->find_if<tr_variant::Map>(TR_KEY_error);
|
||||
ASSERT_NE(error, nullptr);
|
||||
auto const error_code = error->value_if<int64_t>(TR_KEY_code);
|
||||
ASSERT_TRUE(error_code);
|
||||
EXPECT_EQ(*error_code, JsonRpc::Error::METHOD_NOT_FOUND);
|
||||
auto const error_message = error->value_if<std::string_view>(TR_KEY_message);
|
||||
ASSERT_TRUE(error_message);
|
||||
EXPECT_EQ(*error_message, "Method not found"sv);
|
||||
auto const id = response_map->value_if<int64_t>(TR_KEY_id);
|
||||
ASSERT_TRUE(id);
|
||||
EXPECT_EQ(*id, 12345);
|
||||
}
|
||||
|
||||
TEST_F(RpcTest, tagNoHandlerLegacy)
|
||||
{
|
||||
auto request_map = tr_variant::Map{ 2U };
|
||||
request_map.try_emplace(TR_KEY_method, "sdgdhsgg");
|
||||
@@ -96,7 +420,7 @@ TEST_F(RpcTest, tagNoHandler)
|
||||
auto response = tr_variant{};
|
||||
tr_rpc_request_exec(
|
||||
session_,
|
||||
tr_variant{ std::move(request_map) },
|
||||
std::move(request_map),
|
||||
[&response](tr_session* /*session*/, tr_variant&& resp) { response = std::move(resp); });
|
||||
|
||||
auto const* const response_map = response.get_if<tr_variant::Map>();
|
||||
@@ -109,6 +433,154 @@ TEST_F(RpcTest, tagNoHandler)
|
||||
EXPECT_EQ(*tag, 12345);
|
||||
}
|
||||
|
||||
TEST_F(RpcTest, batch)
|
||||
{
|
||||
auto request_vec = tr_variant::Vector{};
|
||||
request_vec.reserve(8U);
|
||||
|
||||
auto request = tr_variant::Map{ 3U };
|
||||
request.try_emplace(TR_KEY_jsonrpc, JsonRpc::Version);
|
||||
request.try_emplace(TR_KEY_method, "session-stats");
|
||||
request.try_emplace(TR_KEY_id, 12345);
|
||||
request_vec.emplace_back(std::move(request));
|
||||
|
||||
request = tr_variant::Map{ 2U };
|
||||
request.try_emplace(TR_KEY_jsonrpc, JsonRpc::Version);
|
||||
request.try_emplace(TR_KEY_method, "session-set");
|
||||
request_vec.emplace_back(std::move(request));
|
||||
|
||||
request = tr_variant::Map{ 3U };
|
||||
request.try_emplace(TR_KEY_jsonrpc, JsonRpc::Version);
|
||||
request.try_emplace(TR_KEY_method, "session-stats");
|
||||
request.try_emplace(TR_KEY_id, "12345"sv);
|
||||
request_vec.emplace_back(std::move(request));
|
||||
|
||||
request = tr_variant::Map{ 1U };
|
||||
request.try_emplace(tr_quark_new("foo"sv), "boo"sv);
|
||||
request_vec.emplace_back(std::move(request));
|
||||
|
||||
request_vec.emplace_back(1);
|
||||
|
||||
request = tr_variant::Map{ 3U };
|
||||
request.try_emplace(TR_KEY_jsonrpc, JsonRpc::Version);
|
||||
request.try_emplace(TR_KEY_method, "dnfsojnsdkjf");
|
||||
request.try_emplace(TR_KEY_id, 12345);
|
||||
request_vec.emplace_back(std::move(request));
|
||||
|
||||
request = tr_variant::Map{ 1U };
|
||||
request.try_emplace(TR_KEY_jsonrpc, JsonRpc::Version);
|
||||
request.try_emplace(TR_KEY_method, "dnfsojnsdkjf");
|
||||
request_vec.emplace_back(std::move(request));
|
||||
|
||||
request = tr_variant::Map{ 2U };
|
||||
request.try_emplace(TR_KEY_method, "session-stats");
|
||||
request.try_emplace(TR_KEY_tag, 12345);
|
||||
request_vec.emplace_back(std::move(request));
|
||||
|
||||
auto response = tr_variant{};
|
||||
tr_rpc_request_exec(
|
||||
session_,
|
||||
std::move(request_vec),
|
||||
[&response](tr_session* /*session*/, tr_variant&& resp) { response = std::move(resp); });
|
||||
|
||||
auto* const response_vec_ptr = response.get_if<tr_variant::Vector>();
|
||||
ASSERT_NE(response_vec_ptr, nullptr);
|
||||
auto const& response_vec = *response_vec_ptr;
|
||||
|
||||
ASSERT_EQ(std::size(response_vec), 6U);
|
||||
|
||||
auto const* response_map = response_vec[0].get_if<tr_variant::Map>();
|
||||
ASSERT_NE(response_map, nullptr);
|
||||
auto const* result = response_map->find_if<tr_variant::Map>(TR_KEY_result);
|
||||
EXPECT_NE(result, nullptr);
|
||||
auto error_it = response_map->find(TR_KEY_error);
|
||||
EXPECT_EQ(error_it, std::end(*response_map));
|
||||
auto id_int = response_map->value_if<int64_t>(TR_KEY_id);
|
||||
ASSERT_TRUE(id_int);
|
||||
EXPECT_EQ(*id_int, 12345);
|
||||
|
||||
response_map = response_vec[1].get_if<tr_variant::Map>();
|
||||
ASSERT_NE(response_map, nullptr);
|
||||
result = response_map->find_if<tr_variant::Map>(TR_KEY_result);
|
||||
EXPECT_NE(result, nullptr);
|
||||
error_it = response_map->find(TR_KEY_error);
|
||||
EXPECT_EQ(error_it, std::end(*response_map));
|
||||
auto id_str = response_map->value_if<std::string_view>(TR_KEY_id);
|
||||
ASSERT_TRUE(id_str);
|
||||
EXPECT_EQ(*id_str, "12345"sv);
|
||||
|
||||
response_map = response_vec[2].get_if<tr_variant::Map>();
|
||||
ASSERT_NE(response_map, nullptr);
|
||||
auto result_it = response_map->find(TR_KEY_result);
|
||||
EXPECT_EQ(result_it, std::end(*response_map));
|
||||
auto error = response_map->find_if<tr_variant::Map>(TR_KEY_error);
|
||||
ASSERT_NE(error, nullptr);
|
||||
auto error_code = error->value_if<int64_t>(TR_KEY_code);
|
||||
ASSERT_TRUE(error_code);
|
||||
EXPECT_EQ(*error_code, -32600); // don't use constants here in case they are wrong
|
||||
auto error_message = error->value_if<std::string_view>(TR_KEY_message);
|
||||
ASSERT_TRUE(error_message);
|
||||
EXPECT_EQ(*error_message, "Invalid Request"sv);
|
||||
auto id_null = response_map->value_if<std::nullptr_t>(TR_KEY_id);
|
||||
EXPECT_TRUE(id_null);
|
||||
|
||||
response_map = response_vec[3].get_if<tr_variant::Map>();
|
||||
ASSERT_NE(response_map, nullptr);
|
||||
result_it = response_map->find(TR_KEY_result);
|
||||
EXPECT_EQ(result_it, std::end(*response_map));
|
||||
error = response_map->find_if<tr_variant::Map>(TR_KEY_error);
|
||||
ASSERT_NE(error, nullptr);
|
||||
error_code = error->value_if<int64_t>(TR_KEY_code);
|
||||
ASSERT_TRUE(error_code);
|
||||
EXPECT_EQ(*error_code, -32600); // don't use constants here in case they are wrong
|
||||
error_message = error->value_if<std::string_view>(TR_KEY_message);
|
||||
ASSERT_TRUE(error_message);
|
||||
EXPECT_EQ(*error_message, "Invalid Request"sv);
|
||||
auto error_data = error->find_if<tr_variant::Map>(TR_KEY_data);
|
||||
ASSERT_NE(error_data, nullptr);
|
||||
auto error_string = error_data->value_if<std::string_view>(TR_KEY_errorString);
|
||||
ASSERT_TRUE(error_string);
|
||||
EXPECT_EQ(*error_string, "request must be an Object"sv);
|
||||
id_null = response_map->value_if<std::nullptr_t>(TR_KEY_id);
|
||||
EXPECT_TRUE(id_null);
|
||||
|
||||
response_map = response_vec[4].get_if<tr_variant::Map>();
|
||||
ASSERT_NE(response_map, nullptr);
|
||||
result_it = response_map->find(TR_KEY_result);
|
||||
EXPECT_EQ(result_it, std::end(*response_map));
|
||||
error = response_map->find_if<tr_variant::Map>(TR_KEY_error);
|
||||
ASSERT_NE(error, nullptr);
|
||||
error_code = error->value_if<int64_t>(TR_KEY_code);
|
||||
ASSERT_TRUE(error_code);
|
||||
EXPECT_EQ(*error_code, -32601); // don't use constants here in case they are wrong
|
||||
error_message = error->value_if<std::string_view>(TR_KEY_message);
|
||||
ASSERT_TRUE(error_message);
|
||||
EXPECT_EQ(*error_message, "Method not found"sv);
|
||||
id_int = response_map->value_if<int64_t>(TR_KEY_id);
|
||||
ASSERT_TRUE(id_int);
|
||||
EXPECT_EQ(*id_int, 12345);
|
||||
|
||||
response_map = response_vec[5].get_if<tr_variant::Map>();
|
||||
ASSERT_NE(response_map, nullptr);
|
||||
result_it = response_map->find(TR_KEY_result);
|
||||
EXPECT_EQ(result_it, std::end(*response_map));
|
||||
error = response_map->find_if<tr_variant::Map>(TR_KEY_error);
|
||||
ASSERT_NE(error, nullptr);
|
||||
error_code = error->value_if<int64_t>(TR_KEY_code);
|
||||
ASSERT_TRUE(error_code);
|
||||
EXPECT_EQ(*error_code, -32600); // don't use constants here in case they are wrong
|
||||
error_message = error->value_if<std::string_view>(TR_KEY_message);
|
||||
ASSERT_TRUE(error_message);
|
||||
EXPECT_EQ(*error_message, "Invalid Request"sv);
|
||||
error_data = error->find_if<tr_variant::Map>(TR_KEY_data);
|
||||
ASSERT_NE(error_data, nullptr);
|
||||
error_string = error_data->value_if<std::string_view>(TR_KEY_errorString);
|
||||
ASSERT_TRUE(error_string);
|
||||
EXPECT_EQ(*error_string, "JSON-RPC version is not 2.0"sv);
|
||||
id_null = response_map->value_if<std::nullptr_t>(TR_KEY_id);
|
||||
EXPECT_TRUE(id_null);
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
@@ -118,8 +590,10 @@ TEST_F(RpcTest, sessionGet)
|
||||
auto* tor = zeroTorrentInit(ZeroTorrentState::NoFiles);
|
||||
EXPECT_NE(nullptr, tor);
|
||||
|
||||
auto request_map = tr_variant::Map{ 1U };
|
||||
auto request_map = tr_variant::Map{ 3U };
|
||||
request_map.try_emplace(TR_KEY_jsonrpc, JsonRpc::Version);
|
||||
request_map.try_emplace(TR_KEY_method, "session-get"sv);
|
||||
request_map.try_emplace(TR_KEY_id, 12345);
|
||||
auto response = tr_variant{};
|
||||
tr_rpc_request_exec(
|
||||
session_,
|
||||
@@ -128,7 +602,7 @@ TEST_F(RpcTest, sessionGet)
|
||||
|
||||
auto* response_map = response.get_if<tr_variant::Map>();
|
||||
ASSERT_NE(response_map, nullptr);
|
||||
auto* args_map = response_map->find_if<tr_variant::Map>(TR_KEY_arguments);
|
||||
auto* args_map = response_map->find_if<tr_variant::Map>(TR_KEY_result);
|
||||
ASSERT_NE(args_map, nullptr);
|
||||
|
||||
// what we expected
|
||||
@@ -231,15 +705,17 @@ TEST_F(RpcTest, torrentGet)
|
||||
auto* tor = zeroTorrentInit(ZeroTorrentState::NoFiles);
|
||||
EXPECT_NE(nullptr, tor);
|
||||
|
||||
auto request = tr_variant::Map{ 1U };
|
||||
auto request = tr_variant::Map{ 3U };
|
||||
|
||||
request.try_emplace(TR_KEY_method, "torrent-get");
|
||||
request.try_emplace(TR_KEY_jsonrpc, JsonRpc::Version);
|
||||
request.try_emplace(TR_KEY_method, "torrent-get"sv);
|
||||
request.try_emplace(TR_KEY_id, 12345);
|
||||
|
||||
auto args_in = tr_variant::Map{ 1U };
|
||||
auto params = tr_variant::Map{ 1U };
|
||||
auto fields = tr_variant::Vector{};
|
||||
fields.emplace_back(tr_quark_get_string_view(TR_KEY_id));
|
||||
args_in.try_emplace(TR_KEY_fields, std::move(fields));
|
||||
request.try_emplace(TR_KEY_arguments, std::move(args_in));
|
||||
params.try_emplace(TR_KEY_fields, std::move(fields));
|
||||
request.try_emplace(TR_KEY_params, std::move(params));
|
||||
|
||||
auto response = tr_variant{};
|
||||
tr_rpc_request_exec(
|
||||
@@ -249,10 +725,10 @@ TEST_F(RpcTest, torrentGet)
|
||||
|
||||
auto* response_map = response.get_if<tr_variant::Map>();
|
||||
ASSERT_NE(response_map, nullptr);
|
||||
auto* args_out = response_map->find_if<tr_variant::Map>(TR_KEY_arguments);
|
||||
ASSERT_NE(args_out, nullptr);
|
||||
auto* result = response_map->find_if<tr_variant::Map>(TR_KEY_result);
|
||||
ASSERT_NE(result, nullptr);
|
||||
|
||||
auto* torrents = args_out->find_if<tr_variant::Vector>(TR_KEY_torrents);
|
||||
auto* torrents = result->find_if<tr_variant::Vector>(TR_KEY_torrents);
|
||||
ASSERT_NE(torrents, nullptr);
|
||||
EXPECT_EQ(1UL, std::size(*torrents));
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { AlertDialog } from './alert-dialog.js';
|
||||
import { Formatter } from './formatter.js';
|
||||
import { RPC } from './remote.js';
|
||||
import { createDialogContainer, makeUUID } from './utils.js';
|
||||
|
||||
const is_ios =
|
||||
@@ -76,20 +77,24 @@ export class OpenDialog extends EventTarget {
|
||||
return;
|
||||
}
|
||||
const o = {
|
||||
arguments: {
|
||||
id: 'webui',
|
||||
jsonrpc: RPC._JsonRpcVersion,
|
||||
method: 'torrent-add',
|
||||
params: {
|
||||
'download-dir': destination,
|
||||
metainfo: contents.slice(Math.max(0, index + key.length)),
|
||||
paused,
|
||||
},
|
||||
method: 'torrent-add',
|
||||
};
|
||||
remote.sendRequest(o, (response) => {
|
||||
if (response.result !== 'success') {
|
||||
alert(`Error adding "${file.name}": ${response.result}`);
|
||||
if ('error' in response) {
|
||||
const message =
|
||||
response.error.data?.errorString ?? response.error.message;
|
||||
alert(`Error adding "${file.name}": ${message}`);
|
||||
controller.setCurrentPopup(
|
||||
new AlertDialog({
|
||||
heading: `Error adding "${file.name}"`,
|
||||
message: response.result,
|
||||
message,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -104,19 +109,21 @@ export class OpenDialog extends EventTarget {
|
||||
url = `magnet:?xt=urn:btih:${url}`;
|
||||
}
|
||||
const o = {
|
||||
arguments: {
|
||||
id: 'webui',
|
||||
jsonrpc: RPC._JsonRpcVersion,
|
||||
method: 'torrent-add',
|
||||
params: {
|
||||
'download-dir': destination,
|
||||
filename: url,
|
||||
paused,
|
||||
},
|
||||
method: 'torrent-add',
|
||||
};
|
||||
remote.sendRequest(o, (payload) => {
|
||||
if (payload.result !== 'success') {
|
||||
if ('error' in payload) {
|
||||
controller.setCurrentPopup(
|
||||
new AlertDialog({
|
||||
heading: `Error adding "${url}"`,
|
||||
message: payload.result,
|
||||
message: payload.error.data?.errorString ?? payload.error.message,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,10 +61,11 @@ export class PrefsDialog extends EventTarget {
|
||||
return;
|
||||
}
|
||||
|
||||
const args = response.result ?? response.error?.data ?? {};
|
||||
const element = this.elements.network.port_status_label[ip_protocol];
|
||||
const is_open = response.arguments['port-is-open'] || false;
|
||||
const is_open = args['port-is-open'] ?? false;
|
||||
element.dataset.open = is_open;
|
||||
if ('port-is-open' in response.arguments) {
|
||||
if ('port-is-open' in args) {
|
||||
setTextContent(element, is_open ? 'Open' : 'Closed');
|
||||
} else {
|
||||
setTextContent(element, 'Error');
|
||||
|
||||
@@ -8,6 +8,7 @@ export const RPC = {
|
||||
_DaemonVersion: 'version',
|
||||
_DownSpeedLimit: 'speed-limit-down',
|
||||
_DownSpeedLimited: 'speed-limit-down-enabled',
|
||||
_JsonRpcVersion: '2.0',
|
||||
_QueueMoveBottom: 'queue-move-bottom',
|
||||
_QueueMoveDown: 'queue-move-down',
|
||||
_QueueMoveTop: 'queue-move-top',
|
||||
@@ -46,12 +47,17 @@ export class Remote {
|
||||
})
|
||||
.then((response) => {
|
||||
response_argument = response;
|
||||
if (response.status === 409) {
|
||||
const error = new Error(Remote._SessionHeader);
|
||||
error.header = response.headers.get(Remote._SessionHeader);
|
||||
throw error;
|
||||
switch (response.status) {
|
||||
case 409: {
|
||||
const error = new Error(Remote._SessionHeader);
|
||||
error.header = response.headers.get(Remote._SessionHeader);
|
||||
throw error;
|
||||
}
|
||||
case 204:
|
||||
return null;
|
||||
default:
|
||||
return response.json();
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((payload) => {
|
||||
if (callback) {
|
||||
@@ -85,6 +91,8 @@ export class Remote {
|
||||
// TODO: return a Promise
|
||||
loadDaemonPrefs(callback, context) {
|
||||
const o = {
|
||||
id: 'webui',
|
||||
jsonrpc: RPC._JsonRpcVersion,
|
||||
method: 'session-get',
|
||||
};
|
||||
this.sendRequest(o, callback, context);
|
||||
@@ -92,36 +100,50 @@ export class Remote {
|
||||
|
||||
checkPort(ip_protocol, callback, context) {
|
||||
const o = {
|
||||
arguments: {
|
||||
id: 'webui',
|
||||
jsonrpc: RPC._JsonRpcVersion,
|
||||
method: 'port-test',
|
||||
params: {
|
||||
ip_protocol,
|
||||
},
|
||||
method: 'port-test',
|
||||
};
|
||||
this.sendRequest(o, callback, context);
|
||||
}
|
||||
|
||||
renameTorrent(torrentIds, oldpath, newname, callback, context) {
|
||||
const o = {
|
||||
arguments: {
|
||||
id: 'webui',
|
||||
jsonrpc: RPC._JsonRpcVersion,
|
||||
method: 'torrent-rename-path',
|
||||
params: {
|
||||
ids: torrentIds,
|
||||
name: newname,
|
||||
path: oldpath,
|
||||
},
|
||||
method: 'torrent-rename-path',
|
||||
};
|
||||
this.sendRequest(o, callback, context);
|
||||
}
|
||||
|
||||
setLabels(torrentIds, labels, callback) {
|
||||
const args = {
|
||||
const params = {
|
||||
ids: torrentIds,
|
||||
labels,
|
||||
};
|
||||
this.sendRequest({ arguments: args, method: 'torrent-set' }, callback);
|
||||
this.sendRequest(
|
||||
{
|
||||
id: 'webui',
|
||||
jsonrpc: RPC._JsonRpcVersion,
|
||||
method: 'torrent-set',
|
||||
params,
|
||||
},
|
||||
callback,
|
||||
);
|
||||
}
|
||||
|
||||
loadDaemonStats(callback, context) {
|
||||
const o = {
|
||||
id: 'webui',
|
||||
jsonrpc: RPC._JsonRpcVersion,
|
||||
method: 'session-stats',
|
||||
};
|
||||
this.sendRequest(o, callback, context);
|
||||
@@ -129,43 +151,46 @@ export class Remote {
|
||||
|
||||
updateTorrents(torrentIds, fields, callback, context) {
|
||||
const o = {
|
||||
arguments: {
|
||||
id: 'webui',
|
||||
jsonrpc: RPC._JsonRpcVersion,
|
||||
method: 'torrent-get',
|
||||
params: {
|
||||
fields,
|
||||
format: 'table',
|
||||
},
|
||||
method: 'torrent-get',
|
||||
};
|
||||
if (torrentIds) {
|
||||
o.arguments.ids = torrentIds;
|
||||
o.params.ids = torrentIds;
|
||||
}
|
||||
this.sendRequest(o, (response) => {
|
||||
const arguments_ = response['arguments'];
|
||||
callback.call(context, arguments_.torrents, arguments_.removed);
|
||||
const { torrents, removed } = response.result;
|
||||
callback.call(context, torrents, removed);
|
||||
});
|
||||
}
|
||||
|
||||
getFreeSpace(dir, callback, context) {
|
||||
const o = {
|
||||
arguments: {
|
||||
path: dir,
|
||||
},
|
||||
id: 'webui',
|
||||
jsonrpc: RPC._JsonRpcVersion,
|
||||
method: 'free-space',
|
||||
params: { path: dir },
|
||||
};
|
||||
this.sendRequest(o, (response) => {
|
||||
const arguments_ = response['arguments'];
|
||||
callback.call(context, arguments_.path, arguments_['size-bytes']);
|
||||
const { path, 'size-bytes': size_bytes } = response.result;
|
||||
callback.call(context, path, size_bytes);
|
||||
});
|
||||
}
|
||||
|
||||
changeFileCommand(torrentId, fileIndices, command) {
|
||||
const arguments_ = {
|
||||
const params = {
|
||||
ids: [torrentId],
|
||||
};
|
||||
arguments_[command] = fileIndices;
|
||||
params[command] = fileIndices;
|
||||
this.sendRequest(
|
||||
{
|
||||
arguments: arguments_,
|
||||
jsonrpc: RPC._JsonRpcVersion,
|
||||
method: 'torrent-set',
|
||||
params,
|
||||
},
|
||||
() => {
|
||||
this._controller.refreshTorrents([torrentId]);
|
||||
@@ -173,14 +198,14 @@ export class Remote {
|
||||
);
|
||||
}
|
||||
|
||||
sendTorrentSetRequests(method, torrent_ids, arguments_, callback, context) {
|
||||
if (!arguments_) {
|
||||
arguments_ = {};
|
||||
}
|
||||
arguments_['ids'] = torrent_ids;
|
||||
sendTorrentSetRequests(method, torrent_ids, params, callback, context) {
|
||||
params ||= {};
|
||||
params.ids = torrent_ids;
|
||||
const o = {
|
||||
arguments: arguments_,
|
||||
id: 'webui',
|
||||
jsonrpc: RPC._JsonRpcVersion,
|
||||
method,
|
||||
params,
|
||||
};
|
||||
this.sendRequest(o, callback, context);
|
||||
}
|
||||
@@ -217,16 +242,17 @@ export class Remote {
|
||||
|
||||
removeTorrents(torrents, trash) {
|
||||
const o = {
|
||||
arguments: {
|
||||
jsonrpc: RPC._JsonRpcVersion,
|
||||
method: 'torrent-remove',
|
||||
params: {
|
||||
'delete-local-data': trash,
|
||||
ids: [],
|
||||
},
|
||||
method: 'torrent-remove',
|
||||
};
|
||||
|
||||
if (torrents) {
|
||||
for (let index = 0, length_ = torrents.length; index < length_; ++index) {
|
||||
o.arguments.ids.push(torrents[index].getId());
|
||||
o.params.ids.push(torrents[index].getId());
|
||||
}
|
||||
}
|
||||
this.sendRequest(o, () => {
|
||||
@@ -254,20 +280,22 @@ export class Remote {
|
||||
url = `magnet:?xt=urn:btih:${url}`;
|
||||
}
|
||||
const o = {
|
||||
arguments: {
|
||||
jsonrpc: RPC._JsonRpcVersion,
|
||||
method: 'torrent-add',
|
||||
params: {
|
||||
filename: url,
|
||||
paused: options.paused,
|
||||
},
|
||||
method: 'torrent-add',
|
||||
};
|
||||
this.sendRequest(o, () => {
|
||||
this._controller.refreshTorrents();
|
||||
});
|
||||
}
|
||||
savePrefs(arguments_) {
|
||||
savePrefs(params) {
|
||||
const o = {
|
||||
arguments: arguments_,
|
||||
jsonrpc: RPC._JsonRpcVersion,
|
||||
method: 'session-set',
|
||||
params,
|
||||
};
|
||||
this.sendRequest(o, () => {
|
||||
this._controller.loadDaemonPrefs();
|
||||
@@ -275,6 +303,7 @@ export class Remote {
|
||||
}
|
||||
updateBlocklist() {
|
||||
const o = {
|
||||
jsonrpc: RPC._JsonRpcVersion,
|
||||
method: 'blocklist-update',
|
||||
};
|
||||
this.sendRequest(o, () => {
|
||||
|
||||
@@ -65,8 +65,8 @@ export class RenameDialog extends EventTarget {
|
||||
file_path,
|
||||
new_name,
|
||||
(response) => {
|
||||
if (response.result === 'success') {
|
||||
const args = response.arguments;
|
||||
if ('result' in response) {
|
||||
const args = response.result;
|
||||
if (handler) {
|
||||
handler.subtree.name = args.name;
|
||||
setTextContent(handler.name_container, args.name);
|
||||
@@ -85,11 +85,13 @@ export class RenameDialog extends EventTarget {
|
||||
} else {
|
||||
tor.refresh(args);
|
||||
}
|
||||
} else if (response.result === 'Invalid argument') {
|
||||
} else {
|
||||
const error_obj = response.error;
|
||||
const err_msg =
|
||||
error_obj.data?.errorString ?? error_obj.message ?? '';
|
||||
const connection_alert = new AlertDialog({
|
||||
heading: `Error renaming "${file_path}"`,
|
||||
message:
|
||||
'Could not rename a torrent or file name. The path to file may have changed/not reflected correctly or the argument is invalid.',
|
||||
message: `${err_msg} (${error_obj.code}`,
|
||||
});
|
||||
this.controller.setCurrentPopup(connection_alert);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export class StatisticsDialog extends EventTarget {
|
||||
this.remote = remote;
|
||||
|
||||
const updateDaemon = () =>
|
||||
this.remote.loadDaemonStats((data) => this._update(data.arguments));
|
||||
this.remote.loadDaemonStats((data) => this._update(data.result));
|
||||
const delay_msec = 5000;
|
||||
this.interval = setInterval(updateDaemon, delay_msec);
|
||||
updateDaemon();
|
||||
|
||||
@@ -353,7 +353,7 @@ export class Transmission extends EventTarget {
|
||||
|
||||
loadDaemonPrefs() {
|
||||
this.remote.loadDaemonPrefs((data) => {
|
||||
this.session_properties = data.arguments;
|
||||
this.session_properties = data.result;
|
||||
this._openTorrentFromUrl();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user