distribustream 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/README
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
Welcome to DistribuStream!
|
2
|
+
|
3
|
+
DistribuStream is a fully open peercasting system which allows on-demand
|
4
|
+
or live streaming media to be delivered at a fraction of the normal cost.
|
5
|
+
|
6
|
+
This README covers the initial public release, known issues, and a general
|
7
|
+
development roadmap.
|
8
|
+
|
9
|
+
--
|
10
|
+
|
11
|
+
Usage:
|
12
|
+
|
13
|
+
The DistribuStream gem includes three config files that can be located in
|
14
|
+
the conf directory of the gem:
|
15
|
+
|
16
|
+
example.yml - A standard example file, configuring for 5kB chunks
|
17
|
+
debug.yml - Same as example.yml, but configured for additional debug info
|
18
|
+
bigchunk.yml - An example config file, using 250kB chunks
|
19
|
+
|
20
|
+
Chunk size controls how large the segments which are exchanged between peers
|
21
|
+
are. Larger chunk sizes reduce the amount of control traffic the server
|
22
|
+
sends, but increase the probability of errors.
|
23
|
+
|
24
|
+
To begin, copy one of these config file and edit it to your choosing. Be
|
25
|
+
sure to set the host address to bind to and optionally the vhost you wish
|
26
|
+
to identify as.
|
27
|
+
|
28
|
+
Next, start the DistribuStream server:
|
29
|
+
|
30
|
+
distribustream --conf myconfig.yml
|
31
|
+
|
32
|
+
The DistribuStream server manages traffic on the peer network. It also handles
|
33
|
+
the checksumming of files.
|
34
|
+
|
35
|
+
You can see what's going on through the web interface, which runs one port
|
36
|
+
above the port you configured the server to listen on. If the server is
|
37
|
+
listening on the default port of 6086, you can reach the web interface at:
|
38
|
+
|
39
|
+
http://myserver.url:6087/
|
40
|
+
|
41
|
+
To populate the server with files, you need to attach the DistribuStream
|
42
|
+
seed. This works off of the same config file as the server, and must
|
43
|
+
be run on the same system:
|
44
|
+
|
45
|
+
dsseed --conf myconfig.yml
|
46
|
+
|
47
|
+
At this point your server is ready to go.
|
48
|
+
|
49
|
+
To test your server, use the DistribuStream client:
|
50
|
+
|
51
|
+
dsclient --url pdtp://myserver.url/file.ext
|
52
|
+
|
53
|
+
This will download file.ext from your DistribuStream server.
|
54
|
+
|
55
|
+
While you can't control the output filename at this point, the client supports
|
56
|
+
non-seekable output such as pipes. To play streaming media as it downloads,
|
57
|
+
you can:
|
58
|
+
|
59
|
+
mkfifo file.ext
|
60
|
+
dsclient --url pdtp://myserver.url/file.ext &
|
61
|
+
mediaplayer file.ext
|
62
|
+
|
63
|
+
--
|
64
|
+
|
65
|
+
Known Issues:
|
66
|
+
|
67
|
+
The client presently stores incoming data in a memory buffer. This causes
|
68
|
+
the client to consume massive amounts of memory as the file downloads.
|
69
|
+
Subsequent releases will fix this by improving the design of the memory
|
70
|
+
buffer, moving to a disk-backed buffer and/or discarding some of the
|
71
|
+
downloaded data after it's been played back.
|
72
|
+
|
73
|
+
The protocol facilitates allowing clients to have a moving window of data
|
74
|
+
in a stream, so they need not retain data which has already been displayed
|
75
|
+
to the user.
|
76
|
+
|
77
|
+
Seeds are presently not authenticated in any way, thus anyone can attach
|
78
|
+
a seed and populate the server with any files of their choosing. However,
|
79
|
+
since file checksumming is done by the server itself, this means that only
|
80
|
+
seeds running on the same system as the server will actually work.
|
81
|
+
|
82
|
+
This will be resolved by either incorporating the seed directly into the
|
83
|
+
DistribuStream server, or adding both authentication and commands for
|
84
|
+
checksumming to the server <-> seed protocol.
|
85
|
+
|
86
|
+
--
|
87
|
+
|
88
|
+
Development Roadmap:
|
89
|
+
|
90
|
+
The immediate goal is to improve the performance of the client, which presently
|
91
|
+
consumes far too much RAM for practical use with large media files. Another
|
92
|
+
immediate goal is solving the above problems with seeds.
|
93
|
+
|
94
|
+
DistribuStream uses an assemblage of various tools which do not work together
|
95
|
+
particularly well. These include the EventMachine Ruby gem, which provides
|
96
|
+
the I/O layer for the DistribuStream server, and the Mongrel web server, which
|
97
|
+
runs independently of EventMachine and uses threads.
|
98
|
+
|
99
|
+
Initial work will focus on converting the existing implementation to a fully
|
100
|
+
EventMachine-based approach which eliminates the use of threads.
|
101
|
+
|
102
|
+
Subsequent work will focus on improving the APIs provided by the various
|
103
|
+
components so that the client and server can both
|
104
|
+
|
105
|
+
Long-term goals include a move to UDP to reduce protocol latency and overhead
|
106
|
+
as well as encrypting all traffic to ensure privacy and security of the
|
107
|
+
data being transmitted.
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
load 'distribustream.gemspec'
|
5
|
+
|
6
|
+
# Default Rake task
|
7
|
+
task :default => :rdoc
|
8
|
+
|
9
|
+
# RDoc
|
10
|
+
Rake::RDocTask.new(:rdoc) do |task|
|
11
|
+
task.rdoc_dir = 'doc'
|
12
|
+
task.title = 'DistribuStream'
|
13
|
+
task.rdoc_files.include('bin/**/*.rb')
|
14
|
+
task.rdoc_files.include('lib/**/*.rb')
|
15
|
+
task.rdoc_files.include('simulation/**/*.rb')
|
16
|
+
task.rdoc_files.include('test/**/*.rb')
|
17
|
+
end
|
18
|
+
|
19
|
+
# Gem
|
20
|
+
Rake::GemPackageTask.new(GEMSPEC) do |pkg|
|
21
|
+
pkg.need_tar = true
|
22
|
+
end
|
23
|
+
|
24
|
+
# RSpec
|
25
|
+
begin
|
26
|
+
require 'spec/rake/spectask'
|
27
|
+
|
28
|
+
Spec::Rake::SpecTask.new(:spec) do |task|
|
29
|
+
task.spec_files = FileList['**/*_spec.rb']
|
30
|
+
end
|
31
|
+
|
32
|
+
Spec::Rake::SpecTask.new(:specfs) do |task|
|
33
|
+
task.spec_files= FileList['**/*_spec.rb']
|
34
|
+
task.spec_opts="-f s".split
|
35
|
+
end
|
36
|
+
|
37
|
+
Spec::Rake::SpecTask.new(:spec_coverage) do |task|
|
38
|
+
task.spec_files = FileList['**/*_spec.rb']
|
39
|
+
task.rcov = true
|
40
|
+
end
|
41
|
+
rescue LoadError
|
42
|
+
end
|
43
|
+
|
44
|
+
|
data/bin/distribustream
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#--
|
3
|
+
# Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
|
4
|
+
# All rights reserved. See COPYING for permissions.
|
5
|
+
#
|
6
|
+
# This source file is distributed as part of the
|
7
|
+
# DistribuStream file transfer system.
|
8
|
+
#
|
9
|
+
# See http://distribustream.rubyforge.org/
|
10
|
+
#++
|
11
|
+
|
12
|
+
require 'rubygems'
|
13
|
+
require 'eventmachine'
|
14
|
+
require 'optparse'
|
15
|
+
require 'logger'
|
16
|
+
require 'mongrel'
|
17
|
+
|
18
|
+
require File.dirname(__FILE__) + '/../lib/pdtp/server'
|
19
|
+
|
20
|
+
common_init $0
|
21
|
+
|
22
|
+
server = PDTP::Server.new
|
23
|
+
server.file_service = PDTP::Server::FileService.new
|
24
|
+
PDTP::Protocol.listener = server
|
25
|
+
|
26
|
+
#set up the mongrel server for serving the stats page
|
27
|
+
class MongrelServerHandler< Mongrel::HttpHandler
|
28
|
+
def initialize(server)
|
29
|
+
@server = server
|
30
|
+
end
|
31
|
+
|
32
|
+
def process(request,response)
|
33
|
+
response.start(200) do |head, out|
|
34
|
+
out.write begin
|
35
|
+
outstr = @server.generate_html_stats
|
36
|
+
rescue Exception=>e
|
37
|
+
outstr = "Exception: #{e}\n#{e.backtrace.join("\n")}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
#run the mongrel server
|
44
|
+
mongrel_server = Mongrel::HttpServer.new '0.0.0.0', @@config[:port] + 1
|
45
|
+
@@log.info "Mongrel server listening on port: #{@@config[:port] + 1}"
|
46
|
+
mongrel_server.register '/', MongrelServerHandler.new(server)
|
47
|
+
mongrel_server.run
|
48
|
+
|
49
|
+
#set root directory
|
50
|
+
server.file_service.root = @@config[:file_root]
|
51
|
+
server.file_service.default_chunk_size = @@config[:chunk_size]
|
52
|
+
|
53
|
+
EventMachine::run do
|
54
|
+
host, port = '0.0.0.0', @@config[:port]
|
55
|
+
EventMachine::start_server host, port, PDTP::Protocol
|
56
|
+
@@log.info "accepting connections with ev=#{EventMachine::VERSION}"
|
57
|
+
@@log.info "host=#{host} port=#{port}"
|
58
|
+
|
59
|
+
EventMachine::add_periodic_timer(2) { server.clear_all_stalled_transfers }
|
60
|
+
end
|
data/bin/dsclient
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#--
|
3
|
+
# Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
|
4
|
+
# All rights reserved. See COPYING for permissions.
|
5
|
+
#
|
6
|
+
# This source file is distributed as part of the
|
7
|
+
# DistribuStream file transfer system.
|
8
|
+
#
|
9
|
+
# See http://distribustream.rubyforge.org/
|
10
|
+
#++
|
11
|
+
|
12
|
+
require 'rubygems'
|
13
|
+
require 'eventmachine'
|
14
|
+
require 'mongrel'
|
15
|
+
require 'optparse'
|
16
|
+
require 'uri'
|
17
|
+
|
18
|
+
require File.dirname(__FILE__) + '/../lib/pdtp/client'
|
19
|
+
|
20
|
+
uri = nil
|
21
|
+
listen_port = 8000
|
22
|
+
|
23
|
+
OptionParser.new do |opts|
|
24
|
+
opts.banner = "Usage: #{$0} [options]"
|
25
|
+
opts.on("--url URL", "Fetch from the specified URL") do |u|
|
26
|
+
uri = URI.parse u
|
27
|
+
end
|
28
|
+
opts.on("--help", "Prints this usage info.") do
|
29
|
+
puts opts
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
opts.on("--listen PORT", "Port to listen on") do |l|
|
33
|
+
listen_port = l.to_i
|
34
|
+
end
|
35
|
+
end.parse!
|
36
|
+
|
37
|
+
raise "Please specify a URL in the form --url URL" unless uri
|
38
|
+
raise "Only pdtp:// URLs are supported" unless uri.scheme == 'pdtp'
|
39
|
+
|
40
|
+
options = { :listen_port => listen_port }
|
41
|
+
options[:port] = uri.port unless uri.port.nil?
|
42
|
+
|
43
|
+
PDTP::Client.get uri.host, uri.path, options
|
data/bin/dsseed
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#--
|
3
|
+
# Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
|
4
|
+
# All rights reserved. See COPYING for permissions.
|
5
|
+
#
|
6
|
+
# This source file is distributed as part of the
|
7
|
+
# DistribuStream file transfer system.
|
8
|
+
#
|
9
|
+
# See http://distribustream.rubyforge.org/
|
10
|
+
#++
|
11
|
+
|
12
|
+
require 'optparse'
|
13
|
+
require 'rubygems'
|
14
|
+
require 'eventmachine'
|
15
|
+
require 'mongrel'
|
16
|
+
|
17
|
+
require File.dirname(__FILE__) + '/../lib/pdtp/client'
|
18
|
+
|
19
|
+
common_init $0
|
20
|
+
|
21
|
+
# Fine all suitable files in the give path
|
22
|
+
def find_files(base_path)
|
23
|
+
require 'find'
|
24
|
+
|
25
|
+
found = []
|
26
|
+
excludes = %w{.svn CVS}
|
27
|
+
base_full = File.expand_path(base_path)
|
28
|
+
|
29
|
+
Find.find(base_full) do |path|
|
30
|
+
if FileTest.directory?(path)
|
31
|
+
next unless excludes.include?(File.basename(path))
|
32
|
+
Find.prune
|
33
|
+
else
|
34
|
+
filename = path[(base_path.size - path.size + 1)..-1] #the entire file path after the base_path
|
35
|
+
found << filename
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
found
|
40
|
+
end
|
41
|
+
|
42
|
+
# Implements the file service for the pdtp protocol
|
43
|
+
class FileServiceProtocol < PDTP::Protocol
|
44
|
+
def initialize(*args)
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
# Called after a connection to the server has been established
|
49
|
+
def connection_completed
|
50
|
+
begin
|
51
|
+
listen_port = @@config[:listen_port]
|
52
|
+
|
53
|
+
#create the client
|
54
|
+
client = PDTP::Client.new
|
55
|
+
PDTP::Protocol.listener = client
|
56
|
+
client.server_connection = self
|
57
|
+
client.generate_client_id listen_port
|
58
|
+
|
59
|
+
# Start a mongrel server on the specified port. If it isnt available, keep trying higher ports
|
60
|
+
begin
|
61
|
+
mongrel_server=Mongrel::HttpServer.new '0.0.0.0', listen_port
|
62
|
+
rescue Exception=>e
|
63
|
+
listen_port+=1
|
64
|
+
retry
|
65
|
+
end
|
66
|
+
|
67
|
+
@@log.info "listening on port #{listen_port}"
|
68
|
+
mongrel_server.register "/", client
|
69
|
+
mongrel_server.run
|
70
|
+
|
71
|
+
# Tell the server a little bit about ourself
|
72
|
+
send_message :client_info, :listen_port => listen_port, :client_id => client.my_id
|
73
|
+
|
74
|
+
@@log.info 'This client is providing'
|
75
|
+
sfs = PDTP::Server::FileService.new
|
76
|
+
sfs.root = @@config[:file_root]
|
77
|
+
client.file_service = sfs #give this client access to all data
|
78
|
+
|
79
|
+
hostname = @@config[:vhost]
|
80
|
+
|
81
|
+
# Provide all the files in the root directory
|
82
|
+
files = find_files @@config[:file_root]
|
83
|
+
files.each { |file| send_message :provide, :url => "http://#{hostname}/#{file}" }
|
84
|
+
rescue Exception => e
|
85
|
+
puts "Exception in connection_completed: #{e}"
|
86
|
+
puts e.backtrace.join("\n")
|
87
|
+
exit
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def unbind
|
92
|
+
super
|
93
|
+
puts "Disconnected from PDTP server."
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Run the EventMachine reactor loop
|
98
|
+
EventMachine::run do
|
99
|
+
host, port, listen_port = @@config[:host], @@config[:port], @@config[:listen_port]
|
100
|
+
connection = EventMachine::connect host, port, FileServiceProtocol
|
101
|
+
@@log.info "connecting with ev=#{EventMachine::VERSION}"
|
102
|
+
@@log.info "host= #{host} port=#{port}"
|
103
|
+
end
|
data/conf/bigchunk.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
# Virtual hostname server identifies itself as
|
3
|
+
:vhost: example.com
|
4
|
+
|
5
|
+
# Hostname or IP address to bind to
|
6
|
+
:host: 0.0.0.0
|
7
|
+
|
8
|
+
# Base port the server listens on
|
9
|
+
:port: 6086
|
10
|
+
|
11
|
+
# Run in quiet mode
|
12
|
+
:quiet: true
|
13
|
+
|
14
|
+
# Base directory from which files are served
|
15
|
+
:file_root: /Users/tony/dstest/files
|
16
|
+
|
17
|
+
# Size of segments to be distributed (in bytes)
|
18
|
+
:chunk_size: 250000
|
data/conf/debug.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
# Virtual hostname server identifies itself as
|
3
|
+
:vhost: example.com
|
4
|
+
|
5
|
+
# Hostname or IP address to bind to
|
6
|
+
:host: 0.0.0.0
|
7
|
+
|
8
|
+
# Base port the server listens on
|
9
|
+
:port: 6086
|
10
|
+
|
11
|
+
# Run in quiet mode
|
12
|
+
:quiet: false
|
13
|
+
|
14
|
+
# Base directory from which files are served
|
15
|
+
:file_root: /Users/tony/dstest/files
|
16
|
+
|
17
|
+
# Size of segments to be distributed (in bytes)
|
18
|
+
:chunk_size: 5000
|
data/conf/example.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
# Virtual hostname server identifies itself as
|
3
|
+
:vhost: example.com
|
4
|
+
|
5
|
+
# Hostname or IP address to bind to
|
6
|
+
:host: 0.0.0.0
|
7
|
+
|
8
|
+
# Base port the server listens on
|
9
|
+
:port: 6086
|
10
|
+
|
11
|
+
# Run in quiet mode
|
12
|
+
:quiet: true
|
13
|
+
|
14
|
+
# Base directory from which files are served
|
15
|
+
:file_root: /Users/tony/dstest/files
|
16
|
+
|
17
|
+
# Size of segments to be distributed (in bytes)
|
18
|
+
:chunk_size: 5000
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
GEMSPEC = Gem::Specification.new do |s|
|
4
|
+
s.name = "distribustream"
|
5
|
+
s.version = "0.1.0"
|
6
|
+
s.date = "2008-10-11"
|
7
|
+
s.summary = "DistribuStream is a fully open peercasting system allowing on-demand or live streaming media to be delivered at a fraction of the normal cost"
|
8
|
+
s.email = "tony@clickcaster.com"
|
9
|
+
s.homepage = "http://distribustream.rubyforge.org"
|
10
|
+
s.rubyforge_project = "distribustream"
|
11
|
+
s.has_rdoc = true
|
12
|
+
s.rdoc_options = ["--exclude", "definitions", "--exclude", "indexes"]
|
13
|
+
s.extra_rdoc_files = ["COPYING", "README", "CHANGES"]
|
14
|
+
s.authors = ["Tony Arcieri", "Ashvin Mysore", "Galen Pahlke", "James Sanders", "Tom Stapleton"]
|
15
|
+
s.files = Dir.glob("{bin,lib,conf}/**/*") + ['Rakefile', 'distribustream.gemspec']
|
16
|
+
s.executables = %w{distribustream dsseed dsclient}
|
17
|
+
s.add_dependency("eventmachine", ">= 0.9.0")
|
18
|
+
s.add_dependency("mongrel", ">= 1.0.1")
|
19
|
+
s.add_dependency("json", ">= 1.1.0")
|
20
|
+
end
|
data/lib/pdtp/client.rb
ADDED
@@ -0,0 +1,195 @@
|
|
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 'mongrel'
|
14
|
+
require 'net/http'
|
15
|
+
require 'thread'
|
16
|
+
require 'digest/md5'
|
17
|
+
|
18
|
+
require File.dirname(__FILE__) + '/common/common_init'
|
19
|
+
require File.dirname(__FILE__) + '/common/protocol'
|
20
|
+
require File.dirname(__FILE__) + '/client/protocol'
|
21
|
+
require File.dirname(__FILE__) + '/client/file_service'
|
22
|
+
require File.dirname(__FILE__) + '/client/transfer'
|
23
|
+
require File.dirname(__FILE__) + '/server/file_service'
|
24
|
+
|
25
|
+
module PDTP
|
26
|
+
# This is the main driver for the client-side implementation
|
27
|
+
# of PDTP. It maintains a single connection to a server and
|
28
|
+
# all the necessary connections to peers. It is responsible
|
29
|
+
# for handling all messages corresponding to these connections.
|
30
|
+
|
31
|
+
# Client inherits from Mongrel::HttpHandler in order to handle
|
32
|
+
# incoming HTTP connections
|
33
|
+
class Client < Mongrel::HttpHandler
|
34
|
+
# Accessor for a client file service instance
|
35
|
+
attr_accessor :file_service
|
36
|
+
attr_accessor :server_connection
|
37
|
+
attr_accessor :my_id
|
38
|
+
|
39
|
+
def self.get(host, path, options = {})
|
40
|
+
path = '/' + path unless path[0] == ?/
|
41
|
+
|
42
|
+
opts = {
|
43
|
+
:host => host,
|
44
|
+
:port => 6086,
|
45
|
+
:file_root => '.',
|
46
|
+
:quiet => true,
|
47
|
+
:listen_port => 8000,
|
48
|
+
:request_url => "http://#{host}#{path}"
|
49
|
+
}.merge(options)
|
50
|
+
|
51
|
+
common_init $0, opts
|
52
|
+
|
53
|
+
# Run the EventMachine reactor loop
|
54
|
+
EventMachine::run do
|
55
|
+
connection = EventMachine::connect host, opts[:port], Client::Protocol
|
56
|
+
@@log.info "connecting with ev=#{EventMachine::VERSION}"
|
57
|
+
@@log.info "host= #{host} port=#{opts[:port]}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize
|
62
|
+
@transfers = []
|
63
|
+
@mutex = Mutex.new
|
64
|
+
end
|
65
|
+
|
66
|
+
# This method is called after a connection to the server
|
67
|
+
# has been successfully established.
|
68
|
+
def connection_created(connection)
|
69
|
+
@@log.debug("[mongrel] Opened connection...");
|
70
|
+
end
|
71
|
+
|
72
|
+
# This method is called when the server connection is destroyed
|
73
|
+
def connection_destroyed(connection)
|
74
|
+
@@log.debug("[mongrel] Closed connection...")
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns a transfer object if the given connection is a peer associated with
|
78
|
+
# that transfer. Otherwise returns nil.
|
79
|
+
def get_transfer(connection)
|
80
|
+
@transfers.each { |t| return t if t.peer == connection }
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
|
84
|
+
# This method is called when an HTTP request is received. It is called in
|
85
|
+
# a separate thread, one for each request.
|
86
|
+
def process(request,response)
|
87
|
+
begin
|
88
|
+
@@log.debug "Creating Transfer::Listener"
|
89
|
+
transfer = Transfer::Listener.new(
|
90
|
+
request,
|
91
|
+
response,
|
92
|
+
@server_connection,
|
93
|
+
@file_service,
|
94
|
+
self
|
95
|
+
)
|
96
|
+
|
97
|
+
#Needs to be locked because multiple threads could attempt to append a transfer at once
|
98
|
+
@mutex.synchronize { @transfers << transfer }
|
99
|
+
transfer.handle_header
|
100
|
+
rescue Exception=>e
|
101
|
+
transfer.write_http_exception(e)
|
102
|
+
end
|
103
|
+
|
104
|
+
transfer.send_completed_message transfer.hash
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns true if the given message refers to the given transfer
|
108
|
+
def transfer_matches?(transfer, message)
|
109
|
+
transfer.peer == message["peer"] and
|
110
|
+
transfer.url == message["url"] and
|
111
|
+
transfer.byte_range == message["range"] and
|
112
|
+
transfer.peer_id == message["peer_id"]
|
113
|
+
end
|
114
|
+
|
115
|
+
# Called when any server message is received. This is the brains of
|
116
|
+
# the client's protocol handling.
|
117
|
+
def dispatch_message(command, message, connection)
|
118
|
+
case command
|
119
|
+
when "tell_info" # Receive and store information for this url
|
120
|
+
info = FileInfo.new message["url"].split('/').last
|
121
|
+
info.file_size = message["size"]
|
122
|
+
info.base_chunk_size = message["chunk_size"]
|
123
|
+
info.streaming = message["streaming"]
|
124
|
+
@file_service.set_info(message["url"], info)
|
125
|
+
when "transfer" # Begin a transfer as a connector
|
126
|
+
transfer = Transfer::Connector.new(message,@server_connection,@file_service,self)
|
127
|
+
|
128
|
+
@@log.debug "TRANSFER STARTING"
|
129
|
+
|
130
|
+
# Run each transfer in its own thread and notify the server upon completion
|
131
|
+
Thread.new(transfer) do |t|
|
132
|
+
begin
|
133
|
+
t.run
|
134
|
+
rescue Exception=>e
|
135
|
+
@@log.info("Exception in dispatch_message: " + e.exception + "\n" + e.backtrace.join("\n"))
|
136
|
+
end
|
137
|
+
t.send_completed_message(t.hash)
|
138
|
+
end
|
139
|
+
when "tell_verify"
|
140
|
+
# We are a listener, and asked for verification of a transfer from a server.
|
141
|
+
# After asking for verification, we stopped running, and must be restarted
|
142
|
+
# if verification is successful
|
143
|
+
|
144
|
+
found=false
|
145
|
+
@transfers.each do |t|
|
146
|
+
if t.matches_message?(message)
|
147
|
+
finished(t)
|
148
|
+
t.tell_verify(message["is_authorized"])
|
149
|
+
found=true
|
150
|
+
break
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
unless found
|
155
|
+
puts "BUG: Tell verify sent for an unknown transfer"
|
156
|
+
exit!
|
157
|
+
end
|
158
|
+
when "hash_verify"
|
159
|
+
@@log.debug "Hash verified for url=#{message["url"]} range=#{message["range"]} hash_ok=#{message["hash_ok"]}"
|
160
|
+
when "protocol_error", "protocol_warn" #ignore
|
161
|
+
else raise "Server sent an unknown message type: #{command} "
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
#Prints the number of transfers associated with this client
|
166
|
+
def print_stats
|
167
|
+
@@log.debug "client: num_transfers=#{@transfers.size}"
|
168
|
+
end
|
169
|
+
|
170
|
+
#Provides a threadsafe mechanism for transfers to report themselves finished
|
171
|
+
def finished(transfer)
|
172
|
+
@mutex.synchronize do
|
173
|
+
@transfers.delete(transfer)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Generate and set the client ID for an instance
|
178
|
+
def generate_client_id(port = 0)
|
179
|
+
@my_id = Client.generate_client_id port
|
180
|
+
end
|
181
|
+
|
182
|
+
# Client ID generator routine
|
183
|
+
def self.generate_client_id(port = 0)
|
184
|
+
md5 = Digest::MD5::new
|
185
|
+
now = Time::now
|
186
|
+
md5.update now.to_s
|
187
|
+
md5.update String(now.usec)
|
188
|
+
md5.update String(rand(0))
|
189
|
+
md5.update String($$)
|
190
|
+
|
191
|
+
#return md5.hexdigest+":#{port}" # long id
|
192
|
+
return md5.hexdigest[0..5] # short id
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|