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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3234eb20fcd3339fcecb2a52c4bfe3bdb0f84ee5
|
4
|
+
data.tar.gz: 88f9959f5111cb4c8bbc082a42a8bd7897b5c95a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ecb115dd277b70cd8769f2300d016b714042777be8665852fabe64f6c45a5bce5cab063b3ba97ad8625bc8eb3361401a63c30bae9e4e3508cc8f17c9a9f16546
|
7
|
+
data.tar.gz: e5a295b9a04b632801c3560fea6b2ce312830364fdfd06255bae7e3b649c040c160e5cb9ec06d4963ba33ded91e531e53ef71811d9f99da0fa344ffbfa0d459f
|
data/bin/torrenter
CHANGED
data/lib/torrenter.rb
CHANGED
@@ -2,57 +2,32 @@ require 'socket'
|
|
2
2
|
require 'digest/sha1'
|
3
3
|
require 'bencode'
|
4
4
|
require 'fileutils'
|
5
|
-
require '
|
6
|
-
require 'torrenter/message/messager'
|
7
|
-
require 'torrenter/message/message_types'
|
5
|
+
require 'torrenter/constants'
|
8
6
|
require 'torrenter/peer'
|
9
|
-
require 'torrenter/
|
7
|
+
require 'torrenter/peer/buffer_state'
|
10
8
|
require 'torrenter/reactor'
|
11
|
-
require 'torrenter/
|
12
|
-
require 'torrenter/
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
9
|
+
require 'torrenter/tracker'
|
10
|
+
require 'torrenter/tracker/http_tracker'
|
11
|
+
require 'torrenter/tracker/udp_tracker'
|
12
|
+
require 'torrenter/torrent_reader'
|
13
|
+
require 'torrenter/torrent_reader/piece'
|
14
|
+
require 'torrenter/torrent_reader/piece_index'
|
15
|
+
require 'torrenter/torrent_reader/piece_constructor'
|
16
|
+
require 'torrenter/version'
|
20
17
|
|
21
18
|
module Torrenter
|
22
19
|
class Torrent
|
20
|
+
|
23
21
|
def start(file)
|
24
|
-
|
25
|
-
|
26
|
-
torrent_file = Torrenter::TorrentReader.new(stream)
|
27
|
-
trackers = torrent_file.access_trackers
|
22
|
+
@torrent = Torrenter::TorrentReader.new(file)
|
23
|
+
@torrent.write_paths
|
28
24
|
|
29
|
-
|
25
|
+
trackers = @torrent.connect_trackers
|
30
26
|
|
31
|
-
loop do
|
32
|
-
@tracker = trackers.shift
|
33
|
-
if @tracker
|
34
|
-
begin
|
35
|
-
Timeout::timeout(10) { @tracker.connect }
|
36
|
-
rescue Timeout::Error
|
37
|
-
puts 'Tracker not responding. Trying next.'
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
if @tracker.connected?
|
42
|
-
break
|
43
|
-
elsif trackers.empty?
|
44
|
-
raise 'Trackers non-responsive'
|
45
|
-
end
|
46
|
-
end
|
47
27
|
|
48
|
-
|
49
|
-
reactor
|
50
|
-
reactor.
|
51
|
-
unless reactor.finished?
|
52
|
-
reactor.message_reactor
|
53
|
-
else
|
54
|
-
reactor.unpack_data
|
55
|
-
end
|
28
|
+
reactor = Reactor.new(trackers, @torrent.piece_index)
|
29
|
+
reactor.extract_peers
|
30
|
+
reactor.message_reactor
|
56
31
|
end
|
57
32
|
end
|
58
33
|
end
|
data/lib/torrenter/peer.rb
CHANGED
@@ -1,27 +1,12 @@
|
|
1
1
|
module Torrenter
|
2
2
|
class Peer
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
@
|
10
|
-
@port = port
|
11
|
-
@info_hash = info_hash
|
12
|
-
@piece_length = piece_length
|
13
|
-
@buffer = ''
|
14
|
-
@blocks = []
|
15
|
-
@dl_rate = 0
|
16
|
-
@piece_index = []
|
17
|
-
end
|
18
|
-
|
19
|
-
def current_size
|
20
|
-
piece_data.bytesize + @buffer.bytesize
|
21
|
-
end
|
22
|
-
|
23
|
-
def piece_data
|
24
|
-
@blocks.join('')
|
3
|
+
attr_reader :buffer, :peer_state, :socket, :ip
|
4
|
+
def initialize(ip, port, params={})
|
5
|
+
@ip = ip
|
6
|
+
@port = port
|
7
|
+
@info_hash = params[:info_hash]
|
8
|
+
@piece_length = params[:left]
|
9
|
+
@peer_state = false
|
25
10
|
end
|
26
11
|
|
27
12
|
def connect
|
@@ -40,13 +25,23 @@ module Torrenter
|
|
40
25
|
|
41
26
|
if @socket
|
42
27
|
puts "Connected!"
|
43
|
-
@socket.write(handshake)
|
44
28
|
@peer_state = true
|
29
|
+
@buffer = BufferState.new(@socket, @info_hash)
|
30
|
+
@buffer.send(handshake)
|
45
31
|
else
|
46
32
|
@peer_state = false
|
47
33
|
end
|
48
34
|
end
|
49
35
|
|
36
|
+
def connection_state(index, blk)
|
37
|
+
if @socket.closed?
|
38
|
+
@peer_state = false
|
39
|
+
@buffer.current_piece = :available
|
40
|
+
else
|
41
|
+
@buffer.messager(index, blk)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
50
45
|
def handshake
|
51
46
|
"#{PROTOCOL}#{@info_hash}#{PEER_ID}"
|
52
47
|
end
|
@@ -54,9 +49,5 @@ module Torrenter
|
|
54
49
|
def connected?
|
55
50
|
@peer_state
|
56
51
|
end
|
57
|
-
|
58
|
-
def update_indices(master)
|
59
|
-
master.each_with_index { |p,i| @piece_index[i] = p }
|
60
|
-
end
|
61
52
|
end
|
62
53
|
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module Torrenter
|
2
|
+
# The buffer state should be responsible ONLY for the handling of non-metadata bytes.
|
3
|
+
# the messaging behavior should and ought to remain with the peer class, though
|
4
|
+
# in truth, it may be better if I didnt do that.
|
5
|
+
|
6
|
+
# So, if the state of the buffer reaches a certain step in its process involving the piece
|
7
|
+
# the buffer state should be fired off.
|
8
|
+
|
9
|
+
# instead of initialiazing it with the buffer,
|
10
|
+
class Peer
|
11
|
+
class BufferState
|
12
|
+
attr_reader :piece
|
13
|
+
def initialize(socket, info_hash)
|
14
|
+
@socket = socket
|
15
|
+
@buffer = ''
|
16
|
+
@info_hash = info_hash
|
17
|
+
end
|
18
|
+
|
19
|
+
def messager(index, blk)
|
20
|
+
if @buffer.empty?
|
21
|
+
recv
|
22
|
+
else
|
23
|
+
case @buffer[4]
|
24
|
+
when nil
|
25
|
+
@buffer.slice!(0..3) if @buffer[0..3] == KEEP_ALIVE
|
26
|
+
when HANDSHAKE
|
27
|
+
parse_handshake
|
28
|
+
when BITFIELD
|
29
|
+
bitfield.each_with_index do |bit, i|
|
30
|
+
blk.call(i, @socket) if bit == '1'
|
31
|
+
end
|
32
|
+
|
33
|
+
send_interested if @buffer.empty?
|
34
|
+
when HAVE
|
35
|
+
if @buffer.bytesize < 9
|
36
|
+
recv
|
37
|
+
else
|
38
|
+
have { |i| blk.call(i, @socket) }
|
39
|
+
end
|
40
|
+
send_interested if @buffer.empty?
|
41
|
+
when INTERESTED
|
42
|
+
parse_interested(index)
|
43
|
+
when PIECE
|
44
|
+
parse_piece(index)
|
45
|
+
when CANCEL
|
46
|
+
@socket.close
|
47
|
+
else
|
48
|
+
recv
|
49
|
+
send(KEEP_ALIVE)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def current_piece=(type)
|
55
|
+
@piece.status = type if @piece
|
56
|
+
end
|
57
|
+
|
58
|
+
def hash_matches?
|
59
|
+
@buffer[28..47] == @info_hash
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse_handshake
|
63
|
+
if !hash_matches?
|
64
|
+
yield
|
65
|
+
else
|
66
|
+
@buffer.slice!(0..67)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def have
|
71
|
+
yield unpack('C').last
|
72
|
+
end
|
73
|
+
|
74
|
+
def bitfield
|
75
|
+
unpack('B').first.split('')
|
76
|
+
end
|
77
|
+
|
78
|
+
def pick_piece(index)
|
79
|
+
@piece = index.find_least(@socket)
|
80
|
+
if @piece
|
81
|
+
puts "#{@piece.index} selected by #{@socket}."
|
82
|
+
else
|
83
|
+
puts "No piece selected!"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def parse_interested(index)
|
88
|
+
if interested?
|
89
|
+
pick_piece(index)
|
90
|
+
request_piece if @piece
|
91
|
+
else
|
92
|
+
@socket.close
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def parse_piece(index)
|
97
|
+
if packet_length?
|
98
|
+
@piece << @buffer.slice!(13..-1)
|
99
|
+
@buffer.clear
|
100
|
+
if @piece.complete?
|
101
|
+
@piece.write_to_file
|
102
|
+
@piece = nil
|
103
|
+
pick_piece(index) unless index.all?
|
104
|
+
end
|
105
|
+
request_piece
|
106
|
+
else
|
107
|
+
recv
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def packet_length?
|
112
|
+
@buffer.size >= msg_length + 4
|
113
|
+
end
|
114
|
+
|
115
|
+
def request_piece
|
116
|
+
send pack(13, "\x06", @piece.index, @piece.chunk, @piece.block) if @piece
|
117
|
+
end
|
118
|
+
|
119
|
+
def send(msg)
|
120
|
+
@socket.sendmsg_nonblock(msg)
|
121
|
+
end
|
122
|
+
|
123
|
+
def recv(bytes=BLOCK)
|
124
|
+
begin
|
125
|
+
@buffer << @socket.recv_nonblock(bytes)
|
126
|
+
rescue *EXCEPTIONS
|
127
|
+
''
|
128
|
+
rescue Errno::ETIMEDOUT
|
129
|
+
if piece_selected?
|
130
|
+
@piece.status = :available
|
131
|
+
end
|
132
|
+
@socket.close
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def piece_selected?
|
137
|
+
@piece
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def send_interested
|
143
|
+
send("\x00\x00\x00\x01\x02")
|
144
|
+
end
|
145
|
+
|
146
|
+
def pack(*msg)
|
147
|
+
msg.map { |m| m.is_a?(Integer) ? [m].pack("I>") : m }.join
|
148
|
+
end
|
149
|
+
|
150
|
+
def unpack(type)
|
151
|
+
@buffer.slice!(0...msg_length + 4)[5..-1].unpack("#{type}*")
|
152
|
+
end
|
153
|
+
|
154
|
+
def msg_length
|
155
|
+
@buffer[0..3].unpack("N*").last
|
156
|
+
end
|
157
|
+
|
158
|
+
def interested?
|
159
|
+
@buffer.slice!(0..4) == "\x00\x00\x00\x01\x01"
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
data/lib/torrenter/reactor.rb
CHANGED
@@ -1,194 +1,45 @@
|
|
1
1
|
module Torrenter
|
2
|
-
class Reactor
|
2
|
+
class Reactor
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
4
|
+
def initialize(trackers, piece_index)
|
5
|
+
@trackers = trackers
|
6
|
+
@piece_index = piece_index
|
12
7
|
end
|
13
8
|
|
14
|
-
def
|
15
|
-
|
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 }
|
9
|
+
def extract_peers
|
10
|
+
@peers = @trackers.map { |tracker| tracker.peer_list if tracker.connected? }.flatten.compact
|
37
11
|
end
|
38
12
|
|
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
13
|
def message_reactor
|
46
|
-
|
47
|
-
|
14
|
+
@piece_index.verify_status
|
15
|
+
@peers.each { |peer| peer.connect }
|
48
16
|
puts "You are now connected to #{active_peers.size} peers."
|
49
17
|
loop do
|
50
18
|
break if finished?
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
@time = Time.now.to_i
|
57
|
-
|
58
|
-
active_peers.each_with_index do |peer, i|
|
59
|
-
|
60
|
-
datasize = peer.current_size
|
61
|
-
|
62
|
-
state = peer.state(@master_index, remaining) do |data, i|
|
63
|
-
if verified?(i, data)
|
64
|
-
@master_index[peer.index] = :downloaded
|
65
|
-
@byte_counter += peer.piece_data.bytesize
|
66
|
-
pack_piece(peer)
|
67
|
-
peer.mark_complete
|
68
|
-
piece_select(peer)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
current = peer.current_size - datasize
|
73
|
-
if state == :interested
|
74
|
-
peer.update_indices(@master_index)
|
75
|
-
piece_select(peer)
|
76
|
-
end
|
77
|
-
|
78
|
-
if current > 0
|
79
|
-
@time_counter += current
|
80
|
-
end
|
81
|
-
end
|
82
|
-
reattempt_disconnected_peer if Time.now.to_i % 30 == 0
|
83
|
-
|
84
|
-
show_status
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def piece_indices
|
89
|
-
active_peers.select { |peer| peer.piece_index }
|
90
|
-
.map { |peer| peer.piece_index }
|
91
|
-
end
|
92
|
-
|
93
|
-
def tally
|
94
|
-
@master_index.map.with_index do |p,i|
|
95
|
-
p == :free ? piece_indices.map { |x| x[i] } : []
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def piece_select(peer, limit=1)
|
100
|
-
# binding.pry if @master_index.count(:free) == 1
|
101
|
-
|
102
|
-
piece_index = peer.piece_index
|
103
|
-
@tally = tally
|
104
|
-
index = nil
|
105
|
-
|
106
|
-
loop do
|
107
|
-
if limit == active_peers.size + 1
|
108
|
-
index = piece_index.index(:free)
|
109
|
-
break
|
110
|
-
elsif index.is_a?(Integer)
|
111
|
-
break
|
112
|
-
end
|
113
|
-
i = 0
|
114
|
-
loop do
|
115
|
-
break if i > piece_index.size - 1|| !index.nil?
|
116
|
-
if @tally[i].count(:free) == limit && piece_index[i] == :free
|
117
|
-
index = i
|
19
|
+
@peers.each do |peer|
|
20
|
+
if peer.peer_state
|
21
|
+
peer.connection_state(@piece_index, have)
|
118
22
|
else
|
119
|
-
|
23
|
+
peer.connect if Time.now.to_i % 60 == 0
|
120
24
|
end
|
121
25
|
end
|
122
|
-
|
123
|
-
|
124
|
-
end
|
125
|
-
|
126
|
-
if index
|
127
|
-
@master_index[index] = :downloading
|
128
|
-
peer.request_piece(index)
|
129
|
-
return index
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
def show_status
|
134
|
-
if (Time.now.to_i - @time) == 1
|
135
|
-
system('clear')
|
136
|
-
puts "#{download_bar} \n Downloading #{$data_dump[/(.+)(?=torrent-data)/]}"\
|
137
|
-
"at #{real} KB/sec #{data_remaining} MB remaining"\
|
26
|
+
@piece_index.clean_peers
|
27
|
+
$status = @piece_index.to_json
|
138
28
|
end
|
139
29
|
end
|
140
30
|
|
141
|
-
def
|
142
|
-
|
143
|
-
end
|
144
|
-
|
145
|
-
def select_index(index)
|
146
|
-
poo = @master_index.map.with_index do |peer,index|
|
147
|
-
active_peers.map { |x| x.piece_index[index] }
|
148
|
-
end
|
31
|
+
def have
|
32
|
+
->(index, peer) { @piece_index[index].add_peer(peer) }
|
149
33
|
end
|
150
34
|
|
151
|
-
def
|
152
|
-
|
35
|
+
def finished?
|
36
|
+
@piece_index.all?
|
153
37
|
end
|
154
38
|
|
155
39
|
def data_remaining
|
156
40
|
(total_file_size - @byte_counter).fdiv(1024).fdiv(1024).round(2)
|
157
41
|
end
|
158
42
|
|
159
|
-
def real
|
160
|
-
@time_counter.fdiv(1024).round(1)
|
161
|
-
end
|
162
|
-
|
163
|
-
def reattempt_disconnected_peer
|
164
|
-
disconnected_peers.sample.connect
|
165
|
-
end
|
166
|
-
|
167
|
-
def disconnected_peers
|
168
|
-
@peers.select { |peer| !peer.connected? }
|
169
|
-
end
|
170
|
-
|
171
|
-
def pack_piece(peer)
|
172
|
-
IO.write($data_dump, peer.piece_data, piece_length * peer.index)
|
173
|
-
end
|
174
|
-
|
175
|
-
def indices
|
176
|
-
@master_index.map do |piece|
|
177
|
-
if piece == :free
|
178
|
-
0
|
179
|
-
elsif piece == :downloaded
|
180
|
-
1
|
181
|
-
else
|
182
|
-
get_status @master_index.index(piece)
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
def get_status(i)
|
188
|
-
peer = @peers.find { |peer| peer.piece_index[i] == :downloading }
|
189
|
-
(peer.buffer.bytesize + peer.piece_size).fdiv(@piece_length)
|
190
|
-
end
|
191
|
-
|
192
43
|
def active_peers
|
193
44
|
@peers.select { |peer| peer.peer_state }
|
194
45
|
end
|
@@ -205,7 +56,7 @@ module Torrenter
|
|
205
56
|
end
|
206
57
|
|
207
58
|
def pieces(type)
|
208
|
-
(@
|
59
|
+
(@piece_index.count(type).fdiv(@piece_index.size) * 100).round
|
209
60
|
end
|
210
61
|
end
|
211
62
|
end
|