File Coverage

blib/lib/Mojo/Transmission.pm
Criterion Covered Total %
statement 80 92 86.9
branch 41 52 78.8
condition 17 27 62.9
subroutine 21 22 95.4
pod 9 9 100.0
total 168 202 83.1


defaults to L.
line stmt bran cond sub pod time code
1             package Mojo::Transmission;
2 4     4   738318 use Mojo::Base -base;
  4         37  
  4         26  
3              
4 4     4   588 use Exporter 'import';
  4         6  
  4         95  
5 4     4   1500 use Mojo::JSON;
  4         69834  
  4         192  
6 4     4   1792 use Mojo::UserAgent;
  4         747143  
  4         33  
7 4     4   157 use Mojo::Util qw(dumper url_escape);
  4         10  
  4         214  
8              
9 4   50 4   21 use constant DEBUG => $ENV{TRANSMISSION_DEBUG} || 0;
  4         8  
  4         268  
10 4     4   22 use constant RETURN_PROMISE => sub { };
  4         7  
  4         5985  
11              
12             our $VERSION = '0.03';
13             our @EXPORT_OK = qw(tr_status);
14              
15             has default_trackers => sub { [split /,/, ($ENV{TRANSMISSION_DEFAULT_TRACKERS} || '')] };
16             has ua => sub { Mojo::UserAgent->new; };
17             has url =>
18             sub { Mojo::URL->new($ENV{TRANSMISSION_RPC_URL} || 'http://localhost:9091/transmission/rpc'); };
19              
20             sub add {
21 3 50   3 1 1991 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
22 3         14 my ($self, $args) = @_;
23 3   100     10 my $url = $args->{url} || '';
24              
25 3 100       8 if ($args->{xt}) {
26 1   50     4 $url = sprintf 'magnet:?xt=%s&dn=%s', map { $_ // '' } @$args{qw(xt dn)};
  2         9  
27 1 50       2 $url .= sprintf '&tr=%s', url_escape $_ for @{$args->{tr} || $self->default_trackers};
  1         6  
28             }
29              
30 3 100       12 unless ($url) {
31 1   50     7 $url = sprintf 'magnet:?xt=urn:btih:%s', $args->{hash} // '';
32 1   50     15 $url .= sprintf '&dn=%s', url_escape($args->{dn} // '');
33 1 50       9 $url .= sprintf '&tr=%s', url_escape $_ for @{$args->{tr} || $self->default_trackers};
  1         5  
34             }
35              
36 3         16 $self->_post('torrent-add', {filename => "$url"}, $cb);
37             }
38              
39 0     0 1 0 sub add_p { shift->add(shift, RETURN_PROMISE) }
40              
41             sub session {
42 6 100   6 1 18396 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
43 6         10 my $self = shift;
44              
45 6 100       26 return $self->_post('session-get', $_[0], $cb) if ref $_[0] eq 'ARRAY';
46 2 50       10 return $self->_post('session-set', $_[0], $cb) if ref $_[0] eq 'HASH';
47 0 0       0 return $self->tap($cb, {error => 'Invalid input.'}) if $cb;
48 0         0 die 'Invalid input.';
49             }
50              
51 2     2 1 1033 sub session_p { shift->session(shift, RETURN_PROMISE) }
52              
53             sub stats {
54 1 50   1 1 608 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
55 1         4 return shift->_post('session-stats', {}, $cb);
56             }
57              
58 1     1 1 4 sub stats_p { shift->_post('session-stats', {}, RETURN_PROMISE) }
59              
60             sub torrent {
61 12 100   12 1 4498 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
62 12         25 my ($self, $args, $id) = @_;
63              
64 12 100       28 if (defined $id) {
65 9 100       21 $id = ref $id ? $id : [$id];
66             }
67              
68 12 100       40 if (ref $args eq 'ARRAY') {
    100          
    100          
69 4         8 $args = {fields => $args};
70 4 100       10 $args->{ids} = $id if defined $id;
71 4         10 return $self->_post('torrent-get', $args, $cb);
72             }
73             elsif (ref $args eq 'HASH') {
74 2 50       9 $args->{ids} = $id if defined $id;
75 2         6 return $self->_post('torrent-set', $args, $cb);
76             }
77             elsif ($args eq 'purge') {
78 2         17 return $self->_post('torrent-remove', {ids => $id, 'delete-local-data' => Mojo::JSON->true},
79             $cb);
80             }
81             else {
82 4         18 return $self->_post("torrent-$args", {ids => $id}, $cb);
83             }
84             }
85              
86 6     6 1 1722 sub torrent_p { shift->torrent(@_, RETURN_PROMISE) }
87              
88             sub _done {
89 10     10   17 my ($self, $cb, $res) = @_;
90 10 100       30 $self->$cb($res) unless $cb eq RETURN_PROMISE;
91 10         41 return $res;
92             }
93              
94             sub _post {
95 23     23   74 my ($self, $method, $req, $cb) = @_;
96 23         51 $req = {arguments => $req, method => $method};
97              
98             # Non-Blocking
99 23 100       43 if ($cb) {
100 10         16 warn '[TRANSMISSION] <<< ', dumper($req), "\n" if DEBUG;
101             my $p = $self->ua->post_p($self->url, $self->_headers, json => $req)->then(sub {
102 10     10   45755 my $tx = shift;
103 10         14 warn '[TRANSMISSION] >>> ', dumper($tx->res->json || $tx->res->error), "\n" if DEBUG;
104 10 50 50     23 return $self->_done($cb, _res($tx)) unless ($tx->res->code // 0) == 409;
105 0         0 $self->{session_id} = $tx->res->headers->header('X-Transmission-Session-Id');
106 0         0 return $self->ua->post_p($self->url, $self->_headers, json => $req);
107             })->then(sub {
108 10 50   10   1032 return $_[0] if ref $_[0] eq 'HASH'; # _done() is already called
109 0         0 my $tx = shift;
110 0         0 warn '[TRANSMISSION] >>> ', dumper($tx->res->json || $tx->res->error), "\n" if DEBUG;
111 0         0 return $self->_done($cb, _res($tx));
112 10         26 });
113              
114 10 100       12407 return $cb eq RETURN_PROMISE ? $p : $self;
115             }
116              
117             # Blocking
118             else {
119 13         17 warn '[TRANSMISSION] <<< ', dumper($req), "\n" if DEBUG;
120 13         31 my $tx = $self->ua->post($self->url, $self->_headers, json => $req);
121 13         6776 warn '[TRANSMISSION] >>> ', dumper($tx->res->json || $tx->res->error), "\n" if DEBUG;
122 13 50 50     25 return _res($tx) unless ($tx->res->code // 0) == 409;
123 0         0 $self->{session_id} = $tx->res->headers->header('X-Transmission-Session-Id');
124 0         0 $tx = $self->ua->post($self->url, $self->_headers, json => $req);
125 0         0 warn '[TRANSMISSION] >>> ', dumper($tx->res->json || $tx->res->error), "\n" if DEBUG;
126 0         0 return _res($tx);
127             }
128             }
129              
130             sub _headers {
131 23     23   164 my $self = shift;
132 23   50     123 return {'X-Transmission-Session-Id' => $self->{session_id} || ''};
133             }
134              
135             sub _res {
136 23   50 23   196 my $res = $_[0]->res->json || {error => $_[0]->res->error};
137 23   66     2547 $res->{error} ||= $res->{result};
138 23 100 66     107 return $res if !$res->{result} or $res->{result} ne 'success';
139 4         17 return $res->{arguments};
140             }
141              
142             my @TR_STATUS = qw(stopped check_wait check download_wait download seed_wait seed);
143 9 100 100 9 1 154 sub tr_status { defined $_[0] && $_[0] >= 0 && $_[0] <= @TR_STATUS ? $TR_STATUS[$_[0]] : '' }
144              
145             1;
146              
147             =encoding utf8
148              
149             =head1 NAME
150              
151             Mojo::Transmission - Client for talking with Transmission BitTorrent daemon
152              
153             =head1 DESCRIPTION
154              
155             L is a very lightweight client for exchanging data with
156             the Transmission BitTorrent daemon using RPC.
157              
158             The documentation in this module might seem sparse, but that is because the API
159             is completely transparent regarding the data-structure received from the
160             L.
161              
162             =head1 SYNOPSIS
163              
164             my $transmission = Mojo::Transmission->new;
165             $transmission->add(url => "http://releases.ubuntu.com/17.10/ubuntu-17.10.1-desktop-amd64.iso.torrent");
166              
167             my $torrents = $transmission->torrent([]);
168             $transmission->torrent(remove => $torrents[0]->{id}) if @$torrents;
169              
170             =head1 ATTRIBUTES
171              
172             =head2 default_trackers
173              
174             $array_ref = $transmission->default_trackers;
175             $transmission = $transmission->default_trackers([$url, ...]);
176              
177             Holds a list of default trackers that can be used by L.
178              
179             =head2 ua
180              
181             $ua = $transmission->ua;
182             $transmission = $transmission->ua(Mojo::UserAgent->new);
183              
184             Holds a L used to issue requests to backend.
185              
186             =head2 url
187              
188             $url = $transmission->url;
189             $transmission = $transmission->url(Mojo::URL->new);
190              
191             L object holding the URL to the transmission daemon.
192             Default to the C environment variable or
193             "http://localhost:9091/transmission/rpc".
194              
195             =head1 METHODS
196              
197             =head2 add
198              
199             # Generic call
200             $res = $transmission->add(\%args);
201             $transmission = $transmission->add(\%args, sub { my ($transmission, $res) = @_ });
202              
203             # magnet:?xt=${xt}&dn=${dn}&tr=${tr}
204             $transmission->add({xt => "...", dn => "...", tr => [...]});
205              
206             # magnet:?xt=urn:btih:${hash}&dn=${dn}&tr=${tr}
207             $transmission->add({hash => "...", dn => "...", tr => [...]});
208              
209             # Custom URL or file
210             $transmission->add({url => "...", tr => [...]});
211              
212             This method can be used to add a torrent. C
213              
214             See also L.
215              
216             =head2 add_p
217              
218             $promise = $transmission->add_p(\%args);
219              
220             Same as L, but returns a promise.
221              
222             =head2 session
223              
224             # session-get
225             $transmission = $transmission->session([], sub { my ($transmission, $res) = @_; });
226             $res = $transmission->session([]);
227              
228             # session-set
229             $transmission = $transmission->session(\%attrs, sub { my ($transmission, $res) = @_; });
230             $res = $transmission->session(\%attrs);
231              
232             Used to get or set Transmission session arguments.
233              
234             See also L.
235              
236             =head2 session_p
237              
238             $promise = $transmission->session_p([]);
239             $promise = $transmission->session_p(\%args);
240              
241             Same as L, but returns a promise.
242              
243             =head2 stats
244              
245             # session-stats
246             $transmission = $transmission->stats(sub { my ($transmission, $res) = @_; });
247             $res = $transmission->stats;
248              
249             Used to retrieve Transmission statistics.
250              
251             =head2 stats_p
252              
253             $promise = $transmission->stats_p;
254              
255             Same as L, but returns a promise.
256              
257             See also L.
258              
259             =head2 torrent
260              
261             # torrent-get
262             $transmission = $transmission->torrent(\@attrs, $id, sub { my ($transmission, $res) = @_; });
263             $res = $transmission->torrent(\@attrs, $id);
264              
265             # torrent-set
266             $transmission = $transmission->torrent(\%attrs, $id, sub { my ($transmission, $res) = @_; });
267             $res = $transmission->torrent(\%attrs, $id);
268              
269             # torrent-$action
270             $transmission = $transmission->torrent(remove => $id, sub { my ($transmission, $res) = @_; });
271             $transmission = $transmission->torrent(start => $id, sub { my ($transmission, $res) = @_; });
272             $transmission = $transmission->torrent(stop => $id, sub { my ($transmission, $res) = @_; });
273             $res = $transmission->torrent($action => $id);
274              
275             # torrent-remove + delete-local-data
276             $transmission = $transmission->torrent(purge => $id, sub { my ($transmission, $res) = @_; });
277              
278             Used to get or set torrent related attributes or execute an action on a torrent.
279              
280             C<$id> can either be a scalar or an array-ref, referring to which torrents to
281             use.
282              
283             See also:
284              
285             =over 4
286              
287             =item * Get torrent attributes
288              
289             L.
290              
291             =item * Set torrent attributes
292              
293             L
294              
295             =item * Torrent actions
296              
297             L.
298              
299             =back
300              
301             =head2 torrent_p
302              
303             $promise = $transmission->torrent_p(\@attrs, ...);
304             $promise = $transmission->torrent_p(\%attrs, ...);
305             $promise = $transmission->torrent_p($action => ...);
306              
307             Same as L, but returns a promise.
308              
309             =head1 FUNCTIONS
310              
311             =head2 tr_status
312              
313             use Mojo::Transmission "tr_status";
314             $str = tr_status $int;
315              
316             Returns a description for the C<$int> status:
317              
318             0 = stopped
319             1 = check_wait
320             2 = check
321             3 = download_wait
322             4 = download
323             5 = seed_wait
324             6 = seed
325              
326             Returns empty string on invalid input.
327              
328             =head1 COPYRIGHT AND LICENSE
329              
330             Copyright (C) 2016, Jan Henning Thorsen
331              
332             This program is free software, you can redistribute it and/or modify it under
333             the terms of the Artistic License version 2.0.
334              
335             =head1 AUTHOR
336              
337             Jan Henning Thorsen - C
338              
339             =cut