torrenter 0.0.5 → 0.0.6

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.
@@ -0,0 +1,219 @@
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,74 +1,110 @@
1
1
  module Torrenter
2
+ # torrent reader should only read the torrent file and return either
3
+ # a UDP tracker object or an HTTP tracker object
2
4
  class TorrentReader
3
5
  attr_reader :stream
4
6
  def initialize(stream)
5
- @stream = stream
6
- @file_list = stream['info']['files'] || stream['info']
7
- @sha = Digest::SHA1.digest(stream['info'].bencode)
8
- @piece_length = stream['info']['piece length']
9
- @sha_list = stream['info']['pieces']
10
- @sha_size = stream['info']['pieces'].size / 20
11
- end
12
-
13
- def determine_protocol
14
- trackers = if @stream['announce-list']
15
- @stream['announce-list'].flatten << @stream['announce']
16
- else
17
- [@stream['announce']]
18
- end
19
-
20
- trackers.each do |track|
21
- tracker = build_tracker(track)
22
-
23
- tracker.connect
24
-
25
- if tracker.connected?
26
- @peers = tracker.address_list
27
- end
7
+ @stream = stream
8
+ end
28
9
 
29
- break if @peers
30
- end
10
+ def info_hash
11
+ Digest::SHA1.digest(@stream['info'].bencode)
12
+ end
13
+
14
+ def sha_hashes
15
+ @stream['info']['pieces']
16
+ end
17
+
18
+ def total_file_size
19
+ file_list.is_a?(Array) ? multiple_file_size : file_list['length']
20
+ end
21
+
22
+ def multiple_file_size
23
+ file_list.map { |file| file['length'] }.inject { |x,y| x + y }
24
+ end
25
+
26
+ def piece_length
27
+ @stream['info']['piece length']
28
+ end
29
+
30
+ def file_list
31
+ @stream['info']['files'] || @stream['info']
32
+ end
33
+
34
+ def announce_url
35
+ @stream['announce']
36
+ end
37
+
38
+ def announce_list
39
+ @stream['announce-list']
40
+ end
41
+
42
+ def url_list
43
+ announce_list ? announce_list.flatten << announce_url : [announce_url]
44
+ end
31
45
 
32
- if @peers
33
- establish_reactor
46
+ def access_trackers
47
+ url_list.map do |track|
48
+ if track.include?("http://")
49
+ HTTPTracker.new(track, @stream)
50
+ else
51
+ UDPTracker.new(track, @stream)
52
+ end
34
53
  end
35
54
  end
36
55
 
37
- def peer_info
38
- {
39
- :info_hash => @sha,
40
- :piece_length => @piece_length,
41
- :sha_list => sha_pieces,
42
- :piece_index => Array.new(@sha_list.size) { false }
43
- }
56
+ def peer_list(bytestring)
57
+ bytestring.chars.each_slice(6).map do |peer_data|
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
44
62
  end
45
63
 
46
- def parse_addresses(addr, size)
47
- Array.new(size) do
48
- peer = { :ip => addr.slice!(0..3).bytes.join('.'),
49
- :port => port(addr.slice!(0..1)) }
50
- Peer.new(peer, @file_list, peer_info)
64
+ def unpack_data
65
+ puts "ALL FINISHED! Transferring data into file(s)."
66
+ @main_folder = $data_dump[/.+(?=\.torrent-data)/]
67
+ create_folders
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) }
51
85
  end
86
+ File.delete($data_dump)
52
87
  end
53
88
 
54
- def port(addr)
55
- addr.unpack("S>").first
89
+ def create_folders
90
+ if multiple_files?
91
+ folders = sub_folders.map { |fold| fold['path'][0..-2].join("/") }.uniq
92
+ folders.each { |folder| FileUtils.mkdir_p(@main_folder + "/#{folder}") }
93
+ else
94
+ FileUtils.mkdir(@main_folder)
95
+ end
56
96
  end
57
97
 
58
- def build_tracker(track)
59
- track.include?('http://') ? HTTPTracker.new(track, stream) : UDPTracker.new(track, stream)
98
+ def sub_folders
99
+ stream['info']['files'].select { |fold| fold['path'].length > 1 }
60
100
  end
61
101
 
62
- def sha_pieces
63
- start, term = -20, 0
64
- Array.new(@sha_size) { start += 20; term += 20; @sha_list[start...term]}
102
+ def multiple_sub_folders?(file)
103
+ file['path'].length > 0
65
104
  end
66
105
 
67
- def establish_reactor
68
- react = Reactor.new(@peers, sha_pieces, @piece_length, @file_list)
69
- react.message_reactor
106
+ def multiple_files?
107
+ file_list.is_a?(Array)
70
108
  end
71
109
  end
72
110
  end
73
-
74
-
@@ -0,0 +1,13 @@
1
+ module Torrenter
2
+ class Tracker < TorrentReader
3
+ def initialize(tracker_url)
4
+ @tracker_url = tracker_url
5
+ end
6
+
7
+ def tracker_connect
8
+ if @tracker_url.include?("http://")
9
+ @tracker_url = URI(@tracker_url)
10
+ @tracker_url.query = URI.encode_www_form(peer_hash)
11
+ end
12
+ end
13
+ end
@@ -1,19 +1,20 @@
1
1
  module Torrenter
2
2
  class UDPTracker < TorrentReader
3
+
3
4
  attr_reader :socket, :response
4
5
 
5
6
  def initialize(tracker, stream)
7
+ super(stream)
6
8
  @url = tracker[/(?<=udp\:\/\/).+(?=\:\d+)/]
7
9
  @port = tracker[/\d+$/].to_i
8
10
  @socket = UDPSocket.new
9
11
  @connection_id = [0x41727101980].pack("Q>")
10
- super(stream)
11
12
  end
12
13
 
13
14
  def connect
15
+ @transaction_id = [rand(1000)].pack("I>")
16
+ @socket.connect(ip_addr, @port)
14
17
  begin
15
- @transaction_id = [rand(1000)].pack("I>")
16
- @socket.connect(ip_addr, @port)
17
18
  send_msg(connect_msg)
18
19
  read_response
19
20
  rescue
@@ -37,15 +38,16 @@ module Torrenter
37
38
  Socket.getaddrinfo(@url, @port)[0][3]
38
39
  end
39
40
 
40
- def address_list
41
+ def bound_peers
41
42
  @connection_id = @response[-8..-1]
42
43
  @transaction_id = [rand(10000)].pack("I>")
43
44
  send_msg(announce_msg)
44
45
 
45
46
  read_response
46
-
47
+
47
48
  parse_announce if @response[0..3] == action(1)
48
- parse_addresses(@response, @response.size / 6)
49
+
50
+ peer_list(@response)
49
51
  end
50
52
 
51
53
  def parse_announce
@@ -76,7 +78,7 @@ module Torrenter
76
78
  end
77
79
 
78
80
  def announce_input
79
- @connection_id + action(1) + @transaction_id + @sha + PEER_ID
81
+ @connection_id + action(1) + @transaction_id + info_hash + PEER_ID
80
82
  end
81
83
 
82
84
  def connect_msg
@@ -86,9 +88,9 @@ module Torrenter
86
88
  def action(n)
87
89
  [n].pack("I>")
88
90
  end
89
-
91
+
90
92
  def announce_msg
91
- @connection_id + action(1) + @transaction_id + @sha + PEER_ID + [0].pack("Q>") + [0].pack("Q>") + [0].pack("Q>") + action(0) + action(0) + action(0) + action(-1) + [@socket.addr[1]].pack(">S")
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")
92
94
  end
93
95
  end
94
- end
96
+ end