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.
@@ -2,34 +2,32 @@ module Torrenter
2
2
  class Peer
3
3
  include Torrenter
4
4
 
5
- attr_reader :socket, :peer, :sha_list, :piece_len, :info_hash, :status, :index, :msg_length
6
- attr_accessor :piece_index, :offset, :buffer, :block_map
7
-
8
- def initialize(peer, file_list, peer_info={})
9
- @peer = peer
10
- @info_hash = peer_info[:info_hash]
11
- @piece_len = peer_info[:piece_length]
12
- @sha_list = peer_info[:sha_list]
13
- @piece_index = peer_info[:piece_index]
14
- @buffer = ''
15
- @block_map = []
16
- @offset = 0
17
- @file_list = file_list
18
- @status = false
5
+ attr_reader :status, :peer_state, :info_hash, :piece_length, :blocks, :buffer, :ip
6
+ attr_accessor :piece_index, :remaining, :index
7
+
8
+ def initialize(ip, port, info_hash, piece_length)
9
+ @ip = ip
10
+ @port = port
11
+ @info_hash = info_hash
12
+ @piece_length = piece_length
13
+ @buffer = ''
14
+ @blocks = []
15
+ @dl_rate = 0
16
+ @piece_index = []
19
17
  end
20
18
 
21
- def total_file_size
22
- if @file_list.is_a?(Array)
23
- @file_list.map { |f| f['length'] }.inject { |x, y| x + y }
24
- else
25
- @file_list['length']
26
- end
19
+ def current_size
20
+ piece_data.bytesize + @buffer.bytesize
21
+ end
22
+
23
+ def piece_data
24
+ @blocks.join('')
27
25
  end
28
26
 
29
27
  def connect
30
- puts "\nConnecting to IP: #{peer[:ip]} PORT: #{peer[:port]}"
28
+ puts "\nConnecting to IP: #{@ip} PORT: #{@port}"
31
29
  begin
32
- Timeout::timeout(1) { @socket = TCPSocket.new(peer[:ip], peer[:port]) }
30
+ Timeout::timeout(1) { @socket = TCPSocket.new(@ip, @port) }
33
31
  rescue Timeout::Error
34
32
  puts "Timed out."
35
33
  rescue Errno::EADDRNOTAVAIL
@@ -43,9 +41,9 @@ module Torrenter
43
41
  if @socket
44
42
  puts "Connected!"
45
43
  @socket.write(handshake)
46
- @status = true
44
+ @peer_state = true
47
45
  else
48
- @status = false
46
+ @peer_state = false
49
47
  end
50
48
  end
51
49
 
@@ -53,8 +51,12 @@ module Torrenter
53
51
  "#{PROTOCOL}#{@info_hash}#{PEER_ID}"
54
52
  end
55
53
 
56
- def msg_num
57
- @buffer.slice!(0..3).unpack("N*").first
54
+ def connected?
55
+ @peer_state
56
+ end
57
+
58
+ def update_indices(master)
59
+ master.each_with_index { |p,i| @piece_index[i] = p }
58
60
  end
59
61
  end
60
62
  end
@@ -1,5 +1,4 @@
1
1
  module Torrenter
2
-
3
2
  # The buffer state should be responsible ONLY for the handling of non-metadata bytes.
4
3
  # the messaging behavior should and ought to remain with the peer class, though
5
4
  # in truth, it may be better if I didnt do that.
@@ -9,9 +8,50 @@ module Torrenter
9
8
 
10
9
  # instead of initialiazing it with the buffer,
11
10
 
12
- class BufferState < Peer
13
- def examine(buffer, msg_length)
14
- binding.pry
11
+ class BufferState
12
+
13
+ def intialize(socket)
14
+ @socket = socket
15
+ @buffer = ''
16
+ end
17
+
18
+ # include Torrenter
19
+
20
+ def state(buffer, master_index)
21
+ buffer_id = buffer[4]
22
+ if buffer_id.nil?
23
+ buffer.slice!(0..3)
24
+ else
25
+ @length = buffer[0..3].unpack("N>")
26
+ case buffer_id
27
+ when BITFIELD then process_bitfield
28
+ when HAVE then process_have
29
+ when INTERESTED then process_interested
30
+ when PIECE then process_piece
31
+ when CHOKE then choke_message
32
+ when HANDSHAKE then process_handshake
33
+ end
34
+ end
35
+ end
36
+
37
+ def process_bitfield
38
+
39
+ end
40
+
41
+ def process_have
42
+
43
+ end
44
+
45
+ def process_piece
46
+
47
+ end
48
+
49
+ def process_handshake
50
+
51
+ end
52
+
53
+ def recv_data
54
+
15
55
  end
16
56
  end
17
57
  end
@@ -1,68 +1,175 @@
1
1
  module Torrenter
2
- class Reactor
3
- include Torrenter
4
- def initialize(peers, sha_list, piece_length, file_list)
5
- @peers = peers
6
- @master_index = Array.new(sha_list.size) { :free }
7
- @blocks = piece_length / BLOCK
8
- @file_list = file_list
9
- @piece_length = piece_length
10
- @sha_list = sha_list
11
- @data_size = if file_list.is_a?(Array)
12
- file_list.map { |f| f['length'] }.inject { |x, y| x + y }
13
- else
14
- file_list['length']
15
- end
16
- end
17
-
18
- def modify_index
19
- IO.write($data_dump, '', 0) unless File.exists?($data_dump)
20
- file_size = File.size($data_dump)
21
- 0.upto(@sha_list.size - 1) do |n|
22
- data = IO.read($data_dump, @piece_length, n * @piece_length) || ''
23
- @master_index[n] = :downloaded if Digest::SHA1.digest(data) == @sha_list[n]
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
24
27
  end
25
- $update = { index: indices }
26
- puts "#{@master_index.count(:downloaded)} pieces are downloaded already."
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 }
27
41
  end
28
42
 
29
43
  # sends the handshake messages.
30
44
 
31
45
  def message_reactor
32
- modify_index
33
- if !@master_index.all? { |index| index == :downloaded }
34
- @peers.each { |peer| peer.connect if active_peers.size < 8 }
35
- puts "You are now connected to #{active_peers.size} peers."
36
- loop do
37
- break if @master_index.all? { |piece| piece == :downloaded }
38
- @peers.each do |peer|
39
- $update = { index: indices, peer_count: peer_data }
40
- piece_count = @master_index.count(:downloaded)
41
-
42
- if peer.status
43
- peer.state(@master_index, @blocks) # unless peer.piece_index.all? { |piece| piece == :downloaded }
44
-
45
- if @master_index.count(:downloaded) > piece_count
46
- system("clear")
47
- puts download_bar + "Downloading from #{active_peers.size} active peers"
48
- end
49
- else
50
- @peers.each { |peer| peer.connect if Time.now.to_i % 60 == 0 }
46
+ connect_peers
47
+
48
+ puts "You are now connected to #{active_peers.size} peers."
49
+ loop do
50
+ break if finished?
51
+
52
+ if Time.now.to_i - @time == 1
53
+ @time_counter = 0
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)
51
69
  end
52
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
53
81
  end
54
- else
55
- # upload_data
82
+ reattempt_disconnected_peer if Time.now.to_i % 30 == 0
83
+
84
+ show_status
56
85
  end
57
- seperate_data_dump_into_files
58
86
  end
59
87
 
60
- def piece_done?(peer)
61
-
88
+ def piece_indices
89
+ active_peers.select { |peer| peer.piece_index }
90
+ .map { |peer| peer.piece_index }
62
91
  end
63
92
 
64
- def block_done?(peer)
65
-
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
118
+ else
119
+ i += 1
120
+ end
121
+ end
122
+
123
+ limit += 1 if index.nil?
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"\
138
+ end
139
+ end
140
+
141
+ def remaining
142
+ total_file_size - @byte_counter
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
149
+ end
150
+
151
+ def peer_indices
152
+ active_peers.map { |peer| peer.piece_index }
153
+ end
154
+
155
+ def data_remaining
156
+ (total_file_size - @byte_counter).fdiv(1024).fdiv(1024).round(2)
157
+ end
158
+
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)
66
173
  end
67
174
 
68
175
  def indices
@@ -77,17 +184,13 @@ module Torrenter
77
184
  end
78
185
  end
79
186
 
80
- def peer_data
81
- active_peers.map { |peer| "ip: #{peer.peer[:ip]} port:#{peer.peer[:port]}" }.join("\n")
82
- end
83
-
84
187
  def get_status(i)
85
188
  peer = @peers.find { |peer| peer.piece_index[i] == :downloading }
86
189
  (peer.buffer.bytesize + peer.piece_size).fdiv(@piece_length)
87
190
  end
88
191
 
89
192
  def active_peers
90
- @peers.select { |peer| peer.status }
193
+ @peers.select { |peer| peer.peer_state }
91
194
  end
92
195
 
93
196
  def index_percentages
@@ -104,42 +207,5 @@ module Torrenter
104
207
  def pieces(type)
105
208
  (@master_index.count(type).fdiv(@master_index.size) * 100).round
106
209
  end
107
-
108
- def stop_downloading
109
- @peers.each { |peer| peer.piece_index.map { |piece| piece = :downloaded} }
110
- end
111
-
112
- def seperate_data_dump_into_files
113
- if multiple_files?
114
- offset = 0
115
- folder = $data_dump[/.+(?=\.torrent-data)/] || FileUtils.mkdir($data_dump[/.+(?=\.torrent-data)/]).join
116
- @file_list.each do |file|
117
-
118
- length = file['length']
119
- filename = file['path'].pop
120
-
121
- if multiple_sub_folders?(file)
122
- subfolders = file['path'].join("/")
123
- folder = folder + "/" + subfolders
124
- FileUtils.mkdir_p("#{folder}", force: true)
125
- end
126
-
127
- File.open("#{folder}/#{filename}", 'a+') { |data| data << IO.read($data_dump, length, offset) }
128
-
129
- offset += length
130
- end
131
- else
132
- File.open("#{folder}/#{@file_list['name']}", 'w') { |data| data << File.read($data_dump) }
133
- end
134
- File.delete($data_dump)
135
- end
136
-
137
- def multiple_sub_folders?(file)
138
- file['path'].size > 1
139
- end
140
-
141
- def multiple_files?
142
- @file_list.is_a?(Array)
143
- end
144
210
  end
145
211
  end
@@ -0,0 +1,10 @@
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
@@ -0,0 +1,166 @@
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