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.
@@ -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
- def initialize(stream)
7
- @stream = stream
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'] || @stream['info']
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 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
53
- end
62
+ def multiple_files?
63
+ file_list.size > 1
54
64
  end
55
65
 
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
66
+ def write_paths
67
+ file_list.each { |file| write_path(file) }
62
68
  end
63
69
 
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) }
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
- File.delete($data_dump)
75
+ IO.write(file, '', 0)
87
76
  end
88
77
 
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
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 sub_folders
99
- stream['info']['files'].select { |fold| fold['path'].length > 1 }
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 multiple_sub_folders?(file)
103
- file['path'].length > 0
104
- end
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
- def multiple_files?
107
- file_list.is_a?(Array)
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
@@ -1,13 +1,17 @@
1
- module Torrenter
2
- class Tracker < TorrentReader
3
- def initialize(tracker_url)
4
- @tracker_url = tracker_url
5
- end
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
- 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)
14
+ return pool
11
15
  end
12
16
  end
13
17
  end