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
@@ -1,10 +1,22 @@
|
|
1
1
|
module Torrenter
|
2
2
|
# torrent reader should only read the torrent file and return either
|
3
3
|
# a UDP tracker object or an HTTP tracker object
|
4
|
+
|
5
|
+
# what is being used or accessed by the torrent reader, and what is being used by the trackers?
|
6
|
+
|
4
7
|
class TorrentReader
|
5
|
-
attr_reader :stream
|
6
|
-
|
7
|
-
|
8
|
+
attr_reader :stream, :pieces
|
9
|
+
|
10
|
+
def initialize(file)
|
11
|
+
@stream = BEncode.load_file(file)
|
12
|
+
end
|
13
|
+
|
14
|
+
def folder
|
15
|
+
if multiple_files?
|
16
|
+
@stream['info']['name']
|
17
|
+
else
|
18
|
+
@stream['info']['name'].gsub(/\.\w+$/, '')
|
19
|
+
end
|
8
20
|
end
|
9
21
|
|
10
22
|
def info_hash
|
@@ -28,7 +40,11 @@ module Torrenter
|
|
28
40
|
end
|
29
41
|
|
30
42
|
def file_list
|
31
|
-
@stream['info']['files']
|
43
|
+
if @stream['info']['files'].nil?
|
44
|
+
[{ 'path' => [@stream['info']['name']], 'length' => @stream['info']['length'] }]
|
45
|
+
else
|
46
|
+
@stream['info']['files']
|
47
|
+
end
|
32
48
|
end
|
33
49
|
|
34
50
|
def announce_url
|
@@ -43,68 +59,46 @@ module Torrenter
|
|
43
59
|
announce_list ? announce_list.flatten << announce_url : [announce_url]
|
44
60
|
end
|
45
61
|
|
46
|
-
def
|
47
|
-
|
48
|
-
if track.include?("http://")
|
49
|
-
HTTPTracker.new(track, @stream)
|
50
|
-
else
|
51
|
-
UDPTracker.new(track, @stream)
|
52
|
-
end
|
53
|
-
end
|
62
|
+
def multiple_files?
|
63
|
+
file_list.size > 1
|
54
64
|
end
|
55
65
|
|
56
|
-
def
|
57
|
-
|
58
|
-
ip = peer_data[0..3].join('').bytes.join('.')
|
59
|
-
port = peer_data[4..5].join('').unpack("S>").first
|
60
|
-
Peer.new(ip, port, info_hash, piece_length)
|
61
|
-
end
|
66
|
+
def write_paths
|
67
|
+
file_list.each { |file| write_path(file) }
|
62
68
|
end
|
63
69
|
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
if multiple_files?
|
70
|
-
offset = 0
|
71
|
-
file_list.each do |file|
|
72
|
-
|
73
|
-
length = file['length']
|
74
|
-
filename = file['path'].join("/")
|
75
|
-
|
76
|
-
File.open("#{@main_folder}/#{filename}", 'a+') do |data|
|
77
|
-
data << IO.read($data_dump, length, offset)
|
78
|
-
end
|
79
|
-
|
80
|
-
offset += length
|
81
|
-
end
|
82
|
-
else
|
83
|
-
FileUtils.mkdir(@main_folder)
|
84
|
-
File.open("#{@main_folder}/#{file_list['name']}", 'w') { |data| data << File.read($data_dump) }
|
70
|
+
def write_path(file)
|
71
|
+
file = "#{folder}/#{file['path'].join('/')}"
|
72
|
+
if !Dir.exist?(File.dirname(file))
|
73
|
+
FileUtils.mkdir_p(File.dirname(file))
|
85
74
|
end
|
86
|
-
|
75
|
+
IO.write(file, '', 0)
|
87
76
|
end
|
88
77
|
|
89
|
-
def
|
90
|
-
|
91
|
-
|
92
|
-
folders.each { |folder| FileUtils.mkdir_p(@main_folder + "/#{folder}") }
|
93
|
-
else
|
94
|
-
FileUtils.mkdir(@main_folder)
|
95
|
-
end
|
78
|
+
def piece_index
|
79
|
+
constructor = PieceConstructor.new(file_list, folder, sha_hashes, piece_length)
|
80
|
+
constructor.make_index
|
96
81
|
end
|
97
82
|
|
98
|
-
def
|
99
|
-
|
83
|
+
def tracker_params
|
84
|
+
{
|
85
|
+
:info_hash => info_hash,
|
86
|
+
:peer_id => PEER_ID,
|
87
|
+
:left => piece_length,
|
88
|
+
:pieces => file_list
|
89
|
+
}
|
100
90
|
end
|
101
91
|
|
102
|
-
def
|
103
|
-
|
104
|
-
|
92
|
+
def connect_trackers
|
93
|
+
url_list.compact.map do |url|
|
94
|
+
tracker = if url.include?('http://')
|
95
|
+
Tracker::HTTPTracker.new(url, tracker_params)
|
96
|
+
else
|
97
|
+
Tracker::UDPTracker.new(url, tracker_params)
|
98
|
+
end
|
105
99
|
|
106
|
-
|
107
|
-
|
100
|
+
tracker.connect
|
101
|
+
end
|
108
102
|
end
|
109
103
|
end
|
110
104
|
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Torrenter
|
2
|
+
class TorrentReader
|
3
|
+
class Piece
|
4
|
+
attr_accessor :range, :path, :hash, :index, :status
|
5
|
+
attr_reader :peers, :data
|
6
|
+
def initialize(piece_length)
|
7
|
+
@range = []
|
8
|
+
@path = []
|
9
|
+
@piece_length = piece_length
|
10
|
+
@status = :available
|
11
|
+
@data = ''
|
12
|
+
@peers = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def tally
|
16
|
+
if @range.length > 0
|
17
|
+
@range.map { |r| r.end - r.begin }.inject { |x,y| x + y }
|
18
|
+
else
|
19
|
+
0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def left
|
24
|
+
@piece_length - tally
|
25
|
+
end
|
26
|
+
|
27
|
+
def full?
|
28
|
+
tally == @piece_length
|
29
|
+
end
|
30
|
+
|
31
|
+
def add(range, path)
|
32
|
+
@range << range
|
33
|
+
@path << path
|
34
|
+
end
|
35
|
+
|
36
|
+
def include?(socket)
|
37
|
+
@peers.include?(socket)
|
38
|
+
end
|
39
|
+
|
40
|
+
def verify
|
41
|
+
@data = if multiple_files?
|
42
|
+
@path.map.with_index { |p, i| read_file(p, i) }.join
|
43
|
+
else
|
44
|
+
File.file?(@path.join) ? (IO.read(@path.join, @piece_length, @range.first.begin) || '') : ''
|
45
|
+
end
|
46
|
+
@status = hash_match? ? :downloaded : :available
|
47
|
+
@data.clear
|
48
|
+
end
|
49
|
+
|
50
|
+
def offset(i)
|
51
|
+
@range[i].end - @range[i].begin
|
52
|
+
end
|
53
|
+
|
54
|
+
def multiple_files?
|
55
|
+
@path.length > 1
|
56
|
+
end
|
57
|
+
|
58
|
+
def write_to_file
|
59
|
+
if multiple_files?
|
60
|
+
@path.each_with_index { |p,i| write_file(p, i) }
|
61
|
+
else
|
62
|
+
IO.write(@path.first, @data, @range.first.begin)
|
63
|
+
end
|
64
|
+
@status = :downloaded
|
65
|
+
@data.clear
|
66
|
+
end
|
67
|
+
|
68
|
+
def complete?
|
69
|
+
@data.bytesize >= tally && hash_match?
|
70
|
+
end
|
71
|
+
|
72
|
+
def block
|
73
|
+
(tally - @data.size) < BLOCK ? tally - @data.size : BLOCK
|
74
|
+
end
|
75
|
+
|
76
|
+
def chunk
|
77
|
+
(@data.bytesize / BLOCK) * BLOCK
|
78
|
+
end
|
79
|
+
|
80
|
+
def <<(buffer)
|
81
|
+
@data << buffer
|
82
|
+
end
|
83
|
+
|
84
|
+
def peer_count(socket)
|
85
|
+
@peers.count(socket)
|
86
|
+
end
|
87
|
+
|
88
|
+
def peer_size
|
89
|
+
@peers.length
|
90
|
+
end
|
91
|
+
|
92
|
+
def add_peer(peer)
|
93
|
+
@peers << peer
|
94
|
+
end
|
95
|
+
|
96
|
+
def remove_peer
|
97
|
+
@peers.each { |p| @peers.delete(p) if p.closed? }
|
98
|
+
end
|
99
|
+
|
100
|
+
def hash_match?
|
101
|
+
Digest::SHA1.digest(@data) == @hash
|
102
|
+
end
|
103
|
+
|
104
|
+
def percent
|
105
|
+
if @status == :downloading
|
106
|
+
return (@data.size).fdiv(@piece_length)
|
107
|
+
elsif @status == :downloaded
|
108
|
+
return 1
|
109
|
+
else
|
110
|
+
return 0
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
def read_file(p, i)
|
116
|
+
binding.pry if offset(i) < 0
|
117
|
+
File.file?(p) ? IO.read(p, offset(i), @range[i].begin) : ''
|
118
|
+
end
|
119
|
+
|
120
|
+
def write_file(p, i)
|
121
|
+
IO.write(p, @data.slice!(0...offset(i)), @range[i].begin)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Torrenter
|
2
|
+
class TorrentReader
|
3
|
+
class PieceConstructor
|
4
|
+
|
5
|
+
attr_reader :index
|
6
|
+
def initialize(files, folder, sha_hashes, piece_length)
|
7
|
+
@files = files
|
8
|
+
@folder = folder
|
9
|
+
@sha_hashes = sha_hashes
|
10
|
+
@piece_length = piece_length
|
11
|
+
@index = PieceIndex.new(piece_length)
|
12
|
+
@excess_bytes = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def index_length
|
16
|
+
@sha_hashes.size / 20
|
17
|
+
end
|
18
|
+
|
19
|
+
def correct?
|
20
|
+
total_left == @arr.map { |x| x.tally }.inject { |x,y| x + y }
|
21
|
+
end
|
22
|
+
|
23
|
+
def total_left
|
24
|
+
@files.map { |f| f['length']}.inject { |x,y| x + y }
|
25
|
+
end
|
26
|
+
|
27
|
+
def final_piece?
|
28
|
+
index_length == @index.size + 1
|
29
|
+
end
|
30
|
+
|
31
|
+
def make_index
|
32
|
+
@piece = Piece.new(@piece_length)
|
33
|
+
@files.each do |file|
|
34
|
+
path = "#{@folder}/#{file['path'].join('/')}"
|
35
|
+
offset = 0
|
36
|
+
if @piece.tally != 0
|
37
|
+
if @piece.left > file['length']
|
38
|
+
@piece.add(0...file['length'], path)
|
39
|
+
else
|
40
|
+
offset = @piece.left
|
41
|
+
@piece.add(0...@piece.left, path)
|
42
|
+
end
|
43
|
+
|
44
|
+
@index << @piece if @piece.full?
|
45
|
+
else
|
46
|
+
offset = 0
|
47
|
+
end
|
48
|
+
|
49
|
+
while offset < ((file['length']) - @piece_length)
|
50
|
+
@piece = Piece.new(@piece_length)
|
51
|
+
@piece.add(offset...(offset += @piece_length), path)
|
52
|
+
@index << @piece if @piece.full?
|
53
|
+
end
|
54
|
+
|
55
|
+
if @piece.full? && offset > 0
|
56
|
+
@piece = Piece.new(@piece_length)
|
57
|
+
@piece.add(offset...file['length'], path)
|
58
|
+
end
|
59
|
+
|
60
|
+
if final_piece?
|
61
|
+
@index << @piece
|
62
|
+
end
|
63
|
+
end
|
64
|
+
set_hash
|
65
|
+
end
|
66
|
+
|
67
|
+
def set_hash
|
68
|
+
0.upto(@index.size - 1) do |i|
|
69
|
+
@index[i].hash = piece_hash(i)
|
70
|
+
@index[i].index = i
|
71
|
+
end
|
72
|
+
|
73
|
+
return @index
|
74
|
+
end
|
75
|
+
|
76
|
+
def piece_hash(n)
|
77
|
+
@sha_hashes[(n * 20)...(n * 20) + 20]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Torrenter
|
2
|
+
class TorrentReader
|
3
|
+
class PieceIndex
|
4
|
+
attr_reader :piece_index, :piece_length
|
5
|
+
def initialize(piece_length)
|
6
|
+
@piece_length = piece_length
|
7
|
+
@piece_index = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def find_least(socket, i=1)
|
11
|
+
return nil if none_remain? || i > peer_count.sort.last
|
12
|
+
index = peer_count.find_index(i)
|
13
|
+
if index && available_pieces[index].include?(socket)
|
14
|
+
piece = available_pieces[index]
|
15
|
+
piece.status = :downloading
|
16
|
+
return piece
|
17
|
+
else
|
18
|
+
find_least(socket, i+1)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def clean_peers
|
23
|
+
@piece_index.each { |piece| piece.remove_peer }
|
24
|
+
end
|
25
|
+
|
26
|
+
def <<(piece)
|
27
|
+
@piece_index << piece
|
28
|
+
end
|
29
|
+
|
30
|
+
def none_remain?
|
31
|
+
count(:available) == 0
|
32
|
+
end
|
33
|
+
|
34
|
+
def peer_count
|
35
|
+
available_pieces.map { |piece| piece.peers.length }
|
36
|
+
end
|
37
|
+
|
38
|
+
def available_pieces
|
39
|
+
@piece_index.select { |piece| piece.status == :available }
|
40
|
+
end
|
41
|
+
|
42
|
+
def count(type)
|
43
|
+
@piece_index.select { |piece| piece.status == type }.length
|
44
|
+
end
|
45
|
+
|
46
|
+
def verify_status
|
47
|
+
@piece_index.each { |piece| piece.verify }
|
48
|
+
end
|
49
|
+
|
50
|
+
def all?
|
51
|
+
@piece_index.all? { |piece| piece.status == :downloaded }
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_peer(index, peer)
|
55
|
+
@piece_index[index].peers << peer if @piece_index[index]
|
56
|
+
end
|
57
|
+
|
58
|
+
def size
|
59
|
+
@piece_index.size
|
60
|
+
end
|
61
|
+
|
62
|
+
def length
|
63
|
+
@piece_index.length
|
64
|
+
end
|
65
|
+
|
66
|
+
def [](index)
|
67
|
+
@piece_index[index]
|
68
|
+
end
|
69
|
+
|
70
|
+
def []=(i, val)
|
71
|
+
@piece_index[i] = Piece.new(i, piece_length, val)
|
72
|
+
end
|
73
|
+
|
74
|
+
def push(val)
|
75
|
+
@piece_index[size] = val
|
76
|
+
end
|
77
|
+
|
78
|
+
def last
|
79
|
+
@piece_index.last
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_json
|
83
|
+
JSON.generate({ :master_index => @piece_index.map { |p| p.percent } })
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/torrenter/tracker.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
|
-
module Torrenter
|
2
|
-
class Tracker
|
3
|
-
def
|
4
|
-
|
5
|
-
|
1
|
+
module Torrenter
|
2
|
+
class Tracker
|
3
|
+
def format_peers(peers)
|
4
|
+
pool = []
|
5
|
+
peers.chars.each_slice(6) do |peer_data|
|
6
|
+
ip = peer_data[0..3].join('').bytes.join('.')
|
7
|
+
port = peer_data[4..5].join('').unpack("S>").first
|
8
|
+
|
9
|
+
if !pool.find { |peer| peer.ip == ip }
|
10
|
+
pool << Peer.new(ip, port, @params)
|
11
|
+
end
|
12
|
+
end
|
6
13
|
|
7
|
-
|
8
|
-
if @tracker_url.include?("http://")
|
9
|
-
@tracker_url = URI(@tracker_url)
|
10
|
-
@tracker_url.query = URI.encode_www_form(peer_hash)
|
14
|
+
return pool
|
11
15
|
end
|
12
16
|
end
|
13
17
|
end
|