torrenter 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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