quartz_torrent 0.0.1

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.
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: