distribustream 0.1.0 → 0.2.0

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.
@@ -0,0 +1,106 @@
1
+ #--
2
+ # Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
3
+ # All rights reserved. See COPYING for permissions.
4
+ #
5
+ # This source file is distributed as part of the
6
+ # DistribuStream file transfer system.
7
+ #
8
+ # See http://distribustream.rubyforge.org/
9
+ #++
10
+
11
+ require 'rubygems'
12
+ require 'eventmachine'
13
+
14
+ module PDTP
15
+ # EventMachine connection adapter for sending and receiving frames with 16-bit length prefixes
16
+ class Packet < EventMachine::Connection
17
+ # Class for processing length prefixes in packet frames
18
+ class Prefix
19
+ attr_reader :length
20
+
21
+ def initialize
22
+ # Constant at 2 bytes, but expandable if we desire
23
+ @length = 2
24
+ reset!
25
+ end
26
+
27
+ def read?
28
+ @length == @read
29
+ end
30
+
31
+ def append(data)
32
+ toread = @length - @read
33
+ new_data = data[0..(toread - 1)]
34
+
35
+ @data << new_data
36
+ @read += new_data.size
37
+ return nil unless @read == @length
38
+
39
+ @size = @data.unpack('n').first
40
+ result = data[toread..data.size]
41
+
42
+ return nil if result.nil? or result.empty?
43
+ result
44
+ end
45
+
46
+ def size
47
+ return false unless read?
48
+ @size
49
+ end
50
+
51
+ def reset!
52
+ @size = nil
53
+ @read = 0
54
+ @data = ''
55
+ end
56
+ end
57
+
58
+ def initialize(*args)
59
+ super
60
+ @prefix = Prefix.new
61
+ @buffer = ''
62
+ end
63
+
64
+ # Callback for processing incoming frames
65
+ def receive_data(data)
66
+ # Read data and append it to the length prefix unless it's already been read
67
+ data = @prefix.append(data) unless @prefix.read?
68
+ return if data.nil?
69
+
70
+ # If we've read the prefix, append the data
71
+ @buffer << data
72
+
73
+ # Don't do anything until we receive the specified amount of data
74
+ return unless @buffer.size >= @prefix.size
75
+
76
+ # Extract the specified amount of data and process it
77
+ data = @buffer[0..(@prefix.size - 1)]
78
+ receive_packet data
79
+
80
+ # Store any remaining data
81
+ remainder = @buffer[@prefix.size..@buffer.size]
82
+
83
+ # Reset the prefix and buffer since we've received a whole frame
84
+ @prefix.reset!
85
+ @buffer = ''
86
+
87
+ # Don't do anything if there's no more data
88
+ return if remainder.nil? or remainder.empty?
89
+
90
+ # Otherwise continue processing the data
91
+ receive_data remainder
92
+ end
93
+
94
+ # Stub for receiving a frame
95
+ def receive_packet(packet)
96
+ end
97
+
98
+ # Send a packet with a specified length prefix
99
+ def send_packet(data)
100
+ length = data.size
101
+ raise ArgumentError, 'oversize packet' if length >= 256**@prefix.length
102
+
103
+ send_data [length].pack('n') << data
104
+ end
105
+ end
106
+ end
@@ -11,7 +11,6 @@
11
11
  require 'rubygems'
12
12
  require 'eventmachine'
13
13
  require 'thread'
14
- require 'uri'
15
14
  require 'ipaddr'
16
15
 
17
16
  begin
@@ -20,6 +19,8 @@ rescue LoadError
20
19
  require 'json'
21
20
  end
22
21
 
22
+ require File.dirname(__FILE__) + '/packet.rb'
23
+
23
24
  module PDTP
24
25
  PROTOCOL_DEBUG=true
25
26
 
@@ -30,9 +31,8 @@ module PDTP
30
31
  end
31
32
 
32
33
  # EventMachine handler class for the PDTP protocol
33
- class Protocol < EventMachine::Protocols::LineAndTextProtocol
34
+ class Protocol < PDTP::Packet
34
35
  @@num_connections = 0
35
- @@listener = nil
36
36
  @@message_params = nil
37
37
  @connection_open = false
38
38
 
@@ -40,15 +40,10 @@ module PDTP
40
40
  @connection_open
41
41
  end
42
42
 
43
- #sets the listener class (Server or Client)
44
- def self.listener=(listener)
45
- @@listener = listener
46
- end
47
-
48
- def initialize(*args)
43
+ def initialize(data)
49
44
  user_data = nil
50
45
  @mutex = Mutex.new
51
- super
46
+ super data
52
47
  end
53
48
 
54
49
  #called by EventMachine after a connection has been established
@@ -64,7 +59,7 @@ module PDTP
64
59
 
65
60
  @@num_connections += 1
66
61
  @connection_open = true
67
- @@listener.connection_created(self) if @@listener.respond_to?(:connection_created)
62
+ connection_created if respond_to? :connection_created
68
63
  end
69
64
 
70
65
  attr_accessor :user_data #users of this class may store arbitrary data here
@@ -79,30 +74,25 @@ module PDTP
79
74
  end
80
75
  end
81
76
 
82
- #override this in a child class to handle messages
83
- def receive_message(command, message)
84
- @@listener.dispatch_message command, message, self
85
- end
86
-
87
77
  #debug routine: returns id of remote peer on this connection
88
78
  def remote_peer_id
89
79
  ret = user_data.client_id rescue nil
90
80
  ret || 'NOID'
91
81
  end
92
82
 
93
- #called for each line of text received over the wire
83
+ #called for each packet of data received over the wire
94
84
  #parses the JSON message and dispatches the message
95
- def receive_line line
85
+ def receive_packet(packet)
96
86
  begin
97
- line.chomp!
98
- @@log.debug "(#{remote_peer_id}) recv: " + line
99
- message = JSON.parse(line) rescue nil
100
- raise ProtocolError.new("JSON couldn't parse: #{line}") if message.nil?
87
+ packet.chomp!
88
+ @@log.debug "(#{remote_peer_id}) recv: " + packet
89
+ message = JSON.parse(packet) rescue nil
90
+ raise ProtocolError.new("JSON couldn't parse: #{packet}") if message.nil?
101
91
  Protocol.validate_message message
102
92
 
103
93
  command, options = message
104
94
  hash_to_range command, options
105
- receive_message command, options
95
+ receive_message(command, options) if respond_to? :receive_message
106
96
  rescue ProtocolError => e
107
97
  @@log.warn "(#{remote_peer_id}) PROTOCOL ERROR: #{e.to_s}"
108
98
  @@log.debug e.backtrace.join("\n")
@@ -166,17 +156,17 @@ module PDTP
166
156
  # Second entry is an options hash/object
167
157
  message = [command.to_s, opts]
168
158
 
169
- @mutex.synchronize do
159
+ #@mutex.synchronize do
170
160
  outstr = JSON.unparse(message) + "\n"
171
161
  @@log.debug "(#{remote_peer_id}) send: #{outstr.chomp}"
172
- send_data outstr
173
- end
162
+ send_packet outstr
163
+ #end
174
164
  end
175
165
 
176
166
  #called by EventMachine when a connection is closed
177
167
  def unbind
178
168
  @@num_connections -= 1
179
- @@listener.connection_destroyed(self) if @@listener.respond_to?(:connection_destroyed)
169
+ connection_destroyed if respond_to? :connection_destroyed
180
170
  @connection_open = false
181
171
  end
182
172
 
@@ -249,7 +239,7 @@ module PDTP
249
239
  mp = {}
250
240
 
251
241
  #must be the first message the client sends
252
- mp["client_info"]={
242
+ mp["register"]={
253
243
  "client_id"=>:string,
254
244
  "listen_port"=>:int
255
245
  }
@@ -277,7 +267,7 @@ module PDTP
277
267
  "url"=>:url,
278
268
  "range"=>:range,
279
269
  "peer_id"=>:string,
280
- "is_authorized"=>:bool
270
+ "authorized"=>:bool
281
271
  }
282
272
 
283
273
  mp["request"]={
@@ -8,133 +8,135 @@
8
8
  # See http://distribustream.rubyforge.org/
9
9
  #++
10
10
 
11
- require File.dirname(__FILE__)+'/trust.rb'
11
+ require File.dirname(__FILE__) + '/trust'
12
12
 
13
13
  module PDTP
14
- #stores information about a single connected client
15
- class ClientInfo
16
- attr_accessor :chunk_info, :trust
17
- attr_accessor :listen_port, :client_id
18
- attr_accessor :transfers
19
-
20
- def initialize
21
- @chunk_info=ChunkInfo.new
22
- @listen_port=6000 #default
23
- @trust=Trust.new
24
- @transfers=Hash.new
25
- end
14
+ class Server
15
+ #stores information about a single connected client
16
+ class ClientInfo
17
+ attr_accessor :chunk_info, :trust
18
+ attr_accessor :listen_port, :client_id
19
+ attr_accessor :transfers
26
20
 
27
- # returns true if this client wants the server to spawn a transfer for it
28
- def wants_download?
29
- transfer_state_allowed=5
30
- total_allowed=10
31
- transferring=0
32
- @transfers.each do |key, t|
33
- transferring=transferring+1 if t.verification_asked
34
- return false if transferring >= transfer_state_allowed
21
+ def initialize
22
+ @chunk_info=ChunkInfo.new
23
+ @listen_port=6000 #default
24
+ @trust=Trust.new
25
+ @transfers=Hash.new
35
26
  end
36
27
 
37
- @transfers.size < total_allowed
38
- end
39
-
40
- #this could have a different definition, but it works fine to use wants_download?
41
- alias_method :wants_upload?, :wants_download?
42
-
43
- #returns a list of all the stalled transfers this client is a part of
44
- def get_stalled_transfers
45
- stalled=[]
46
- timeout=20.0
47
- now=Time.now
48
- @transfers.each do |key,t|
49
- #only delete if we are the acceptor to prevent race conditions
50
- next if t.acceptor.user_data != self
51
- if now-t.creation_time > timeout and not t.verification_asked
52
- stalled << t
28
+ # returns true if this client wants the server to spawn a transfer for it
29
+ def wants_download?
30
+ transfer_state_allowed=5
31
+ total_allowed=10
32
+ transferring=0
33
+ @transfers.each do |key, t|
34
+ transferring=transferring+1 if t.verification_asked
35
+ return false if transferring >= transfer_state_allowed
53
36
  end
54
- end
55
- stalled
56
- end
57
- end
58
37
 
59
- #stores information about the chunks requested or provided by a client
60
- class ChunkInfo
61
- def initialize
62
- @files={}
63
- end
38
+ @transfers.size < total_allowed
39
+ end
64
40
 
65
- #each chunk can either be provided, requested, transfer, or none
66
- def provide(filename,range); set(filename,range,:provided) ; end
67
- def unprovide(filename,range); set(filename,range, :none); end
68
- def request(filename,range); set(filename,range, :requested); end
69
- def unrequest(filename,range); set(filename,range, :none); end
70
- def transfer(filename,range); set(filename,range, :transfer); end
71
-
72
- def provided?(filename,chunk); get(filename,chunk) == :provided; end
73
- def requested?(filename,chunk); get(filename,chunk) == :requested; end
74
-
75
- #returns a high priority requested chunk
76
- def high_priority_chunk
77
- #right now return any chunk
78
- @files.each do |name,file|
79
- file.each_index do |i|
80
- return [name,i] if file[i]==:requested
81
- end
82
- end
83
-
84
- nil
85
- end
41
+ #this could have a different definition, but it works fine to use wants_download?
42
+ alias_method :wants_upload?, :wants_download?
86
43
 
87
- #calls a block for each chunk of the specified type
88
- def each_chunk_of_type(type)
89
- @files.each do |name,file|
90
- file.each_index do |i|
91
- yield(name,i) if file[i]==type
44
+ #returns a list of all the stalled transfers this client is a part of
45
+ def get_stalled_transfers
46
+ stalled=[]
47
+ timeout=20.0
48
+ now=Time.now
49
+ @transfers.each do |key,t|
50
+ #only delete if we are the acceptor to prevent race conditions
51
+ next if t.acceptor.user_data != self
52
+ if now-t.creation_time > timeout and not t.verification_asked
53
+ stalled << t
54
+ end
92
55
  end
93
- end
56
+ stalled
57
+ end
94
58
  end
95
59
 
96
- class FileStats
97
- attr_accessor :file_chunks, :chunks_requested,:url
98
- attr_accessor :chunks_provided, :chunks_transferring
99
-
60
+ #stores information about the chunks requested or provided by a client
61
+ class ChunkInfo
100
62
  def initialize
101
- @url=""
102
- @file_chunks=0
103
- @chunks_requested=0
104
- @chunks_provided=0
105
- @chunks_transferring=0
63
+ @files={}
106
64
  end
107
- end
108
65
 
109
- #returns an array of FileStats objects for debug output
110
- def get_file_stats
111
- stats=[]
112
- @files.each do |name,file|
113
- fs=FileStats.new
114
- fs.file_chunks=file.size
115
- fs.url=name
116
- file.each do |chunk|
117
- fs.chunks_requested+=1 if chunk==:requested
118
- fs.chunks_provided+=1 if chunk==:provided
119
- fs.chunks_transferring+=1 if chunk==:transfer
66
+ #each chunk can either be provided, requested, transfer, or none
67
+ def provide(filename,range); set(filename,range,:provided) ; end
68
+ def unprovide(filename,range); set(filename,range, :none); end
69
+ def request(filename,range); set(filename,range, :requested); end
70
+ def unrequest(filename,range); set(filename,range, :none); end
71
+ def transfer(filename,range); set(filename,range, :transfer); end
72
+
73
+ def provided?(filename,chunk); get(filename,chunk) == :provided; end
74
+ def requested?(filename,chunk); get(filename,chunk) == :requested; end
75
+
76
+ #returns a high priority requested chunk
77
+ def high_priority_chunk
78
+ #right now return any chunk
79
+ @files.each do |name,file|
80
+ file.each_index do |i|
81
+ return [name,i] if file[i]==:requested
82
+ end
83
+ end
84
+
85
+ nil
86
+ end
87
+
88
+ #calls a block for each chunk of the specified type
89
+ def each_chunk_of_type(type)
90
+ @files.each do |name,file|
91
+ file.each_index do |i|
92
+ yield(name,i) if file[i]==type
93
+ end
94
+ end
95
+ end
96
+
97
+ class FileStats
98
+ attr_accessor :file_chunks, :chunks_requested,:url
99
+ attr_accessor :chunks_provided, :chunks_transferring
100
+
101
+ def initialize
102
+ @url=""
103
+ @file_chunks=0
104
+ @chunks_requested=0
105
+ @chunks_provided=0
106
+ @chunks_transferring=0
120
107
  end
121
- stats << fs
122
108
  end
123
-
124
- stats
125
- end
126
109
 
127
- #########
128
- protected
129
- #########
110
+ #returns an array of FileStats objects for debug output
111
+ def get_file_stats
112
+ stats=[]
113
+ @files.each do |name,file|
114
+ fs=FileStats.new
115
+ fs.file_chunks=file.size
116
+ fs.url=name
117
+ file.each do |chunk|
118
+ fs.chunks_requested+=1 if chunk==:requested
119
+ fs.chunks_provided+=1 if chunk==:provided
120
+ fs.chunks_transferring+=1 if chunk==:transfer
121
+ end
122
+ stats << fs
123
+ end
130
124
 
131
- def get(filename,chunk)
132
- @files[filename][chunk] rescue :neither
133
- end
125
+ stats
126
+ end
127
+
128
+ #########
129
+ protected
130
+ #########
134
131
 
135
- def set(filename,range,state)
136
- chunks=@files[filename]||=Array.new
137
- range.each { |i| chunks[i]=state }
132
+ def get(filename,chunk)
133
+ @files[filename][chunk] rescue :neither
134
+ end
135
+
136
+ def set(filename,range,state)
137
+ chunks=@files[filename]||=Array.new
138
+ range.each { |i| chunks[i]=state }
139
+ end
138
140
  end
139
141
  end
140
142
  end
@@ -0,0 +1,35 @@
1
+ #--
2
+ # Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
3
+ # All rights reserved. See COPYING for permissions.
4
+ #
5
+ # This source file is distributed as part of the
6
+ # DistribuStream file transfer system.
7
+ #
8
+ # See http://distribustream.rubyforge.org/
9
+ #++
10
+
11
+ require File.dirname(__FILE__) + '/../common/protocol'
12
+
13
+ module PDTP
14
+ class Server
15
+ class Connection < PDTP::Protocol
16
+ attr_accessor :server
17
+ attr_accessor :user_data
18
+
19
+ def connection_completed
20
+ raise(RuntimeError, 'server was never initialized') unless @server
21
+ @server.connection_created self
22
+ end
23
+
24
+ def connection_destroyed
25
+ raise(RuntimeError, 'server was never initialized') unless @server
26
+ @server.connection_destroyed self
27
+ end
28
+
29
+ def receive_message(command, message)
30
+ raise(RuntimeError, 'server was never initialized') unless @server
31
+ @server.dispatch_message command, message, self
32
+ end
33
+ end
34
+ end
35
+ end