distribustream 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|