distribustream 0.1.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 +2 -0
- data/COPYING +674 -0
- data/README +107 -0
- data/Rakefile +44 -0
- data/bin/distribustream +60 -0
- data/bin/dsclient +43 -0
- data/bin/dsseed +103 -0
- data/conf/bigchunk.yml +18 -0
- data/conf/debug.yml +18 -0
- data/conf/example.yml +18 -0
- data/distribustream.gemspec +20 -0
- data/lib/pdtp/client.rb +195 -0
- data/lib/pdtp/client/file_buffer.rb +128 -0
- data/lib/pdtp/client/file_buffer_spec.rb +154 -0
- data/lib/pdtp/client/file_service.rb +60 -0
- data/lib/pdtp/client/protocol.rb +66 -0
- data/lib/pdtp/client/transfer.rb +229 -0
- data/lib/pdtp/common/common_init.rb +122 -0
- data/lib/pdtp/common/file_service.rb +69 -0
- data/lib/pdtp/common/file_service_spec.rb +91 -0
- data/lib/pdtp/common/protocol.rb +346 -0
- data/lib/pdtp/common/protocol_spec.rb +68 -0
- data/lib/pdtp/server.rb +368 -0
- data/lib/pdtp/server/client_info.rb +140 -0
- data/lib/pdtp/server/file_service.rb +89 -0
- data/lib/pdtp/server/transfer.rb +62 -0
- data/lib/pdtp/server/trust.rb +88 -0
- data/lib/pdtp/server/trust_spec.rb +40 -0
- metadata +114 -0
@@ -0,0 +1,122 @@
|
|
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
|
+
#Provides functions used for initialization by both the client and server
|
12
|
+
|
13
|
+
require 'optparse'
|
14
|
+
require 'logger'
|
15
|
+
|
16
|
+
require File.dirname(__FILE__) + '/protocol'
|
17
|
+
|
18
|
+
STDOUT.sync=true
|
19
|
+
STDERR.sync=true
|
20
|
+
|
21
|
+
@@log=Logger.new(STDOUT)
|
22
|
+
@@log.datetime_format=""
|
23
|
+
|
24
|
+
CONFIG_TYPES = {
|
25
|
+
:host => :string,
|
26
|
+
:vhost => :string,
|
27
|
+
:port => :int,
|
28
|
+
:listen_port => :int,
|
29
|
+
:file_root => :string,
|
30
|
+
:quiet => :bool,
|
31
|
+
:chunk_size => :int,
|
32
|
+
:request_url => :string
|
33
|
+
}
|
34
|
+
|
35
|
+
#prints banner and loads config file
|
36
|
+
def common_init(program_name, config = nil)
|
37
|
+
@@config = config || {
|
38
|
+
:host => '0.0.0.0',
|
39
|
+
:port => 6086, #server port
|
40
|
+
:listen_port => 8000, #client listen port
|
41
|
+
:file_root => '.',
|
42
|
+
:chunk_size => 5000,
|
43
|
+
:quiet => true
|
44
|
+
}
|
45
|
+
|
46
|
+
config_filename=nil
|
47
|
+
|
48
|
+
unless config
|
49
|
+
OptionParser.new do |opts|
|
50
|
+
opts.banner = "Usage: #{program_name} [options]"
|
51
|
+
opts.on("--config CONFIGFILE", "Load specified config file.") do |c|
|
52
|
+
config_filename=c
|
53
|
+
end
|
54
|
+
opts.on("--help", "Prints this usage info.") do
|
55
|
+
puts opts
|
56
|
+
exit
|
57
|
+
end
|
58
|
+
end.parse!
|
59
|
+
end
|
60
|
+
|
61
|
+
puts "#{program_name} starting. Run '#{program_name} --help' for more info."
|
62
|
+
|
63
|
+
load_config_file(config_filename) unless config
|
64
|
+
|
65
|
+
begin
|
66
|
+
@@config[:file_root]=File.expand_path(@@config[:file_root])
|
67
|
+
rescue
|
68
|
+
puts "Invalid path specified for file_root"
|
69
|
+
return
|
70
|
+
end
|
71
|
+
|
72
|
+
puts "@@config=#{@@config.inspect}"
|
73
|
+
validate_config_options
|
74
|
+
handle_config_options
|
75
|
+
end
|
76
|
+
|
77
|
+
#loads a config file specified by config_filename
|
78
|
+
def load_config_file(config_filename)
|
79
|
+
if config_filename.nil?
|
80
|
+
puts "No config file specified. Using defaults."
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
confstr=File.read(config_filename) rescue nil
|
85
|
+
if confstr.nil?
|
86
|
+
puts "Unable to open config file: #{config_filename}"
|
87
|
+
exit
|
88
|
+
end
|
89
|
+
|
90
|
+
begin
|
91
|
+
new_config = YAML.load confstr
|
92
|
+
@@config.merge!(new_config)
|
93
|
+
@@config[:vhost] ||= @@config[:host] # Use host as vhost unless specified
|
94
|
+
rescue Exception => e
|
95
|
+
puts "Error parsing config file: #{config_filename}"
|
96
|
+
puts e
|
97
|
+
exit
|
98
|
+
end
|
99
|
+
|
100
|
+
puts "Loaded config file: #{config_filename}"
|
101
|
+
end
|
102
|
+
|
103
|
+
#make sure all the config options are of the right type
|
104
|
+
def validate_config_options
|
105
|
+
@@config.each do |key,val|
|
106
|
+
type=CONFIG_TYPES[key]
|
107
|
+
if type.nil?
|
108
|
+
puts "Unknown parameter: #{key}"
|
109
|
+
exit
|
110
|
+
end
|
111
|
+
|
112
|
+
unless PDTP::Protocol.obj_matches_type?(val,type)
|
113
|
+
puts "Parameter: #{key} is not of type: #{type}"
|
114
|
+
exit
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
#responds to config options that are used by both client and server
|
120
|
+
def handle_config_options
|
121
|
+
@@log.level=Logger::INFO if @@config[:quiet]
|
122
|
+
end
|
@@ -0,0 +1,69 @@
|
|
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
|
+
module PDTP
|
12
|
+
#provides information about a single file on the network
|
13
|
+
class FileInfo
|
14
|
+
attr_accessor :file_size, :base_chunk_size, :streaming
|
15
|
+
|
16
|
+
#number of chunks in the file
|
17
|
+
def num_chunks
|
18
|
+
return 0 if @file_size==0
|
19
|
+
(@file_size - 1) / @base_chunk_size + 1
|
20
|
+
end
|
21
|
+
|
22
|
+
#size of the specified chunk
|
23
|
+
def chunk_size(chunkid)
|
24
|
+
raise "Invalid chunkid #{chunkid}" if chunkid<0 or chunkid>=num_chunks
|
25
|
+
chunkid == num_chunks - 1 ? @file_size - @base_chunk_size * chunkid : @base_chunk_size
|
26
|
+
end
|
27
|
+
|
28
|
+
#range of bytes taken up by this chunk in the entire file
|
29
|
+
def chunk_range(chunkid)
|
30
|
+
start_byte = chunkid * @base_chunk_size
|
31
|
+
end_byte = start_byte + chunk_size(chunkid) - 1
|
32
|
+
start_byte..end_byte
|
33
|
+
end
|
34
|
+
|
35
|
+
#returns the chunkid that contains the requested byte offset
|
36
|
+
def chunk_from_offset(offset)
|
37
|
+
raise "Invalid offset #{offset}" if offset < 0 or offset >= @file_size
|
38
|
+
offset / @base_chunk_size
|
39
|
+
end
|
40
|
+
|
41
|
+
#takes a byte_range in the file and returns an equivalent chunk range
|
42
|
+
#if exclude_partial is true, chunks that are not completely covered by the byte range are left out
|
43
|
+
def chunk_range_from_byte_range(byte_range,exclude_partial=true)
|
44
|
+
min=chunk_from_offset(byte_range.first)
|
45
|
+
min+=1 if exclude_partial and byte_range.first > min*@base_chunk_size
|
46
|
+
|
47
|
+
max_byte=byte_range.last
|
48
|
+
max_byte=@file_size-1 if max_byte==-1 or max_byte>=@file_size
|
49
|
+
max=chunk_from_offset(max_byte)
|
50
|
+
max-=1 if exclude_partial and max_byte<chunk_range(max).last
|
51
|
+
min..max
|
52
|
+
end
|
53
|
+
|
54
|
+
#returns a string containing the data for this chunk
|
55
|
+
#range specifies a range of bytes local to this chunk
|
56
|
+
#implemented in Client and Server file services
|
57
|
+
def chunk_data(chunkid,range=nil)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# base class for ClientFileService and ServerFileService.
|
62
|
+
# provides shared functionality
|
63
|
+
|
64
|
+
class FileService
|
65
|
+
#returns a FileInfo class associated with the url, or nil if the file isnt known
|
66
|
+
def get_info(url)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,91 @@
|
|
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__) + '/file_service'
|
12
|
+
|
13
|
+
describe "A FileInfo with chunk_size=1" do
|
14
|
+
before(:each) do
|
15
|
+
@fi=PDTP::FileInfo.new
|
16
|
+
@fi.file_size=5
|
17
|
+
@fi.base_chunk_size=1
|
18
|
+
end
|
19
|
+
|
20
|
+
it "chunk_size works" do
|
21
|
+
@fi.chunk_size(0).should == 1
|
22
|
+
@fi.chunk_size(3).should == 1
|
23
|
+
@fi.chunk_size(4).should == 1
|
24
|
+
|
25
|
+
proc{ @fi.chunk_size(-1)}.should raise_error
|
26
|
+
proc{ @fi.chunk_size(5)}.should raise_error
|
27
|
+
end
|
28
|
+
|
29
|
+
it "num_chunks works" do
|
30
|
+
@fi.num_chunks.should == 5
|
31
|
+
end
|
32
|
+
|
33
|
+
it "chunk_from_offset works" do
|
34
|
+
@fi.chunk_from_offset(0).should == 0
|
35
|
+
@fi.chunk_from_offset(4).should == 4
|
36
|
+
proc{@fi.chunk_from_offset(5)}.should raise_error
|
37
|
+
end
|
38
|
+
|
39
|
+
it "chunk_range_from_byte_range works" do
|
40
|
+
@fi.chunk_range_from_byte_range(0..4,false).should == (0..4)
|
41
|
+
@fi.chunk_range_from_byte_range(0..4,true).should == (0..4)
|
42
|
+
proc{@fi.chunk_range_from_byte_range(-1..3,true)}.should raise_error
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "A FileInfo with chunk_size=256 and file_size=768" do
|
48
|
+
before(:each) do
|
49
|
+
@fi=PDTP::FileInfo.new
|
50
|
+
@fi.base_chunk_size=256
|
51
|
+
@fi.file_size=768
|
52
|
+
end
|
53
|
+
|
54
|
+
it "chunk_size works" do
|
55
|
+
@fi.chunk_size(0).should == 256
|
56
|
+
@fi.chunk_size(2).should == 256
|
57
|
+
proc{@fi.chunk_size(3)}.should raise_error
|
58
|
+
end
|
59
|
+
|
60
|
+
it "num_chunks works" do
|
61
|
+
@fi.num_chunks.should == 3
|
62
|
+
end
|
63
|
+
|
64
|
+
it "chunk_from_offset works" do
|
65
|
+
@fi.chunk_from_offset(256).should == 1
|
66
|
+
@fi.chunk_from_offset(255).should == 0
|
67
|
+
end
|
68
|
+
|
69
|
+
it "chunk_range_from_byte_range works" do
|
70
|
+
@fi.chunk_range_from_byte_range(256..511,true).should == (1..1)
|
71
|
+
@fi.chunk_range_from_byte_range(256..511,false).should == (1..1)
|
72
|
+
@fi.chunk_range_from_byte_range(255..512,true).should == (1..1)
|
73
|
+
@fi.chunk_range_from_byte_range(255..512,false).should == (0..2)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "A FileInfo with chunk_size=256 and file_size=255" do
|
78
|
+
before(:each) do
|
79
|
+
@fi=PDTP::FileInfo.new
|
80
|
+
@fi.base_chunk_size=256
|
81
|
+
@fi.file_size=255
|
82
|
+
end
|
83
|
+
|
84
|
+
it "num_chunks works" do
|
85
|
+
@fi.num_chunks.should ==1
|
86
|
+
end
|
87
|
+
|
88
|
+
it "chunk_from_offset works" do
|
89
|
+
@fi.chunk_from_offset(254).should == 0
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,346 @@
|
|
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
|
+
require 'thread'
|
14
|
+
require 'uri'
|
15
|
+
require 'ipaddr'
|
16
|
+
|
17
|
+
begin
|
18
|
+
require 'fjson'
|
19
|
+
rescue LoadError
|
20
|
+
require 'json'
|
21
|
+
end
|
22
|
+
|
23
|
+
module PDTP
|
24
|
+
PROTOCOL_DEBUG=true
|
25
|
+
|
26
|
+
class ProtocolError < Exception
|
27
|
+
end
|
28
|
+
|
29
|
+
class ProtocolWarn < Exception
|
30
|
+
end
|
31
|
+
|
32
|
+
# EventMachine handler class for the PDTP protocol
|
33
|
+
class Protocol < EventMachine::Protocols::LineAndTextProtocol
|
34
|
+
@@num_connections = 0
|
35
|
+
@@listener = nil
|
36
|
+
@@message_params = nil
|
37
|
+
@connection_open = false
|
38
|
+
|
39
|
+
def connection_open?
|
40
|
+
@connection_open
|
41
|
+
end
|
42
|
+
|
43
|
+
#sets the listener class (Server or Client)
|
44
|
+
def self.listener=(listener)
|
45
|
+
@@listener = listener
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(*args)
|
49
|
+
user_data = nil
|
50
|
+
@mutex = Mutex.new
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
#called by EventMachine after a connection has been established
|
55
|
+
def post_init
|
56
|
+
# a cache of the peer info because eventmachine seems to drop it before we want
|
57
|
+
peername = get_peername
|
58
|
+
if peername.nil?
|
59
|
+
@cached_peer_info = ["<Peername nil!!!>", 91119] if peername.nil?
|
60
|
+
else
|
61
|
+
port, addr = Socket.unpack_sockaddr_in(peername)
|
62
|
+
@cached_peer_info = [addr.to_s, port.to_i]
|
63
|
+
end
|
64
|
+
|
65
|
+
@@num_connections += 1
|
66
|
+
@connection_open = true
|
67
|
+
@@listener.connection_created(self) if @@listener.respond_to?(:connection_created)
|
68
|
+
end
|
69
|
+
|
70
|
+
attr_accessor :user_data #users of this class may store arbitrary data here
|
71
|
+
|
72
|
+
#close a connection, but first send the specified error message
|
73
|
+
def error_close_connection(error)
|
74
|
+
if PROTOCOL_DEBUG
|
75
|
+
send_message :protocol_error, :message => msg
|
76
|
+
close_connection(true) # close after writing
|
77
|
+
else
|
78
|
+
close_connection
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
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
|
+
#debug routine: returns id of remote peer on this connection
|
88
|
+
def remote_peer_id
|
89
|
+
ret = user_data.client_id rescue nil
|
90
|
+
ret || 'NOID'
|
91
|
+
end
|
92
|
+
|
93
|
+
#called for each line of text received over the wire
|
94
|
+
#parses the JSON message and dispatches the message
|
95
|
+
def receive_line line
|
96
|
+
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?
|
101
|
+
Protocol.validate_message message
|
102
|
+
|
103
|
+
command, options = message
|
104
|
+
hash_to_range command, options
|
105
|
+
receive_message command, options
|
106
|
+
rescue ProtocolError => e
|
107
|
+
@@log.warn "(#{remote_peer_id}) PROTOCOL ERROR: #{e.to_s}"
|
108
|
+
@@log.debug e.backtrace.join("\n")
|
109
|
+
error_close_connection e.to_s
|
110
|
+
rescue ProtocolWarn => e
|
111
|
+
send_message :protocol_warn, :message => e.to_s
|
112
|
+
rescue Exception => e
|
113
|
+
puts "(#{remote_peer_id}) UNKNOWN EXCEPTION #{e.to_s}"
|
114
|
+
puts e.backtrace.join("\n")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
RANGENAMES = %w{chunk_range range byte_range}
|
119
|
+
|
120
|
+
#converts Ruby Range classes in the message to PDTP protocol hashes with min and max
|
121
|
+
# 0..-1 => nil (entire file)
|
122
|
+
# 10..-1 => {"min"=>10} (contents of file >= 10)
|
123
|
+
def range_to_hash(message)
|
124
|
+
message.each do |key,value|
|
125
|
+
if value.class==Range
|
126
|
+
if value==(0..-1)
|
127
|
+
message.delete(key)
|
128
|
+
elsif value.last==-1
|
129
|
+
message[key]={"min"=>value.first}
|
130
|
+
else
|
131
|
+
message[key]={"min"=>value.first,"max"=>value.last}
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
#converts a PDTP protocol min and max hash to a Ruby Range class
|
138
|
+
def hash_to_range(command, message)
|
139
|
+
key="range"
|
140
|
+
auto_types=["provide","request"] #these types assume a range if it isnt specified
|
141
|
+
auto_types.each do |type|
|
142
|
+
if command == type and message[key].nil?
|
143
|
+
message[key]={} # assume entire file if not specified
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
if message[key]
|
148
|
+
raise if message[key].class!=Hash
|
149
|
+
min=message[key]["min"]
|
150
|
+
max=message[key]["max"]
|
151
|
+
message[key]= (min ? min : 0)..(max ? max : -1)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
#sends a message, in the internal Hash format, over the wire
|
156
|
+
def send_message(command, opts = {})
|
157
|
+
#message = opts.merge(:type => command.to_s)
|
158
|
+
|
159
|
+
# Stringify all option keys
|
160
|
+
opts = opts.map { |k,v| [k.to_s, v] }.inject({}) { |h,(k,v)| h[k] = v; h }
|
161
|
+
|
162
|
+
# Convert all Ruby ranges to JSON objects representing them
|
163
|
+
range_to_hash opts
|
164
|
+
|
165
|
+
# Message format is a JSON array with the command (string) as the first entry
|
166
|
+
# Second entry is an options hash/object
|
167
|
+
message = [command.to_s, opts]
|
168
|
+
|
169
|
+
@mutex.synchronize do
|
170
|
+
outstr = JSON.unparse(message) + "\n"
|
171
|
+
@@log.debug "(#{remote_peer_id}) send: #{outstr.chomp}"
|
172
|
+
send_data outstr
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
#called by EventMachine when a connection is closed
|
177
|
+
def unbind
|
178
|
+
@@num_connections -= 1
|
179
|
+
@@listener.connection_destroyed(self) if @@listener.respond_to?(:connection_destroyed)
|
180
|
+
@connection_open = false
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.print_info
|
184
|
+
puts "num_connections=#{@@num_connections}"
|
185
|
+
end
|
186
|
+
|
187
|
+
#returns the ip address and port in an array [ip, port]
|
188
|
+
def get_peer_info
|
189
|
+
@cached_peer_info
|
190
|
+
end
|
191
|
+
|
192
|
+
def to_s
|
193
|
+
addr,port = get_peer_info
|
194
|
+
"#{addr}:#{port}"
|
195
|
+
end
|
196
|
+
|
197
|
+
#makes sure that the message is valid.
|
198
|
+
#if not, throws a ProtocolError
|
199
|
+
def self.validate_message(message)
|
200
|
+
raise ProtocolError.new("Message is not a JSON array") unless message.is_a? Array
|
201
|
+
command, options = message
|
202
|
+
|
203
|
+
@@message_params ||= define_message_params
|
204
|
+
|
205
|
+
params = @@message_params[command] rescue nil
|
206
|
+
raise ProtocolError.new("Invalid message type: #{command}") if params.nil?
|
207
|
+
|
208
|
+
params.each do |name,type|
|
209
|
+
if type.class == Optional
|
210
|
+
next if options[name].nil? #dont worry about it if they dont have this param
|
211
|
+
type = type.type #grab the real type from within the optional class
|
212
|
+
end
|
213
|
+
|
214
|
+
raise ProtocolError.new("required parameter: '#{name}' missing for message: '#{command}'") if options[name].nil?
|
215
|
+
unless obj_matches_type?(options[name], type)
|
216
|
+
raise ProtocolError.new("parameter: '#{name}' val='#{options[name]}' is not of type: '#{type}' for message: '#{command}' ")
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# an optional field of the specified type
|
222
|
+
class Optional
|
223
|
+
attr_accessor :type
|
224
|
+
def initialize(type)
|
225
|
+
@type=type
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
#returns whether or not a given ruby object matches the specified type
|
230
|
+
#available types:
|
231
|
+
# :url, :range, :ip, :int, :bool, :string
|
232
|
+
def self.obj_matches_type?(obj,type)
|
233
|
+
case type
|
234
|
+
when :url then obj.class == String
|
235
|
+
when :range then obj.class == Range or obj.class == Hash
|
236
|
+
when :int then obj.class == Fixnum
|
237
|
+
when :bool then obj == true or obj == false
|
238
|
+
when :string then obj.class == String
|
239
|
+
when :ip
|
240
|
+
ip = IPAddr.new(obj) rescue nil
|
241
|
+
!ip.nil?
|
242
|
+
else
|
243
|
+
raise "Invalid type specified: #{type}"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
#this function defines the required fields for each message
|
248
|
+
def self.define_message_params
|
249
|
+
mp = {}
|
250
|
+
|
251
|
+
#must be the first message the client sends
|
252
|
+
mp["client_info"]={
|
253
|
+
"client_id"=>:string,
|
254
|
+
"listen_port"=>:int
|
255
|
+
}
|
256
|
+
|
257
|
+
mp["ask_info"]={
|
258
|
+
"url"=>:url
|
259
|
+
}
|
260
|
+
|
261
|
+
mp["tell_info"]={
|
262
|
+
"url"=>:url,
|
263
|
+
"size"=>Optional.new(:int),
|
264
|
+
"chunk_size"=>Optional.new(:int),
|
265
|
+
"streaming"=>Optional.new(:bool)
|
266
|
+
}
|
267
|
+
|
268
|
+
mp["ask_verify"]={
|
269
|
+
"peer"=>:ip,
|
270
|
+
"url"=>:url,
|
271
|
+
"range"=>:range,
|
272
|
+
"peer_id"=>:string
|
273
|
+
}
|
274
|
+
|
275
|
+
mp["tell_verify"]={
|
276
|
+
"peer"=>:ip,
|
277
|
+
"url"=>:url,
|
278
|
+
"range"=>:range,
|
279
|
+
"peer_id"=>:string,
|
280
|
+
"is_authorized"=>:bool
|
281
|
+
}
|
282
|
+
|
283
|
+
mp["request"]={
|
284
|
+
"url"=>:url,
|
285
|
+
"range"=>Optional.new(:range)
|
286
|
+
}
|
287
|
+
|
288
|
+
mp["provide"]={
|
289
|
+
"url"=>:url,
|
290
|
+
"range"=>Optional.new(:range)
|
291
|
+
}
|
292
|
+
|
293
|
+
mp["unrequest"]={
|
294
|
+
"url"=>:url,
|
295
|
+
"range"=>Optional.new(:range)
|
296
|
+
}
|
297
|
+
|
298
|
+
mp["unprovide"]={
|
299
|
+
"url"=>:url,
|
300
|
+
"range"=>Optional.new(:range)
|
301
|
+
}
|
302
|
+
|
303
|
+
#the taker sends this message when a transfer finishes
|
304
|
+
#if there is an error in the transfer, dont set a hash
|
305
|
+
#to signify failure
|
306
|
+
#when this is received from the taker, the connection is considered done for all parties
|
307
|
+
#
|
308
|
+
#The giver also sends this message when they are done transferring.
|
309
|
+
#this closes the connection on their side, allowing them to start other transfers
|
310
|
+
#It leaves the connection open on the taker side to allow them to decide if the transfer was successful
|
311
|
+
#the hash parameter is ignored when sent by the giver
|
312
|
+
mp["completed"]={
|
313
|
+
#"peer"=>:ip, no longer used
|
314
|
+
"url"=>:url,
|
315
|
+
"range"=>:range,
|
316
|
+
"peer_id"=>:string,
|
317
|
+
"hash"=>Optional.new(:string)
|
318
|
+
}
|
319
|
+
|
320
|
+
mp["hash_verify"]={
|
321
|
+
"url"=>:url,
|
322
|
+
"range"=>:range,
|
323
|
+
"hash_ok"=>:bool
|
324
|
+
}
|
325
|
+
|
326
|
+
mp["transfer"]={
|
327
|
+
"host"=>:string,
|
328
|
+
"port"=>:int,
|
329
|
+
"method"=>:string,
|
330
|
+
"url"=>:url,
|
331
|
+
"range"=>:range,
|
332
|
+
"peer_id"=>:string
|
333
|
+
}
|
334
|
+
|
335
|
+
mp["protocol_error"]={
|
336
|
+
"message"=>Optional.new(:string)
|
337
|
+
}
|
338
|
+
|
339
|
+
mp["protocol_warn"]={
|
340
|
+
"message"=>Optional.new(:string)
|
341
|
+
}
|
342
|
+
|
343
|
+
mp
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|