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.
- data/CHANGES +11 -0
- data/Rakefile +14 -10
- data/bin/dsclient +86 -22
- data/bin/dsseed +8 -79
- data/bin/dstream +20 -0
- data/conf/bigchunk.yml +1 -1
- data/conf/debug.yml +1 -1
- data/conf/example.yml +1 -1
- data/distribustream.gemspec +3 -3
- data/lib/pdtp/client/callbacks.rb +29 -0
- data/lib/pdtp/client/connection.rb +114 -0
- data/lib/pdtp/client/file_buffer.rb +93 -103
- data/lib/pdtp/client/file_service.rb +6 -3
- data/lib/pdtp/client/http_handler.rb +77 -0
- data/lib/pdtp/client/transfer.rb +13 -14
- data/lib/pdtp/client.rb +73 -156
- data/lib/pdtp/common/packet.rb +106 -0
- data/lib/pdtp/common/protocol.rb +19 -29
- data/lib/pdtp/server/client_info.rb +109 -107
- data/lib/pdtp/server/connection.rb +35 -0
- data/lib/pdtp/server/dispatcher.rb +370 -0
- data/lib/pdtp/server/file_service.rb +1 -1
- data/lib/pdtp/server/file_service_protocol.rb +116 -0
- data/lib/pdtp/server/stats_handler.rb +23 -0
- data/lib/pdtp/server/trust.rb +60 -58
- data/lib/pdtp/server.rb +45 -346
- metadata +12 -9
- data/bin/distribustream +0 -60
- data/lib/pdtp/client/file_buffer_spec.rb +0 -154
- data/lib/pdtp/client/protocol.rb +0 -66
- data/lib/pdtp/common/file_service_spec.rb +0 -91
- data/lib/pdtp/common/protocol_spec.rb +0 -68
- data/lib/pdtp/server/trust_spec.rb +0 -40
@@ -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
|
data/lib/pdtp/common/protocol.rb
CHANGED
@@ -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 <
|
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
|
-
|
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
|
-
|
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
|
83
|
+
#called for each packet of data received over the wire
|
94
84
|
#parses the JSON message and dispatches the message
|
95
|
-
def
|
85
|
+
def receive_packet(packet)
|
96
86
|
begin
|
97
|
-
|
98
|
-
@@log.debug "(#{remote_peer_id}) recv: " +
|
99
|
-
message = JSON.parse(
|
100
|
-
raise ProtocolError.new("JSON couldn't parse: #{
|
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
|
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
|
-
|
159
|
+
#@mutex.synchronize do
|
170
160
|
outstr = JSON.unparse(message) + "\n"
|
171
161
|
@@log.debug "(#{remote_peer_id}) send: #{outstr.chomp}"
|
172
|
-
|
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
|
-
|
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["
|
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
|
-
"
|
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
|
11
|
+
require File.dirname(__FILE__) + '/trust'
|
12
12
|
|
13
13
|
module PDTP
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
60
|
-
|
61
|
-
def initialize
|
62
|
-
@files={}
|
63
|
-
end
|
38
|
+
@transfers.size < total_allowed
|
39
|
+
end
|
64
40
|
|
65
|
-
|
66
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
56
|
+
stalled
|
57
|
+
end
|
94
58
|
end
|
95
59
|
|
96
|
-
|
97
|
-
|
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
|
-
@
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
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
|
-
|
132
|
-
|
133
|
-
|
125
|
+
stats
|
126
|
+
end
|
127
|
+
|
128
|
+
#########
|
129
|
+
protected
|
130
|
+
#########
|
134
131
|
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|