[[tags: egg transmission]]
== transmission
[[toc:]]
=== Description
An egg to work with the
[[https://github.com/transmission/transmission|Transmission]] (v3.01) RPC
protocol (v17). It assumes familiarity with the
[[https://github.com/transmission/transmission/blob/6e1b89d9a7bc2e1cf40884d67fbcef3968ed2ff0/docs/rpc-spec.md#231-csrf-protection|spec]].
This egg may still work with a more recent version of the spec, but it's not
guaranteed. An example of a change in the spec that doesn't require any change
in the API is this:
17 | 3.01 | yes | torrent-get | new arg "file-count"
17 | 3.01 | yes | torrent-get | new arg "primary-mime-type"
This is because the library doesn't check if the fields are valid (this is
intentional).
These changes, however:
16 | 3.00 | yes | session-get | new request arg "fields"
16 | 3.00 | yes | torrent-get | new request arg "format"
16 | 3.00 | yes | torrent-set | new arg "labels"
Require API changes, because otherwise the {{fields}} and {{format}}
arguments can't be used.
'''NOTE:''' This egg uses [[medea]] to read and write JSON, which by default
reads object keys as symbols. This isn't great, so don't use this library with
untrusted Transmission clients if you can avoid it. In the future the reader
will be changed to read the keys as strings instead.
=== Author
siiky
=== Repository
[[https://github.com/siiky/transmission.scm]]
=== Requirements
The following eggs are required for using this egg:
* [[http-client]]
* [[intarweb]]
* [[medea]]
* [[r7rs]]
* [[srfi-1]]
* [[srfi-189]]
* [[uri-common]]
The following eggs are required for testing this egg:
* [[srfi-1]]
* [[test]]
=== API
==== {{transmission}} module
===== Parameters
*scheme*
*host*
*url*
*port*
*username*
*password*
*session-id*
===== High-level RPC API
Every method of the spec is defined, and naming is followed almost directly. In
the spec, all methods and most arguments follow the {{kebab-case}} convention.
The exceptions are a few arguments in {{camelCase}} -- these are converted to
{{kebab-case}} in this egg: e.g., the key argument for {{queuePosition}} is
called {{queue-position}}. Note, however, that the messages are left untouched:
a message to/from the server will still use {{queuePosition}} as the key,
'''NOT''' {{queue-position}}.
Almost all required parameters are positional arguments in the library -- the
only exception is the {{ids}} argument, which is always a key argument, even
for methods with required {{ids}}, because it defaults to no IDs to avoid
acting on torrents by accident.
All optional arguments in the spec are key arguments in the library.
torrent-source/filename
torrent-source/metainfo
Create a torrent source to use with {{torrent-add}}. A torrent can be added
from a magnet URL; from a torrent file, given its path, which must be readable
by the Transmission daemon; or the contents of the torrent file, encoded in
Base64.
{{torrent-source/filename}} is used for magnets and file paths;
{{torrent-source/metainfo}} is used for Base64 encoded torrent files.
====== Results
Every API procedure returns a {{result}}, which is a SRFI-189 {{Either}}
object. The most useful and most commonly used procedures for handling results
are renamed and exported. On top of these, some others are defined.
(true . _)
(false . _)
{{true}} and {{false}} return {{#t}} and {{#f}} respectively, independently of
the arguments they're called with.
(result/error obj ...)
(result/error? obj)
(result/ok obj ...)
(result/ok? obj)
(result? obj)
(result-bind result mproc1 mproc2 ...)
(result-ref result failure [success])
(exception->result pred thunk)
Renames of SRFI-189. The {{result/error}} related functions are the {{left}}
related functions from SRFI-189; and the {{result/ok}} related functions are
the {{right}} related functions from SRFI-189.
(result/error-ref result #!optional (fail false))
(result/ok-ref result #!optional (fail false))
If the object is of the expected alternative ({{result/error}} or
{{result/ok}}), return the values contained in it; otherwise, call {{fail}} on
the values contained in the object, which by default, returns {{#f}}.
(result-ref* result)
Return the values contained in a result object, whether it's a {{result/error}}
or {{result/ok}}.
(default-error-proc result/con #!optional tag req resp)
(with-transmission-result result success-proc #!optional (error-proc default-error-proc))
Convenient way to handle a single RPC call's result.
{{with-transmission-result}} is similar to {{result-ref}} ({{either-ref}} from
SRFI-189), except that the order of the success and failure procedures is
swapped. The failure procedure defaults to {{default-error-proc}}, which calls
{{error}} with a generic error message.
{{success-proc}} should be a procedure of 4 parameters: the {{arguments}} and
{{tag}} fields of a reply message, the {{uri-common}} request object, and the
{{intarweb}} response object.
{{failure-proc}} should be a procedure of 4 parameters, 3 of them optional:
{{result/con}}, {{tag}}, {{req}}, and {{resp}}. {{result}} and {{tag}} are the
fields of a reply message, {{req}} is the {{uri-common}} request object, and
{{resp}} is the {{intarweb}} response object. {{tag}}, {{req}}, and {{resp}}
are either all false or none false. If they're false, {{result/con}} is a
{{condition}} object thrown during the API call; if they're not false, then
{{result/con}} is a {{result}} string.
===== Low-level API
(make-rpc-call (method (required required-handler) ...) (key default key-handler) ...)
(make-rpc-call method (key default key-handler) ...)
(define-rpc-call (method required ...) key ...)
(define-rpc-call method key ...)
(export-rpc-call (method required ...) key ...)
(export-rpc-call method key ...)
(export-3.1/4.6 method)
{{make-rpc-call}} creates a procedure to represent an RPC method.
{{define-rpc-call}} is like {{make-rpc-call}} but defines the created
procedure.
{{export-rpc-call}} is like {{define-rpc-call}} but exports the defined
procedure.
{{export-3.1/4.6}} exports RPC procedures of sections 3.1 and 4.6 of the spec,
which have a single optional {{ids}} argument.
{{method}} is a method name, which will be used as the method name in messages
and, in the case of {{define-rpc-call}}, {{export-rpc-call}}, and
{{export-3.1/4.6}}, the name of the defined procedure.
{{required}} is the name of a required argument. It will not be used in
messages.
{{key}} is the name of an optional (key) argument. It will be used as the key
name in the created procedure, but not in messages.
{{required-handler}} and {{key-handler}} are functions that will process a
given value, to assure it has the right type/format, and return an entry ready
to be inserted into the {{arguments}} object of a message, i.e., they must return
a {{(key . value)}} pair that will later be converted to JSON.
{{default}} is the default value of an optional (key) argument.
(rpc-call method #!key (arguments #f) (tag #f))
Make an RPC call. {{method}} is the name of the RPC method, and {{arguments}}
is the {{arguments}} object, containing both required and optional arguments.
(handle-409 condition request message)
(http-call request message)
(update-request-session-id request #!optional (session-id (*session-id*)))
(make-serialized-message method arguments tag)
(make-message method arguments tag)
(make-rpc-request host url port username password #!optional (session-id (*session-id*)))
==== {{transmission.utils}} module
(alist-let alist (formal ...) body ...)
(alist-let/and alist (formal ...) body ...)
(alist-let/nor alist (formal ...) body ...)
Where {{formal}} is either {{key}}, which will be used both as the key name and
the variable, or {{(var key)}}, which will be the variable name and key
respectively.
Equivalent to:
(let ((var (alist-ref 'key alist))
...)
body
...)
Except that, with {{alist-let/and}}, if {{alist}} is false, the whole
expression evaluates to false; and with {{alist-let/nor}}, if {{alist}} is
false, the whole expression evaluates to true.
Example:
(alist-let/and '((ant . 3) (bunny . 5) (Cat . 3))
(ant bunny (cat Cat))
(list ant bunny cat))
Expands to something like this:
(let ((%alist '((ant . 3) (bunny . 5) (Cat . 3))))
(let ((ant (alist-ref 'ant %alist))
(bunny (alist-ref 'bunny %alist))
(cat (alist-ref 'Cat %alist)))
(list ant bunny cat)))
(unique-tag! #!optional (new-n #f))
Return an unique tag, that starts at 0 and is incremented by 1 on each call. If
{{new-n}} is given and is a {{fixnum?}}, set the internal variable to {{new-n}}
for future use.
status/stopped
status/check-wait
status/check
status/download-wait
status/download
status/seed-wait
status/seed
The torrent status constants.
priority/low
priority/normal
priority/high
The priority constants -- for {{bandwidth-priority}}.
=== Example
The following example lists all the torrents that satisfy a certain predicate:
they must be seeding, have an upload ratio greater than 1, and the download
path must be {{/some/path/}} (note that, to Transmission, {{/some/path}} and
{{/some/path/}} are not the same).
(import srfi-1 transmission transmission.utils)
(parameterize ((*host* "hostname")
(*username* "username")
(*password* "password"))
(let ((tag (unique-tag!)))
(with-transmission-result
(torrent-get '("downloadDir" "id" "name" "status" "uploadRatio") #:ids #f #:tag tag)
(lambda (arguments tag req resp)
(define (want-torrent? tor)
(alist-let/and tor (downloadDir status uploadRatio)
(and (= status status/seed)
(> uploadRatio 1)
(string=? downloadDir "/some/path/"))))
(alist-let/and arguments (torrents)
(let ((wanted-tors (filter want-torrent? (vector->list torrents))))
(for-each print wanted-tors))))
; NOTE: The error case handling procedure doesn't have to accept
; #!optional arguments -- the "missing" arguments will be #f.
(lambda (result tag req resp)
(error 'here "torrent-get call failed with the following error" result)))))
For more examples take a look at
[[https://github.com/siiky/transmission-examples]] (no longer in this egg's
download directory). The example above is similar to {{transmission-filter}}
from that repo.
=== License
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to
=== Version History
==== 0.2.1 (2021/01/10)
* {{default-error-proc}} now supports the case where the result was just a
condition;
* Add (hopefully) useful example programs to the {{examples}} directory -- you
can get these from the repo or the {{chicken-install}} cache directory;
* Export {{alist-let}} from {{transmission.utils}}.
==== 0.2.0 (2020/12/09)
* Return a more functional {{result}} object from API calls instead of a more
C-like {{reply}} for easier error handling. There's no chance to mistake an
error for a good result now;
* Improve {{alist-let/and}} and {{alist-let/nor}} to support optional variable
names, and save the value resulting of evaluating the alist expression;
* Add {{priority}} constants;
* Rename {{unique-tag}} to {{unique-tag!}};
* Replace [[vector-lib]] with [[r7rs]] because [[srfi-189]] depends on it
anyway.
==== 0.1.1 (2020/12/02)
Add {{with-transmission-reply}} and update the documentation accordingly.
==== 0.1.0 (2020/12/01)
Initial release with all methods defined, and almost all tested.