quartz_torrent 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/bin/quartztorrent_download +127 -0
  2. data/bin/quartztorrent_download_curses +841 -0
  3. data/bin/quartztorrent_magnet_from_torrent +32 -0
  4. data/bin/quartztorrent_show_info +62 -0
  5. data/lib/quartz_torrent.rb +2 -0
  6. data/lib/quartz_torrent/bitfield.rb +314 -0
  7. data/lib/quartz_torrent/blockstate.rb +354 -0
  8. data/lib/quartz_torrent/classifiedpeers.rb +95 -0
  9. data/lib/quartz_torrent/extension.rb +37 -0
  10. data/lib/quartz_torrent/filemanager.rb +543 -0
  11. data/lib/quartz_torrent/formatter.rb +92 -0
  12. data/lib/quartz_torrent/httptrackerclient.rb +121 -0
  13. data/lib/quartz_torrent/interruptiblesleep.rb +27 -0
  14. data/lib/quartz_torrent/log.rb +132 -0
  15. data/lib/quartz_torrent/magnet.rb +92 -0
  16. data/lib/quartz_torrent/memprofiler.rb +27 -0
  17. data/lib/quartz_torrent/metainfo.rb +221 -0
  18. data/lib/quartz_torrent/metainfopiecestate.rb +265 -0
  19. data/lib/quartz_torrent/peer.rb +145 -0
  20. data/lib/quartz_torrent/peerclient.rb +1627 -0
  21. data/lib/quartz_torrent/peerholder.rb +123 -0
  22. data/lib/quartz_torrent/peermanager.rb +170 -0
  23. data/lib/quartz_torrent/peermsg.rb +502 -0
  24. data/lib/quartz_torrent/peermsgserialization.rb +102 -0
  25. data/lib/quartz_torrent/piecemanagerrequestmetadata.rb +12 -0
  26. data/lib/quartz_torrent/rate.rb +58 -0
  27. data/lib/quartz_torrent/ratelimit.rb +48 -0
  28. data/lib/quartz_torrent/reactor.rb +949 -0
  29. data/lib/quartz_torrent/regionmap.rb +124 -0
  30. data/lib/quartz_torrent/semaphore.rb +43 -0
  31. data/lib/quartz_torrent/trackerclient.rb +271 -0
  32. data/lib/quartz_torrent/udptrackerclient.rb +70 -0
  33. data/lib/quartz_torrent/udptrackermsg.rb +250 -0
  34. data/lib/quartz_torrent/util.rb +100 -0
  35. metadata +195 -0
@@ -0,0 +1,250 @@
1
+ module QuartzTorrent
2
+ class UdpTrackerMessage
3
+ ActionConnect = 0
4
+ ActionAnnounce = 1
5
+ ActionScrape = 2
6
+ ActionError = 3
7
+
8
+ EventNone = 0
9
+ EventCompleted = 1
10
+ EventStarted = 2
11
+ EventStopped = 3
12
+
13
+ # Pack the number 'num' as a network byte order signed integer, 'len' bytes long.
14
+ # Negative numbers are written in two's-compliment notation.
15
+ def self.packAsNetworkOrder(num, len)
16
+ result = ""
17
+ len.times do
18
+ result << (num & 0xff)
19
+ num >>= 8
20
+ end
21
+ result.reverse
22
+ end
23
+
24
+ # Unpack the number stored in 'str' assuming it is a network byte order signed integer.
25
+ # Negative numbers are assumed to be in two's-compliment notation.
26
+ def self.unpackNetworkOrder(str, len = nil)
27
+ result = 0
28
+ first = true
29
+ negative = false
30
+ index = 0
31
+ str.each_byte do |b|
32
+ if first
33
+ negative = (b & 0x80) > 0
34
+ first = false
35
+ end
36
+ result <<= 8
37
+ result += b
38
+ index += 1
39
+ break if len && index == len
40
+ end
41
+ if negative
42
+ # Internally the value is being represented unsigned. To make it signed,
43
+ # we first take the ones compliment of the value, remove the sign bit, and add one.
44
+ # This takes the two's compliment of the two's compliment of the number, which results
45
+ # in the absolute value of the original number. Finally we use the unary - operator to
46
+ # make the value negative.
47
+ result = -(((~result) & 0x7fffffffffffffff) + 1)
48
+ end
49
+ result
50
+ end
51
+ end
52
+
53
+ # Superclass for UDP tracker requests.
54
+ class UdpTrackerRequest
55
+ def initialize
56
+ @connectionId = 0x41727101980
57
+ @action = UdpTrackerMessage::ActionConnect
58
+ # Get a number that is a valid 32-bit signed integer.
59
+ @transactionId = rand(0x10000000)-8000000
60
+ end
61
+
62
+ # Get the connectionId as an integer
63
+ attr_reader :connectionId
64
+ # Get the action as an integer. Should be one of the UdpTrackerMessage::Action* constants
65
+ attr_reader :action
66
+ # Get the transactionId as an integer.
67
+ attr_reader :transactionId
68
+
69
+ # Set the connectionId. Value must be an integer
70
+ def connectionId=(v)
71
+ raise "The 'connectionId' field must be an integer" if ! v.is_a?(Integer)
72
+ @connectionId = v
73
+ end
74
+ # Set the action. Value should be one of the UdpTrackerMessage::Action* constants
75
+ def action=(v)
76
+ raise "The 'action' field must be an integer" if ! v.is_a?(Integer)
77
+ @action = v
78
+ end
79
+ # Set the transactionId. Value must be an integer. If not set a random number is used as per the specification.
80
+ def transactionId=(v)
81
+ raise "The 'transactionId' field must be an integer" if ! v.is_a?(Integer)
82
+ @transactionId = v
83
+ end
84
+ end
85
+
86
+ # Superclass for UDP tracker responses
87
+ class UdpTrackerResponse
88
+ def initialize
89
+ @connectionId = nil
90
+ @action = nil
91
+ @transactionId = nil
92
+ end
93
+
94
+ # ConnectionId as an integer
95
+ attr_accessor :connectionId
96
+ # Get the action as an integer. Should be one of the UdpTrackerMessage::Action* constants
97
+ attr_accessor :action
98
+ # Get the transactionId as an integer
99
+ attr_accessor :transactionId
100
+ end
101
+
102
+ class UdpTrackerConnectRequest < UdpTrackerRequest
103
+ def serialize
104
+ result = UdpTrackerMessage::packAsNetworkOrder(@connectionId, 8)
105
+ result << UdpTrackerMessage::packAsNetworkOrder(@action, 4)
106
+ result << UdpTrackerMessage::packAsNetworkOrder(@transactionId, 4)
107
+ result
108
+ end
109
+ end
110
+
111
+ class UdpTrackerConnectResponse < UdpTrackerResponse
112
+ def initialize
113
+ super
114
+ end
115
+
116
+ def self.unserialize(msg)
117
+ raise "Invalid connect response: it is #{msg.length} when it must be at least 16" if msg.length < 16
118
+ result = UdpTrackerConnectResponse.new
119
+ result.action = UdpTrackerMessage::unpackNetworkOrder(msg,4)
120
+ result.transactionId = UdpTrackerMessage::unpackNetworkOrder(msg[4,4],4)
121
+ result.connectionId = UdpTrackerMessage::unpackNetworkOrder(msg[8,8],8)
122
+ raise "Invalid connect response: action is not connect" if result.action != UdpTrackerMessage::ActionConnect
123
+ result
124
+ end
125
+
126
+ def self.tohex(str)
127
+ result = ""
128
+ str.each_byte do |b|
129
+ result << b.to_s(16)
130
+ end
131
+ result
132
+ end
133
+ end
134
+
135
+ class UdpTrackerAnnounceRequest < UdpTrackerRequest
136
+ def initialize(connectionId)
137
+ super()
138
+ @connectionId = connectionId
139
+ @action = UdpTrackerMessage::ActionAnnounce
140
+ @infoHash = nil
141
+ @peerId = nil
142
+ @downloaded = nil
143
+ @left = nil
144
+ @uploaded = nil
145
+ @event = nil
146
+ # 0 means allow tracker to assume the sender's IP address is the one it's looking for
147
+ # http://www.rasterbar.com/products/libtorrent/udp_tracker_protocol.html
148
+ @ipAddress = 0
149
+ @key = rand(0xffffffff)
150
+ # Number of peers requested: default.
151
+ @numWant = -1
152
+ @port = nil
153
+ end
154
+
155
+ attr_accessor :infoHash
156
+ attr_accessor :peerId
157
+ attr_reader :downloaded
158
+ attr_reader :left
159
+ attr_reader :uploaded
160
+ attr_reader :event
161
+ attr_accessor :ipAddress
162
+ attr_accessor :key
163
+ attr_accessor :numWant
164
+ attr_accessor :port
165
+
166
+ def downloaded=(v)
167
+ raise "The 'downloaded' field must be an integer" if ! v.is_a?(Integer)
168
+ @downloaded = v
169
+ end
170
+
171
+ def left=(v)
172
+ raise "The 'left' field must be an integer" if ! v.is_a?(Integer)
173
+ @left = v
174
+ end
175
+
176
+ def uploaded=(v)
177
+ raise "The 'uploaded' field must be an integer" if ! v.is_a?(Integer)
178
+ @uploaded = v
179
+ end
180
+
181
+ def event=(v)
182
+ raise "The 'event' field must be an integer" if ! v.is_a?(Integer)
183
+ @event = v
184
+ end
185
+
186
+ def numWant=(v)
187
+ raise "The 'numWant' field must be an integer" if ! v.is_a?(Integer)
188
+ @numWant = v
189
+ end
190
+
191
+ def port=(v)
192
+ raise "The 'port' field must be an integer" if ! v.is_a?(Integer)
193
+ @port = v
194
+ end
195
+
196
+ def serialize
197
+ result = UdpTrackerMessage::packAsNetworkOrder(@connectionId, 8)
198
+ result << UdpTrackerMessage::packAsNetworkOrder(@action, 4)
199
+ result << UdpTrackerMessage::packAsNetworkOrder(@transactionId, 4)
200
+ result << infoHash
201
+ result << peerId
202
+ result << UdpTrackerMessage::packAsNetworkOrder(@downloaded, 8)
203
+ result << UdpTrackerMessage::packAsNetworkOrder(@left, 8)
204
+ result << UdpTrackerMessage::packAsNetworkOrder(@uploaded, 8)
205
+ result << UdpTrackerMessage::packAsNetworkOrder(@event, 4)
206
+ result << UdpTrackerMessage::packAsNetworkOrder(@ipAddress, 4)
207
+ result << UdpTrackerMessage::packAsNetworkOrder(@key, 4)
208
+ result << UdpTrackerMessage::packAsNetworkOrder(@numWant, 4)
209
+ result << UdpTrackerMessage::packAsNetworkOrder(@port, 2)
210
+ result
211
+ end
212
+ end
213
+
214
+ class UdpTrackerAnnounceResponse < UdpTrackerResponse
215
+ def initialize
216
+ super
217
+ @interval = nil
218
+ @leechers = nil
219
+ @seeders = nil
220
+ @ips = []
221
+ @ports = []
222
+ end
223
+
224
+ attr_accessor :interval
225
+ attr_accessor :leechers
226
+ attr_accessor :seeders
227
+ attr_accessor :ips
228
+ attr_accessor :ports
229
+
230
+ def self.unserialize(msg)
231
+ raise "Invalid connect response: it is #{msg.length} when it must be at least 20" if msg.length < 20
232
+ result = UdpTrackerAnnounceResponse.new
233
+ result.action = UdpTrackerMessage::unpackNetworkOrder(msg,4)
234
+ result.transactionId = UdpTrackerMessage::unpackNetworkOrder(msg[4,4],4)
235
+ result.interval = UdpTrackerMessage::unpackNetworkOrder(msg[8,4],4)
236
+ result.leechers = UdpTrackerMessage::unpackNetworkOrder(msg[12,4],4)
237
+ result.seeders = UdpTrackerMessage::unpackNetworkOrder(msg[16,4],4)
238
+
239
+ index = 20
240
+ while index+6 < msg.length
241
+ result.ips.push msg[index,4]
242
+ result.ports.push msg[index+4,2]
243
+ index += 6
244
+ end
245
+ result
246
+ end
247
+ end
248
+
249
+
250
+ end
@@ -0,0 +1,100 @@
1
+ class Hash
2
+ # Treat the array as a hash of lists. This method will append 'value' to the list
3
+ # at key 'key' in the hash. If there is no list for 'key', one is created.
4
+ def pushToList(key, value)
5
+ list = self[key]
6
+ if ! list
7
+ list = []
8
+ self[key] = list
9
+ end
10
+ list.push value
11
+ end
12
+ end
13
+
14
+ module QuartzTorrent
15
+ # This is Linux specific: system call number for gettid
16
+ SYSCALL_GETTID = 224
17
+
18
+ # Return a hex string representing the bytes in the passed string.
19
+ def self.bytesToHex(v, addSpaces = nil)
20
+ s = ""
21
+ v.each_byte{ |b|
22
+ hex = b.to_s(16)
23
+ hex = "0" + hex if hex.length == 1
24
+ s << hex
25
+ s << " " if addSpaces == :add_spaces
26
+ }
27
+ s
28
+ end
29
+
30
+ # Given a hex string representing a sequence of bytes, convert it the the original bytes. Inverse of bytesToHex.
31
+ def self.hexToBytes(v)
32
+ [v].pack "H*"
33
+ end
34
+
35
+ # Shuffle the subset of elements in the given array between start and start+length-1 inclusive.
36
+ def self.arrayShuffleRange!(array, start, length)
37
+ raise "Invalid range" if start + length > array.size
38
+
39
+ (start+length).downto(start+1) do |i|
40
+ r = start + rand(i-start)
41
+ array[r], array[i-1] = array[i-1], array[r]
42
+ end
43
+ true
44
+ end
45
+
46
+ # Store Linux Lightweight process ids (LWPID) on each thread.
47
+ # If this is called a while before logBacktraces the backtraces will
48
+ # include lwpids.
49
+ def self.setThreadLwpid(thread = nil)
50
+ # This function works by calling the GETTID system call in Linux. That
51
+ # system call must be called in the thread that we want to get the lwpid of,
52
+ # but the user may not have created those threads and so can't call the system call
53
+ # in those threads (think Sinatra). To get around this this function runs code in the
54
+ # thread by adding a trace function to the thread, and on the first traced operation
55
+ # stores the LWPID on the thread and unregisters itself.
56
+
57
+ isLinux = RUBY_PLATFORM.downcase.include?("linux")
58
+ return if !isLinux
59
+
60
+ tracer = Proc.new do
61
+ Thread.current[:lwpid] = syscall(SYSCALL_GETTID) if ! Thread.current[:lwpid] && isLinux
62
+ Thread.current.set_trace_func(nil)
63
+ end
64
+
65
+ if thread
66
+ thread.set_trace_func(tracer)
67
+ else
68
+ Thread.list.each { |thread| thread.set_trace_func(tracer) }
69
+ end
70
+ end
71
+
72
+ # Log backtraces of all threads currently running. The threads are logged to the
73
+ # passed io, or if that's nil they are written to the logger named 'util' at error level.
74
+ def self.logBacktraces(io)
75
+ logger = nil
76
+ logger = LogManager.getLogger("util") if ! io
77
+ isLinux = RUBY_PLATFORM.downcase.include?("linux")
78
+
79
+ Thread.list.each do |thread|
80
+ lwpid = ""
81
+
82
+ setThreadLwpid thread if ! thread[:lwpid] && isLinux
83
+ lwpid = " [lwpid #{thread[:lwpid]}]" if thread[:lwpid]
84
+
85
+ msg = "Thread #{thread[:name]} #{thread.object_id}#{lwpid}: #{thread.status}\n " + (thread.backtrace ? thread.backtrace.join("\n ") : "no backtrace")
86
+ if io
87
+ io.puts msg
88
+ else
89
+ logger.error msg
90
+ end
91
+ end
92
+ end
93
+
94
+ # Method to set a few thread-local variables useful in debugging. Threads should call this when started.
95
+ def self.initThread(name)
96
+ Thread.current[:name] = name
97
+ end
98
+
99
+ end
100
+
metadata ADDED
@@ -0,0 +1,195 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quartz_torrent
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jeff Williams
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bencode
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.8'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '0.8'
30
+ - !ruby/object:Gem::Dependency
31
+ name: pqueue
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '2.0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '2.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: base32
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '0.2'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.2'
62
+ - !ruby/object:Gem::Dependency
63
+ name: log4r
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '1.1'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '1.1'
78
+ - !ruby/object:Gem::Dependency
79
+ name: minitest
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: yard
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: redcarpet
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: A pure ruby bittorrent library
127
+ email:
128
+ executables:
129
+ - quartztorrent_download
130
+ - quartztorrent_download_curses
131
+ - quartztorrent_magnet_from_torrent
132
+ - quartztorrent_show_info
133
+ extensions: []
134
+ extra_rdoc_files: []
135
+ files:
136
+ - lib/quartz_torrent.rb
137
+ - lib/quartz_torrent/blockstate.rb
138
+ - lib/quartz_torrent/memprofiler.rb
139
+ - lib/quartz_torrent/regionmap.rb
140
+ - lib/quartz_torrent/httptrackerclient.rb
141
+ - lib/quartz_torrent/interruptiblesleep.rb
142
+ - lib/quartz_torrent/extension.rb
143
+ - lib/quartz_torrent/util.rb
144
+ - lib/quartz_torrent/log.rb
145
+ - lib/quartz_torrent/metainfo.rb
146
+ - lib/quartz_torrent/trackerclient.rb
147
+ - lib/quartz_torrent/peermsgserialization.rb
148
+ - lib/quartz_torrent/semaphore.rb
149
+ - lib/quartz_torrent/reactor.rb
150
+ - lib/quartz_torrent/metainfopiecestate.rb
151
+ - lib/quartz_torrent/peer.rb
152
+ - lib/quartz_torrent/peerclient.rb
153
+ - lib/quartz_torrent/formatter.rb
154
+ - lib/quartz_torrent/piecemanagerrequestmetadata.rb
155
+ - lib/quartz_torrent/peerholder.rb
156
+ - lib/quartz_torrent/rate.rb
157
+ - lib/quartz_torrent/peermanager.rb
158
+ - lib/quartz_torrent/bitfield.rb
159
+ - lib/quartz_torrent/udptrackermsg.rb
160
+ - lib/quartz_torrent/ratelimit.rb
161
+ - lib/quartz_torrent/classifiedpeers.rb
162
+ - lib/quartz_torrent/peermsg.rb
163
+ - lib/quartz_torrent/filemanager.rb
164
+ - lib/quartz_torrent/magnet.rb
165
+ - lib/quartz_torrent/udptrackerclient.rb
166
+ - bin/quartztorrent_download
167
+ - bin/quartztorrent_download_curses
168
+ - bin/quartztorrent_magnet_from_torrent
169
+ - bin/quartztorrent_show_info
170
+ homepage: https://github.com/jeffwilliams/quartz-torrent
171
+ licenses: []
172
+ post_install_message:
173
+ rdoc_options: []
174
+ require_paths:
175
+ - lib
176
+ required_ruby_version: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ! '>='
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ required_rubygems_version: !ruby/object:Gem::Requirement
183
+ none: false
184
+ requirements:
185
+ - - ! '>='
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ requirements: []
189
+ rubyforge_project:
190
+ rubygems_version: 1.8.23
191
+ signing_key:
192
+ specification_version: 3
193
+ summary: A bittorrent library
194
+ test_files: []
195
+ has_rdoc: