BitTorrent-Simple
view release on metacpan or search on metacpan
script/torrent view on Meta::CPAN
? ($global_offset + $written) : $fstart;
my $write_end = ($global_offset + $data_len < $fend)
? ($global_offset + $data_len) : $fend;
my $file_off = $write_start - $fstart;
my $data_off = $write_start - $global_offset;
my $wlen = $write_end - $write_start;
my $fh = $file_handles->[$i];
sysseek($fh, $file_off, 0);
syswrite($fh, $piece_data, $wlen, $data_off);
$written = $write_end - $global_offset;
}
}
# âââ Formatting âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
sub format_size {
my ($b) = @_;
return sprintf("%.2f GB", $b / 1073741824) if $b >= 1073741824;
return sprintf("%.2f MB", $b / 1048576) if $b >= 1048576;
return sprintf("%.2f KB", $b / 1024) if $b >= 1024;
return "$b B";
}
# âââ Thread-Safe Shared State ââââââââââââââââââââââââââââââââââââââââââââââââ
my @g_have :shared; # 1 if piece is verified and written
my @g_pending :shared; # 1 if piece is being downloaded by a thread
my $g_done :shared = 0;
my $g_dl :shared = 0; # bytes downloaded
my $g_lock :shared; # mutex for piece selection
# âââ Peer Worker (runs in its own thread) ââââââââââââââââââââââââââââââââââââ
sub peer_worker {
my ($ip, $port, $info_hash, $peer_id, $piece_length, $total_size,
$piece_hashes, $num_pieces, $files, $output_dir, $start_time) = @_;
my $tag = "$ip:$port";
# Early exit if download is already complete
{ lock($g_lock); return if $g_done >= $num_pieces; }
# Each thread opens its own file handles for writing
my @fhs;
for my $file (@$files) {
my $path = "$output_dir/$file->{path}";
open my $fh, '+<:raw', $path or do {
warn " [$tag] Cannot open $path: $!\n";
return;
};
push @fhs, $fh;
}
# Connect
my $socket = IO::Socket::INET->new(
PeerAddr => $ip,
PeerPort => $port,
Proto => 'tcp',
Timeout => CONNECT_TIMEOUT,
);
unless ($socket) {
print " [$tag] connect failed\n";
close $_ for @fhs;
return;
}
binmode($socket);
# Handshake
my $rpid = do_handshake($socket, $info_hash, $peer_id);
unless ($rpid) {
print " [$tag] handshake failed\n";
close $socket;
close $_ for @fhs;
return;
}
print " [$tag] connected\n";
# Per-peer state
my $peer_choking = 1;
my $peer_bf = "\0" x ceil($num_pieces / 8);
# Send interested
send_msg($socket, MSG_INTERESTED, '');
# Wait for unchoke (up to 50 messages)
eval {
for (1 .. 50) {
last unless $peer_choking;
my $msg = read_msg($socket);
last unless defined $msg;
my ($id, $payload) = @$msg;
next unless defined $id;
if ($id == MSG_UNCHOKE) {
$peer_choking = 0;
} elsif ($id == MSG_BITFIELD) {
$peer_bf = $payload;
} elsif ($id == MSG_HAVE) {
if (length($payload) >= 4) {
my $idx = unpack('N', $payload);
bf_set(\$peer_bf, $idx);
}
}
}
};
if ($peer_choking) {
print " [$tag] never unchoked\n";
close $socket;
close $_ for @fhs;
return;
}
# Download loop
eval {
while (1) {
# Check if all pieces are done
{ lock($g_lock); last if $g_done >= $num_pieces; }
( run in 2.312 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )