| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
1
|
|
|
1
|
|
234268
|
use v5.40; |
|
|
1
|
|
|
|
|
5
|
|
|
2
|
1
|
|
|
1
|
|
9
|
use feature 'class'; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
183
|
|
|
3
|
1
|
|
|
1
|
|
9
|
no warnings 'experimental::class'; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
53
|
|
|
4
|
1
|
|
|
1
|
|
677
|
use Net::BitTorrent::Emitter; |
|
|
1
|
|
|
|
|
4
|
|
|
|
1
|
|
|
|
|
108
|
|
|
5
|
|
|
|
|
|
|
class Net::BitTorrent::Torrent::Generator v2.0.0 : isa(Net::BitTorrent::Emitter) { |
|
6
|
1
|
|
|
1
|
|
704
|
use Net::BitTorrent::Protocol::BEP03::Bencode qw[bencode]; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
101
|
|
|
7
|
1
|
|
|
1
|
|
658
|
use Digest::SHA qw[sha1 sha256]; |
|
|
1
|
|
|
|
|
4179
|
|
|
|
1
|
|
|
|
|
145
|
|
|
8
|
1
|
|
|
1
|
|
1009
|
use Path::Tiny; |
|
|
1
|
|
|
|
|
18967
|
|
|
|
1
|
|
|
|
|
2178
|
|
|
9
|
|
|
|
|
|
|
field $base_path : param; |
|
10
|
|
|
|
|
|
|
field $piece_length : param = 262144; # 256KiB |
|
11
|
|
|
|
|
|
|
field @files; |
|
12
|
|
|
|
|
|
|
field @trackers; |
|
13
|
|
|
|
|
|
|
field @nodes; |
|
14
|
|
|
|
|
|
|
field $private = 0; |
|
15
|
|
|
|
|
|
|
field $align_files = 0; |
|
16
|
|
|
|
|
|
|
method set_align_files ($val) { $align_files = $val } |
|
17
|
|
|
|
|
|
|
method set_private ($val) { $private = $val } |
|
18
|
|
|
|
|
|
|
method add_tracker ($url) { push @trackers, $url } |
|
19
|
|
|
|
|
|
|
method add_node ( $h, $p ) { push @nodes, [ $h, $p ] } |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
method add_file ($rel_path) { |
|
22
|
|
|
|
|
|
|
my $abs = path($base_path)->child($rel_path); |
|
23
|
|
|
|
|
|
|
if ( !$abs->exists ) { |
|
24
|
|
|
|
|
|
|
$self->_emit( log => "File does not exist: $abs", level => 'fatal' ); |
|
25
|
|
|
|
|
|
|
return; |
|
26
|
|
|
|
|
|
|
} |
|
27
|
|
|
|
|
|
|
if ( $align_files && @files && $files[-1]{size} % $piece_length != 0 ) { |
|
28
|
|
|
|
|
|
|
my $pad = $piece_length - ( $files[-1]{size} % $piece_length ); |
|
29
|
|
|
|
|
|
|
push @files, { rel => ".pad/$pad", size => $pad, padding => 1 }; |
|
30
|
|
|
|
|
|
|
} |
|
31
|
|
|
|
|
|
|
push @files, { rel => $rel_path, abs => $abs, size => $abs->stat->size }; |
|
32
|
|
|
|
|
|
|
} |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
method generate_v1 () { |
|
35
|
|
|
|
|
|
|
my $info = $self->_base_info(); |
|
36
|
|
|
|
|
|
|
$info->{pieces} = $self->_generate_pieces_v1(); |
|
37
|
|
|
|
|
|
|
return $self->_wrap_torrent($info); |
|
38
|
|
|
|
|
|
|
} |
|
39
|
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
method generate_v2 () { |
|
41
|
|
|
|
|
|
|
my ( $file_tree, $piece_layers ) = $self->_generate_v2_data(); |
|
42
|
|
|
|
|
|
|
my $info = { |
|
43
|
|
|
|
|
|
|
name => path($base_path)->basename, |
|
44
|
|
|
|
|
|
|
'piece length' => $piece_length, |
|
45
|
|
|
|
|
|
|
'file tree' => $file_tree, |
|
46
|
|
|
|
|
|
|
'meta version' => 2, |
|
47
|
|
|
|
|
|
|
private => $private, |
|
48
|
|
|
|
|
|
|
}; |
|
49
|
|
|
|
|
|
|
return $self->_wrap_torrent( $info, $piece_layers ); |
|
50
|
|
|
|
|
|
|
} |
|
51
|
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
method generate_hybrid () { |
|
53
|
|
|
|
|
|
|
my ( $file_tree, $piece_layers ) = $self->_generate_v2_data(); |
|
54
|
|
|
|
|
|
|
my $info = $self->_base_info(); |
|
55
|
|
|
|
|
|
|
$info->{'file tree'} = $file_tree; |
|
56
|
|
|
|
|
|
|
$info->{'meta version'} = 2; |
|
57
|
|
|
|
|
|
|
$info->{pieces} = $self->_generate_pieces_v1(); |
|
58
|
|
|
|
|
|
|
return $self->_wrap_torrent( $info, $piece_layers ); |
|
59
|
|
|
|
|
|
|
} |
|
60
|
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
method _base_info () { |
|
62
|
|
|
|
|
|
|
my $info = { name => path($base_path)->basename, 'piece length' => $piece_length, private => $private, }; |
|
63
|
|
|
|
|
|
|
if ( @files == 1 && !$files[0]{padding} ) { |
|
64
|
|
|
|
|
|
|
$info->{length} = $files[0]{size}; |
|
65
|
|
|
|
|
|
|
} |
|
66
|
|
|
|
|
|
|
else { |
|
67
|
|
|
|
|
|
|
$info->{files} = [ map { { length => $_->{size}, path => [ split m{/}, $_->{rel} ] } } @files ]; |
|
68
|
|
|
|
|
|
|
} |
|
69
|
|
|
|
|
|
|
return $info; |
|
70
|
|
|
|
|
|
|
} |
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
method _wrap_torrent ( $info, $piece_layers = undef ) { |
|
73
|
|
|
|
|
|
|
my $torrent = { info => $info, 'created by' => 'Net::BitTorrent 2.0.0', 'creation date' => time(), }; |
|
74
|
|
|
|
|
|
|
$torrent->{'piece layers'} = $piece_layers if $piece_layers; |
|
75
|
|
|
|
|
|
|
$torrent->{announce} = $trackers[0] if @trackers; |
|
76
|
|
|
|
|
|
|
$torrent->{'announce-list'} = [ map { [$_] } @trackers ] if @trackers > 1; |
|
77
|
|
|
|
|
|
|
$torrent->{nodes} = \@nodes if @nodes; |
|
78
|
|
|
|
|
|
|
return bencode($torrent); |
|
79
|
|
|
|
|
|
|
} |
|
80
|
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
method _generate_pieces_v1 () { |
|
82
|
|
|
|
|
|
|
my $pieces = ''; |
|
83
|
|
|
|
|
|
|
my $buffer = ''; |
|
84
|
|
|
|
|
|
|
for my $file (@files) { |
|
85
|
|
|
|
|
|
|
if ( $file->{padding} ) { |
|
86
|
|
|
|
|
|
|
$buffer .= "\0" x $file->{size}; |
|
87
|
|
|
|
|
|
|
while ( length($buffer) >= $piece_length ) { |
|
88
|
|
|
|
|
|
|
$pieces .= sha1( substr( $buffer, 0, $piece_length, '' ) ); |
|
89
|
|
|
|
|
|
|
} |
|
90
|
|
|
|
|
|
|
next; |
|
91
|
|
|
|
|
|
|
} |
|
92
|
|
|
|
|
|
|
my $fh = $file->{abs}->openr_raw; |
|
93
|
|
|
|
|
|
|
while ( read( $fh, my $chunk, $piece_length - length($buffer) ) ) { |
|
94
|
|
|
|
|
|
|
$buffer .= $chunk; |
|
95
|
|
|
|
|
|
|
if ( length($buffer) == $piece_length ) { |
|
96
|
|
|
|
|
|
|
$pieces .= sha1($buffer); |
|
97
|
|
|
|
|
|
|
$buffer = ''; |
|
98
|
|
|
|
|
|
|
} |
|
99
|
|
|
|
|
|
|
} |
|
100
|
|
|
|
|
|
|
} |
|
101
|
|
|
|
|
|
|
$pieces .= sha1($buffer) if length($buffer) > 0; |
|
102
|
|
|
|
|
|
|
return $pieces; |
|
103
|
|
|
|
|
|
|
} |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
method _generate_v2_data () { |
|
106
|
1
|
|
|
1
|
|
657
|
use Digest::Merkle::SHA256; |
|
|
1
|
|
|
|
|
2977
|
|
|
|
1
|
|
|
|
|
585
|
|
|
107
|
|
|
|
|
|
|
my $file_tree = {}; |
|
108
|
|
|
|
|
|
|
my %piece_layers; |
|
109
|
|
|
|
|
|
|
for my $file ( grep { !$_->{padding} } @files ) { |
|
110
|
|
|
|
|
|
|
my $merkle = Digest::Merkle::SHA256->new( file_size => $file->{size} ); |
|
111
|
|
|
|
|
|
|
my $fh = $file->{abs}->openr_raw; |
|
112
|
|
|
|
|
|
|
my $block_idx = 0; |
|
113
|
|
|
|
|
|
|
while ( read( $fh, my $block, 16384 ) ) { |
|
114
|
|
|
|
|
|
|
$merkle->set_block( $block_idx++, sha256($block) ); |
|
115
|
|
|
|
|
|
|
} |
|
116
|
|
|
|
|
|
|
my @path = split m{/}, $file->{rel}; |
|
117
|
|
|
|
|
|
|
my $curr = $file_tree; |
|
118
|
|
|
|
|
|
|
my $name = pop @path; |
|
119
|
|
|
|
|
|
|
$curr = ( $curr->{$_} //= {} ) for @path; |
|
120
|
|
|
|
|
|
|
$curr->{$name} = { '' => { length => $file->{size}, 'pieces root' => $merkle->root } }; |
|
121
|
|
|
|
|
|
|
if ( $file->{size} > $piece_length ) { |
|
122
|
|
|
|
|
|
|
$piece_layers{ $merkle->root } = $merkle->get_piece_layer($piece_length); |
|
123
|
|
|
|
|
|
|
} |
|
124
|
|
|
|
|
|
|
} |
|
125
|
|
|
|
|
|
|
return ( $file_tree, \%piece_layers ); |
|
126
|
|
|
|
|
|
|
} |
|
127
|
|
|
|
|
|
|
} 1; |