AnyEvent-BitTorrent
view release on metacpan or search on metacpan
lib/AnyEvent/BitTorrent.pm view on Meta::CPAN
my $FILE = Type::Tiny->new(name => 'File',
parent => Str,
constraint => sub { -f $_ },
message => sub {"$_ isn't an existing file"},
);
my $RESERVED = Type::Tiny->new(name => 'Reserved',
parent => Str,
constraint => sub { length $_ == 8 },
message => sub {'reserved data is malformed'}
);
my $PEERID = Type::Tiny->new(
name => 'PeerID',
parent => Str,
constraint => sub { length $_ == 20 },
message => sub {
'Peer ID must be 20 chars in length';
}
);
my $INFOHASH = Type::Tiny->new(
name => 'Infohash',
parent => Str,
constraint => sub { length $_ == 20 },
message => sub {
'Infohashes are 20 bytes in length';
}
);
#
has port => (is => 'ro',
isa => Int,
default => sub {0},
writer => '_set_port'
);
has socket => (is => 'ro',
isa => Ref,
init_arg => undef,
predicate => '_has_socket',
builder => '_build_socket'
);
sub _build_socket {
my $s = shift;
tcp_server undef, $s->port, sub {
my ($fh, $host, $port) = @_;
AE::log info => 'Accepted connection from %s:%d', $host, $port;
return $fh->destroy if $s->state eq 'stopped';
my $handle = AnyEvent::Handle->new(
fh => $fh,
on_error => sub {
my ($hdl, $fatal, $msg) = @_;
# XXX - callback
AE::log error => 'Socket error: ' . $msg;
$s->_del_peer($hdl);
},
on_eof => sub {
my $h = shift;
AE::log info => 'Socket EOF';
$s->_del_peer($h);
},
on_read => sub {
AE::log debug => 'Read Socket';
$s->_on_read_incoming(@_);
}
);
$s->_add_peer($handle);
}, sub {
my ($fh, $thishost, $thisport) = @_;
$s->_set_port($thisport);
AE::log info => "bound to $thishost, port $thisport";
};
}
has path => (is => 'ro',
isa => $FILE,
required => 1
);
has reserved => (is => 'ro',
builder => '_build_reserved',
isa => $RESERVED
);
sub _build_reserved {
my $reserved = "\0" x 8;
#vec($reserved, 5, 8) = 0x10; # Ext Protocol
vec($reserved, 7, 8) = 0x04; # Fast Ext
AE::log debug => '_build_reserved() => ' . $reserved;
$reserved;
}
has peerid => (is => 'ro',
isa => $PEERID,
init_arg => undef,
required => 1,
builder => '_build_peerid'
);
sub _build_peerid {
return pack(
'a20',
(sprintf(
'-AB%01d%01d%01d%1s-%7s%-5s',
($VERSION =~ m[^v?(\d+)\.(\d+)\.(\d+)]),
($VERSION =~ m[[^\d\.^v]] ? 'U' : 'S'),
(join '',
map {
['A' .. 'Z', 'a' .. 'z', 0 .. 9, qw[- . _ ~]]->[rand(66)]
} 1 .. 7
),
[qw[KaiLi April Aaron Sanko]]->[rand 4]
)
)
);
}
has bitfield => (is => 'ro',
lazy => 1,
builder => '_build_bitfield',
isa => Str,
init_arg => undef,
);
sub _build_bitfield { pack 'b*', "\0" x shift->piece_count }
sub wanted {
my $s = shift;
my $wanted = '';
for my $findex (0 .. $#{$s->files}) {
my $prio = !!$s->files->[$findex]{priority};
for my $index ($s->_file_to_range($findex)) {
vec($wanted, $index, 1) = $prio && !vec($s->bitfield, $index, 1);
}
}
AE::log debug => '->wanted() => ' . unpack 'b*', $wanted;
$wanted;
}
sub complete {
my $s = shift;
-1 == index substr(unpack('b*', $s->wanted), 0, $s->piece_count + 1), 1;
}
sub seed {
my $s = shift;
-1 == index substr(unpack('b*', $s->bitfield), 0, $s->piece_count + 1), 0;
}
sub _left {
my $s = shift;
$s->piece_length * scalar grep {$_} split '',
substr unpack('b*', $s->wanted), 0, $s->piece_count + 1;
}
has $_ => (is => 'ro',
isa => Int,
default => sub {0},
writer => '_set_' . $_
) for qw[uploaded downloaded];
has infohash => (is => 'ro',
lazy => 1,
builder => '_build_infohash',
isa => $INFOHASH,
init_arg => undef
);
sub _build_infohash { sha1(bencode(shift->metadata->{info})) }
has metadata => (is => 'ro',
lazy_build => 1,
builder => '_build_metadata',
lazy => 1,
isa => HashRef,
init_arg => undef
);
sub _build_metadata {
my $s = shift;
#return if ref $s ne __PACKAGE__; # Applying roles makes deep rec
open my $fh, '<', $s->path;
sysread $fh, my $raw, -s $fh;
my $metadata = bdecode $raw;
AE::log debug => sub {
require Data::Dump;
'_build_metadata() => ' . Data::Dump::dump($metadata);
};
$metadata;
}
sub name { shift->metadata->{info}{name} }
sub pieces { shift->metadata->{info}{pieces} }
sub piece_length { shift->metadata->{info}{'piece length'} }
sub piece_count {
my $s = shift;
my $count = $s->size / $s->piece_length;
int($count) + (($count == int $count) ? 1 : 0);
}
has basedir => (
is => 'ro',
lazy => 1,
isa => Str,
required => 1,
default => sub { File::Spec->rel2abs(File::Spec->curdir) },
trigger => sub {
my ($s, $n, $o) = @_;
$o // return;
$s->_clear_files; # So they can be rebuilt with the new basedir
}
);
has files => (is => 'ro',
lazy => 1,
builder => '_build_files',
isa => ArrayRef [HashRef],
init_arg => undef,
clearer => '_clear_files'
);
sub _build_files {
my $s = shift;
defined $s->metadata->{info}{files} ?
[
map {
{priority => 1,
fh => undef,
mode => 'c',
length => $_->{length},
timeout => undef,
path =>
File::Spec->rel2abs(
File::Spec->catfile($s->basedir, $s->name, @{$_->{path}})
)
}
} @{$s->metadata->{info}{files}}
]
: [
{priority => 1,
fh => undef,
mode => 'c',
length => $s->metadata->{info}{length},
timeout => undef,
path =>
File::Spec->rel2abs(File::Spec->catfile($s->basedir, $s->name))
}
];
}
has size => (is => 'ro',
lazy => 1,
builder => '_build_size',
isa => Int,
init_arg => undef
);
sub _build_size {
my $s = shift;
my $ret = 0;
$ret += $_->{length} for @{$s->files};
AE::log debug => '_build_size() => ' . $ret;
$ret;
}
sub _open {
my ($s, $i, $m) = @_;
AE::log
trace => 'Opening file #%d (%s) for %s',
$i, $s->files->[$i]->{path}, $m;
return 1 if $s->files->[$i]->{mode} eq $m;
if (defined $s->files->[$i]->{fh}) {
AE::log trace => 'Closing %s', $s->files->[$i]->{fh};
flock $s->files->[$i]->{fh}, LOCK_UN;
close $s->files->[$i]->{fh};
$s->files->[$i]->{fh} = ();
}
if ($m eq 'r') {
AE::log trace => 'Opening %s to read', $s->files->[$i]->{path};
sysopen($s->files->[$i]->{fh}, $s->files->[$i]->{path}, O_RDONLY)
|| return;
flock($s->files->[$i]->{fh}, LOCK_SH) || return;
weaken $s unless isweak $s;
my $x = $i;
$s->files->[$x]->{timeout}
= AE::timer(500, 0, sub { $s // return; $s->_open($x, 'c') });
}
elsif ($m eq 'w') {
AE::log trace => 'Opening %s to write', $s->files->[$i]->{path};
my @split = File::Spec->splitdir($s->files->[$i]->{path});
pop @split; # File name itself
my $dir = File::Spec->catdir(@split);
File::Path::mkpath($dir) if !-d $dir;
sysopen($s->files->[$i]->{fh},
$s->files->[$i]->{path},
O_WRONLY | O_CREAT)
|| return;
flock $s->files->[$i]->{fh}, LOCK_EX;
truncate $s->files->[$i]->{fh}, $s->files->[$i]->{length}
if -s $s->files->[$i]->{fh}
!= $s->files->[$i]->{length}; # XXX - pre-allocate files
weaken $s unless isweak $s;
my $x = $i;
$s->files->[$x]->{timeout}
= AE::timer(60, 0, sub { $s // return; $s->_open($x, 'c') });
}
elsif ($m eq 'c') { $s->files->[$i]->{timeout} = () }
else {return}
return $s->files->[$i]->{mode} = $m;
}
has piece_cache => (is => 'ro', isa => HashRef, default => sub { {} });
sub _cache_path {
my $s = shift;
File::Spec->catfile($s->basedir,
(scalar @{$s->files} == 1 ? () : $s->name),
'~ABPartFile_-'
. uc(substr(unpack('H*', $s->infohash), 0, 10))
. '.dat'
);
}
sub _write_cache {
my ($s, $f, $o, $d) = @_;
my $path = $s->_cache_path;
AE::log
debug =>
'Attempting to store %d bytes to cache file (%s) [$f=%s, $o=%s]',
length($d), $path, $f, $o;
my @split = File::Spec->splitdir($path);
pop @split; # File name itself
my $dir = File::Spec->catdir(@split);
File::Path::mkpath($dir) if !-d $dir;
sysopen(my ($fh), $path, O_WRONLY | O_CREAT)
|| return;
flock $fh, LOCK_EX;
my $pos = sysseek $fh, 0, SEEK_CUR;
my $w = syswrite $fh, $d;
flock $fh, LOCK_UN;
close $fh;
$s->piece_cache->{$f}{$o} = $pos;
AE::log debug => 'Wrote %d bytes to cache file', $w;
return $w;
}
sub _read_cache {
my ($s, $f, $o, $l) = @_;
$s->piece_cache->{$f} // return;
$s->piece_cache->{$f}{$o} // return;
my $path = $s->_cache_path;
AE::log
debug =>
'Attempting to read %d bytes from cache file (%s) [$f=%s, $o=%s]',
$l, $path, $f, $o;
sysopen(my ($fh), $path, O_RDONLY)
|| return;
flock $fh, LOCK_SH;
sysseek $fh, $s->piece_cache->{$f}{$o}, SEEK_SET;
my $w = sysread $fh, my ($d), $l;
flock $fh, LOCK_UN;
close $fh;
return $d;
}
sub _read {
my ($s, $index, $offset, $length) = @_;
AE::log
debug =>
'Attempting to read %d bytes from piece %d starting at %d bytes',
$length, $index, $offset;
my $data = '';
my $file_index = 0;
my $total_offset = ($index * $s->piece_length) + $offset;
SEARCH:
while ($total_offset > $s->files->[$file_index]->{length}) {
$total_offset -= $s->files->[$file_index]->{length};
$file_index++;
AE::log
trace =>
'Searching for location... $total_offset = %d, $file_index = %d',
$total_offset, $file_index;
last SEARCH # XXX - return?
if not defined $s->files->[$file_index]->{length};
}
READ: while ((defined $length) && ($length > 0)) {
my $this_read
= (
($total_offset + $length) >= $s->files->[$file_index]->{length})
?
($s->files->[$file_index]->{length} - $total_offset)
: $length;
AE::log
trace =>
'Attempting to read %d bytes from file #%d (%s), starting at %d',
$this_read,
$file_index, $s->files->[$file_index]->{path}, $total_offset;
if ( (!-f $s->files->[$file_index]->{path})
|| (!$s->_open($file_index, 'r')))
{ $data .= $s->_read_cache($file_index, $total_offset, $this_read)
// ("\0" x $this_read);
AE::log note => 'Failed to open file. Using null chars instead.';
}
else {
sysseek $s->files->[$file_index]->{fh}, $total_offset, SEEK_SET;
sysread $s->files->[$file_index]->{fh}, my ($_data), $this_read;
$data .= $_data if $_data;
AE::log
trace =>
'Read %d bytes of data from file (%d bytes collected so far)',
length $_data, length $data;
weaken $s unless isweak $s;
my $x = $file_index;
$s->files->[$x]->{timeout}
= AE::timer(500, 0, sub { $s // return; $s->_open($x, 'c') });
}
$file_index++;
$length -= $this_read;
AE::log
trace => 'Still need to read %d bytes',
$length;
last READ if not defined $s->files->[$file_index];
$total_offset = 0;
}
AE::log trace => 'Returning %d bytes of data', length $data;
return $data;
}
sub _write {
my ($s, $index, $offset, $data) = @_;
AE::log
debug =>
'Attempting to write %d bytes from piece %d starting at %d bytes',
length($data), $index, $offset;
my $file_index = 0;
my $total_offset = int(($index * $s->piece_length) + ($offset || 0));
AE::log
debug => '...calculated offset == %d',
$total_offset;
SEARCH:
while ($total_offset > $s->files->[$file_index]->{length}) {
$total_offset -= $s->files->[$file_index]->{length};
$file_index++;
AE::log
trace =>
'Searching for location... $total_offset = %d, $file_index = %d',
$total_offset, $file_index;
last SEARCH # XXX - return?
if not defined $s->files->[$file_index]->{length};
}
WRITE: while ((defined $data) && (length $data > 0)) {
my $this_write
= (($total_offset + length $data)
>= $s->files->[$file_index]->{length})
?
($s->files->[$file_index]->{length} - $total_offset)
: length $data;
AE::log
trace =>
'Attempting to write %d bytes from file #%d (%s), starting at %d',
$this_write,
$file_index, $s->files->[$file_index]->{path}, $total_offset;
if ($s->files->[$file_index]->{priority} == 0) {
$s->_write_cache($file_index, $total_offset, substr $data, 0,
$this_write, '');
AE::log trace => 'Wrote data to cache...';
}
else {
$s->_open($file_index, 'w');
sysseek $s->files->[$file_index]->{fh}, $total_offset, SEEK_SET;
my $w = syswrite $s->files->[$file_index]->{fh}, substr $data, 0,
$this_write, '';
AE::log
trace => 'Wrote %d bytes of data to file (%d bytes left)',
$w, length $data;
weaken $s unless isweak $s;
my $x = $file_index;
$s->files->[$x]->{timeout}
= AE::timer(120, 0, sub { $s // return; $s->_open($x, 'c') });
}
$file_index++;
last WRITE if not defined $s->files->[$file_index];
$total_offset = 0;
}
return length $data;
}
sub hashcheck (;@) {
my $s = shift;
my @indexes = @_ ? @_ : (0 .. $s->piece_count);
AE::log trace => sub {
require Data::Dump;
'Hashcheck of : ' . Data::Dump::dump(\@indexes);
};
$s->bitfield; # Makes sure it's built
my $total_size = $s->size;
for my $index (@indexes) {
next if $index < 0 || $index > $s->piece_count;
lib/AnyEvent/BitTorrent.pm view on Meta::CPAN
peers6 => '',
announcer => undef,
ticker => AE::timer(
1,
15 * 60,
sub {
return if $s->state eq 'stopped';
$s->announce('started');
}
),
failures => 0
}
} defined $s->metadata->{announce} ? [$s->metadata->{announce}]
: (),
defined $s->metadata->{'announce-list'}
? @{$s->metadata->{'announce-list'}}
: ()
];
AE::log trace => sub {
require Data::Dump;
'$trackers before shuffle => ' . Data::Dump::dump($trackers);
};
$shuffle->($trackers);
$shuffle->($_->{urls}) for @$trackers;
AE::log trace => sub {
require Data::Dump;
'$trackers after shuffle => ' . Data::Dump::dump($trackers);
};
$trackers;
}
sub announce {
my ($s, $e) = @_;
return if $a++ > 10; # Retry attempts
for my $tier (@{$s->trackers}) {
$tier->{announcer} //= $s->_announce_tier($e, $tier);
}
}
sub _announce_tier {
my ($s, $e, $tier) = @_;
my @urls = grep {m[^https?://]} @{$tier->{urls}};
return if $tier->{failures} > 5;
return if $#{$tier->{urls}} < 0; # Empty tier?
return if $tier->{urls}[0] !~ m[^https?://.+];
local $AnyEvent::HTTP::USERAGENT
= 'AnyEvent::BitTorrent/' . $AnyEvent::BitTorrent::VERSION;
my $_url = $tier->{urls}[0] . '?info_hash=' . sub {
local $_ = shift;
s/([^A-Za-z0-9])/sprintf("%%%2.2X", ord($1))/ge;
$_;
}
->($s->infohash)
. ('&peer_id=' . $s->peerid)
. ('&uploaded=' . $s->uploaded)
. ('&downloaded=' . $s->downloaded)
. ('&left=' . $s->_left)
. ('&port=' . $s->port)
. '&compact=1'
. ($e ? '&event=' . $e : '');
AE::log debug => 'Announce URL: ' . $_url;
http_get $_url, sub {
my ($body, $hdr) = @_;
AE::log trace => sub {
require Data::Dump;
'Announce response: ' . Data::Dump::dump($body, $hdr);
};
$tier->{announcer} = ();
if ($hdr->{Status} =~ /^2/) {
my $reply = bdecode($body);
if (defined $reply->{'failure reason'}) { # XXX - Callback?
push @{$tier->{urls}}, shift @{$tier->{urls}};
$s->_announce_tier($e, $tier);
$tier->{'failure reason'} = $reply->{'failure reason'};
$tier->{failures}++;
}
else { # XXX - Callback?
$tier->{failures} = $tier->{'failure reason'} = 0;
$tier->{peers}
= compact_ipv4(
uncompact_ipv4($tier->{peers} . $reply->{peers}))
if $reply->{peers};
$tier->{peers6}
= compact_ipv6(
uncompact_ipv6($tier->{peers6} . $reply->{peers6}))
if $reply->{peers6};
$tier->{complete} = $reply->{complete};
$tier->{incomplete} = $reply->{incomplete};
$tier->{ticker} = AE::timer(
$reply->{interval} // (15 * 60),
$reply->{interval} // (15 * 60),
sub {
return if $s->state eq 'stopped';
$s->_announce_tier($e, $tier);
}
);
}
}
else { # XXX - Callback?
$tier->{'failure reason'}
= "HTTP Error: $hdr->{Status} $hdr->{Reason}\n";
$tier->{failures}++;
push @{$tier->{urls}}, shift @{$tier->{urls}};
$s->_announce_tier($e, $tier);
}
}
}
has _choke_timer => (
is => 'bare',
isa => Ref,
init_arg => undef,
required => 1,
default => sub {
my $s = shift;
AE::timer(
15, 45,
sub {
return if $s->state ne 'active';
AE::log trace => 'Choke timer...';
my @interested
= grep { $_->{remote_interested} && $_->{remote_choked} }
lib/AnyEvent/BitTorrent.pm view on Meta::CPAN
$msg // 'Connection timed out';
return if !$fatal;
},
on_connect => sub {
my ($h, $host, $port, $retry) = @_;
AE::log
trace => 'Connection established with %s:%d',
$host, $port;
$s->_add_peer($handle);
$s->_send_handshake($handle);
},
on_eof => sub {
my $h = shift;
AE::log trace => 'EOF from peer';
$s->_del_peer($h);
},
on_read => sub {
$s->_on_read(@_);
}
);
return $handle;
}
sub _on_read_incoming {
my ($s, $h) = @_;
$h->rbuf // return;
# XXX - Handle things if the stream is encrypted
my $packet = parse_packet(\$h->rbuf);
return if !$packet;
AE::log trace => sub {
require Data::Dump;
'Incoming packet: ' . Data::Dump::dump($packet);
};
if (defined $packet->{error}) {
return $s->_del_peer($h);
}
elsif ($packet->{type} == $HANDSHAKE) {
ref $packet->{payload} // return;
return if ref $packet->{payload} ne 'ARRAY';
$s->peers->{$h}{reserved} = $packet->{payload}[0];
return $s->_del_peer($h)
if $packet->{payload}[1] ne $s->infohash;
$s->peers->{$h}{peerid} = $packet->{payload}[2];
$s->_send_handshake($h);
$s->_send_bitfield($h);
$s->peers->{$h}{timeout}
= AE::timer(60, 0, sub { $s->_del_peer($h) });
$s->peers->{$h}{bitfield} = pack 'b*', (0 x $s->piece_count);
$h->on_read(sub { $s->_on_read(@_) });
}
else { # This should never happen
}
1;
}
sub _on_read {
my ($s, $h) = @_;
while (my $packet = parse_packet(\$h->rbuf)) {
last if !$packet;
AE::log debug => sub {
require Data::Dump;
'Incoming packet: ' . Data::Dump::dump($packet->{error});
};
if (defined $packet->{error}) {
$s->_del_peer($h);
return;
}
elsif ($packet->{type} eq $KEEPALIVE) {
# Do nothing!
}
elsif ($packet->{type} == $HANDSHAKE) {
ref $packet->{payload} // return;
$s->peers->{$h}{reserved} = $packet->{payload}[0];
return $s->_del_peer($h)
if $packet->{payload}[1] ne $s->infohash;
$s->peers->{$h}{peerid} = $packet->{payload}[2];
$s->_send_bitfield($h);
$s->peers->{$h}{timeout}
= AE::timer(60, 0, sub { $s->_del_peer($h) });
$s->peers->{$h}{bitfield} = pack 'b*', (0 x $s->piece_count);
}
elsif ($packet->{type} == $INTERESTED) {
$s->peers->{$h}{remote_interested} = 1;
}
elsif ($packet->{type} == $NOT_INTERESTED) {
$s->peers->{$h}{remote_interested} = 0;
# XXX - Clear any requests in queue
# XXX - Send choke just to be sure
}
elsif ($packet->{type} == $CHOKE) {
$s->peers->{$h}{local_choked} = 1;
if (!(vec($s->peers->{$h}{reserved}, 7, 1) & 0x04)) {
for my $req (@{$s->peers->{$h}{local_requests}}) {
$s->working_pieces->{$req->[0]}{$req->[1]}[3] = ()
unless
defined $s->working_pieces->{$req->[0]}{$req->[1]}[4];
}
}
$s->_consider_peer($s->peers->{$h});
}
elsif ($packet->{type} == $UNCHOKE) {
$s->peers->{$h}{local_choked} = 0;
$s->peers->{$h}{timeout}
= AE::timer(120, 0, sub { $s->_del_peer($h) });
$s->_request_pieces($s->peers->{$h});
}
elsif ($packet->{type} == $HAVE) {
vec($s->peers->{$h}{bitfield}, $packet->{payload}, 1) = 1;
$s->_consider_peer($s->peers->{$h});
$s->peers->{$h}{timeout}
= AE::timer(60, 0, sub { $s->_del_peer($h) });
}
elsif ($packet->{type} == $BITFIELD) {
$s->peers->{$h}{bitfield} = $packet->{payload};
$s->_consider_peer($s->peers->{$h});
}
elsif ($packet->{type} == $REQUEST) {
$s->peers->{$h}{timeout}
lib/AnyEvent/BitTorrent.pm view on Meta::CPAN
$s->working_pieces->{$index}{$offset} = [
$index, $offset,
$_block_size,
$p, undef,
AE::timer(
60, 0,
sub {
$p // return;
$p->{handle} // return;
$s->_send_encrypted($p->{handle},
build_cancel($index, $offset, $_block_size));
$s->working_pieces->{$index}{$offset}[3] = ();
$p->{local_requests} = [
grep {
$_->[0] != $index
|| $_->[1] != $offset
|| $_->[2] != $_block_size
} @{$p->{local_requests}}
];
$p->{timeout} = AE::timer(45, 0,
sub { $s->_del_peer($p->{handle}) });
#$s->_request_pieces( $p) # XXX - Ask a different peer
}
)
];
weaken($s->working_pieces->{$index}{$offset}[3])
unless isweak($s->working_pieces->{$index}{$offset}[3]);
push @{$p->{local_requests}}, [$index, $offset, $_block_size];
}
}
# Cheap callback system
has on_hash_pass => (
is => 'rw',
isa => CodeRef,
default => sub {
sub { !!1 }
},
clearer => '_no_hash_pass'
);
sub _trigger_hash_pass { shift->on_hash_pass()->(@_) }
has on_hash_fail => (
is => 'rw',
isa => CodeRef,
default => sub {
sub { !!1 }
},
clearer => '_no_hash_fail'
);
sub _trigger_hash_fail { shift->on_hash_fail()->(@_) }
#
has state => (is => 'ro',
isa => Enum [qw[active stopped paused]],
writer => '_set_state',
default => sub {'active'}
);
sub stop {
my $s = shift;
AE::log debug => 'Stopping...';
return if $s->state eq 'stopped';
AE::log trace => 'Announcing "stopped" event to trackers...';
$s->announce('stopped');
AE::log trace => 'Disconnecting peers...';
$s->_clear_peers;
AE::log trace => 'Stopping new peers ticker...';
$s->_clear_peer_timer;
AE::log trace => 'Closing files...';
$s->_open($_, 'c') for 0 .. $#{$s->files};
AE::log trace => 'Setting internal status...';
$s->_set_state('stopped');
}
sub start {
my $s = shift;
AE::log debug => 'Starting...';
$s->announce('started') unless $s->state eq 'active';
$s->peers;
AE::log trace => 'Starting new peers ticker...';
$s->_peer_timer;
AE::log trace => 'Setting internal status...';
$s->_set_state('active');
}
sub pause {
my $s = shift;
AE::log debug => 'Pausing...';
$s->peers;
AE::log trace => 'Starting new peers ticker...';
$s->_peer_timer;
AE::log trace => 'Setting internal status...';
$s->_set_state('paused');
}
#
sub BUILD {
my ($s, $a) = @_;
AE::log debug => 'BUILD()';
$s->start && AE::log debug => 'Calling ->start()'
if $s->state eq 'active';
$s->paused && AE::log debug => 'Calling ->paused() '
if $s->state eq 'paused';
}
# Testing stuff goes here
sub _send_encrypted {
my ($s, $h, $packet) = @_;
return if !$h; # XXX - $s->_del_peer($p->{handle})
AE::log trace => sub {
require Data::Dump;
'Outgoing packet: ' . Data::Dump::dump($packet);
};
return $h->push_write($packet);
}
sub _send_handshake {
my ($s, $h) = @_;
AE::log trace => 'Outgoing handshake';
$h->push_write(build_handshake($s->reserved, $s->infohash, $s->peerid));
}
1337;
=pod
=head1 NAME
AnyEvent::BitTorrent - Yet Another BitTorrent Client Module
=head1 Synopsis
use AnyEvent::BitTorrent;
my $client = AnyEvent::BitTorrent->new( path => 'some.torrent' );
AE::cv->recv;
=head1 Description
This is a painfully simple BitTorrent client written on a whim that implements
the absolute basics. For a full list of what's currently supported, what you
will likely find in a future version, and what you'll never get from this, see
the section entitled "L<This Module is Lame!|/"This Module is Lame!">"
=head1 Methods
The API, much like the module itself, is simple.
Anything you find by skimming the source is likely not ready for public use
and will be subject to change before C<v1.0.0>. Here's the public interface as
of this version:
=head2 C<new( ... )>
my $c = AnyEvent::BitTorrent->new(
path => 'some/legal.torrent',
basedir => './storage/',
port => 6881,
on_hash_pass => sub { ... },
on_hash_fail => sub { ... },
state => 'stopped',
piece_cache => $quick_restore
);
( run in 4.652 seconds using v1.01-cache-2.11-cpan-97f6503c9c8 )