torrenter 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/torrenter +5 -2
- data/lib/torrenter.rb +17 -42
- data/lib/torrenter/{message/message_types.rb → constants.rb} +1 -0
- data/lib/torrenter/peer.rb +18 -27
- data/lib/torrenter/peer/buffer_state.rb +164 -0
- data/lib/torrenter/reactor.rb +19 -168
- data/lib/torrenter/torrent_reader.rb +48 -54
- data/lib/torrenter/torrent_reader/piece.rb +125 -0
- data/lib/torrenter/torrent_reader/piece_constructor.rb +81 -0
- data/lib/torrenter/torrent_reader/piece_index.rb +87 -0
- data/lib/torrenter/tracker.rb +13 -9
- data/lib/torrenter/tracker/http_tracker.rb +41 -0
- data/lib/torrenter/tracker/udp_tracker.rb +107 -0
- data/lib/torrenter/version.rb +1 -1
- metadata +9 -11
- data/lib/torrenter/file_creator.rb +0 -0
- data/lib/torrenter/http_tracker.rb +0 -44
- data/lib/torrenter/message/messager.rb +0 -164
- data/lib/torrenter/peer_state.rb +0 -57
- data/lib/torrenter/setup.rb +0 -10
- data/lib/torrenter/test_msg.rb +0 -166
- data/lib/torrenter/test_reactor.rb +0 -219
- data/lib/torrenter/udp_tracker.rb +0 -96
data/lib/torrenter/setup.rb
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
require 'bencode'
|
2
|
-
require 'pry'
|
3
|
-
require 'digest/sha1'
|
4
|
-
require './lib/torrenter/torrent_reader.rb'
|
5
|
-
require './lib/torrenter/udp_tracker.rb'
|
6
|
-
require './lib/torrenter/http_tracker.rb'
|
7
|
-
|
8
|
-
stream = BEncode.load_file('thrones.torrent')
|
9
|
-
reader = TorrentReader.new(stream)
|
10
|
-
binding.pry
|
data/lib/torrenter/test_msg.rb
DELETED
@@ -1,166 +0,0 @@
|
|
1
|
-
module Torrenter
|
2
|
-
# KEEP_ALIVE = "\x00\x00\x00\x00"
|
3
|
-
# BLOCK = 2**14
|
4
|
-
# these methods get mixed in with the Peer class as a way to help
|
5
|
-
# organize and parse the byte-encoded data. The intention is to shorten
|
6
|
-
# and shrink the complexity of the Peer class.
|
7
|
-
# the following methods are responsible solely for data retrieval and data transmission
|
8
|
-
|
9
|
-
def state(master_index)
|
10
|
-
recv_data if @buffer.size <= 3
|
11
|
-
if @buffer[4].nil?
|
12
|
-
@buffer.slice!(0..3) if @buffer[0..3] == KEEP_ALIVE
|
13
|
-
else
|
14
|
-
@length = @buffer[0..3].unpack("N*").last
|
15
|
-
@buffer_id = @buffer[4]
|
16
|
-
case @buffer_id
|
17
|
-
when BITFIELD
|
18
|
-
process_bitfield(master_index.size, clear_msg)
|
19
|
-
when HAVE
|
20
|
-
process_have(clear_msg)
|
21
|
-
when INTERESTED
|
22
|
-
@buffer = ''
|
23
|
-
|
24
|
-
|
25
|
-
return :interested
|
26
|
-
when PIECE
|
27
|
-
process_piece
|
28
|
-
when CHOKE
|
29
|
-
choke_message
|
30
|
-
when HANDSHAKE
|
31
|
-
process_handshake
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def choke_message
|
37
|
-
@peer_state = false
|
38
|
-
end
|
39
|
-
|
40
|
-
def clear_msg
|
41
|
-
@buffer.slice!(0..@length + 3)
|
42
|
-
end
|
43
|
-
|
44
|
-
def request_message(bytes=BLOCK)
|
45
|
-
@msg = "index: #{@index} offset: #{@blocks.size * BLOCK} bytes: #{bytes}"
|
46
|
-
send_data(pack(13) + "\x06" + pack(@index) + pack(@blocks.size * BLOCK) + pack(bytes))
|
47
|
-
end
|
48
|
-
|
49
|
-
def pack(msg)
|
50
|
-
[msg].pack("I>")
|
51
|
-
end
|
52
|
-
|
53
|
-
def process_bitfield(size, msg)
|
54
|
-
index = msg[5..-1].unpack("B#{size}").join.split('')
|
55
|
-
@piece_index = index.map { |bit| bit == '1' ? :free : :unavailable }
|
56
|
-
send_interested if @buffer.empty?
|
57
|
-
end
|
58
|
-
|
59
|
-
def process_have(msg)
|
60
|
-
@piece_index[msg[5..-1].unpack("C*").last] = :free
|
61
|
-
send_interested if @buffer.empty?
|
62
|
-
end
|
63
|
-
|
64
|
-
def request_piece(index)
|
65
|
-
@index = index
|
66
|
-
@piece_index[@index] = :downloading
|
67
|
-
|
68
|
-
request_message
|
69
|
-
end
|
70
|
-
|
71
|
-
def send_interested
|
72
|
-
send_data("\x00\x00\x00\x01\x02")
|
73
|
-
end
|
74
|
-
|
75
|
-
def buffer_length?
|
76
|
-
@buffer.bytesize >= @length + 4
|
77
|
-
end
|
78
|
-
|
79
|
-
def process_piece
|
80
|
-
if buffer_length?
|
81
|
-
if buffer_complete?
|
82
|
-
pack_buffer
|
83
|
-
end
|
84
|
-
|
85
|
-
diff = @remaining - @blocks.join('').bytesize
|
86
|
-
if diff < BLOCK
|
87
|
-
request_message(diff)
|
88
|
-
else
|
89
|
-
request_message
|
90
|
-
end
|
91
|
-
end
|
92
|
-
recv_data
|
93
|
-
end
|
94
|
-
|
95
|
-
def mark_complete
|
96
|
-
@blocks = []
|
97
|
-
@piece_index[@index] = :downloaded
|
98
|
-
end
|
99
|
-
|
100
|
-
def last_piece?
|
101
|
-
@remaining
|
102
|
-
end
|
103
|
-
|
104
|
-
def piece_complete?
|
105
|
-
piece_size == @piece_length
|
106
|
-
end
|
107
|
-
|
108
|
-
def buffer_size
|
109
|
-
@buffer[13..-1].bytesize
|
110
|
-
end
|
111
|
-
|
112
|
-
def pack_buffer
|
113
|
-
@blocks << @buffer.slice!(13..-1)
|
114
|
-
@buffer.clear
|
115
|
-
end
|
116
|
-
|
117
|
-
def buffer_complete?
|
118
|
-
(@buffer.length - 13) == BLOCK || (@buffer.length - 13) == @remaining
|
119
|
-
end
|
120
|
-
|
121
|
-
# will need a last piece sort of thing inserted in here
|
122
|
-
|
123
|
-
def piece_full?
|
124
|
-
@blocks.join('').size == @piece_len
|
125
|
-
end
|
126
|
-
|
127
|
-
def piece_size
|
128
|
-
@blocks.join('').bytesize + (@buffer.bytesize - 13)
|
129
|
-
end
|
130
|
-
|
131
|
-
def process_handshake
|
132
|
-
@buffer.slice!(0..67) if hash_check?
|
133
|
-
end
|
134
|
-
|
135
|
-
def hash_check?
|
136
|
-
@buffer[28..47] == @info_hash
|
137
|
-
end
|
138
|
-
|
139
|
-
def send_data(msg)
|
140
|
-
begin
|
141
|
-
Timeout::timeout(2) { @socket.sendmsg_nonblock(msg) }
|
142
|
-
rescue Timeout::Error
|
143
|
-
''
|
144
|
-
rescue IO::EAGAINWaitReadable
|
145
|
-
''
|
146
|
-
rescue *EXCEPTIONS
|
147
|
-
''
|
148
|
-
rescue Errno::ETIMEDOUT
|
149
|
-
@peer_state = false
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def recv_data(bytes=BLOCK)
|
154
|
-
begin
|
155
|
-
Timeout::timeout(5) { @buffer << @socket.recv_nonblock(bytes) }
|
156
|
-
rescue Timeout::Error
|
157
|
-
''
|
158
|
-
rescue Errno::ETIMEDOUT
|
159
|
-
@peer_state = false
|
160
|
-
rescue *EXCEPTIONS
|
161
|
-
''
|
162
|
-
rescue IO::EAGAINWaitReadable
|
163
|
-
''
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
@@ -1,219 +0,0 @@
|
|
1
|
-
module Torrenter
|
2
|
-
class Reactor < Torrenter::TorrentReader
|
3
|
-
|
4
|
-
attr_accessor :master_index
|
5
|
-
def initialize(peers, stream)
|
6
|
-
super(stream)
|
7
|
-
@peers = peers
|
8
|
-
@master_index = Array.new(sha_hashes.bytesize / 20) { :free }
|
9
|
-
@time_counter = 0
|
10
|
-
@time = Time.now.to_i
|
11
|
-
@byte_counter = 0
|
12
|
-
end
|
13
|
-
|
14
|
-
def check_for_existing_data
|
15
|
-
# if the torrent data file is not present, one will be created
|
16
|
-
IO.write($data_dump, '', 0) unless File.file?($data_dump)
|
17
|
-
|
18
|
-
0.upto(@master_index.size - 1) do |n|
|
19
|
-
# this needs to get changed in order to account for
|
20
|
-
# the last piece not being equal to the piece_length
|
21
|
-
data = IO.read($data_dump, piece_length, n * piece_length) || ''
|
22
|
-
# binding.pry
|
23
|
-
if verified?(n, data)
|
24
|
-
@master_index[n] = :downloaded
|
25
|
-
@byte_counter += data.bytesize
|
26
|
-
end
|
27
|
-
end
|
28
|
-
puts "#{@master_index.count(:downloaded)} pieces downloaded"
|
29
|
-
end
|
30
|
-
|
31
|
-
def verified?(loc, piece)
|
32
|
-
Digest::SHA1.digest(piece) == sha_hashes[loc * 20..(loc * 20) + 19]
|
33
|
-
end
|
34
|
-
|
35
|
-
def finished?
|
36
|
-
@master_index.all? { |index| index == :downloaded }
|
37
|
-
end
|
38
|
-
|
39
|
-
def connect_peers
|
40
|
-
@peers.each { |peer| peer.connect if active_peers.size < 8 }
|
41
|
-
end
|
42
|
-
|
43
|
-
# sends the handshake messages.
|
44
|
-
|
45
|
-
def message_reactor
|
46
|
-
@peers.each { |peer| peer.connect if active_peers.length < 8 }
|
47
|
-
if !finished?
|
48
|
-
peer.state(@master_index) do |loc, buf|
|
49
|
-
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def piece_indices
|
55
|
-
active_peers.select { |peer| peer.piece_index }
|
56
|
-
.map { |peer| peer.piece_index }
|
57
|
-
end
|
58
|
-
|
59
|
-
def tally
|
60
|
-
@master_index.map.with_index do |p,i|
|
61
|
-
p == :free ? piece_indices.map { |x| x[i] } : []
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def piece_select(peer, limit=1)
|
66
|
-
binding.pry if @master_index.count(:free) == 1
|
67
|
-
puts "#{peer.ip} picking a piece"
|
68
|
-
piece_index = peer.piece_index
|
69
|
-
@tally = tally
|
70
|
-
index = nil
|
71
|
-
|
72
|
-
loop do
|
73
|
-
if limit == active_peers.size + 1
|
74
|
-
index = piece_index.index(:free)
|
75
|
-
break
|
76
|
-
elsif index.is_a?(Integer)
|
77
|
-
break
|
78
|
-
end
|
79
|
-
i = 0
|
80
|
-
loop do
|
81
|
-
break if i > piece_index.size - 1|| !index.nil?
|
82
|
-
if @tally[i].count(:free) == limit && piece_index[i] == :free
|
83
|
-
index = i
|
84
|
-
else
|
85
|
-
i += 1
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
limit += 1 if index.nil?
|
90
|
-
end
|
91
|
-
|
92
|
-
if index
|
93
|
-
@master_index[index] = :downloading
|
94
|
-
peer.request_piece(index)
|
95
|
-
p index
|
96
|
-
return index
|
97
|
-
else
|
98
|
-
binding.pry
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def show_status
|
103
|
-
if (Time.now.to_i - @time) == 1
|
104
|
-
system('clear')
|
105
|
-
puts "#{download_bar} \n Downloading #{$data_dump[/(.+)(?=torrent-data)/]} at #{real} KB/sec with #{pieces_left} pieces left to download"
|
106
|
-
puts "and #{data_remaining} MB remaining ------ #{active_peers.size} connected."
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def remaining
|
111
|
-
total_file_size - @byte_counter
|
112
|
-
end
|
113
|
-
|
114
|
-
def select_index(index)
|
115
|
-
poo = @master_index.map.with_index do |peer,index|
|
116
|
-
active_peers.map { |x| x.piece_index[index] }
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
def peer_indices
|
121
|
-
active_peers.map { |peer| peer.piece_index }
|
122
|
-
end
|
123
|
-
|
124
|
-
def data_remaining
|
125
|
-
(total_file_size - @byte_counter).fdiv(1024).fdiv(1024).round(2)
|
126
|
-
end
|
127
|
-
|
128
|
-
def pieces_left
|
129
|
-
@master_index.count(:free)
|
130
|
-
end
|
131
|
-
|
132
|
-
def real
|
133
|
-
@time_counter.fdiv(1024).round(1)
|
134
|
-
end
|
135
|
-
|
136
|
-
def reattempt_disconnected_peer
|
137
|
-
disconnected_peers.sample.connect
|
138
|
-
end
|
139
|
-
|
140
|
-
def disconnected_peers
|
141
|
-
@peers.select { |peer| !peer.connected? }
|
142
|
-
end
|
143
|
-
|
144
|
-
def pack_piece(peer)
|
145
|
-
IO.write($data_dump, peer.piece_data, piece_length * peer.index)
|
146
|
-
end
|
147
|
-
|
148
|
-
def indices
|
149
|
-
@master_index.map do |piece|
|
150
|
-
if piece == :free
|
151
|
-
0
|
152
|
-
elsif piece == :downloaded
|
153
|
-
1
|
154
|
-
else
|
155
|
-
get_status @master_index.index(piece)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
def get_status(i)
|
161
|
-
peer = @peers.find { |peer| peer.piece_index[i] == :downloading }
|
162
|
-
(peer.buffer.bytesize + peer.piece_size).fdiv(@piece_length)
|
163
|
-
end
|
164
|
-
|
165
|
-
def active_peers
|
166
|
-
@peers.select { |peer| peer.peer_state }
|
167
|
-
end
|
168
|
-
|
169
|
-
def index_percentages
|
170
|
-
active_peers.map do |peer|
|
171
|
-
size = peer.buffer.bytesize + peer.piece_size
|
172
|
-
[peer.index, (size.fdiv @piece_length) * 100]
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
def download_bar
|
177
|
-
("\u2588" * pieces(:downloaded)) + ("\u2593" * pieces(:downloading)) + (" " * pieces(:free)) + " %#{pieces(:downloaded)} downloaded "
|
178
|
-
end
|
179
|
-
|
180
|
-
def pieces(type)
|
181
|
-
(@master_index.count(type).fdiv(@master_index.size) * 100).round
|
182
|
-
end
|
183
|
-
|
184
|
-
def seperate_data_dump_into_files
|
185
|
-
folder = $data_dump[/.+(?=\.torrent-data)/]
|
186
|
-
|
187
|
-
if multiple_files?
|
188
|
-
offset = 0
|
189
|
-
|
190
|
-
file_list.each do |file|
|
191
|
-
|
192
|
-
length = file['length']
|
193
|
-
filename = file['path'].pop
|
194
|
-
|
195
|
-
if multiple_sub_folders?(file)
|
196
|
-
subfolders = file['path'].join("/")
|
197
|
-
folder = folder + "/" + subfolders
|
198
|
-
FileUtils.mkdir_p("#{folder}", force: true)
|
199
|
-
end
|
200
|
-
|
201
|
-
File.open("#{folder}/#{filename}", 'a+') { |data| data << IO.read($data_dump, length, offset) }
|
202
|
-
offset += length
|
203
|
-
end
|
204
|
-
else
|
205
|
-
FileUtils.mkdir("#{folder}")
|
206
|
-
File.open("#{folder}/#{file_list['name']}", 'w') { |data| data << File.read($data_dump) }
|
207
|
-
end
|
208
|
-
File.delete($data_dump)
|
209
|
-
end
|
210
|
-
|
211
|
-
def multiple_sub_folders?(file)
|
212
|
-
file['path'].size > 1
|
213
|
-
end
|
214
|
-
|
215
|
-
def multiple_files?
|
216
|
-
file_list.is_a?(Array)
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
@@ -1,96 +0,0 @@
|
|
1
|
-
module Torrenter
|
2
|
-
class UDPTracker < TorrentReader
|
3
|
-
|
4
|
-
attr_reader :socket, :response
|
5
|
-
|
6
|
-
def initialize(tracker, stream)
|
7
|
-
super(stream)
|
8
|
-
@url = tracker[/(?<=udp\:\/\/).+(?=\:\d+)/]
|
9
|
-
@port = tracker[/\d+$/].to_i
|
10
|
-
@socket = UDPSocket.new
|
11
|
-
@connection_id = [0x41727101980].pack("Q>")
|
12
|
-
end
|
13
|
-
|
14
|
-
def connect
|
15
|
-
@transaction_id = [rand(1000)].pack("I>")
|
16
|
-
@socket.connect(ip_addr, @port)
|
17
|
-
begin
|
18
|
-
send_msg(connect_msg)
|
19
|
-
read_response
|
20
|
-
rescue
|
21
|
-
false
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def send_msg(msg)
|
26
|
-
begin
|
27
|
-
@socket.send(msg, 0)
|
28
|
-
rescue
|
29
|
-
false
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def connected?
|
34
|
-
@response
|
35
|
-
end
|
36
|
-
|
37
|
-
def ip_addr
|
38
|
-
Socket.getaddrinfo(@url, @port)[0][3]
|
39
|
-
end
|
40
|
-
|
41
|
-
def bound_peers
|
42
|
-
@connection_id = @response[-8..-1]
|
43
|
-
@transaction_id = [rand(10000)].pack("I>")
|
44
|
-
send_msg(announce_msg)
|
45
|
-
|
46
|
-
read_response
|
47
|
-
|
48
|
-
parse_announce if @response[0..3] == action(1)
|
49
|
-
|
50
|
-
peer_list(@response)
|
51
|
-
end
|
52
|
-
|
53
|
-
def parse_announce
|
54
|
-
if @response[4..7] == @transaction_id
|
55
|
-
res = @response.slice!(0..11)
|
56
|
-
leechers = @response.slice!(0..3).unpack("I>").first
|
57
|
-
seeders = @response.slice!(0..3).unpack("I>").first
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def send_message
|
62
|
-
begin
|
63
|
-
@socket.send(@msg, 0)
|
64
|
-
rescue
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def read_response
|
69
|
-
begin
|
70
|
-
@response = @socket.recv(1028)
|
71
|
-
rescue Exception => e
|
72
|
-
e
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def connect_match?
|
77
|
-
data[0] == (action(0) + @transaction_id + @connection_id)
|
78
|
-
end
|
79
|
-
|
80
|
-
def announce_input
|
81
|
-
@connection_id + action(1) + @transaction_id + info_hash + PEER_ID
|
82
|
-
end
|
83
|
-
|
84
|
-
def connect_msg
|
85
|
-
@connection_id + action(0) + @transaction_id
|
86
|
-
end
|
87
|
-
|
88
|
-
def action(n)
|
89
|
-
[n].pack("I>")
|
90
|
-
end
|
91
|
-
|
92
|
-
def announce_msg
|
93
|
-
@connection_id + action(1) + @transaction_id + info_hash + PEER_ID + [0].pack("Q>") + [0].pack("Q>") + [0].pack("Q>") + action(0) + action(0) + action(0) + action(-1) + [@socket.addr[1]].pack(">S")
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|