File Coverage

blib/lib/Mojo/Transmission.pm
Criterion Covered Total %
statement 63 83 75.9
branch 30 46 65.2
condition 14 27 51.8
subroutine 14 17 82.3
pod 5 5 100.0
total 126 178 70.7


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