File Coverage

blib/lib/Net/BitTorrent/Torrent.pm
Criterion Covered Total %
statement 581 712 81.6
branch 226 372 60.7
condition 55 117 47.0
subroutine 80 88 90.9
pod 37 37 100.0
total 979 1326 73.8


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             package Net::BitTorrent::Torrent;
3             {
4 11     11   3041 use strict;
  11         22  
  11         398  
5 11     11   54 use warnings;
  11         23  
  11         338  
6 11     11   1959 use Digest::SHA qw[sha1_hex];
  11         8524  
  11         620  
7 11     11   69 use Carp qw[carp carp];
  11         19  
  11         503  
8 11     11   61 use Cwd qw[cwd];
  11         18  
  11         575  
9 11     11   8974 use File::Spec::Functions qw[rel2abs catfile];
  11         9086  
  11         903  
10 11     11   73 use Scalar::Util qw[blessed weaken refaddr];
  11         21  
  11         787  
11 11     11   59 use List::Util qw[sum shuffle max min];
  11         22  
  11         1130  
12 11     11   68 use Fcntl qw[/O_/ /SEEK/ :flock];
  11         21  
  11         5825  
13 11     11   65 use vars qw[@EXPORT_OK %EXPORT_TAGS];
  11         109  
  11         528  
14 11     11   58 use Exporter qw[];
  11         18  
  11         750  
15             *import = *import = *Exporter::import;
16             @EXPORT_OK = qw[
17             STARTED CHECKING START_AFTER_CHECK CHECKED
18             ERROR PAUSED LOADED QUEUED
19             ];
20             %EXPORT_TAGS = (status => [@EXPORT_OK], all => [@EXPORT_OK]);
21 11     11   56 use lib q[../../../lib];
  11         17  
  11         93  
22 11     11   2433 use Net::BitTorrent::Util qw[:bencode :compact];
  11         50  
  11         1408  
23 11     11   12251 use Net::BitTorrent::Peer qw[];
  11         1481  
  11         371  
24 11     11   10068 use Net::BitTorrent::Torrent::File;
  11         47  
  11         533  
25 11     11   7634 use Net::BitTorrent::Torrent::Tracker;
  11         62  
  11         423  
26 11     11   74 use version qw[qv];
  11         23  
  11         67  
27             our $VERSION_BASE = 51; our $UNSTABLE_RELEASE = 0; our $VERSION = sprintf(($UNSTABLE_RELEASE ? q[%.3f_%03d] : q[%.3f]), (version->new(($VERSION_BASE))->numify / 1000), $UNSTABLE_RELEASE);
28             my %REGISTRY = ();
29             my @CONTENTS = \my (%_client, %path, %_basedir,
30             %size, %files, %trackers,
31             %infohash, %uploaded, %downloaded,
32             %bitfield, %_working_pieces, %_block_length,
33             %raw_data, %status, %error,
34             %_event, %resume_path, %_nodes
35             );
36 454     454 1 2165 sub STARTED {1}
37 1246     1246 1 5157 sub CHECKING {2}
38 61     61 1 170 sub START_AFTER_CHECK {4}
39 127     127 1 548 sub CHECKED {8}
40 147     147 1 569 sub ERROR {16}
41 99     99 1 429 sub PAUSED {32}
42 152     152 1 396 sub LOADED {64}
43 1541     1541 1 6248 sub QUEUED {128}
44              
45             sub new {
46 87     87 1 159279 my ($class, $args) = @_;
47 87         541 my $self = bless \$class, $class;
48 87 100 100     1229 if ((!$args) || (ref($args) ne q[HASH])) {
49 2         5667 carp q[Net::BitTorrent::Torrent->new({ }) requires ]
50             . q[parameters to be passed as a hashref];
51 2         48 return;
52             }
53 85 100       660 if (!$args->{q[Path]}) {
54 2         643 carp
55             sprintf(
56             q[Net::BitTorrent::Torrent->new({ }) requires a 'Path' parameter]
57             );
58 2         84 return;
59             }
60 83 100       3042 if (not -f $args->{q[Path]}) {
61 1         127 carp
62             sprintf(
63             q[Net::BitTorrent::Torrent->new({ }) cannot find '%s'],
64             $args->{q[Path]});
65 1         32 return;
66             }
67 82 100 100     1191 if (($args->{q[Client]})
      100        
68             && ( (!blessed $args->{q[Client]})
69             || (!$args->{q[Client]}->isa(q[Net::BitTorrent])))
70             )
71 10         3561 { carp q[Net::BitTorrent::Torrent->new({ }) requires a ]
72             . q[blessed Net::BitTorrent object in the 'Client' parameter];
73 10         521 return;
74             }
75 72 100 100     650 if ( $args->{q[BlockLength]}
76             and $args->{q[BlockLength]} !~ m[^\d+$])
77 5         877 { carp q[Net::BitTorrent::Torrent->new({ }) requires an ]
78             . q[integer 'BlockLength' parameter];
79 5         204 delete $args->{q[BlockLength]};
80             }
81 72 100 100     484 if ($args->{q[Status]} and $args->{q[Status]} !~ m[^\d+$]) {
82 5         1227 carp q[Net::BitTorrent::Torrent->new({ }) requires an ]
83             . q[integer 'Status' parameter. Falling back to defaults.];
84 5         500 delete $args->{q[Status]};
85             }
86 72         534 $args->{q[Path]} = rel2abs($args->{q[Path]});
87 72 100       102658 $args->{q[BaseDir]} = rel2abs(
88             defined($args->{q[BaseDir]}) ? $args->{q[BaseDir]} : cwd());
89 72         1359 my ($TORRENT_FH, $TORRENT_RAW);
90 72 50       7101 if (not sysopen($TORRENT_FH, $args->{q[Path]}, O_RDONLY)) {
91 0         0 carp
92             sprintf(
93             q[Net::BitTorrent::Torrent->new({ }) could not open '%s': %s],
94             $args->{q[Path]}, $!);
95 0         0 return;
96             }
97 72         1096 flock($TORRENT_FH, LOCK_SH);
98 72 50       6156 if (sysread($TORRENT_FH, $TORRENT_RAW, -s $args->{q[Path]})
99             != -s $args->{q[Path]})
100 0         0 { carp sprintf(
101             q[Net::BitTorrent::Torrent->new({ }) could not read all %d bytes of '%s' (Read %d instead)],
102             -s $args->{q[Path]},
103             $args->{q[Path]}, length($TORRENT_RAW)
104             );
105 0         0 return;
106             }
107 72         593 flock($TORRENT_FH, LOCK_UN);
108 72         742 $raw_data{refaddr $self} = bdecode($TORRENT_RAW);
109 72         9664 close($TORRENT_FH);
110 72         184 undef $TORRENT_FH;
111 72         641 undef $TORRENT_RAW;
112 72 100       824 if (!$raw_data{refaddr $self}) {
113 1         482 carp q[Malformed .torrent];
114 1         75 return;
115             }
116 71 50       1584 if (length(unpack(q[H*], $raw_data{refaddr $self}{q[info]}{q[pieces]})
117             ) < 40
118             )
119 0         0 { return;
120             }
121 71 50       1406 if (length(unpack(q[H*], $raw_data{refaddr $self}{q[info]}{q[pieces]})
122             ) % 40
123             )
124 0         0 { return;
125             }
126 71         632 $infohash{refaddr $self}
127             = sha1_hex(bencode($raw_data{refaddr $self}{q[info]}));
128 71         477 $path{refaddr $self} = $args->{q[Path]};
129 71         314 $_basedir{refaddr $self} = $args->{q[BaseDir]};
130 71         296 $_working_pieces{refaddr $self} = {};
131 71 100       413 $_block_length{refaddr $self} = (defined $args->{q[BlockLength]}
132             ? $args->{q[BlockLength]}
133             : (2**14)
134             );
135 71         568 $downloaded{refaddr $self} = 0;
136 71         316 $uploaded{refaddr $self} = 0;
137 71         356 $_nodes{refaddr $self} = q[];
138 71         460 ${$bitfield{refaddr $self}}
  71         339  
139             = pack(q[b*], qq[\0] x $self->piece_count);
140 71         164 my @_files;
141              
142 71 100       482 if (defined $raw_data{refaddr $self}{q[info]}{q[files]}) {
143 53         410 for my $file (@{$raw_data{refaddr $self}{q[info]}{q[files]}}) {
  53         485  
144 106         991 push @_files,
145             [catfile($_basedir{refaddr $self},
146             $raw_data{refaddr $self}{q[info]}{q[name]},
147 106         521 @{$file->{q[path]}}
148             ),
149             $file->{q[length]}
150             ];
151             }
152             }
153             else {
154 18         261 push @_files,
155             [catfile($_basedir{refaddr $self},
156             $raw_data{refaddr $self}{q[info]}{q[name]}
157             ),
158             $raw_data{refaddr $self}{q[info]}{q[length]}
159             ];
160             }
161 71         313 $size{refaddr $self} = 0;
162 71         176 for my $_file (@_files) {
163 124         274 my ($path, $size) = @$_file;
164 124         305 $path =~ s[\.\.][]g;
165 124         472 $path =~ m[(.+)];
166 124         509 $path = $1;
167 124 0 33     5592 if ( defined $raw_data{refaddr $self}{q[encoding]}
      33        
      33        
168             and $raw_data{refaddr $self}{q[encoding]} !~ m[^utf-?8$]i
169             and not utf8::is_utf8($path)
170             and require Encode)
171 0         0 { $path =
172             Encode::decode(Encode::find_encoding(
173             $raw_data{refaddr $self}{q[encoding]}
174             )->name,
175             $path
176             );
177             }
178 124         513 push(@{$files{refaddr $self}},
  124         2495  
179             Net::BitTorrent::Torrent::File->new(
180             {Size => $size,
181             Path => $path,
182             Torrent => $self,
183 124         339 Index => scalar(@{$files{refaddr $self}})
184             }
185             )
186             );
187 124         932 $size{refaddr $self} += $size;
188             }
189 71         4050 $trackers{refaddr $self} = [];
190 71 50       829 foreach my $_tier ($raw_data{refaddr $self}{q[announce-list]}
  9 100       49  
191             ? @{$raw_data{refaddr $self}{q[announce-list]}}
192             : $raw_data{refaddr $self}{q[announce]}
193             ? [$raw_data{refaddr $self}{q[announce]}]
194             : ()
195             )
196 18         29 { push(@{$trackers{refaddr $self}},
  18         224  
197             Net::BitTorrent::Torrent::Tracker->new(
198             {Torrent => $self, URLs => $_tier}
199             )
200             );
201             }
202 71 50 66     1558 if ( ($args->{q[Client]})
      66        
203             && (blessed $args->{q[Client]})
204             && ($args->{q[Client]}->isa(q[Net::BitTorrent])))
205 41 100       264 { foreach my $_node ($raw_data{refaddr $self}{q[nodes]}
  4         28  
206             ? @{$raw_data{refaddr $self}{q[nodes]}}
207             : ()
208             )
209 4         47 { $args->{q[Client]}->_dht->add_node(
210             {ip => $_node->[0], port => $_node->[1]});
211             }
212             }
213 71   100     490 $args->{q[Status]} ||= 0;
214 71 50       387 $args->{q[Status]} ^= CHECKING if $args->{q[Status]} & CHECKING;
215 71 50       2624 $args->{q[Status]} ^= CHECKED if $args->{q[Status]} & CHECKED;
216 71 50       266 $args->{q[Status]} ^= ERROR if $args->{q[Status]} & ERROR;
217 71 50       316 $args->{q[Status]} ^= LOADED if $args->{q[Status]} & LOADED;
218 71         135 ${$status{refaddr $self}} = $args->{q[Status]};
  71         313  
219 71         132 ${$status{refaddr $self}} |= LOADED;
  71         294  
220 71         320 ${$error{refaddr $self}} = undef;
  71         19730  
221              
222             # Resume system v2
223 71         153 my $_start = 1;
224 71         263 $resume_path{refaddr $self} = undef;
225 71 50       475 if ($args->{q[Resume]}) {
226 0         0 $resume_path{refaddr $self} = $args->{q[Resume]};
227 0         0 my $_resume_data;
228 0 0       0 if (-f $args->{q[Resume]}) {
229 0         0 open(my ($_RD), q[<], $resume_path{refaddr $self});
230 0         0 sysread($_RD, $_resume_data, -s $_RD);
231 0         0 close $_RD;
232             }
233 0 0       0 if ($_resume_data) {
234 0         0 $_start = 0;
235 0         0 $_resume_data = bdecode($_resume_data);
236              
237             # Resume system
238 0 0 0     0 if ( $_resume_data->{q[.format]}
      0        
      0        
239             && $_resume_data->{q[.format]} eq
240             q[Net::BitTorrent resume]
241             && $_resume_data->{q[.version]}
242             && $_resume_data->{q[.version]} <= 2 # apiver
243             )
244 0 0       0 { $_nodes{refaddr $self}
245             = $_resume_data->{q[peers]}
246             ? $_resume_data->{q[peers]}
247             : q[];
248 0         0 my $_okay = 1;
249 0         0 for my $_index (0 .. $#{$files{refaddr $self}}) {
  0         0  
250 0 0 0     0 if ((!-f $files{refaddr $self}->[$_index]->path
      0        
      0        
251             && $_resume_data->{q[files]}[$_index]{q[mtime]}
252             )
253             || ((stat($files{refaddr $self}->[$_index]->path))
254             [9]
255             || 0 != $_resume_data->{q[files]}[$_index]
256             {q[mtime]})
257             )
258 0         0 { ${$status{refaddr $self}} |= START_AFTER_CHECK;
  0         0  
259 0         0 $_okay = 0;
260             }
261 0         0 $files{refaddr $self}->[$_index]->set_priority(
262             $_resume_data->{q[files]}[$_index]{q[priority]});
263             }
264 0 0       0 if (!$_okay) {
265 0         0 $self->_set_error(
266             q[Bad resume data. Please hashcheck.]);
267             }
268             else {
269 0         0 ${$bitfield{refaddr $self}}
  0         0  
270             = $_resume_data->{q[bitfield]};
271              
272             # Accept resume data is the same as hashchecking
273 0         0 my $start_after_check
274 0         0 = ${$status{refaddr $self}} & START_AFTER_CHECK;
275 0         0 ${$status{refaddr $self}} ^= START_AFTER_CHECK
  0         0  
276 0 0       0 if ${$status{refaddr $self}} & START_AFTER_CHECK;
277 0         0 ${$status{refaddr $self}} ^= CHECKED
  0         0  
278 0 0       0 if !(${$status{refaddr $self}} & CHECKED);
279 0 0       0 if ($start_after_check) { $_start = 1; }
  0         0  
280              
281             # Reload Blocks
282 0         0 for my $_piece (@{$_resume_data->{q[working]}}) {
  0         0  
283 0         0 $_working_pieces{refaddr $self}
284             {$_piece->{q[Index]}} = {
285             Index => $_piece->{q[Index]},
286             Priority => $_piece->{q[Priority]},
287             Blocks_Requested => [
288 0         0 map { {} } 1 .. $_piece->{q[Block_Count]}
289             ],
290             Blocks_Received => [
291             map {
292 0         0 vec($_piece->{q[Blocks_Received]},
293             $_, 1)
294             } 1 .. $_piece->{q[Block_Count]}
295             ],
296             Block_Length => $_piece->{q[Block_Length]},
297             Block_Length_Last =>
298             $_piece->{q[Block_Length_Last]},
299             Block_Count => $_piece->{q[Block_Count]},
300             Length => $_piece->{q[Length]},
301             Endgame => $_piece->{q[Endgame]},
302             Slow => 1, # $_piece->{q[Slow]},
303             mtime => time
304             };
305             }
306             }
307             }
308             }
309             }
310              
311             # Threads stuff
312 71         359 weaken($REGISTRY{refaddr $self} = $self);
313 71 50       244 if ($threads::shared::threads_shared) {
314 0         0 threads::shared::share($bitfield{refaddr $self});
315 0         0 threads::shared::share($status{refaddr $self});
316 0         0 threads::shared::share($error{refaddr $self});
317             }
318 71         308 $$self = $infohash{refaddr $self};
319 71 100       280 if ($args->{q[Client]}) {
320 41         312 $self->queue($args->{q[Client]});
321             $_client{refaddr $self}->_schedule(
322             {Time => time + 25,
323 18     18   133 Code => sub { shift->_dht_announce },
324 41         840 Object => $self
325             }
326             );
327             $_client{refaddr $self}->_schedule(
328             {Time => time,
329 19     19   126 Code => sub { shift->_dht_scrape },
330 41         668 Object => $self
331             }
332             );
333             }
334 71 100 66     510 $self->start if $_start && (${$status{refaddr $self}} & QUEUED);
  71         378  
335 71         437 $self->_new_peer(); # XXX - temporary multi-thread vs schedule fix
336 71         556 return $self;
337             }
338              
339             # Accessors | Public
340 712     712 1 23678 sub infohash { return $infohash{refaddr +shift}; }
341 112     112 1 1078 sub trackers { return $trackers{refaddr +shift}; }
342 167     167 1 310 sub bitfield { return ${$bitfield{refaddr +shift}}; }
  167         1949  
343 15     15 1 161 sub path { return $path{refaddr +shift}; }
344 5     5 1 3525 sub resume_path { return $resume_path{refaddr +shift}; }
345 28     28 1 1453 sub files { return $files{refaddr +shift}; }
346 5     5 1 43 sub size { return $size{refaddr +shift}; }
347 8333     8333 1 24828 sub status { return ${$status{refaddr +shift}}; }
  8333         66762  
348 30     30 1 427 sub downloaded { return $downloaded{refaddr +shift}; }
349 30     30 1 314 sub uploaded { return $uploaded{refaddr +shift}; }
350 0     0 1 0 sub error { return ${$error{refaddr +shift}}; }
  0         0  
351 5     5 1 45 sub comment { return $raw_data{refaddr +shift}{q[comment]}; }
352 5     5 1 2387 sub created_by { return $raw_data{refaddr +shift}{q[created by]}; }
353              
354             sub creation_date {
355 5     5 1 2888 return $raw_data{refaddr +shift}{q[creation date]};
356             }
357 5     5 1 3526 sub name { return $raw_data{refaddr +shift}{q[info]}{q[name]}; }
358              
359             sub private {
360 210 50   210 1 3893 return $raw_data{refaddr +shift}{q[info]}{q[private]} ? 1 : 0;
361             }
362              
363             sub raw_data {
364 25     25 1 29147 my ($self, $raw) = @_;
365 25 100       313 return $raw
366             ? $raw_data{refaddr $self}
367             : bencode $raw_data{refaddr $self};
368             }
369              
370             sub is_complete {
371 234     234 1 16468 my ($self) = @_;
372 234 50       428 return if (${$status{refaddr $self}} & CHECKING);
  234         1380  
373 234 100       1425 return unpack(q[b*], $self->_wanted) !~ m[1] ? 1 : 0;
374             }
375              
376             sub piece_count { # XXX - cache?
377 1029     1029 1 1882 my ($self) = @_;
378             return
379             int(
380             length(
381 1029         36673 unpack(q[H*], $raw_data{refaddr $self}{q[info]}{q[pieces]})
382             ) / 40
383             );
384             }
385              
386             sub peers {
387 503     503 1 869 my ($self) = @_;
388 503 50       760 return if (${$status{refaddr $self}} & CHECKING);
  503         2096  
389 503 50       712 return if !(${$status{refaddr $self}} & QUEUED);
  503         1996  
390 503         2934 my $_connections = $_client{refaddr $self}->_connections;
391 2771 100 66     23024 return map {
392 503         2051 ( ($_->{q[Object]}->isa(q[Net::BitTorrent::Peer]))
393             and ($_->{q[Object]}->torrent)
394             and ($_->{q[Object]}->torrent eq $self))
395             ? $_->{q[Object]}
396             : ()
397             } values %$_connections;
398             }
399              
400             # Mutators | Private
401             sub _add_node {
402 0     0   0 my ($self, $node) = @_;
403 0         0 return $_nodes{refaddr $self} .= compact($node);
404             }
405              
406             sub _set_bitfield {
407 15     15   196 my ($self, $new_value) = @_;
408 15 100       23 return if (${$status{refaddr $self}} & CHECKING);
  15         86  
409 10 100       15 return if length ${$bitfield{refaddr $self}} != length $new_value;
  10         94  
410              
411             # XXX - make sure bitfield conforms to what we expect it to be
412 5         12 return ${$bitfield{refaddr $self}} = $new_value;
  5         53  
413             }
414              
415             sub _set_status {
416 5     5   12 my ($self, $new_value) = @_;
417 5 50       9 return if (${$status{refaddr $self}} & CHECKING);
  5         40  
418              
419             # XXX - make sure status conforms to what we expect it to be
420 5         15 return ${$status{refaddr $self}} = $new_value;
  5         55  
421             }
422              
423             sub _set_error {
424 15     15   37 my ($self, $msg) = @_;
425 15         24 ${$error{refaddr $self}} = $msg;
  15         59  
426 15 100       21 $self->stop() if ${$status{refaddr $self}} & STARTED;
  15         70  
427 15         23 ${$status{refaddr $self}} |= ERROR;
  15         56  
428 15         34 return 1;
429             }
430              
431             sub _set_block_length {
432 0     0   0 my ($self, $value) = @_;
433 0 0       0 return if $value !~ m[^\d+$];
434 0         0 return $_block_length{refaddr $self} = $value;
435             }
436              
437             # Accessors | Private
438 609     609   15255 sub _client { return $_client{refaddr +shift}; }
439 15     15   20823 sub _block_length { return $_block_length{refaddr +shift} }
440 0     0   0 sub _nodes { return $_nodes{refaddr +shift}; }
441 0     0   0 sub _working_pieces { return $_working_pieces{refaddr +shift}; }
442 0     0   0 sub _basedir { return $_basedir{refaddr +shift}; }
443              
444             sub _wanted {
445 379     379   5051 my ($self) = @_;
446 379         1310 my $wanted = q[0] x $self->piece_count;
447 379         2129 my $p_size = $raw_data{refaddr $self}{q[info]}{q[piece length]};
448 379         3062 my $offset = 0;
449 379         592 for my $file (@{$files{refaddr $self}}) {
  379         1973  
450 729         1300 my $start = ($offset / $p_size);
451 729         3286 my $end = (($offset + $file->size) / $p_size);
452 729 100       3445 if ($file->priority ? 1 : 0) {
    100          
453 721 50       3224 substr($wanted, $start,
454             ($end - $start + 1),
455             (($file->priority ? 1 : 0) x ($end - $start + 1)));
456             }
457 729         2331 $offset += $file->size;
458             }
459             return (
460 379         1842 pack(q[b*], $wanted)
461 379         1708 | ${$bitfield{refaddr $self}} ^ ${$bitfield{refaddr $self}});
  379         7144  
462             }
463              
464             sub _weights {
465 18     18   33 my ($self) = @_;
466 18         26 my %_weights;
467 18         80 my $p_size = $raw_data{refaddr $self}{q[info]}{q[piece length]};
468 18         32 my $offset = 0;
469 18         28 for my $file (@{$files{refaddr $self}}) {
  18         162  
470 36         119 my $priority = $file->priority;
471 36         68 my $start = ($offset / $p_size);
472 36         147 my $end = (($offset + $file->size) / $p_size);
473 36         117 $offset += $file->size;
474 36 50       99 next if !$priority;
475 54         513 grep {
476 36         77 $_weights{$_} = $priority
477 54 50       65 if !vec(${$bitfield{refaddr $self}}, $_, 1)
478             } $start .. $end;
479             }
480 18         272 return %_weights;
481             }
482              
483             # Methods | Public
484             sub hashcheck {
485 23     23 1 166 my ($self) = @_;
486 23 50       55 return if (${$status{refaddr $self}} & PAUSED);
  23         111  
487 23 50       45 return if (${$status{refaddr $self}} & CHECKING);
  23         91  
488 23         78 ${$bitfield{refaddr $self}} # empty it first
  23         82  
489             = pack(q[b*], qq[\0] x $self->piece_count);
490 23         46 my $start_after_check = ${$status{refaddr $self}} & START_AFTER_CHECK;
  23         140  
491 0         0 ${$status{refaddr $self}} |= CHECKING
  23         96  
492 23 50       41 if !${$status{refaddr $self}} & CHECKING;
493 23         71 for my $index (0 .. ($self->piece_count - 1)) {
494 279         776 $self->_check_piece_by_index($index);
495             }
496 5         22 (${$status{refaddr $self}} ^= START_AFTER_CHECK)
  23         107  
497 23 100       55 if ${$status{refaddr $self}} & START_AFTER_CHECK;
498 23         141 ${$status{refaddr $self}} ^= CHECKED
  23         150  
499 23 50       37 if !(${$status{refaddr $self}} & CHECKED);
500 0         0 ${$status{refaddr $self}} ^= CHECKING
  23         177  
501 23 50       44 if ${$status{refaddr $self}} & CHECKING;
502 23 100       60 if ($start_after_check) { $self->start(); }
  5         21  
503 23         116 return 1;
504             }
505              
506             sub pause {
507 10     10 1 58 my ($self) = @_;
508 10 50       21 if (!${$status{refaddr $self}} & QUEUED) {
  10         56  
509 0         0 carp q[Cannot pause an orphan torrent];
510 0         0 return;
511             }
512 10 50       21 if (!${$status{refaddr $self}} & STARTED) {
  10         76  
513 0         0 carp q[Cannot pause a stopped torrent];
514 0         0 return;
515             }
516 10         15 return ${$status{refaddr $self}} |= PAUSED;
  10         46  
517             }
518              
519             sub start {
520 56     56 1 137 my ($self) = @_;
521 56 100       118 return if !(${$status{refaddr $self}} & QUEUED);
  56         298  
522 0         0 ${$status{refaddr $self}} ^= ERROR
  51         241  
523 51 50       101 if ${$status{refaddr $self}} & ERROR;
524 5         25 ${$status{refaddr $self}} ^= PAUSED
  51         275  
525 51 100       116 if ${$status{refaddr $self}} & PAUSED;
526 51 100       90 if (!(${$status{refaddr $self}} & STARTED)) {
  51         313  
527 46         76 ${$status{refaddr $self}} |= STARTED;
  46         206  
528 46         87 for my $tracker (@{$trackers{refaddr $self}}) {
  46         238  
529 8         65 $tracker->_announce(q[started]);
530             }
531             }
532 51         97 return ${$status{refaddr $self}};
  51         275  
533             }
534              
535             sub stop {
536 7     7 1 53 my ($self) = @_;
537 7 50       16 return if !(${$status{refaddr $self}} & QUEUED);
  7         49  
538 7         37 for my $_peer ($self->peers) {
539 0         0 $_peer->_disconnect(q[Torrent has been stopped]);
540             }
541 7         19 for my $_file (@{$files{refaddr $self}}) { $_file->_close(); }
  7         36  
  12         74  
542 7 50       16 if (${$status{refaddr $self}} & STARTED) {
  7         42  
543 7         13 ${$status{refaddr $self}} ^= STARTED;
  7         33  
544 7         18 for my $tracker (@{$trackers{refaddr $self}}) {
  7         42  
545 2         14 $tracker->_announce(q[stopped]);
546             }
547             }
548 7         17 return !!${$status{refaddr $self}} & STARTED;
  7         36  
549             }
550              
551             sub queue {
552 41     41 1 93 my ($self, $client) = @_;
553 41 50 33     706 if ( (!$client)
      33        
554             || (!blessed $client)
555             || (!$client->isa(q[Net::BitTorrent])))
556 0         0 { carp q[Net::BitTorrent::Torrent->queue() requires a ]
557             . q[blessed Net::BitTorrent object];
558 0         0 return;
559             }
560 41 50 33     266 if ($_client{refaddr $self} or ${$status{refaddr $self}} & QUEUED) {
  41         220  
561 0         0 carp q[Cannot serve the same .torrent more than once];
562 0         0 return;
563             }
564 41         169 $_client{refaddr $self} = $client;
565 41         200 weaken $_client{refaddr $self};
566 41         66 ${$status{refaddr $self}} ^= QUEUED;
  41         10290  
567              
568             #$self->_new_peer();
569 41         188 return $_client{refaddr $self};
570             }
571              
572             # Methods | Private
573             sub _add_uploaded {
574 20     20   47 my ($self, $amount) = @_;
575 20 50       35 return if (${$status{refaddr $self}} & CHECKING);
  20         111  
576 20 50       30 return if !(${$status{refaddr $self}} & QUEUED);
  20         83  
577 20 50       57 return if not $amount;
578 20 100       251 $uploaded{refaddr $self} += (($amount =~ m[^\d+$]) ? $amount : 0);
579             }
580              
581             sub _add_downloaded {
582 18     18   47 my ($self, $amount) = @_;
583 18 50       28 return if (${$status{refaddr $self}} & CHECKING);
  18         107  
584 18 50       29 return if !(${$status{refaddr $self}} & QUEUED);
  18         75  
585 18 100       287 $downloaded{refaddr $self} += (($amount =~ m[^\d+$]) ? $amount : 0);
586             }
587              
588             sub _new_peer {
589 213     213   518 my ($self) = @_;
590 213 100       1539 return if not defined $_client{refaddr $self};
591             $_client{refaddr $self}->_schedule(
592             {Time => time + ($self->is_complete ? 60 : 5),
593 142 50   142   1592 Code => sub { shift->_new_peer if @_; },
594 183 100       1821 Object => $self
595             }
596             );
597 183 50       732 return if (${$status{refaddr $self}} & CHECKING);
  183         843  
598 183 50       361 return if !(${$status{refaddr $self}} & STARTED);
  183         1111  
599 183 50       325 return if !(${$status{refaddr $self}} & QUEUED);
  183         951  
600              
601             # Don't bother if we're at the hard limit
602             return
603 183 50       927 if scalar $self->peers
604             >= $_client{refaddr $self}->_peers_per_torrent;
605              
606             #
607 754 100       6315 my $half_open = scalar(
608             grep {
609 183         952 $_->{q[Object]}->isa(q[Net::BitTorrent::Peer])
610             and not defined $_->{q[Object]}->peerid
611 183         341 } values %{$_client{refaddr $self}->_connections}
612             );
613              
614             #warn sprintf q[%d half open peers], $half_open;
615             # List of peers to make sure we're not already connected to this peer
616 183         711 my @peers = $self->peers;
617              
618             # If we haven't any nodes in cache, gather them from various sources
619 183 100       1238 if (!$_nodes{refaddr $self}) {
620 173 100       919 $_nodes{refaddr $self}
621             = $_client{refaddr $self}->_dht->_peers($self->infohash)
622             if !$self->private;
623 173         617 for my $tier (@{$trackers{refaddr $self}}) {
  173         933  
624 82         181 for my $url (@{$tier->urls}) {
  82         682  
625 82         760 $_nodes{refaddr $self} .= $url->_peers;
626             }
627             }
628             }
629              
630             # Don't bother if we haven't any nodes to try
631 183 100       1153 return if !$_nodes{refaddr $self};
632              
633             # Inflate the list and try them one-by-one
634 66         560 my @nodes = uncompact($_nodes{refaddr $self});
635 66         523 for ($half_open .. $_client{refaddr $self}->_half_open - 1) {
636 199 100       640 last if !@nodes;
637 152         281 my $node = shift @nodes;
638             next
639 644   50     2074 if scalar grep {
      50        
640 152 100       325 sprintf(q[%s:%d], ($_->host || q[]), ($_->port || 0)) eq
641             $node # already connected to this peer
642             } @peers;
643 32         253 my $ok = $_client{refaddr $self}
644             ->_event(q[ip_filter], {Address => $node});
645 32 50 33     126 if (defined $ok and $ok == 0) { next; }
  0         0  
646 32         423 my $peer =
647             Net::BitTorrent::Peer->new({Address => $node,
648             Torrent => $self,
649             Source => q[TODO]
650             }
651             );
652             }
653              
654             # Store only nodes we haven't tried yet
655 66         330 $_nodes{refaddr $self} = compact(@nodes);
656              
657             # Return
658 66         278 return 1;
659             }
660              
661             sub _add_tracker {
662 12     12   101 my ($self, $tier) = @_;
663 12 50       49 carp q[Please, pass new tier in an array ref...]
664             unless ref $tier eq q[ARRAY];
665 12         196 my $tracker = Net::BitTorrent::Torrent::Tracker->new(
666             {Torrent => $self, URLs => $tier});
667 12         76 $tracker->_announce(q[started]);
668 12         21 return push(@{$trackers{refaddr $self}}, $tracker);
  12         64  
669             }
670              
671             sub _piece_by_index {
672 41     41   100 my ($self, $index) = @_;
673 41 50       54 return if !${$status{refaddr $self}} & STARTED;
  41         195  
674 41 50       60 return if (${$status{refaddr $self}} & CHECKING);
  41         151  
675 41 100       54 return if !(${$status{refaddr $self}} & QUEUED);
  41         139  
676 36 100 66     230 if ((!defined $index) || ($index !~ m[^\d+$])) {
677 15         2111 carp
678             q[Net::BitTorrent::Torrent->_piece_by_index() requires an index];
679 15         1511 return;
680             }
681 21 100       160 return $_working_pieces{refaddr $self}{$index}
682             ? $_working_pieces{refaddr $self}{$index}
683             : ();
684             }
685              
686             sub _pick_piece {
687 22     22   48 my ($self, $peer) = @_;
688 22 100       84 return if $self->is_complete;
689 18 50       40 return if !${$status{refaddr $self}} & STARTED;
  18         106  
690 18 50       31 return if (${$status{refaddr $self}} & CHECKING);
  18         85  
691 18 50       33 return if !(${$status{refaddr $self}} & QUEUED);
  18         113  
692 18 50       105 if (!$_client{refaddr $self}) {
693 0         0 carp
694             q[Net::BitTorrent::Torrent->_pick_piece(PEER) will not on an orphan torrent];
695 0         0 return;
696             }
697 18 50 33     36 if ( (!${$status{refaddr $self}} & STARTED)
  18         78  
  18         102  
698             || (${$status{refaddr $self}} & CHECKING))
699 0         0 { carp
700             q[Net::BitTorrent::Torrent->_pick_piece(PEER) will not work while hashchecking];
701 0         0 return;
702             }
703 18 50 33     548 if ( (!$peer)
      33        
704             || (!blessed $peer)
705             || (!$peer->isa(q[Net::BitTorrent::Peer])))
706 0         0 { carp
707             q[Net::BitTorrent::Torrent->_pick_piece(PEER) requires a peer];
708 0         0 return;
709             }
710 18         43 my $piece;
711 18         57 my $_wanted = $self->_wanted;
712 18         119 my $relevence = $peer->bitfield & $_wanted;
713 18 50       126 return if unpack(q[b*], $relevence) !~ m[1];
714 18 50       317 my $endgame = ( # XXX - static ratio
715             (sum(split(q[], unpack(q[b*], $_wanted)))
716             <= (length(unpack(q[b*], $_wanted)) * .1)
717             ) ? 1 : 0
718             );
719              
720             #warn sprintf q[Endgame | %d <= %d (%d) ? %d],
721             # sum(split(q[], unpack(q[b*], $_wanted))),
722             # (length(unpack(q[b*], $_wanted)) * .1),
723             # length(unpack(q[b*], $_wanted)),
724             # $endgame;
725 18         48 my $unrequested_blocks = 0;
726 18         40 for my $index (keys %{$_working_pieces{refaddr $self}}) {
  18         254  
727 13         106 $unrequested_blocks += scalar grep {
728 13         89 !keys %{$_working_pieces{refaddr $self}{$index}
  13         16  
729             {q[Blocks_Requested]}[$_]}
730             } 0 .. $_working_pieces{refaddr $self}{$index}{q[Block_Count]}
731             - 1;
732             }
733 18 50       50 if (scalar(grep { $_->{q[Slow]} == 1 }
  13 50       59  
  18 50       192  
734             values %{$_working_pieces{refaddr $self}}
735             ) >= 3
736             )
737 18         226 { my @indexes
738 0         0 = grep { $_working_pieces{refaddr $self}{$_}{q[Slow]} == 1 }
  0         0  
739 0         0 keys %{$_working_pieces{refaddr $self}};
740 0         0 for my $index (@indexes) {
741 0 0       0 if (vec($relevence, $index, 1) == 1) {
742 0 0       0 if (($endgame
  0 0       0  
743             ? index($_working_pieces{refaddr $self}{$index}
744             {q[Blocks_Received]},
745             0,
746             0
747             )
748 0         0 : scalar grep { scalar keys %$_ }
749             @{ $_working_pieces{refaddr $self}{$index}
750             {q[Blocks_Requested]}
751             }
752             ) != -1
753             )
754 0         0 { $piece = $_working_pieces{refaddr $self}{$index};
755 0         0 last;
756             }
757             }
758             }
759             }
760             elsif (
761 18         88 scalar(values %{$_working_pieces{refaddr $self}}) >= (
762             ( $unrequested_blocks > (
763             int($raw_data{refaddr $self}{q[info]}{q[piece length]}
764             / $_block_length{refaddr $self}
765             ) / 4
766             ) ? 0 : 1
767             ) + scalar keys %{$_working_pieces{refaddr $self}}
768             )
769             )
770 0         0 { my @indexes = sort {
771 0         0 (scalar grep { scalar keys %$_ }
  0         0  
772             @{
773 0         0 $_working_pieces{refaddr $self}{$a}{q[Blocks_Requested]}
774             }
775 0         0 ) <=> (scalar grep { scalar keys %$_ }
776             @{
777 0         0 $_working_pieces{refaddr $self}{$b}
778             {q[Blocks_Requested]}
779             }
780             )
781 0         0 } keys %{$_working_pieces{refaddr $self}};
782 0         0 for my $index (@indexes) {
783 0 0       0 if (vec($relevence, $index, 1) == 1) {
784 0 0       0 if (($endgame
  0 0       0  
785             ? index($_working_pieces{refaddr $self}{$index}
786             {q[Blocks_Received]},
787             0,
788             0
789             )
790 0         0 : scalar grep { scalar keys %$_ }
791             @{ $_working_pieces{refaddr $self}{$index}
792             {q[Blocks_Requested]}
793             }
794             ) != -1
795             )
796 0         0 { $piece = $_working_pieces{refaddr $self}{$index};
797 0         0 last;
798             }
799             }
800             }
801             }
802             else {
803 18         62 my %weights = $self->_weights;
804 18 50       59 return if not keys %weights;
805 18         63 my $total = sum values %weights; # [id://230661]
806 18         125 my $rand_val = $total * rand;
807 18         25 my $index;
808 18         83 for my $i (reverse sort keys %weights) {
809 36         69 $rand_val -= $weights{$i};
810 36 100 100     254 if ($rand_val <= 0
811             && vec($relevence, $i, 1) == 1)
812 18         29 { $index = $i;
813 18         44 last;
814             }
815             }
816 18 50       127 return if not defined $index;
817 18 50       178 my $_piece_length = ( # XXX - save some time and cache this?
818             ($index == int(
819             $size{refaddr $self}
820             / $raw_data{refaddr $self}{q[info]}{q[piece length]}
821             )
822             )
823             ? ($size{refaddr $self} % $raw_data{refaddr $self}{q[info]}
824             {q[piece length]})
825             : ($raw_data{refaddr $self}{q[info]}{q[piece length]})
826             );
827 18 50       140 my $block_length = (
828             ($raw_data{refaddr $self}{q[info]}{q[piece length]}
829             < $_block_length{refaddr $self}
830             )
831             ? ($raw_data{refaddr $self}{q[info]}{q[piece length]})
832             : $_block_length{refaddr $self}
833             );
834 18         77 my $block_length_last
835             = ($raw_data{refaddr $self}{q[info]}{q[piece length]}
836             % $_piece_length);
837 18 50       59 my $block_count
838             = (int($_piece_length / $block_length)
839             + ($block_length_last ? 1 : 0));
840 18         82 $piece = {Index => $index,
841             Priority => $weights{$index},
842 18         314 Blocks_Requested => [map { {} } 1 .. $block_count],
843 18         53 Blocks_Received => [map {0} 1 .. $block_count],
844             Block_Length => $block_length,
845             Block_Length_Last => $block_length_last,
846             Block_Count => $block_count,
847             Length => $_piece_length,
848             Endgame => $endgame,
849             Slow => 1,
850             mtime => 0
851             };
852             }
853 18 50       256 if ($piece) {
854 18 100       102 if (not
855             defined $_working_pieces{refaddr $self}{$piece->{q[Index]}})
856 5         24 { $_working_pieces{refaddr $self}{$piece->{q[Index]}} = $piece;
857 5         24 $_working_pieces{refaddr $self}{$piece->{q[Index]}}
858             {q[Endgame]} = $endgame;
859             }
860             }
861 18 50       288 return $piece
862             ? $_working_pieces{refaddr $self}{$piece->{q[Index]}}
863             : ();
864             }
865              
866             sub _write_data {
867 3     3   10 my ($self, $index, $offset, $data) = @_;
868 3 50       5 return if !${$status{refaddr $self}} & STARTED;
  3         15  
869 3 50       5 return if (${$status{refaddr $self}} & CHECKING);
  3         13  
870 3 50       5 return if !(${$status{refaddr $self}} & QUEUED);
  3         15  
871 3 50       28 if ((length($$data) + (
872             ($raw_data{refaddr $self}{q[info]}{q[piece length]} * $index)
873             + $offset
874             )
875             ) > $size{refaddr $self}
876             )
877 0         0 { carp q[Too much data or bad offset data for this torrent];
878 0         0 return;
879             }
880 3         6 my $file_index = 0;
881 3   50     23 my $total_offset
882             = int(
883             (($index * $raw_data{refaddr $self}{q[info]}{q[piece length]}))
884             + ($offset || 0));
885             SEARCH:
886 3         22 while ($total_offset > $files{refaddr $self}->[$file_index]->size) {
887 0         0 $total_offset -= $files{refaddr $self}->[$file_index]->size;
888 0         0 $file_index++;
889             last SEARCH # XXX - return?
890 0 0       0 if not defined $files{refaddr $self}->[$file_index]->size;
891             }
892 3         14 WRITE: while (length $$data > 0) {
893 6 100       28 my $this_write
894             = ($total_offset + length $$data
895             > $files{refaddr $self}->[$file_index]->size)
896             ? $files{refaddr $self}->[$file_index]->size - $total_offset
897             : length $$data;
898 6 50       35 $files{refaddr $self}->[$file_index]->_open(q[w]) or return;
899 6         38 $files{refaddr $self}->[$file_index]->_sysseek($total_offset);
900 6 50       223 $files{refaddr $self}->[$file_index]
901             ->_write(substr($$data, 0, $this_write, q[]))
902             or return;
903 6         12 $file_index++;
904             last WRITE
905 6 100       30 if not defined $files{refaddr $self}->[$file_index];
906 3         10 $total_offset = 0;
907             }
908 3         18 return 1;
909             }
910              
911             sub _read_data {
912 292     292   579 my ($self, $index, $offset, $length) = @_;
913 292 50 33     1575 return if !defined $index || $index !~ m[^\d+$];
914 292 50 33     1415 return if !defined $offset || $offset !~ m[^\d+$];
915 292 50 33     1313 return if !defined $length || $length !~ m[^\d+$];
916 292         612 my $data = q[];
917 292 50       2562 if (($length + (
918             ($raw_data{refaddr $self}{q[info]}{q[piece length]} * $index)
919             + $offset
920             )
921             ) > $size{refaddr $self}
922             )
923 0         0 { carp q[Too much or bad offset data for this torrent];
924 0         0 return;
925             }
926 292         378 my $file_index = 0;
927 292   50     9993 my $total_offset
928             = int(
929             (($index * $raw_data{refaddr $self}{q[info]}{q[piece length]}))
930             + ($offset || 0));
931             SEARCH:
932 292         1699 while ($total_offset > $files{refaddr $self}->[$file_index]->size) {
933 19         107 $total_offset -= $files{refaddr $self}->[$file_index]->size;
934 19         32 $file_index++;
935             last SEARCH # XXX - return?
936 19 50       98 if not defined $files{refaddr $self}->[$file_index]->size;
937             }
938 292   33     1359 READ: while ((defined $length) && ($length > 0)) {
939 308 100       1429 my $this_read
940             = (($total_offset + $length)
941             >= $files{refaddr $self}->[$file_index]->size)
942             ? ($files{refaddr $self}->[$file_index]->size - $total_offset)
943             : $length;
944 308 100       3017 $files{refaddr $self}->[$file_index]->_open(q[r]) or return;
945 35         192 $files{refaddr $self}->[$file_index]->_sysseek($total_offset);
946 35         251 my $_data
947             = $files{refaddr $self}->[$file_index]->_read($this_read);
948 35 50       1022 $data .= $_data if $_data;
949 35         48 $file_index++;
950 35         52 $length -= $this_read;
951 35 100       214 last READ if not defined $files{refaddr $self}->[$file_index];
952 16         84 $total_offset = 0;
953             }
954 19         79 return \$data;
955             }
956              
957             sub _check_piece_by_index {
958 287     287   433 my ($self, $index) = @_;
959 287 50 33     1690 if ((!defined $index) || ($index !~ m[^\d+$])) {
960 0         0 carp q[Net::BitTorrent::Torrent->_check_piece_by_index( INDEX ) ]
961             . q[requires an index.];
962 0         0 return;
963             }
964 287         779 delete $_working_pieces{refaddr $self}{$index};
965 287 100       2819 my $data =
966             $self->_read_data(
967             $index, 0,
968             ($index == ($self->piece_count - 1)
969             ? ($size{refaddr $self} % $raw_data{refaddr $self}{q[info]}
970             {q[piece length]})
971             : $raw_data{refaddr $self}{q[info]}{q[piece length]}
972             )
973             );
974 287 100 66     4126 if ((!$data)
975             or (sha1_hex($$data) ne substr(
976             unpack(
977             q[H*],
978             $raw_data{refaddr $self}{q[info]}{q[pieces]}
979             ),
980             $index * 40,
981             40
982             )
983             )
984             )
985 273         412 { vec(${$bitfield{refaddr $self}}, $index, 1) = 0;
  273         3915  
986 273         1450 $self->_event(q[piece_hash_fail],
987             {Torrent => $self, Index => $index});
988 273         929 return 0;
989             }
990 14 100       31 if (vec(${$bitfield{refaddr $self}}, $index, 1) == 0) {
  14         82  
991 9         15 vec(${$bitfield{refaddr $self}}, $index, 1) = 1;
  9         50  
992 9         54 $self->_event(q[piece_hash_pass],
993             {Torrent => $self, Index => $index});
994             }
995 14         111 return 1;
996             }
997              
998             # Methods | Private | DHT
999             sub _dht_announce {
1000 18     18   51 my ($self) = @_;
1001             $_client{refaddr $self}->_schedule(
1002             {Time => time + 120,
1003 0     0   0 Code => sub { shift->_dht_announce },
1004 18         348 Object => $self
1005             }
1006             );
1007 18 50       59 return if !${$status{refaddr $self}} & STARTED;
  18         143  
1008 18 50       36 return if (${$status{refaddr $self}} & CHECKING);
  18         101  
1009 18 50       40 return if !(${$status{refaddr $self}} & QUEUED);
  18         112  
1010 18 100       118 return if $self->private;
1011 12 50       101 return if !$_client{refaddr $self}->_use_dht;
1012 12         149 $_client{refaddr $self}->_dht->_announce($self);
1013             $_client{refaddr $self}->_schedule(
1014             { Time => time + 15,
1015             Code => sub {
1016 12     12   40 my ($s) = @_;
1017 12 50       123 $_client{refaddr $s}->_dht->_scrape($s)
1018             if $_client{refaddr $s}->_use_dht;
1019             },
1020 12         404 Object => $self
1021             }
1022             );
1023             }
1024              
1025             sub _dht_scrape {
1026 20     20   53 my ($self) = @_;
1027             $_client{refaddr $self}->_schedule(
1028             {Time => time + 60,
1029 1     1   7 Code => sub { shift->_dht_scrape },
1030 20         344 Object => $self
1031             }
1032             );
1033 20 50       77 return if !(${$status{refaddr $self}} & STARTED);
  20         133  
1034 20 50       36 return if (${$status{refaddr $self}} & CHECKING);
  20         104  
1035 20 50       41 return if !(${$status{refaddr $self}} & QUEUED);
  20         103  
1036 20 100       93 return if $self->private;
1037 13 50       123 $_client{refaddr $self}->_dht->_scrape($self)
1038             if $_client{refaddr $self}->_use_dht;
1039             }
1040              
1041             # Methods | Public | Callback system
1042             sub on_event {
1043 29     29 1 301 my ($self, $type, $method) = @_;
1044 29 50       71 carp sprintf q[Unknown callback: %s], $type
1045             unless ___check_event($type);
1046 29         167 $_event{refaddr $self}{$type} = $method;
1047             }
1048              
1049             # Methods | Private | Callback system
1050             sub _event {
1051 476     476   969 my ($self, $type, $args) = @_;
1052 476 50       954 carp sprintf
1053             q[Unknown event: %s. This is a bug in Net::BitTorrent::Torrent; Report it.],
1054             $type
1055             unless ___check_event($type);
1056 476         1988 $_client{refaddr $self}->_event($type, $args)
1057 476 100       591 if ${$status{refaddr $self}} & QUEUED;
1058 476 100       19274 return $_event{refaddr $self}{$type}
1059             ? $_event{refaddr $self}{$type}($self, $args)
1060             : ();
1061             }
1062              
1063             # Functions | Private | Callback system
1064             sub ___check_event {
1065 505     505   916 my $type = shift;
1066 505         991 return scalar grep { $_ eq $type } qw[
  6565         12590  
1067             tracker_connect tracker_disconnect
1068             tracker_read tracker_write
1069             tracker_success tracker_failure
1070             piece_hash_pass piece_hash_fail
1071             file_open file_close
1072             file_read file_write
1073             file_error
1074             ];
1075             }
1076              
1077             # Methods | Public | Alpha
1078             sub save_resume_data {
1079 10     10 1 2789 my ($self, $file) = @_;
1080 10   66     367 $file ||= $resume_path{refaddr $self};
1081 10 100       80 return if !$file; # Don't even bother without a file to write to
1082              
1083             # Make sure file handles are closed so we don't mess up 'mtime' times
1084 5         13 for my $_file (@{$files{refaddr $self}}) { $_file->_close }
  5         31  
  8         42  
1085              
1086             # Gather nodes from various sources
1087             # Internal
1088 5         26 my $_nodes = $_nodes{refaddr $self};
1089              
1090             # DHT
1091 5 50 33     10 $_nodes .= (((${$status{refaddr $self}} & QUEUED) && !$self->private)
1092             ? $_client{refaddr $self}->_dht->_peers($self->infohash)
1093             : q[]
1094             );
1095              
1096             # Trackers
1097 5         13 for my $tier (@{$trackers{refaddr $self}}) {
  5         29  
1098 2         4 for my $url (@{$tier->urls}) { $_nodes .= $url->_peers; }
  2         140  
  2         13  
1099             }
1100              
1101             # The resume data proper
1102 5         21 my %resume_data = (
1103             q[.format] => q[Net::BitTorrent resume],
1104             q[.t] => time,
1105             q[.version] => 2,
1106 8 50       37 bitfield => ${$bitfield{refaddr $self}},
1107             files => [
1108             map {
1109 5         23 {priority => $_->priority,
1110             mtime => (-f $_->path ? (stat($_->path))[9] : 0)
1111             }
1112 0         0 } @{$files{refaddr $self}}
1113             ],
1114             peers => ($_nodes ? $_nodes : q[]),
1115             working => [
1116             map {
1117 5         79 {Block_Count => $_->{q[Block_Count]},
1118             Endgame => $_->{q[Endgame]},
1119             Blocks_Received =>
1120 0         0 pack(q[b*], join q[], @{$_->{q[Blocks_Received]}}),
1121             Index => $_->{q[Index]},
1122             Slow => $_->{q[Slow]},
1123             Block_Length => $_->{q[Block_Length]},
1124             Block_Length_Last => $_->{q[Block_Length_Last]},
1125             Length => $_->{q[Length]},
1126             Priority => $_->{q[Priority]}
1127             }
1128 5 50       17 } values %{$_working_pieces{refaddr $self}}
1129             ]
1130             );
1131              
1132             # Write it to disk
1133 5 50       366 open(my ($_RD), q[>], $file) || return;
1134 5 50       42 syswrite($_RD, bencode(\%resume_data)) || return;
1135 5         9073 return close $_RD;
1136             }
1137              
1138             # Methods | Public | Utility
1139             sub as_string {
1140 25     25 1 58 my ($self, $advanced) = @_;
1141 25         82 my $wanted = $self->_wanted;
1142 10         60 my $dump
1143             = !$advanced ? $self->infohash : sprintf <<'END',
1144             Net::BitTorrent::Torrent
1145             Path: %s
1146             Name: %s
1147             Infohash: %s
1148             Base Directory: %s
1149             Size: %s bytes
1150             Status: %d (%s.)
1151             DHT Status: %s
1152             Progress: %3.2f%% complete (%d bytes up / %d bytes down)
1153             [%s]
1154             ----------
1155             Pieces: %d x %d bytes
1156             Working: %s
1157             %s
1158             ----------
1159             ...has %d file%s:
1160             %s
1161             ----------
1162             ...has %d tracker tier%s:
1163             %s
1164             ----------
1165             END
1166             $self->path, $raw_data{refaddr $self}{q[info]}{q[name]},
1167             $self->infohash(), $_basedir{refaddr $self}, $size{refaddr $self},
1168 50         145 ${$status{refaddr $self}}, $self->_status_as_string(),
1169             ($self->private ? q[Disabled [Private]] : q[Enabled.]),
1170 486         14219 100 - (grep {$_} split //,
1171             unpack(q[b*], $wanted) / $self->piece_count * 100
1172             ),
1173             $uploaded{refaddr $self}, $downloaded{refaddr $self}, (
1174             sprintf q[%s],
1175             join q[],
1176             map {
1177 486 100       3128 vec(${$bitfield{refaddr $self}}, $_, 1) ? q[|] # have
    50          
    100          
1178             : $_working_pieces{refaddr $self}{$_} ? q[*] # working
1179             : vec($wanted, $_, 1) ? q[ ] # missing
1180             : q[x] # don't want
1181             } 0 .. $self->piece_count - 1
1182             ),
1183             $self->piece_count(),
1184             $raw_data{refaddr $self}{q[info]}{q[piece length]},
1185 0         0 (scalar keys %{$_working_pieces{refaddr $self}} || q[N/A]), (
1186             join qq[\n],
1187             map {
1188 0         0 my $index = $_;
1189 0         0 sprintf q[%4d [%s] % 3.2f%%], $index, join(
1190             q[],
1191             map {
1192 0         0 $_working_pieces{refaddr $self}{$index}
1193             {q[Blocks_Received]}[$_] ? q[|]
1194             : scalar
1195 0         0 keys %{$_working_pieces{refaddr $self}{$index}
1196             {q[Blocks_Requested]}[$_]} == 1 ? q[*]
1197             : scalar
1198 0 0       0 keys %{$_working_pieces{refaddr $self}{$index}
    0          
    0          
1199             {q[Blocks_Requested]}[$_]} ? q[!]
1200             : q[ ]
1201             } 0 .. $_working_pieces{refaddr $self}{$index}
1202             {q[Block_Count]} - 1
1203             ),
1204 0         0 (scalar(grep {$_}
1205             @{
1206 0         0 $_working_pieces{refaddr $self}{$index}
1207             {q[Blocks_Received]}
1208             }
1209             )
1210             / $_working_pieces{refaddr $self}{$index}
1211             {q[Block_Count]}
1212             ) * 100;
1213 10         52 } sort { $a <=> $b }
1214 10         41 keys %{$_working_pieces{refaddr $self}}
1215             ),
1216 10         52 scalar @{$files{refaddr $self}},
1217 16         72 @{$files{refaddr $self}} != 1 ? q[s] : q[],
1218 10         42 join(qq[\n ], map { $_->path } @{$files{refaddr $self}}),
  10         251  
1219 10         57 scalar @{$trackers{refaddr $self}},
1220 4         26 @{$trackers{refaddr $self}} != 1 ? q[s] : q[],
1221             join(qq[\n ],
1222 4         235 map { $_->url }
1223 25 50 50     154 map { @{$_->urls} } @{$trackers{refaddr $self}}
  4 100       7  
  10 50       219  
    100          
1224             );
1225 25 100       259 return defined wantarray ? $dump : print STDERR qq[$dump\n];
1226             }
1227              
1228             sub _status_as_string {
1229 10     10   20 my ($self) = @_;
1230 80         196 return ucfirst join q[, ],
1231 10         62 grep {$_}
1232 10         60 (${$status{refaddr $self}} & LOADED) ? q[was loaded okay] : q[],
1233 10         44 (${$status{refaddr $self}} & STARTED) ? q[is started]
1234             : q[is stopped],
1235 10         43 (${$status{refaddr $self}} & CHECKING)
1236             ? q[is currently hashchecking]
1237             : q[],
1238 10         45 (${$status{refaddr $self}} & START_AFTER_CHECK)
1239             ? q[needs hashchecking]
1240 10         43 : q[], (${$status{refaddr $self}} & CHECKED) ? q[has been checked]
1241             : q[has not been checked],
1242 10         43 (${$status{refaddr $self}} & PAUSED) ? q[has been paused] : q[],
1243 10         234 (${$status{refaddr $self}} & QUEUED) ? q[is queued]
1244             : q[is good for informational use only],
1245 10 50       18 (${$status{refaddr $self}} & ERROR) ? q[but has an error] : q[];
    50          
    50          
    50          
    50          
    50          
    50          
    50          
1246             }
1247              
1248             sub CLONE {
1249 0     0   0 for my $_oID (keys %REGISTRY) {
1250 0         0 my $_obj = $REGISTRY{$_oID};
1251 0         0 my $_nID = refaddr $_obj;
1252 0         0 for (@CONTENTS) {
1253 0         0 $_->{$_nID} = $_->{$_oID};
1254 0         0 delete $_->{$_oID};
1255             }
1256 0         0 weaken $_client{$_nID};
1257 0         0 weaken($REGISTRY{$_nID} = $_obj);
1258 0         0 delete $REGISTRY{$_oID};
1259             }
1260 0         0 return 1;
1261             }
1262             DESTROY {
1263 61     61   31638 my ($self) = @_;
1264 61         277 for (@CONTENTS) { delete $_->{refaddr $self}; }
  1098         3651  
1265 61         1596 return delete $REGISTRY{refaddr $self};
1266             }
1267             1;
1268             }
1269              
1270             =pod
1271              
1272             =head1 NAME
1273              
1274             Net::BitTorrent::Torrent - Class Representing a Single .torrent File
1275              
1276             =head1 Synopsis
1277              
1278             use Net::BitTorrent::Torrent;
1279              
1280             my $torrent = Net::BitTorrent::Torrent->new({Path => q[a.legal.torrent]})
1281             or die q[Cannot load .torrent];
1282              
1283             $torrent->on_event(
1284             q[piece_hash_pass],
1285             sub {
1286             printf qq[%s is % 3.2f%% complete\r], $torrent->name,
1287             (scalar grep {$_} split q[], unpack q[b*], $torrent->bitfield)
1288             / $torrent->piece_count * 100;
1289             }
1290             );
1291              
1292             $torrent->hashcheck; # Verify any existing data
1293              
1294             =head1 Description
1295              
1296             C objects are typically created by the
1297             C class.
1298              
1299             Standalone C objects can be made for
1300             informational use. See L and
1301             L.
1302              
1303             =head1 Constructor
1304              
1305             =over
1306              
1307             =item C
1308              
1309             Creates a C object. This constructor is
1310             called by
1311             Ladd_torrent( )|Net::BitTorrent/"add_torrent ( { ... } )">.
1312              
1313             C accepts arguments as a hash, using key-value pairs:
1314              
1315             =over
1316              
1317             =item C
1318              
1319             The root directory used to store the files related to this torrent. This
1320             directory is created if not preexisting.
1321              
1322             This is an optional parameter.
1323              
1324             Default: C<./> (Current working directory)
1325              
1326             =item C
1327              
1328             The L object this torrent will
1329             eventually be served from.
1330              
1331             This is an optional parameter.
1332              
1333             No default. Without a defined parent client, his object is very limited
1334             in capability. Basic information and L only.
1335             Orphan objects are obviously not L automatically
1336             and must be added to a client L.
1337              
1338             =item C
1339              
1340             Filename of the .torrent file to load.
1341              
1342             This is the only required parameter.
1343              
1344             =item C
1345              
1346             The filename used to gather and store resume data.
1347              
1348             This is an optional parameter.
1349              
1350             No default. Without a defined resume file, resume data will not be
1351             written on calls to
1352             L without a
1353             C parameter.
1354              
1355             =item C
1356              
1357             Initial status of the torrent. This parameter is ORed with the loaded
1358             and queued (if applicable) values.
1359              
1360             For example, you could set the torrent to automatically start after
1361             L with
1362             C<{ [...] Status =E START_AFTER_CHECK, [...] }>.
1363              
1364             To import all supported statuses into your namespace, use the
1365             C keyword.
1366              
1367             This is an optional parameter.
1368              
1369             Default: 1 (started)
1370              
1371             See also: L
1372              
1373             Note: This is alpha code and may not work correctly.
1374              
1375             =back
1376              
1377             =back
1378              
1379             =head1 Methods
1380              
1381             =over
1382              
1383             =item C
1384              
1385             Returns a bitfield representing the pieces that have been successfully
1386             downloaded.
1387              
1388             =item C
1389              
1390             Returns the (optional) comment the original creator included in the
1391             .torrent metadata.
1392              
1393             =item C
1394              
1395             Returns the (optional) "created by" string included in the .torrent
1396             metadata. This is usually a software version.
1397              
1398             =item C
1399              
1400             Returns the (optional) creation time of the torrent, in standard UNIX
1401             epoch format.
1402              
1403             =item C
1404              
1405             Returns the total amount downloaded from remote peers since the client
1406             started transferring data related to this .torrent.
1407              
1408             See also: L
1409              
1410             =item C
1411              
1412             Returns the most recent error that caused the software to set the
1413             error L. Torrents with active errors are
1414             automatically stopped and must be L.
1415              
1416             See also: L, L
1417              
1418             =item C
1419              
1420             Returns a list of
1421             L objects
1422             representing all files contained in the related .torrent file.
1423              
1424             =item C
1425              
1426             Verifies the integrity of all L
1427             associated with this torrent.
1428              
1429             This is a blocking method; all processing will stop until this function
1430             returns.
1431              
1432             See also: L, L
1433              
1434             =item C
1435              
1436             Returns the 20 byte SHA1 hash used to identify this torrent internally,
1437             with trackers, and with remote peers.
1438              
1439             =item C
1440              
1441             Returns a bool value based on download progress. Returns C when we
1442             have completed every L with a
1443             priority above C<0>. Otherwise, returns C.
1444              
1445             See also:
1446             Lpriority()|Net::BitTorrent::Torrent::File/"priority( )">
1447              
1448             =item C
1449              
1450             Returns the advisory name used when creating the related files on disk.
1451              
1452             In a single file torrent, this is used as the filename by default. In a
1453             multiple file torrent, this is used as the containing directory for
1454             related files.
1455              
1456             =item C
1457              
1458             Net::BitTorrent::Torrent provides per-torrent callbacks. For example,
1459             to catch all attempts to read from a file, use
1460             C<$torrent-Eon_event( 'file_read', \&on_read )>. These per-
1461             torrent callbacks are especially useful for standalone torrents.
1462              
1463             See the L section for more.
1464              
1465             =item C
1466              
1467             Returns the L of the torrent this object represents.
1468              
1469             =item C
1470              
1471             Pauses an active torrent without closing related sockets.
1472              
1473             See also: L, L,
1474             L
1475              
1476             =item C
1477              
1478             Returns a list of remote L related to this
1479             torrent.
1480              
1481             =item C
1482              
1483             The number of pieces this torrent's data is broken into.
1484              
1485             =item C
1486              
1487             Returns bool value dependent on whether the private flag is set in the
1488             .torrent metadata. Private torrents disallow information sharing via DHT
1489             and PEX.
1490              
1491             =item C
1492              
1493             Adds a standalone (or orphan) torrent object to the particular
1494             L object's queue.
1495              
1496             See also:
1497             L
1498              
1499             =item C
1500              
1501             Returns the bencoded metadata found in the .torrent file. This method
1502             returns the original metadata in either bencoded form or as a raw hash
1503             (if you have other plans for the data) depending on the boolean value of
1504             the optional C parameter.
1505              
1506             =item C
1507              
1508             Returns the default path used to
1509             L. This value is set
1510             in the C parameter to L.
1511              
1512             =item C
1513              
1514             One end of Net::BitTorrent's resume system. This method writes the
1515             data to the file specified in the call to L
1516             or (if defined) to the C parameter.
1517              
1518             See also:
1519             L
1520             and
1521             L
1522             in L
1523              
1524             =item C
1525              
1526             Returns the total size of all files listed in the .torrent file.
1527              
1528             =item C
1529              
1530             Returns the internal status of this C object.
1531             States are bitwise C values of...
1532              
1533             =begin html
1534              
1535            
1536            
1537            
1538            
1539             Value
1540            
1541            
1542             Type
1543            
1544            
1545             Notes
1546            
1547            
1548            
1549            
1550            
1551            
1552             1
1553            
1554            
1555             STARTED
1556            
1557            
1558             Client is (making an attempt to be) active in the swarm
1559            
1560            
1561            
1562            
1563             2
1564            
1565            
1566             CHECKING
1567            
1568            
1569             Currently hashchecking (possibly in another thread)
1570            
1571            
1572            
1573            
1574             4
1575            
1576            
1577             START_AFTER_CHECK
1578            
1579            
1580             (Unused in this version)
1581            
1582            
1583            
1584            
1585             8
1586            
1587            
1588             CHECKED
1589            
1590            
1591             Files of this torrent have been checked
1592            
1593            
1594            
1595            
1596             16
1597            
1598            
1599             ERROR
1600            
1601            
1602             Activity is halted and may require user intervention
1603             (Unused in this version)
1604            
1605            
1606            
1607            
1608             32
1609            
1610            
1611             PAUSED
1612            
1613            
1614             Sockets are kept open but no piece data is sent or requested
1615            
1616            
1617            
1618            
1619             64
1620            
1621            
1622             LOADED
1623            
1624            
1625             Torrent has been parsed without error
1626            
1627            
1628            
1629            
1630             128
1631            
1632            
1633             QUEUED
1634            
1635            
1636             Has an associated Net::BitTorrent parent
1637            
1638            
1639            
1640            
1641              
1642             =end html
1643              
1644             =begin :text,wiki
1645              
1646             1 = STARTED (Client is (making an attempt to be) active in the swarm)
1647             2 = CHECKING (Currently hashchecking (possibly in another thread))
1648             4 = START_AFTER_CHECK*
1649             8 = CHECKED (Files of this torrent have been checked)
1650             16 = ERROR (Activity is halted and may require user intervention)
1651             32 = PAUSED (Sockets are kept open but no piece data is sent or requested)
1652             64 = LOADED (Torrent has been parsed without error)
1653             128 = QUEUED (Has an associated Net::BitTorrent parent)
1654              
1655             * Currently unused
1656              
1657             =end :text,wiki
1658              
1659             For example, a status of C<201> implies the torrent is
1660             C.
1661              
1662             When torrents have the a status that indicates an error, they must be
1663             L (if possible). The reason for the error I
1664             be returned by L.
1665              
1666             Import the C<:status> tag and you'll get the various status keywords in
1667             your namespace.
1668              
1669             =begin :podcoverage
1670              
1671             =over
1672              
1673             =item STARTED
1674              
1675             =item CHECKING
1676              
1677             =item START_AFTER_CHECK
1678              
1679             =item CHECKED
1680              
1681             =item ERROR
1682              
1683             =item PAUSED
1684              
1685             =item LOADED
1686              
1687             =item QUEUED
1688              
1689             =back
1690              
1691             =end :podcoverage
1692              
1693             Note: This is alpha and may not work as advertised. Yet.
1694              
1695             =item C
1696              
1697             Starts a paused or stopped torrent.
1698              
1699             See also: L, L,
1700             L
1701              
1702             =item C
1703              
1704             Stops an active or paused torrent. All related sockets (peers) are
1705             disconnected and all files are closed.
1706              
1707             See also: L, L,
1708             L
1709              
1710             =item C
1711              
1712             Returns a list of all
1713             L
1714             objects related to the torrent.
1715              
1716             =item C
1717              
1718             Returns the total amount uploaded to remote peers since the client
1719             started transferring data related to this .torrent.
1720              
1721             See also: L
1722              
1723             =item C
1724              
1725             Returns a 'ready to print' dump of the object's data structure. If
1726             called in void context, the structure is printed to C.
1727             C is a boolean value.
1728              
1729             =back
1730              
1731             =head1 Events
1732              
1733             When triggered, per-torrent callbacks receive two arguments: the
1734             C object and a hashref containing pertinent
1735             information. Per-torrent callbacks also trigger client-wide callbacks
1736             when the current torrent is queued.
1737              
1738             Per-torrent callbacks are limited to tracker-, piece-, and file-related
1739             events. See L for client-wide
1740             callbacks.
1741              
1742             =head1 Author
1743              
1744             Sanko Robinson - http://sankorobinson.com/
1745              
1746             CPAN ID: SANKO
1747              
1748             =head1 License and Legal
1749              
1750             Copyright (C) 2008-2009 by Sanko Robinson Esanko@cpan.orgE
1751              
1752             This program is free software; you can redistribute it and/or modify
1753             it under the terms of The Artistic License 2.0. See the F
1754             file included with this distribution or
1755             http://www.perlfoundation.org/artistic_license_2_0. For
1756             clarification, see http://www.perlfoundation.org/artistic_2_0_notes.
1757              
1758             When separated from the distribution, all POD documentation is covered
1759             by the Creative Commons Attribution-Share Alike 3.0 License. See
1760             http://creativecommons.org/licenses/by-sa/3.0/us/legalcode. For
1761             clarification, see http://creativecommons.org/licenses/by-sa/3.0/us/.
1762              
1763             Neither this module nor the L is affiliated with
1764             BitTorrent, Inc.
1765              
1766             =for svn $Id: Torrent.pm 64e98b0 2009-09-12 05:23:14Z sanko@cpan.org $
1767              
1768             =cut