client.py
1 """Client module. 2 3 This module defines the ClientException and Client classes, which are used to communicate with a remote aria2c 4 process through the JSON-RPC protocol. 5 """ 6 7 from __future__ import annotations 8 9 import json 10 from typing import Any, Callable, ClassVar, Union 11 12 import requests 13 import websocket 14 from loguru import logger 15 16 from aria2p.utils import SignalHandler 17 18 DEFAULT_ID = -1 19 DEFAULT_HOST = "http://localhost" 20 DEFAULT_PORT = 6800 21 DEFAULT_TIMEOUT: float = 60.0 22 23 JSONRPC_PARSER_ERROR = -32700 24 JSONRPC_INVALID_REQUEST = -32600 25 JSONRPC_METHOD_NOT_FOUND = -32601 26 JSONRPC_INVALID_PARAMS = -32602 27 JSONRPC_INTERNAL_ERROR = -32603 28 29 JSONRPC_CODES = { 30 JSONRPC_PARSER_ERROR: "Invalid JSON was received by the server.", 31 JSONRPC_INVALID_REQUEST: "The JSON sent is not a valid Request object.", 32 JSONRPC_METHOD_NOT_FOUND: "The method does not exist / is not available.", 33 JSONRPC_INVALID_PARAMS: "Invalid method parameter(s).", 34 JSONRPC_INTERNAL_ERROR: "Internal JSON-RPC error.", 35 } 36 37 NOTIFICATION_START = "aria2.onDownloadStart" 38 NOTIFICATION_PAUSE = "aria2.onDownloadPause" 39 NOTIFICATION_STOP = "aria2.onDownloadStop" 40 NOTIFICATION_COMPLETE = "aria2.onDownloadComplete" 41 NOTIFICATION_ERROR = "aria2.onDownloadError" 42 NOTIFICATION_BT_COMPLETE = "aria2.onBtDownloadComplete" 43 44 NOTIFICATION_TYPES = [ 45 NOTIFICATION_START, 46 NOTIFICATION_PAUSE, 47 NOTIFICATION_STOP, 48 NOTIFICATION_COMPLETE, 49 NOTIFICATION_ERROR, 50 NOTIFICATION_BT_COMPLETE, 51 ] 52 53 CallsType = list[tuple[str, list[str], Union[str, int]]] 54 Multicalls2Type = list[tuple[str, list[str]]] 55 CallReturnType = Union[dict, list, str, int] 56 57 58 class ClientException(Exception): # noqa: N818 59 """An exception specific to JSON-RPC errors.""" 60 61 def __init__(self, code: int, message: str) -> None: 62 """Initialize the exception. 63 64 Parameters: 65 code: The error code. 66 message: The error message. 67 """ 68 super().__init__() 69 if code in JSONRPC_CODES: 70 message = f"{JSONRPC_CODES[code]}\n{message}" 71 72 self.code = code 73 self.message = message 74 75 def __str__(self): 76 return self.message 77 78 def __bool__(self): 79 return False 80 81 82 class Client: 83 """The JSON-RPC client class. 84 85 In this documentation, all the following terms refer to the same entity, the remote aria2c process: 86 remote process, remote server, server, daemon process, background process, remote. 87 88 This class implements method to communicate with a daemon aria2c process through the JSON-RPC protocol. 89 Each method offered by the aria2c process is implemented in this class, in snake_case instead of camelCase 90 (example: add_uri instead of addUri). 91 92 The class defines a `METHODS` variable which contains the names of the available methods. 93 94 The class is instantiated using an address and port, and optionally a secret token. The token is never passed 95 as a method argument. 96 97 The class provides utility methods: 98 99 - `call`, which performs a JSON-RPC call for a single method; 100 - `batch_call`, which performs a JSON-RPC call for a list of methods; 101 - `multicall2`, which is an equivalent of multicall, but easier to use; 102 - `post`, which is responsible for actually sending a payload to the remote process using a POST request; 103 - `get_payload`, which is used to build payloads; 104 - `get_params`, which is used to build list of parameters. 105 """ 106 107 ADD_URI = "aria2.addUri" 108 ADD_TORRENT = "aria2.addTorrent" 109 ADD_METALINK = "aria2.addMetalink" 110 REMOVE = "aria2.remove" 111 FORCE_REMOVE = "aria2.forceRemove" 112 PAUSE = "aria2.pause" 113 PAUSE_ALL = "aria2.pauseAll" 114 FORCE_PAUSE = "aria2.forcePause" 115 FORCE_PAUSE_ALL = "aria2.forcePauseAll" 116 UNPAUSE = "aria2.unpause" 117 UNPAUSE_ALL = "aria2.unpauseAll" 118 TELL_STATUS = "aria2.tellStatus" 119 GET_URIS = "aria2.getUris" 120 GET_FILES = "aria2.getFiles" 121 GET_PEERS = "aria2.getPeers" 122 GET_SERVERS = "aria2.getServers" 123 TELL_ACTIVE = "aria2.tellActive" 124 TELL_WAITING = "aria2.tellWaiting" 125 TELL_STOPPED = "aria2.tellStopped" 126 CHANGE_POSITION = "aria2.changePosition" 127 CHANGE_URI = "aria2.changeUri" 128 GET_OPTION = "aria2.getOption" 129 CHANGE_OPTION = "aria2.changeOption" 130 GET_GLOBAL_OPTION = "aria2.getGlobalOption" 131 CHANGE_GLOBAL_OPTION = "aria2.changeGlobalOption" 132 GET_GLOBAL_STAT = "aria2.getGlobalStat" 133 PURGE_DOWNLOAD_RESULT = "aria2.purgeDownloadResult" 134 REMOVE_DOWNLOAD_RESULT = "aria2.removeDownloadResult" 135 GET_VERSION = "aria2.getVersion" 136 GET_SESSION_INFO = "aria2.getSessionInfo" 137 SHUTDOWN = "aria2.shutdown" 138 FORCE_SHUTDOWN = "aria2.forceShutdown" 139 SAVE_SESSION = "aria2.saveSession" 140 MULTICALL = "system.multicall" 141 LIST_METHODS = "system.listMethods" 142 LIST_NOTIFICATIONS = "system.listNotifications" 143 144 METHODS: ClassVar[list[str]] = [ 145 ADD_URI, 146 ADD_TORRENT, 147 ADD_METALINK, 148 REMOVE, 149 FORCE_REMOVE, 150 PAUSE, 151 PAUSE_ALL, 152 FORCE_PAUSE, 153 FORCE_PAUSE_ALL, 154 UNPAUSE, 155 UNPAUSE_ALL, 156 TELL_STATUS, 157 GET_URIS, 158 GET_FILES, 159 GET_PEERS, 160 GET_SERVERS, 161 TELL_ACTIVE, 162 TELL_WAITING, 163 TELL_STOPPED, 164 CHANGE_POSITION, 165 CHANGE_URI, 166 GET_OPTION, 167 CHANGE_OPTION, 168 GET_GLOBAL_OPTION, 169 CHANGE_GLOBAL_OPTION, 170 GET_GLOBAL_STAT, 171 PURGE_DOWNLOAD_RESULT, 172 REMOVE_DOWNLOAD_RESULT, 173 GET_VERSION, 174 GET_SESSION_INFO, 175 SHUTDOWN, 176 FORCE_SHUTDOWN, 177 SAVE_SESSION, 178 MULTICALL, 179 LIST_METHODS, 180 LIST_NOTIFICATIONS, 181 ] 182 183 def __init__( 184 self, 185 host: str = DEFAULT_HOST, 186 port: int = DEFAULT_PORT, 187 secret: str = "", 188 timeout: float = DEFAULT_TIMEOUT, 189 ) -> None: 190 """Initialize the object. 191 192 Parameters: 193 host: The remote process address. 194 port: The remote process port. 195 secret: The secret token. 196 timeout: The timeout to use for requests towards the remote server. 197 """ 198 host = host.rstrip("/") 199 200 self.host = host 201 self.port = port 202 self.secret = secret 203 self.timeout = timeout 204 self.listening = False 205 206 def __str__(self): 207 return self.server 208 209 def __repr__(self): 210 return f"Client(host='{self.host}', port={self.port}, secret='********')" 211 212 @property 213 def server(self) -> str: 214 """Return the full remote process / server address. 215 216 Returns: 217 The server address. 218 """ 219 return f"{self.host}:{self.port}/jsonrpc" 220 221 @property 222 def ws_server(self) -> str: 223 """Return the full WebSocket remote server address. 224 225 Returns: 226 The WebSocket server address. 227 """ 228 return f"ws{self.host[4:]}:{self.port}/jsonrpc" 229 230 def call( 231 self, 232 method: str, 233 params: list[Any] | None = None, 234 msg_id: int | str | None = None, 235 insert_secret: bool = True, # noqa: FBT001,FBT002 236 ) -> CallReturnType: 237 """Call a single JSON-RPC method. 238 239 Parameters: 240 method: The method name. You can use the constant defined in [`Client`][aria2p.client.Client]. 241 params: A list of parameters. 242 msg_id: The ID of the call, sent back with the server's answer. 243 insert_secret: Whether to insert the secret token in the parameters or not. 244 245 Returns: 246 The answer from the server, as a Python object. 247 """ 248 params = self.get_params(*(params or [])) 249 250 if insert_secret and self.secret: 251 if method.startswith("aria2."): 252 params.insert(0, f"token:{self.secret}") 253 elif method == self.MULTICALL: 254 for param in params[0]: 255 param["params"].insert(0, f"token:{self.secret}") 256 257 payload: str = self.get_payload(method, params, msg_id=msg_id) # type: ignore 258 return self.res_or_raise(self.post(payload)) 259 260 def batch_call( 261 self, 262 calls: CallsType, 263 insert_secret: bool = True, # noqa: FBT001,FBT002 264 ) -> list[CallReturnType]: 265 """Call multiple methods in one request. 266 267 A batch call is simply a list of full payloads, sent at once to the remote process. The differences with a 268 multicall are: 269 270 - multicall is a special "system" method, whereas batch_call is simply the concatenation of several methods 271 - multicall payloads define the "jsonrpc" and "id" keys only once, whereas these keys are repeated in 272 each part of the batch_call payload 273 - as a result of the previous line, you must pass different IDs to the batch_call methods, whereas the 274 ID in multicall is optional 275 276 Parameters: 277 calls: A list of tuples composed of method name, parameters and ID. 278 insert_secret: Whether to insert the secret token in the parameters or not. 279 280 Returns: 281 The results for each call in the batch. 282 """ 283 payloads = [] 284 285 for method, params, msg_id in calls: 286 params = self.get_params(*params) # noqa: PLW2901 287 if insert_secret and self.secret and method.startswith("aria2."): 288 params.insert(0, f"token:{self.secret}") 289 payloads.append(self.get_payload(method, params, msg_id, as_json=False)) 290 291 payload: str = json.dumps(payloads) 292 responses = self.post(payload) 293 return [self.res_or_raise(resp) for resp in responses] 294 295 def multicall2(self, calls: Multicalls2Type, insert_secret: bool = True) -> CallReturnType: # noqa: FBT001,FBT002 296 """Call multiple methods in one request. 297 298 A method equivalent to multicall, but with a simplified usage. 299 300 Instead of providing dictionaries with "methodName" and "params" keys and values, this method allows you 301 to provide the values only, in tuples of length 2. 302 303 With a classic multicall, you would write your params like: 304 305 [ 306 {"methodName": client.REMOVE, "params": ["0000000000000001"]}, 307 {"methodName": client.REMOVE, "params": ["2fa07b6e85c40205"]}, 308 ] 309 310 With multicall2, you can reduce the verbosity: 311 312 [ 313 (client.REMOVE, ["0000000000000001"]), 314 (client.REMOVE, ["2fa07b6e85c40205"]), 315 ] 316 317 Note: 318 multicall2 is not part of the JSON-RPC protocol specification. 319 It is implemented here as a simple convenience method. 320 321 Parameters: 322 calls: List of tuples composed of method name and parameters. 323 insert_secret: Whether to insert the secret token in the parameters or not. 324 325 Returns: 326 The answer from the server, as a Python object (dict / list / str / int). 327 """ 328 multicall_params = [] 329 330 for method, params in calls: 331 params = self.get_params(*params) # noqa: PLW2901 332 if insert_secret and self.secret and method.startswith("aria2."): 333 params.insert(0, f"token:{self.secret}") 334 multicall_params.append({"methodName": method, "params": params}) 335 336 payload: str = self.get_payload(self.MULTICALL, [multicall_params]) # type: ignore 337 return self.res_or_raise(self.post(payload)) 338 339 def post(self, payload: str) -> dict: 340 """Send a POST request to the server. 341 342 The response is a JSON string, which we then load as a Python object. 343 344 Parameters: 345 payload: The payload / data to send to the remote process. It contains the following key-value pairs: 346 "jsonrpc": "2.0", "method": method, "id": id, "params": params (optional). 347 348 Returns: 349 The answer from the server, as a Python dictionary. 350 """ 351 return requests.post(self.server, data=payload, timeout=self.timeout).json() 352 353 @staticmethod 354 def response_as_exception(response: dict) -> ClientException: 355 """Transform the response as a [`ClientException`][aria2p.client.ClientException] instance and return it. 356 357 Parameters: 358 response: A response sent by the server. 359 360 Returns: 361 An instance of the [`ClientException`][aria2p.client.ClientException] class. 362 """ 363 return ClientException(response["error"]["code"], response["error"]["message"]) 364 365 @staticmethod 366 def res_or_raise(response: dict) -> CallReturnType: 367 """Return the result of the response, or raise an error with code and message. 368 369 Parameters: 370 response: A response sent by the server. 371 372 Returns: 373 The "result" value of the response. 374 375 Raises: 376 ClientException: When the response contains an error (client/server error). 377 See the [`ClientException`][aria2p.client.ClientException] class. 378 """ 379 if "error" in response: 380 raise Client.response_as_exception(response) 381 return response["result"] 382 383 @staticmethod 384 def get_payload( 385 method: str, 386 params: list[Any] | None = None, 387 msg_id: int | str | None = None, 388 as_json: bool = True, # noqa: FBT001,FBT002 389 ) -> str | dict: 390 """Build a payload. 391 392 Parameters: 393 method: The method name. You can use the constant defined in [`Client`][aria2p.client.Client]. 394 params: The list of parameters. 395 msg_id: The ID of the call, sent back with the server's answer. 396 as_json: Whether to return the payload as a JSON-string or Python dictionary. 397 398 Returns: 399 The payload as a JSON string or as Python dictionary. 400 """ 401 payload: dict[str, Any] = {"jsonrpc": "2.0", "method": method} 402 403 if msg_id is None: 404 payload["id"] = DEFAULT_ID 405 else: 406 payload["id"] = msg_id 407 408 if params: 409 payload["params"] = params 410 411 return json.dumps(payload) if as_json else payload 412 413 @staticmethod 414 def get_params(*args: Any) -> list: 415 """Build the list of parameters. 416 417 This method simply removes the `None` values from the given arguments. 418 419 Parameters: 420 *args: List of parameters. 421 422 Returns: 423 A new list, with `None` values filtered out. 424 """ 425 return [_ for _ in args if _ is not None] 426 427 def add_uri( 428 self, 429 uris: list[str], 430 options: dict | None = None, 431 position: int | None = None, 432 ) -> str: 433 """Add a new download. 434 435 This method adds a new download and returns the GID of the newly registered download. 436 437 Original signature: 438 439 aria2.addUri([secret], uris[, options[, position]]) 440 441 Parameters: 442 uris: `uris` is an array of HTTP/FTP/SFTP/BitTorrent URIs (strings) pointing to the same resource. 443 If you mix URIs pointing to different resources, 444 then the download may fail or be corrupted without aria2 complaining. 445 When adding BitTorrent Magnet URIs, 446 uris must have only one element and it should be BitTorrent Magnet URI. 447 options: `options` is a struct and its members are pairs of option name and value. 448 See [Options][aria2p.options.Options] for more details. 449 position: If `position` is given, it must be an integer starting from 0. 450 The new download will be inserted at `position` in the waiting queue. 451 If `position` is omitted or `position` is larger than the current size of the queue, 452 the new download is appended to the end of the queue. 453 454 Returns: 455 The GID of the created download. 456 457 Examples: 458 **Original JSON-RPC Example** 459 460 The following example adds http://example.org/file: 461 462 >>> import urllib2, json 463 >>> jsonreq = json.dumps( 464 ... { 465 ... "jsonrpc": "2.0", 466 ... "id": "qwer", 467 ... "method": "aria2.addUri", 468 ... "params": [["http://example.org/file"]], 469 ... } 470 ... ) 471 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 472 >>> c.read() 473 '{"id":"qwer","jsonrpc":"2.0","result":"0000000000000001"}' 474 """ 475 return self.call(self.ADD_URI, params=[uris, options, position]) # type: ignore 476 477 def add_torrent( 478 self, 479 torrent: str, 480 uris: list[str], 481 options: dict | None = None, 482 position: int | None = None, 483 ) -> str: 484 """Add a BitTorrent download. 485 486 This method adds a BitTorrent download by uploading a ".torrent" file and returns the GID of the 487 newly registered download. 488 489 Original signature: 490 491 aria2.addTorrent([secret], torrent[, uris[, options[, position]]]) 492 493 If you want to add a BitTorrent Magnet URI, use the [`add_uri()`][aria2p.client.Client.add_uri] method instead. 494 495 If [`--rpc-save-upload-metadata`][aria2p.options.Options.rpc_save_upload_metadata] is true, 496 the uploaded data is saved as a file named as the hex string of SHA-1 hash of data plus ".torrent" 497 in the directory specified by [`--dir`][aria2p.options.Options.dir] option. 498 E.g. a file name might be 0a3893293e27ac0490424c06de4d09242215f0a6.torrent. 499 If a file with the same name already exists, it is overwritten! 500 If the file cannot be saved successfully 501 or [`--rpc-save-upload-metadata`][aria2p.options.Options.rpc_save_upload_metadata] is false, 502 the downloads added by this method are not saved by [`--save-session`][aria2p.options.Options.save_session]. 503 504 Parameters: 505 torrent: `torrent` must be a base64-encoded string containing the contents of the ".torrent" file. 506 uris: `uris` is an array of URIs (string). `uris` is used for Web-seeding. 507 For single file torrents, the URI can be a complete URI pointing to the resource; if URI ends with /, 508 name in torrent file is added. For multi-file torrents, name and path in torrent are added to form a URI 509 for each file. 510 options: `options` is a struct and its members are pairs of option name and value. 511 See [Options][aria2p.options.Options] for more details. 512 position: If `position` is given, it must be an integer starting from 0. 513 The new download will be inserted at `position` in the waiting queue. 514 If `position` is omitted or `position` is larger than the current size of the queue, 515 the new download is appended to the end of the queue. 516 517 Returns: 518 The GID of the created download. 519 520 Examples: 521 **Original JSON-RPC Example** 522 523 The following examples add local file file.torrent. 524 525 >>> import urllib2, json, base64 526 >>> torrent = base64.b64encode(open("file.torrent").read()) 527 >>> jsonreq = json.dumps( 528 ... { 529 ... "jsonrpc": "2.0", 530 ... "id": "asdf", 531 ... "method": "aria2.addTorrent", 532 ... "params": [torrent], 533 ... } 534 ... ) 535 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 536 >>> c.read() 537 '{"id":"asdf","jsonrpc":"2.0","result":"0000000000000001"}' 538 """ 539 return self.call(self.ADD_TORRENT, [torrent, uris, options, position]) # type: ignore 540 541 def add_metalink( 542 self, 543 metalink: str, 544 options: dict | None = None, 545 position: int | None = None, 546 ) -> list[str]: 547 """Add a Metalink download. 548 549 This method adds a Metalink download by uploading a ".metalink" file 550 and returns an array of GIDs of newly registered downloads. 551 552 Original signature: 553 554 aria2.addMetalink([secret], metalink[, options[, position]]) 555 556 If [`--rpc-save-upload-metadata`][aria2p.options.Options.rpc_save_upload_metadata] is true, 557 the uploaded data is saved as a file named hex string of SHA-1 hash of data plus ".metalink" 558 in the directory specified by [`--dir`][aria2p.options.Options.dir] option. 559 E.g. a file name might be 0a3893293e27ac0490424c06de4d09242215f0a6.metalink. 560 If a file with the same name already exists, it is overwritten! 561 If the file cannot be saved successfully 562 or [`--rpc-save-upload-metadata`][aria2p.options.Options.rpc_save_upload_metadata] is false, 563 the downloads added by this method are not saved by [`--save-session`][aria2p.options.Options.save_session]. 564 565 Parameters: 566 metalink: `metalink` is a base64-encoded string which contains the contents of the ".metalink" file. 567 options: `options` is a struct and its members are pairs of option name and value. 568 See [Options][aria2p.options.Options] for more details. 569 position: If `position` is given, it must be an integer starting from 0. 570 The new download will be inserted at `position` in the waiting queue. 571 If `position` is omitted or `position` is larger than the current size of the queue, 572 the new download is appended to the end of the queue. 573 574 Returns: 575 The GID of the created download. 576 577 Examples: 578 **Original JSON-RPC Example** 579 580 The following examples add local file file.meta4. 581 582 >>> import urllib2, json, base64 583 >>> metalink = base64.b64encode(open("file.meta4").read()) 584 >>> jsonreq = json.dumps( 585 ... { 586 ... "jsonrpc": "2.0", 587 ... "id": "qwer", 588 ... "method": "aria2.addMetalink", 589 ... "params": [metalink], 590 ... } 591 ... ) 592 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 593 >>> c.read() 594 '{"id":"qwer","jsonrpc":"2.0","result":["0000000000000001"]}' 595 """ 596 return self.call(self.ADD_METALINK, [metalink, options, position]) # type: ignore 597 598 def remove(self, gid: str) -> str: 599 """Remove a download. 600 601 This method removes the download denoted by gid (string). If the specified download is in progress, 602 it is first stopped. The status of the removed download becomes removed. This method returns GID of 603 removed download. 604 605 Original signature: 606 607 aria2.remove([secret], gid) 608 609 Parameters: 610 gid: The download to remove. 611 612 Returns: 613 The GID of the removed download. 614 615 Examples: 616 **Original JSON-RPC Example** 617 618 The following examples remove a download with GID#0000000000000001. 619 620 >>> import urllib2, json 621 >>> jsonreq = json.dumps( 622 ... { 623 ... "jsonrpc": "2.0", 624 ... "id": "qwer", 625 ... "method": "aria2.remove", 626 ... "params": ["0000000000000001"], 627 ... } 628 ... ) 629 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 630 >>> c.read() 631 '{"id":"qwer","jsonrpc":"2.0","result":"0000000000000001"}' 632 """ 633 return self.call(self.REMOVE, [gid]) # type: ignore[return-value] 634 635 def force_remove(self, gid: str) -> str: 636 """Force remove a download. 637 638 This method removes the download denoted by gid. 639 This method behaves just like [`remove()`][aria2p.client.Client.remove] except 640 that this method removes the download without performing any actions which take time, such as contacting 641 BitTorrent trackers to unregister the download first. 642 643 Original signature: 644 645 aria2.forceRemove([secret], gid) 646 647 Parameters: 648 gid: The download to force remove. 649 650 Returns: 651 The GID of the removed download. 652 """ 653 return self.call(self.FORCE_REMOVE, [gid]) # type: ignore 654 655 def pause(self, gid: str) -> str: 656 """Pause a download. 657 658 This method pauses the download denoted by gid (string). 659 The status of paused download becomes paused. 660 If the download was active, the download is placed in the front of waiting queue. 661 While the status is paused, the download is not started. 662 To change status to waiting, use the [`unpause()`][aria2p.client.Client.unpause] method. 663 664 Original signature: 665 666 aria2.pause([secret], gid) 667 668 Parameters: 669 gid: The download to pause. 670 671 Returns: 672 The GID of the paused download. 673 """ 674 return self.call(self.PAUSE, [gid]) # type: ignore 675 676 def pause_all(self) -> str: 677 """Pause all active/waiting downloads. 678 679 This method is equal to calling [`pause()`][aria2p.client.Client.pause] for every active/waiting download. 680 681 Original signature: 682 683 aria2.pauseAll([secret]) 684 685 Returns: 686 `"OK"`. 687 """ 688 return self.call(self.PAUSE_ALL) # type: ignore 689 690 def force_pause(self, gid: str) -> str: 691 """Force pause a download. 692 693 This method pauses the download denoted by gid. 694 This method behaves just like [`pause()`][aria2p.client.Client.pause] except that 695 this method pauses downloads without performing any actions which take time, 696 such as contacting BitTorrent trackers to unregister the download first. 697 698 Original signature: 699 700 aria2.forcePause([secret], gid) 701 702 Parameters: 703 gid: The download to force pause. 704 705 Returns: 706 The GID of the paused download. 707 """ 708 return self.call(self.FORCE_PAUSE, [gid]) # type: ignore 709 710 def force_pause_all(self) -> str: 711 """Force pause all active/waiting downloads. 712 713 This method is equal to calling [`force_pause()`][aria2p.client.Client.force_pause] for every active/waiting download. 714 715 Original signature: 716 717 aria2.forcePauseAll([secret]) 718 719 Returns: 720 `"OK"`. 721 """ 722 return self.call(self.FORCE_PAUSE_ALL) # type: ignore 723 724 def unpause(self, gid: str) -> str: 725 """Resume a download. 726 727 This method changes the status of the download denoted by gid (string) from paused to waiting, 728 making the download eligible to be restarted. This method returns the GID of the unpaused download. 729 730 Original signature: 731 732 aria2.unpause([secret], gid) 733 734 Parameters: 735 gid: The download to resume. 736 737 Returns: 738 The GID of the resumed download. 739 """ 740 return self.call(self.UNPAUSE, [gid]) # type: ignore 741 742 def unpause_all(self) -> str: 743 """Resume all downloads. 744 745 This method is equal to calling [`unpause()`][aria2p.client.Client.unpause] for every active/waiting download. 746 747 Original signature: 748 749 aria2.unpauseAll([secret]) 750 751 Returns: 752 `"OK"`. 753 """ 754 return self.call(self.UNPAUSE_ALL) # type: ignore 755 756 def tell_status(self, gid: str, keys: list[str] | None = None) -> dict: 757 """Tell status of a download. 758 759 This method returns the progress of the download denoted by gid (string). keys is an array of strings. If 760 specified, the response contains only keys in the keys array. If keys is empty or omitted, the response 761 contains all keys. This is useful when you just want specific keys and avoid unnecessary transfers. For 762 example, `tell_status("0000000000000001", ["gid", "status"])` returns the gid and status keys only. The 763 response is a struct and contains following keys. Values are strings. 764 765 - `gid`: GID of the download. 766 - `status`: active for currently downloading/seeding downloads. waiting for downloads in the queue; download is 767 not started. paused for paused downloads. error for downloads that were stopped because of error. 768 complete for stopped and completed downloads. removed for the downloads removed by user. 769 - `totalLength`: Total length of the download in bytes. 770 - `completedLength`: Completed length of the download in bytes. 771 - `uploadLength`: Uploaded length of the download in bytes. 772 - `bitfield`: Hexadecimal representation of the download progress. The highest bit corresponds to the piece at 773 index 0. Any set bits indicate loaded pieces, while unset bits indicate not yet loaded and/or missing 774 pieces. Any overflow bits at the end are set to zero. When the download was not started yet, this key 775 will not be included in the response. 776 - `downloadSpeed`: Download speed of this download measured in bytes/sec. 777 - `uploadSpeed`: Upload speed of this download measured in bytes/sec. 778 - `infoHash`: InfoHash. BitTorrent only. 779 - `numSeeders`: The number of seeders aria2 has connected to. BitTorrent only. 780 - `seeder` true if the local endpoint is a seeder. Otherwise false. BitTorrent only. 781 - `pieceLength`: Piece length in bytes. 782 - `numPieces`: The number of pieces. 783 - `connections`: The number of peers/servers aria2 has connected to. 784 - `errorCode`: The code of the last error for this item, if any. The value is a string. The error codes are defined 785 in the EXIT STATUS section. This value is only available for stopped/completed downloads. 786 - `errorMessage`: The (hopefully) human readable error message associated to errorCode. 787 - `followedBy`: List of GIDs which are generated as the result of this download. For example, when aria2 downloads a 788 Metalink file, it generates downloads described in the Metalink 789 (see the [`--follow-metalink`][aria2p.options.Options.follow_metalink] option). 790 This value is useful to track auto-generated downloads. If there are no such downloads, 791 this key will not be included in the response. 792 - `following`: The reverse link for followedBy. 793 A download included in followedBy has this object's GID in its following value. 794 - `belongsTo`: GID of a parent download. Some downloads are a part of another download. For example, if a file in a 795 Metalink has BitTorrent resources, the downloads of ".torrent" files are parts of that parent. If 796 this download has no parent, this key will not be included in the response. 797 - `dir`:Directory to save files. 798 - `files`: Return the list of files. 799 The elements of this list are the same structs used in [`get_files()`][aria2p.client.Client.get_files] method. 800 - `bittorrent`: Struct which contains information retrieved from the .torrent (file). BitTorrent only. 801 It contains the following keys: 802 - `announceList`: List of lists of announce URIs. If the torrent contains announce and no announce-list, announce 803 is converted to the announce-list format. 804 - `comment`: The comment of the torrent. comment.utf-8 is used if available. 805 - `creationDate`: The creation time of the torrent. The value is an integer since the epoch, measured in seconds. 806 - `mode`: File mode of the torrent. The value is either single or multi. 807 - `info`: Struct which contains data from Info dictionary. It contains following keys. 808 - `name`: name in info dictionary. name.utf-8 is used if available. 809 - `verifiedLength`: The number of verified number of bytes while the files are being hash checked. This key exists only 810 when this download is being hash checked. 811 - `verifyIntegrityPending`: true if this download is waiting for the hash check in a queue. 812 This key exists only when this download is in the queue. 813 814 Original signature: 815 816 aria2.tellStatus([secret], gid[, keys]) 817 818 Parameters: 819 gid: The download to tell status of. 820 keys: The keys to return. 821 822 Returns: 823 The details of a download. 824 825 Examples: 826 **Original JSON-RPC Example** 827 828 The following example gets information about a download with GID#0000000000000001: 829 830 >>> import urllib2, json 831 >>> from pprint import pprint 832 >>> jsonreq = json.dumps( 833 ... { 834 ... "jsonrpc": "2.0", 835 ... "id": "qwer", 836 ... "method": "aria2.tellStatus", 837 ... "params": ["0000000000000001"], 838 ... } 839 ... ) 840 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 841 >>> pprint(json.loads(c.read())) 842 {u'id': u'qwer', 843 u'jsonrpc': u'2.0', 844 u'result': {u'bitfield': u'0000000000', 845 u'completedLength': u'901120', 846 u'connections': u'1', 847 u'dir': u'/downloads', 848 u'downloadSpeed': u'15158', 849 u'files': [{u'index': u'1', 850 u'length': u'34896138', 851 u'completedLength': u'34896138', 852 u'path': u'/downloads/file', 853 u'selected': u'true', 854 u'uris': [{u'status': u'used', 855 u'uri': u'http://example.org/file'}]}], 856 u'gid': u'0000000000000001', 857 u'numPieces': u'34', 858 u'pieceLength': u'1048576', 859 u'status': u'active', 860 u'totalLength': u'34896138', 861 u'uploadLength': u'0', 862 u'uploadSpeed': u'0'}} 863 864 The following example gets only specific keys: 865 866 >>> jsonreq = json.dumps( 867 ... { 868 ... "jsonrpc": "2.0", 869 ... "id": "qwer", 870 ... "method": "aria2.tellStatus", 871 ... "params": [ 872 ... "0000000000000001", 873 ... ["gid", "totalLength", "completedLength"], 874 ... ], 875 ... } 876 ... ) 877 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 878 >>> pprint(json.loads(c.read())) 879 {u'id': u'qwer', 880 u'jsonrpc': u'2.0', 881 u'result': {u'completedLength': u'5701632', 882 u'gid': u'0000000000000001', 883 u'totalLength': u'34896138'}} 884 """ 885 return self.call(self.TELL_STATUS, [gid, keys]) # type: ignore 886 887 def get_uris(self, gid: str) -> dict: 888 """Return URIs used in a download. 889 890 This method returns the URIs used in the download denoted by gid (string). The response is an array of 891 structs and it contains following keys. Values are string. 892 893 - `uri`: URI 894 - `status`: 'used' if the URI is in use. 'waiting' if the URI is still waiting in the queue. 895 896 Original signature: 897 898 aria2.getUris([secret], gid) 899 900 Parameters: 901 gid: The download to list URIs of. 902 903 Returns: 904 The URIs used in a download. 905 906 Examples: 907 **Original JSON-RPC Example** 908 909 >>> import urllib2, json 910 >>> from pprint import pprint 911 >>> jsonreq = json.dumps( 912 ... { 913 ... "jsonrpc": "2.0", 914 ... "id": "qwer", 915 ... "method": "aria2.getUris", 916 ... "params": ["0000000000000001"], 917 ... } 918 ... ) 919 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 920 >>> pprint(json.loads(c.read())) 921 {u'id': u'qwer', 922 u'jsonrpc': u'2.0', 923 u'result': [{u'status': u'used', 924 u'uri': u'http://example.org/file'}]} 925 """ 926 return self.call(self.GET_URIS, [gid]) # type: ignore 927 928 def get_files(self, gid: str) -> dict: 929 """Return file list of a download. 930 931 This method returns the file list of the download denoted by gid (string). The response is an array of 932 structs which contain following keys. Values are strings. 933 934 - `index`: Index of the file, starting at 1, in the same order as files appear in the multi-file torrent. 935 - `path`: File path. 936 - `length`: File size in bytes. 937 - `completedLength`: Completed length of this file in bytes. 938 Please note that it is possible that sum of `completedLength` 939 is less than the `completedLength` returned by the [`tell_status()`][aria2p.client.Client.tell_status] method. 940 This is because `completedLength` in [`get_files()`][aria2p.client.Client.get_files] only includes completed pieces. 941 On the other hand, `completedLength` in [`tell_status()`][aria2p.client.Client.tell_status] 942 also includes partially completed pieces. 943 - `selected`: true if this file is selected by [`--select-file`][aria2p.options.Options.select_file] option. 944 If [`--select-file`][aria2p.options.Options.select_file] is not specified 945 or this is single-file torrent or not a torrent download at all, this value is always true. Otherwise false. 946 - `uris` Returns a list of URIs for this file. 947 The element type is the same struct used in the [`get_uris()`][aria2p.client.Client.get_uris] method. 948 949 Original signature: 950 951 aria2.getFiles([secret], gid) 952 953 Parameters: 954 gid: The download to list files of. 955 956 Returns: 957 The file list of a download. 958 959 Examples: 960 **Original JSON-RPC Example** 961 962 >>> import urllib2, json 963 >>> from pprint import pprint 964 >>> jsonreq = json.dumps( 965 ... { 966 ... "jsonrpc": "2.0", 967 ... "id": "qwer", 968 ... "method": "aria2.getFiles", 969 ... "params": ["0000000000000001"], 970 ... } 971 ... ) 972 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 973 >>> pprint(json.loads(c.read())) 974 {u'id': u'qwer', 975 u'jsonrpc': u'2.0', 976 u'result': [{u'index': u'1', 977 u'length': u'34896138', 978 u'completedLength': u'34896138', 979 u'path': u'/downloads/file', 980 u'selected': u'true', 981 u'uris': [{u'status': u'used', 982 u'uri': u'http://example.org/file'}]}]} 983 """ 984 return self.call(self.GET_FILES, [gid]) # type: ignore 985 986 def get_peers(self, gid: str) -> dict: 987 """Return peers list of a download. 988 989 This method returns the list of peers of the download denoted by gid (string). This method is for BitTorrent 990 only. The response is an array of structs and contains the following keys. Values are strings. 991 992 - `peerId`: Percent-encoded peer ID. 993 - `ip`: IP address of the peer. 994 - `port`: Port number of the peer. 995 - `bitfield`: Hexadecimal representation of the download progress of the peer. The highest bit corresponds to 996 the piece at index 0. Set bits indicate the piece is available and unset bits indicate the piece is 997 missing. Any spare bits at the end are set to zero. 998 - `amChoking`: true if aria2 is choking the peer. Otherwise false. 999 - `peerChoking`: true if the peer is choking aria2. Otherwise false. 1000 - `downloadSpeed`: Download speed (byte/sec) that this client obtains from the peer. 1001 - `uploadSpeed`: Upload speed(byte/sec) that this client uploads to the peer. 1002 - `seeder`: true if this peer is a seeder. Otherwise false. 1003 1004 Original signature: 1005 1006 aria2.getPeers([secret], gid) 1007 1008 Parameters: 1009 gid: The download to get peers from. 1010 1011 Returns: 1012 The peers connected to a download. 1013 1014 Examples: 1015 **Original JSON-RPC Example** 1016 1017 >>> import urllib2, json 1018 >>> from pprint import pprint 1019 >>> jsonreq = json.dumps( 1020 ... { 1021 ... "jsonrpc": "2.0", 1022 ... "id": "qwer", 1023 ... "method": "aria2.getPeers", 1024 ... "params": ["0000000000000001"], 1025 ... } 1026 ... ) 1027 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 1028 >>> pprint(json.loads(c.read())) 1029 {u'id': u'qwer', 1030 u'jsonrpc': u'2.0', 1031 u'result': [{u'amChoking': u'true', 1032 u'bitfield': u'ffffffffffffffffffffffffffffffffffffffff', 1033 u'downloadSpeed': u'10602', 1034 u'ip': u'10.0.0.9', 1035 u'peerChoking': u'false', 1036 u'peerId': u'aria2%2F1%2E10%2E5%2D%87%2A%EDz%2F%F7%E6', 1037 u'port': u'6881', 1038 u'seeder': u'true', 1039 u'uploadSpeed': u'0'}, 1040 {u'amChoking': u'false', 1041 u'bitfield': u'ffffeff0fffffffbfffffff9fffffcfff7f4ffff', 1042 u'downloadSpeed': u'8654', 1043 u'ip': u'10.0.0.30', 1044 u'peerChoking': u'false', 1045 u'peerId': u'bittorrent client758', 1046 u'port': u'37842', 1047 u'seeder': u'false', 1048 u'uploadSpeed': u'6890'}]} 1049 """ 1050 return self.call(self.GET_PEERS, [gid]) # type: ignore 1051 1052 def get_servers(self, gid: str) -> dict: 1053 """Return servers currently connected for a download. 1054 1055 This method returns currently connected HTTP(S)/FTP/SFTP servers of the download denoted by gid (string). The 1056 response is an array of structs and contains the following keys. Values are strings. 1057 1058 - `index`: Index of the file, starting at 1, in the same order as files appear in the multi-file metalink. 1059 - `servers`: A list of structs which contain the following keys. 1060 - `uri`: Original URI. 1061 - `currentUri`: This is the URI currently used for downloading. 1062 If redirection is involved, currentUri and uri may differ. 1063 - `downloadSpeed`: Download speed (byte/sec). 1064 1065 Original signature: 1066 1067 aria2.getServers([secret], gid) 1068 1069 Parameters: 1070 gid: The download to get servers from. 1071 1072 Returns: 1073 The servers connected to a download. 1074 1075 Examples: 1076 **Original JSON-RPC Example** 1077 1078 >>> import urllib2, json 1079 >>> from pprint import pprint 1080 >>> jsonreq = json.dumps( 1081 ... { 1082 ... "jsonrpc": "2.0", 1083 ... "id": "qwer", 1084 ... "method": "aria2.getServers", 1085 ... "params": ["0000000000000001"], 1086 ... } 1087 ... ) 1088 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 1089 >>> pprint(json.loads(c.read())) 1090 {u'id': u'qwer', 1091 u'jsonrpc': u'2.0', 1092 u'result': [{u'index': u'1', 1093 u'servers': [{u'currentUri': u'http://example.org/file', 1094 u'downloadSpeed': u'10467', 1095 u'uri': u'http://example.org/file'}]}]} 1096 """ 1097 return self.call(self.GET_SERVERS, [gid]) # type: ignore 1098 1099 def tell_active(self, keys: list[str] | None = None) -> list[dict]: 1100 """Return the list of active downloads. 1101 1102 Original signature: 1103 1104 aria2.tellActive([secret][, keys]) 1105 1106 Parameters: 1107 keys: The keys to return. Please refer to the [`tell_status()`][aria2p.client.Client.tell_status] method. 1108 1109 Returns: 1110 An array of the same structs as returned by the [`tell_status()`][aria2p.client.Client.tell_status] method. 1111 """ 1112 return self.call(self.TELL_ACTIVE, [keys]) # type: ignore 1113 1114 def tell_waiting(self, offset: int, num: int, keys: list[str] | None = None) -> list[dict]: 1115 """Return the list of waiting downloads. 1116 1117 This method returns a list of waiting downloads, including paused ones. 1118 1119 Original signature: 1120 1121 aria2.tellWaiting([secret], offset, num[, keys]) 1122 1123 Parameters: 1124 offset: An integer to specify the offset from the download waiting at the front. 1125 If `offset` is a positive integer, this method returns downloads in the range of [`offset`, `offset` + `num`). 1126 `offset` can be a negative integer. `offset == -1` points last download in the waiting queue and `offset == -2` 1127 points the download before the last download, and so on. Downloads in the response are in reversed order then. 1128 For example, imagine three downloads "A","B" and "C" are waiting in this order. `tell_waiting(0, 1)` 1129 returns `["A"]`. `tell_waiting(1, 2)` returns `["B", "C"]`. `tell_waiting(-1, 2)` returns `["C", "B"]`. 1130 num: An integer to specify the maximum number of downloads to be returned. 1131 keys: The keys to return. Please refer to the [`tell_status()`][aria2p.client.Client.tell_status] method. 1132 1133 Returns: 1134 An array of the same structs as returned by [`tell_status()`][aria2p.client.Client.tell_status] method. 1135 """ 1136 return self.call(self.TELL_WAITING, [offset, num, keys]) # type: ignore 1137 1138 def tell_stopped(self, offset: int, num: int, keys: list[str] | None = None) -> list[dict]: 1139 """Return the list of stopped downloads. 1140 1141 This method returns a list of stopped downloads. offset is an integer and specifies the offset from the 1142 least recently stopped download. 1143 1144 Original signature: 1145 1146 aria2.tellStopped([secret], offset, num[, keys]) 1147 1148 Parameters: 1149 offset: Same semantics as described in the [`tell_waiting()`][aria2p.client.Client.tell_waiting] method. 1150 num: An integer to specify the maximum number of downloads to be returned. 1151 keys: The keys to return. Please refer to the [`tell_status()`][aria2p.client.Client.tell_status] method. 1152 1153 Returns: 1154 An array of the same structs as returned by the [`tell_status()`][aria2p.client.Client.tell_status] method. 1155 """ 1156 return self.call(self.TELL_STOPPED, [offset, num, keys]) # type: ignore 1157 1158 def change_position(self, gid: str, pos: int, how: str) -> int: 1159 """Change position of a download. 1160 1161 This method changes the position of the download denoted by `gid` in the queue. 1162 1163 Original signature: 1164 1165 aria2.changePosition([secret], gid, pos, how) 1166 1167 Parameters: 1168 gid: The download to change the position of. 1169 pos: An integer. 1170 how: `POS_SET`, `POS_CUR` or `POS_END`. 1171 1172 - If `how` is `POS_SET`, it moves the download to a position relative to the beginning of the queue. 1173 - If `how` is `POS_CUR`, it moves the download to a position relative to the current position. 1174 - If `how` is `POS_END`, it moves the download to a position relative to the end of the queue. 1175 - If the destination position is less than 0 or beyond the end of the queue, 1176 it moves the download to the beginning or the end of the queue respectively. 1177 1178 For example, if GID#0000000000000001 is currently in position 3, 1179 `change_position('0000000000000001', -1, 'POS_CUR')` will change its position to 2. Additionally 1180 `change_position('0000000000000001', 0, 'POS_SET')` will change its position to 0 (the beginning of the queue). 1181 1182 Returns: 1183 An integer denoting the resulting position. 1184 1185 Examples: 1186 **Original JSON-RPC Example** 1187 1188 The following examples move the download GID#0000000000000001 to the front of the queue. 1189 1190 >>> import urllib2, json 1191 >>> from pprint import pprint 1192 >>> jsonreq = json.dumps( 1193 ... { 1194 ... "jsonrpc": "2.0", 1195 ... "id": "qwer", 1196 ... "method": "aria2.changePosition", 1197 ... "params": ["0000000000000001", 0, "POS_SET"], 1198 ... } 1199 ... ) 1200 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 1201 >>> pprint(json.loads(c.read())) 1202 {u'id': u'qwer', u'jsonrpc': u'2.0', u'result': 0} 1203 """ 1204 return self.call(self.CHANGE_POSITION, [gid, pos, how]) # type: ignore 1205 1206 def change_uri( 1207 self, 1208 gid: str, 1209 file_index: int, 1210 del_uris: list[str], 1211 add_uris: list[str], 1212 position: int | None = None, 1213 ) -> list[int]: 1214 """Remove the URIs in `del_uris` from and appends the URIs in `add_uris` to download denoted by gid. 1215 1216 Original signature: 1217 1218 aria2.changeUri([secret], gid, fileIndex, delUris, addUris[, position]) 1219 1220 Parameters: 1221 gid: The download to change URIs of. 1222 file_index: Used to select which file to remove/attach given URIs. `file_index` is 1-based. 1223 del_uris: List of strings. 1224 add_uris: List of strings. 1225 position: Used to specify where URIs are inserted in the existing waiting URI list. `position` is 0-based. 1226 When position is omitted, URIs are appended to the back of the list. 1227 This method first executes the removal and then the addition. 1228 `position` is the position after URIs are removed, not the position when this 1229 method is called. 1230 1231 A download can contain multiple files and URIs are attached to each file. 1232 When removing an URI, if the same URIs exist in download, only one of them is removed for 1233 each URI in `del_uris`. In other words, if there are three URIs http://example.org/aria2 and you want 1234 remove them all, you have to specify (at least) 3 http://example.org/aria2 in `del_uris`. 1235 1236 Returns: 1237 A list which contains two integers. 1238 The first integer is the number of URIs deleted. 1239 The second integer is the number of URIs added. 1240 1241 Examples: 1242 **Original JSON-RPC Example** 1243 1244 The following examples add the URI http://example.org/file to the file whose index is 1 and belongs to the 1245 download GID#0000000000000001. 1246 1247 >>> import urllib2, json 1248 >>> from pprint import pprint 1249 >>> jsonreq = json.dumps({'jsonrpc':'2.0', 'id':'qwer', 1250 ... 'method':'aria2.changeUri', 1251 ... 'params':['0000000000000001', 1, [], 1252 ['http://example.org/file']]}) 1253 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 1254 >>> pprint(json.loads(c.read())) 1255 {u'id': u'qwer', u'jsonrpc': u'2.0', u'result': [0, 1]} 1256 """ 1257 return self.call(self.CHANGE_URI, [gid, file_index, del_uris, add_uris, position]) # type: ignore 1258 1259 def get_option(self, gid: str) -> dict: 1260 """Return options of a download. 1261 1262 Original signature: 1263 1264 aria2.getOption([secret], gid) 1265 1266 Parameters: 1267 gid: The download to get the options of. 1268 1269 Returns: 1270 A struct where keys are the names of options. The values are strings. 1271 Note that this method does not return options which have 1272 no default value and have not been set on the command-line, in configuration files or RPC methods. 1273 1274 Examples: 1275 **Original JSON-RPC Example** 1276 1277 The following examples get options of the download GID#0000000000000001. 1278 1279 >>> import urllib2, json 1280 >>> from pprint import pprint 1281 >>> jsonreq = json.dumps( 1282 ... { 1283 ... "jsonrpc": "2.0", 1284 ... "id": "qwer", 1285 ... "method": "aria2.getOption", 1286 ... "params": ["0000000000000001"], 1287 ... } 1288 ... ) 1289 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 1290 >>> pprint(json.loads(c.read())) 1291 {u'id': u'qwer', 1292 u'jsonrpc': u'2.0', 1293 u'result': {u'allow-overwrite': u'false', 1294 u'allow-piece-length-change': u'false', 1295 u'always-resume': u'true', 1296 u'async-dns': u'true', 1297 ... 1298 """ 1299 return self.call(self.GET_OPTION, [gid]) # type: ignore 1300 1301 def change_option(self, gid: str, options: dict) -> str: 1302 """Change a download options dynamically. 1303 1304 Original signature: 1305 1306 aria2.changeOption([secret], gid, options) 1307 1308 Parameters: 1309 gid: The download to change options of. 1310 options: The options listed in Input File subsection are available, except for following options: 1311 1312 - `dry-run` 1313 - `metalink-base-uri` 1314 - `parameterized-uri` 1315 - `pause` 1316 - `piece-length` 1317 - `rpc-save-upload-metadata` 1318 1319 Except for the following options, changing the other options of active download makes it restart (restart 1320 itself is managed by aria2, and no user intervention is required): 1321 1322 - `bt-max-peers` 1323 - `bt-request-peer-speed-limit` 1324 - `bt-remove-unselected-file` 1325 - `force-save` 1326 - `max-download-limit` 1327 - `max-upload-limit` 1328 1329 Returns: 1330 `"OK"` for success. 1331 1332 Examples: 1333 **Original JSON-RPC Example** 1334 1335 The following examples set the max-download-limit option to 20K for the download GID#0000000000000001. 1336 1337 >>> import urllib2, json 1338 >>> from pprint import pprint 1339 >>> jsonreq = json.dumps( 1340 ... { 1341 ... "jsonrpc": "2.0", 1342 ... "id": "qwer", 1343 ... "method": "aria2.changeOption", 1344 ... "params": ["0000000000000001", {"max-download-limit": "10K"}], 1345 ... } 1346 ... ) 1347 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 1348 >>> pprint(json.loads(c.read())) 1349 {u'id': u'qwer', u'jsonrpc': u'2.0', u'result': u'OK'} 1350 """ 1351 return self.call(self.CHANGE_OPTION, [gid, options]) # type: ignore 1352 1353 def get_global_option(self) -> dict: 1354 """Return the global options. 1355 1356 Note that this method does not return options which have no default value and have not 1357 been set on the command-line, in configuration files or RPC methods. Because global options are used as a 1358 template for the options of newly added downloads, the response contains keys returned by the 1359 [`get_option()`][aria2p.client.Client.get_option] method. 1360 1361 Original signature: 1362 1363 aria2.getGlobalOption([secret]) 1364 1365 Returns: 1366 The global options. The response is a struct. Its keys are the names of options. 1367 Values are strings. 1368 """ 1369 return self.call(self.GET_GLOBAL_OPTION) # type: ignore 1370 1371 def change_global_option(self, options: dict) -> str: 1372 """Change the global options dynamically. 1373 1374 Original signature: 1375 1376 aria2.changeGlobalOption([secret], options) 1377 1378 Parameters: 1379 options: The following options are available: 1380 1381 - `bt-max-open-files` 1382 - `download-result` 1383 - `keep-unfinished-download-result` 1384 - `log` 1385 - `log-level` 1386 - `max-concurrent-downloads` 1387 - `max-download-result` 1388 - `max-overall-download-limit` 1389 - `max-overall-upload-limit` 1390 - `optimize-concurrent-downloads` 1391 - `save-cookies` 1392 - `save-session` 1393 - `server-stat-of` 1394 1395 In addition, options listed in the Input File subsection are available, except for following options: 1396 `checksum`, `index-out`, `out`, `pause` and `select-file`. 1397 1398 With the log option, you can dynamically start logging or change log file. To stop logging, specify an 1399 empty string ("") as the parameter value. Note that log file is always opened in append mode. 1400 1401 Returns: 1402 `"OK"` for success. 1403 """ 1404 return self.call(self.CHANGE_GLOBAL_OPTION, [options]) # type: ignore 1405 1406 def get_global_stat(self) -> dict: 1407 """Return global statistics such as the overall download and upload speeds. 1408 1409 Original signature: 1410 1411 aria2.getGlobalStat([secret]) 1412 1413 Returns: 1414 A struct that contains the following keys (values are strings): 1415 1416 - `downloadSpeed`: Overall download speed (byte/sec). 1417 - `uploadSpeed`: Overall upload speed(byte/sec). 1418 - `numActive`: The number of active downloads. 1419 - `numWaiting`: The number of waiting downloads. 1420 - `numStopped`: The number of stopped downloads in the current session. This value is capped by the 1421 [`--max-download-result`][aria2p.options.Options.max_download_result] option. 1422 - `numStoppedTotal`: The number of stopped downloads in the current session and not capped by the 1423 [`--max-download-result`][aria2p.options.Options.max_download_result] option. 1424 1425 Examples: 1426 **Original JSON-RPC Example** 1427 1428 >>> import urllib2, json 1429 >>> from pprint import pprint 1430 >>> jsonreq = json.dumps( 1431 ... {"jsonrpc": "2.0", "id": "qwer", "method": "aria2.getGlobalStat"} 1432 ... ) 1433 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 1434 >>> pprint(json.loads(c.read())) 1435 {u'id': u'qwer', 1436 u'jsonrpc': u'2.0', 1437 u'result': {u'downloadSpeed': u'21846', 1438 u'numActive': u'2', 1439 u'numStopped': u'0', 1440 u'numWaiting': u'0', 1441 u'uploadSpeed': u'0'}} 1442 """ 1443 return self.call(self.GET_GLOBAL_STAT) # type: ignore 1444 1445 def purge_download_result(self) -> str: 1446 """Purge completed/error/removed downloads from memory. 1447 1448 Original signature: 1449 1450 aria2.purgeDownloadResult([secret]) 1451 1452 Returns: 1453 `"OK"`. 1454 """ 1455 return self.call(self.PURGE_DOWNLOAD_RESULT) # type: ignore 1456 1457 def remove_download_result(self, gid: str) -> str: 1458 """Remove a completed/error/removed download from memory. 1459 1460 Original signature: 1461 1462 aria2.removeDownloadResult([secret], gid) 1463 1464 Parameters: 1465 gid: The download result to remove. 1466 1467 Returns: 1468 `"OK"` for success. 1469 1470 Examples: 1471 **Original JSON-RPC Example** 1472 1473 The following examples remove the download result of the download GID#0000000000000001. 1474 1475 >>> import urllib2, json 1476 >>> from pprint import pprint 1477 >>> jsonreq = json.dumps( 1478 ... { 1479 ... "jsonrpc": "2.0", 1480 ... "id": "qwer", 1481 ... "method": "aria2.removeDownloadResult", 1482 ... "params": ["0000000000000001"], 1483 ... } 1484 ... ) 1485 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 1486 >>> pprint(json.loads(c.read())) 1487 {u'id': u'qwer', u'jsonrpc': u'2.0', u'result': u'OK'} 1488 """ 1489 return self.call(self.REMOVE_DOWNLOAD_RESULT, [gid]) # type: ignore 1490 1491 def get_version(self) -> str: 1492 """Return aria2 version and the list of enabled features. 1493 1494 Original signature: 1495 1496 aria2.getVersion([secret]) 1497 1498 Returns: 1499 A struct that contains the following keys: 1500 1501 - `version`: Version number of aria2 as a string. 1502 - `enabledFeatures`: List of enabled features. Each feature is given as a string. 1503 1504 Examples: 1505 **Original JSON-RPC Example** 1506 1507 >>> import urllib2, json 1508 >>> from pprint import pprint 1509 >>> jsonreq = json.dumps( 1510 ... {"jsonrpc": "2.0", "id": "qwer", "method": "aria2.getVersion"} 1511 ... ) 1512 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 1513 >>> pprint(json.loads(c.read())) 1514 {u'id': u'qwer', 1515 u'jsonrpc': u'2.0', 1516 u'result': {u'enabledFeatures': [u'Async DNS', 1517 u'BitTorrent', 1518 u'Firefox3 Cookie', 1519 u'GZip', 1520 u'HTTPS', 1521 u'Message Digest', 1522 u'Metalink', 1523 u'XML-RPC'], 1524 u'version': u'1.11.0'}} 1525 """ 1526 return self.call(self.GET_VERSION) # type: ignore 1527 1528 def get_session_info(self) -> dict: 1529 """Return session information. 1530 1531 Returns: 1532 A struct that contains the `sessionId` key, which is generated each time aria2 is invoked. 1533 1534 Original signature: 1535 1536 aria2.getSessionInfo([secret]) 1537 1538 Examples: 1539 **Original JSON-RPC Example** 1540 1541 >>> import urllib2, json 1542 >>> from pprint import pprint 1543 >>> jsonreq = json.dumps( 1544 ... {"jsonrpc": "2.0", "id": "qwer", "method": "aria2.getSessionInfo"} 1545 ... ) 1546 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 1547 >>> pprint(json.loads(c.read())) 1548 {u'id': u'qwer', 1549 u'jsonrpc': u'2.0', 1550 u'result': {u'sessionId': u'cd6a3bc6a1de28eb5bfa181e5f6b916d44af31a9'}} 1551 """ 1552 return self.call(self.GET_SESSION_INFO) # type: ignore 1553 1554 def shutdown(self) -> str: 1555 """Shutdown aria2. 1556 1557 Original signature: 1558 1559 aria2.shutdown([secret]) 1560 1561 Returns: 1562 `"OK"`. 1563 """ 1564 return self.call(self.SHUTDOWN) # type: ignore 1565 1566 def force_shutdown(self) -> str: 1567 """Force shutdown aria2. 1568 1569 This method shuts down aria2. This method behaves like [`shutdown()`][aria2p.client.Client.shutdown] without performing any 1570 actions which take time, such as contacting BitTorrent trackers to unregister downloads first. 1571 1572 Original signature: 1573 1574 aria2.forceShutdown([secret]) 1575 1576 Returns: 1577 `"OK"`. 1578 """ 1579 return self.call(self.FORCE_SHUTDOWN) # type: ignore 1580 1581 def save_session(self) -> str: 1582 """Save the current session to a file. 1583 1584 This method saves the current session to a file specified 1585 by the [`--save-session`][aria2p.options.Options.save_session] option. 1586 1587 Original signature: 1588 1589 aria2.saveSession([secret]) 1590 1591 Returns: 1592 `"OK"` if it succeeds. 1593 """ 1594 return self.call(self.SAVE_SESSION) # type: ignore 1595 1596 # system 1597 def multicall(self, methods: list[dict]) -> list[CallReturnType]: 1598 """Call multiple methods in a single request. 1599 1600 This methods encapsulates multiple method calls in a single request. 1601 1602 Original signature: 1603 1604 system.multicall(methods) 1605 1606 Parameters: 1607 methods: An array of structs. The structs contain two keys: `methodName` and `params`. 1608 - `methodName` is the method name to call and 1609 - `params` is array containing parameters to the method call. 1610 1611 Returns: 1612 An array of responses. 1613 The elements will be either a one-item array containing the return value of the method call or a struct of fault 1614 element if an encapsulated method call fails. 1615 1616 Examples: 1617 **Original JSON-RPC Example** 1618 1619 In the following examples, we add 2 downloads. The first one is http://example.org/file and the second one is 1620 file.torrent. 1621 1622 >>> import urllib2, json, base64 1623 >>> from pprint import pprint 1624 >>> jsonreq = json.dumps( 1625 ... { 1626 ... "jsonrpc": "2.0", 1627 ... "id": "qwer", 1628 ... "method": "system.multicall", 1629 ... "params": [ 1630 ... [ 1631 ... { 1632 ... "methodName": "aria2.addUri", 1633 ... "params": [["http://example.org"]], 1634 ... }, 1635 ... { 1636 ... "methodName": "aria2.addTorrent", 1637 ... "params": [base64.b64encode(open("file.torrent").read())], 1638 ... }, 1639 ... ] 1640 ... ], 1641 ... } 1642 ... ) 1643 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 1644 >>> pprint(json.loads(c.read())) 1645 {u'id': u'qwer', u'jsonrpc': u'2.0', u'result': [[u'0000000000000001'], [u'd2703803b52216d1']]} 1646 1647 JSON-RPC additionally supports Batch requests as described in the JSON-RPC 2.0 Specification: 1648 1649 >>> jsonreq = json.dumps( 1650 ... [ 1651 ... { 1652 ... "jsonrpc": "2.0", 1653 ... "id": "qwer", 1654 ... "method": "aria2.addUri", 1655 ... "params": [["http://example.org"]], 1656 ... }, 1657 ... { 1658 ... "jsonrpc": "2.0", 1659 ... "id": "asdf", 1660 ... "method": "aria2.addTorrent", 1661 ... "params": [base64.b64encode(open("file.torrent").read())], 1662 ... }, 1663 ... ] 1664 ... ) 1665 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 1666 >>> pprint(json.loads(c.read())) 1667 [{u'id': u'qwer', u'jsonrpc': u'2.0', u'result': u'0000000000000001'}, 1668 {u'id': u'asdf', u'jsonrpc': u'2.0', u'result': u'd2703803b52216d1'}] 1669 """ 1670 return self.call(self.MULTICALL, [methods]) # type: ignore 1671 1672 def list_methods(self) -> list[str]: 1673 """Return the available RPC methods. 1674 1675 This method returns all the available RPC methods in an array of string. Unlike other methods, 1676 this method does not require secret token. This is safe because this method just returns the available 1677 method names. 1678 1679 Original signature: 1680 1681 system.listMethods() 1682 1683 Returns: 1684 The list of available RPC methods. 1685 1686 Examples: 1687 **Original JSON-RPC Example** 1688 1689 >>> import urllib2, json 1690 >>> from pprint import pprint 1691 >>> jsonreq = json.dumps( 1692 ... {"jsonrpc": "2.0", "id": "qwer", "method": "system.listMethods"} 1693 ... ) 1694 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 1695 >>> pprint(json.loads(c.read())) 1696 {u'id': u'qwer', 1697 u'jsonrpc': u'2.0', 1698 u'result': [u'aria2.addUri', 1699 u'aria2.addTorrent', 1700 ... 1701 """ 1702 return self.call(self.LIST_METHODS) # type: ignore 1703 1704 def list_notifications(self) -> list[str]: 1705 """Return all the available RPC notifications. 1706 1707 This method returns all the available RPC notifications in an array of string. Unlike other methods, 1708 this method does not require secret token. This is safe because this method just returns the available 1709 notifications names. 1710 1711 Original signature: 1712 1713 system.listNotifications() 1714 1715 Returns: 1716 The list of available RPC notifications. 1717 1718 Examples: 1719 **Original JSON-RPC Example** 1720 1721 >>> import urllib2, json 1722 >>> from pprint import pprint 1723 >>> jsonreq = json.dumps( 1724 ... {"jsonrpc": "2.0", "id": "qwer", "method": "system.listNotifications"} 1725 ... ) 1726 >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq) 1727 >>> pprint(json.loads(c.read())) 1728 {u'id': u'qwer', 1729 u'jsonrpc': u'2.0', 1730 u'result': [u'aria2.onDownloadStart', 1731 u'aria2.onDownloadPause', 1732 ... 1733 """ 1734 return self.call(self.LIST_NOTIFICATIONS) # type: ignore 1735 1736 # notifications 1737 def listen_to_notifications( 1738 self, 1739 on_download_start: Callable | None = None, 1740 on_download_pause: Callable | None = None, 1741 on_download_stop: Callable | None = None, 1742 on_download_complete: Callable | None = None, 1743 on_download_error: Callable | None = None, 1744 on_bt_download_complete: Callable | None = None, 1745 timeout: int = 5, 1746 handle_signals: bool = True, # noqa: FBT001,FBT002 1747 ) -> None: 1748 """Start listening to aria2 notifications via WebSocket. 1749 1750 This method opens a WebSocket connection to the server and wait for notifications (or events) to be received. 1751 It accepts callbacks as arguments, which are functions accepting one parameter called "gid", for each type 1752 of notification. 1753 1754 Stop listening to notifications with the [`stop_listening`][aria2p.client.Client.stop_listening] method. 1755 1756 Parameters: 1757 on_download_start: Callback for the `onDownloadStart` event. 1758 on_download_pause: Callback for the `onDownloadPause` event. 1759 on_download_stop: Callback for the `onDownloadStop` event. 1760 on_download_complete: Callback for the `onDownloadComplete` event. 1761 on_download_error: Callback for the `onDownloadError` event. 1762 on_bt_download_complete: Callback for the `onBtDownloadComplete` event. 1763 timeout: Timeout when waiting for data to be received. Use a small value for faster reactivity 1764 when stopping to listen. Default is 5 seconds. 1765 handle_signals: Whether to add signal handlers to gracefully stop the loop on SIGTERM and SIGINT. 1766 """ 1767 self.listening = True 1768 ws_server = self.ws_server 1769 log_prefix = f"Notifications ({ws_server})" 1770 1771 logger.debug(f"{log_prefix}: opening WebSocket with timeout={timeout}") 1772 try: 1773 socket = websocket.create_connection(ws_server, timeout=timeout) 1774 except (ConnectionRefusedError, ConnectionResetError): 1775 logger.error(f"{log_prefix}: connection refused. Is the server running?") 1776 return 1777 1778 callbacks = { 1779 NOTIFICATION_START: on_download_start, 1780 NOTIFICATION_PAUSE: on_download_pause, 1781 NOTIFICATION_STOP: on_download_stop, 1782 NOTIFICATION_COMPLETE: on_download_complete, 1783 NOTIFICATION_ERROR: on_download_error, 1784 NOTIFICATION_BT_COMPLETE: on_bt_download_complete, 1785 } 1786 1787 stopped = SignalHandler(["SIGTERM", "SIGINT"]) if handle_signals else False 1788 1789 while not stopped: 1790 logger.debug(f"{log_prefix}: waiting for data over WebSocket") 1791 try: 1792 message = socket.recv() 1793 except websocket.WebSocketConnectionClosedException: 1794 logger.error(f"{log_prefix}: connection to server was closed. Is the server running?") 1795 break 1796 except websocket.WebSocketTimeoutException: 1797 logger.debug(f"{log_prefix}: reached timeout ({timeout}s)") 1798 else: 1799 notification = Notification.get_or_raise(json.loads(message)) 1800 logger.info( 1801 f"{log_prefix}: received {notification.type} with gid={notification.gid}", 1802 ) 1803 callback = callbacks.get(notification.type) 1804 if callable(callback): 1805 logger.debug(f"{log_prefix}: calling {callback} with gid={notification.gid}") 1806 callback(notification.gid) 1807 else: 1808 logger.debug(f"{log_prefix}: no callback given for type " + notification.type) 1809 1810 if not self.listening: 1811 logger.debug(f"{log_prefix}: stopped listening") 1812 break 1813 1814 if stopped: 1815 logger.debug(f"{log_prefix}: stopped listening after receiving a signal") 1816 self.listening = False 1817 1818 logger.debug(f"{log_prefix}: closing WebSocket") 1819 socket.close() 1820 1821 def stop_listening(self) -> None: 1822 """Stop listening to notifications. 1823 1824 Although this method returns instantly, the actual listening loop can take some time to break out, 1825 depending on the timeout that was given to [`Client.listen_to_notifications`][aria2p.client.Client.listen_to_notifications]. 1826 """ 1827 self.listening = False 1828 1829 1830 class Notification: 1831 """A helper class for notifications. 1832 1833 You should not need to use this class. It simply provides methods to instantiate a notification with a 1834 message received from the server through a WebSocket, or to raise a ClientException if the message is invalid. 1835 """ 1836 1837 def __init__(self, event_type: str, gid: str) -> None: 1838 """Initialize the object. 1839 1840 Parameters: 1841 event_type: The notification type. Possible types are available in the NOTIFICATION_TYPES variable. 1842 gid: The GID of the download related to the notification. 1843 """ 1844 self.type = event_type 1845 self.gid = gid 1846 1847 @staticmethod 1848 def get_or_raise(message: dict) -> Notification: 1849 """Raise a ClientException when the message is invalid or return a Notification instance. 1850 1851 Parameters: 1852 message: The JSON-loaded message received over WebSocket. 1853 1854 Returns: 1855 A Notification instance if the message is valid. 1856 1857 Raises: 1858 ClientException: When the message contains an error. 1859 """ 1860 if "error" in message: 1861 raise Client.response_as_exception(message) 1862 return Notification.from_message(message) 1863 1864 @staticmethod 1865 def from_message(message: dict) -> Notification: 1866 """Return an instance of Notification. 1867 1868 This method expects a valid message (not containing errors). 1869 1870 Parameters: 1871 message: A valid message received over WebSocket. 1872 1873 Returns: 1874 A Notification instance. 1875 """ 1876 return Notification(event_type=message["method"], gid=message["params"][0]["gid"])