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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b84f8e49f69b5654aedf1edd2b894bf3d9d9a612
4
- data.tar.gz: 6aa38459649cc8e4c8ce770799f46a161967d457
3
+ metadata.gz: 3234eb20fcd3339fcecb2a52c4bfe3bdb0f84ee5
4
+ data.tar.gz: 88f9959f5111cb4c8bbc082a42a8bd7897b5c95a
5
5
  SHA512:
6
- metadata.gz: 29dbc732d5bb03d5fb76362bb5825622c9ecbf14cc03cb352ddc3d780238154a1b60084fcbcb6b7f9ab9f00a75b401516e3bd243d6822d56ce129180ac225d68
7
- data.tar.gz: 80d1d9d22e3186ebb45e555f70ae272c7beb472c39e3ea80796a83527ab50529e4b60a1bd7ed9b281b4fb243ce1cb2899af569a7d522e957c92d0b61046f5477
6
+ metadata.gz: ecb115dd277b70cd8769f2300d016b714042777be8665852fabe64f6c45a5bce5cab063b3ba97ad8625bc8eb3361401a63c30bae9e4e3508cc8f17c9a9f16546
7
+ data.tar.gz: e5a295b9a04b632801c3560fea6b2ce312830364fdfd06255bae7e3b649c040c160e5cb9ec06d4963ba33ded91e531e53ef71811d9f99da0fa344ffbfa0d459f
@@ -5,5 +5,8 @@ require 'net/http'
5
5
  require 'fileutils'
6
6
  require 'torrenter'
7
7
  file = ARGV[0]
8
- $data_dump = "#{file}-data"
9
- Torrenter::Torrent.new.start(file)
8
+ if File.file?(file)
9
+ Torrenter::Torrent.new.start(ARGV[0])
10
+ else
11
+ puts "That's not a file, silly."
12
+ end
@@ -2,57 +2,32 @@ require 'socket'
2
2
  require 'digest/sha1'
3
3
  require 'bencode'
4
4
  require 'fileutils'
5
- require 'pry'
6
- require 'torrenter/message/messager'
7
- require 'torrenter/message/message_types'
5
+ require 'torrenter/constants'
8
6
  require 'torrenter/peer'
9
- require 'torrenter/torrent_reader'
7
+ require 'torrenter/peer/buffer_state'
10
8
  require 'torrenter/reactor'
11
- require 'torrenter/http_tracker'
12
- require 'torrenter/udp_tracker'
13
-
14
- # reader just reads the torrent file
15
- # trackers just get peer ips and ports
16
- # peers just hold peer data
17
- # reactor uses the peer data to connect to the peers
18
- # the buffer state should be a class on its own.
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
- IO.write($data_dump, '', 0) if !File.exists?($data_dump)
25
- stream = BEncode.load_file(file)
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
- # returns an array of initialized UDP and/or HTTP Tracker class objects
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
- peers = @tracker.bound_peers
49
- reactor = Reactor.new(peers, stream)
50
- reactor.check_for_existing_data
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
@@ -6,6 +6,7 @@ module Torrenter
6
6
  HANDSHAKE = "T"
7
7
  HAVE = "\x04"
8
8
  BITFIELD = "\x05"
9
+ CANCEL = "\b"
9
10
  PIECE = "\a"
10
11
  CHOKE = "\x00"
11
12
  PROTOCOL = "\x13BitTorrent protocol\x00\x00\x00\x00\x00\x00\x00\x00"
@@ -1,27 +1,12 @@
1
1
  module Torrenter
2
2
  class Peer
3
- include Torrenter
4
-
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 = []
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
@@ -1,194 +1,45 @@
1
1
  module Torrenter
2
- class Reactor < Torrenter::TorrentReader
2
+ class Reactor
3
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
4
+ def initialize(trackers, piece_index)
5
+ @trackers = trackers
6
+ @piece_index = piece_index
12
7
  end
13
8
 
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 }
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
- connect_peers
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
- 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)
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
- i += 1
23
+ peer.connect if Time.now.to_i % 60 == 0
120
24
  end
121
25
  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"\
26
+ @piece_index.clean_peers
27
+ $status = @piece_index.to_json
138
28
  end
139
29
  end
140
30
 
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
31
+ def have
32
+ ->(index, peer) { @piece_index[index].add_peer(peer) }
149
33
  end
150
34
 
151
- def peer_indices
152
- active_peers.map { |peer| peer.piece_index }
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
- (@master_index.count(type).fdiv(@master_index.size) * 100).round
59
+ (@piece_index.count(type).fdiv(@piece_index.size) * 100).round
209
60
  end
210
61
  end
211
62
  end