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 CHANGED
@@ -1,2 +1,13 @@
1
+ Version 0.2.0
2
+ * PDTP::Server and PDTP::Client now expose APIs intended for public consumption
3
+ They may change slightly down the road, but will remain largely the same
4
+
5
+ * PDTP::Client now uses a Tempfile for its internal buffer, drastically
6
+ improving its memory usage
7
+
8
+ * The protocol now uses length prefix framing, rather than using CRLF encoding.
9
+ This breaks protocol compatibility with the 0.1.0 release but should represent
10
+ the last major change to the protocol format.
11
+
1
12
  Version 0.1.0
2
13
  * Initial release
data/Rakefile CHANGED
@@ -25,20 +25,24 @@ end
25
25
  begin
26
26
  require 'spec/rake/spectask'
27
27
 
28
+ SPECS = FileList['spec/**/*_spec.rb']
29
+
28
30
  Spec::Rake::SpecTask.new(:spec) do |task|
29
- task.spec_files = FileList['**/*_spec.rb']
31
+ task.spec_files = SPECS
30
32
  end
31
33
 
32
- Spec::Rake::SpecTask.new(:specfs) do |task|
33
- task.spec_files= FileList['**/*_spec.rb']
34
- task.spec_opts="-f s".split
34
+ namespace :spec do
35
+ Spec::Rake::SpecTask.new(:print) do |task|
36
+ task.spec_files = SPECS
37
+ task.spec_opts="-f s".split
38
+ end
39
+
40
+ Spec::Rake::SpecTask.new(:rcov) do |task|
41
+ task.spec_files = SPECS
42
+ task.rcov = true
43
+ task.rcov_opts = ['--exclude', 'spec']
44
+ end
35
45
  end
36
46
 
37
- Spec::Rake::SpecTask.new(:spec_coverage) do |task|
38
- task.spec_files = FileList['**/*_spec.rb']
39
- task.rcov = true
40
- end
41
47
  rescue LoadError
42
48
  end
43
-
44
-
data/bin/dsclient CHANGED
@@ -1,4 +1,24 @@
1
1
  #!/usr/bin/env ruby
2
+
3
+ # == Synopsis
4
+ #
5
+ # dsclient: DistribuStream client application
6
+ #
7
+ # == Usage
8
+ #
9
+ # dsclient [OPTION] ... URI
10
+ #
11
+ # -h, --help:
12
+ # Show help
13
+ #
14
+ # --output file, -o file:
15
+ # File to save to. Use '-' for stdout
16
+ #
17
+ # --listen_port port, -p port:
18
+ # Port for the local HTTP server to listen on
19
+ #
20
+ # URI: An address in the pdtp:// scheme to retrieve from the server
21
+
2
22
  #--
3
23
  # Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
4
24
  # All rights reserved. See COPYING for permissions.
@@ -9,35 +29,79 @@
9
29
  # See http://distribustream.rubyforge.org/
10
30
  #++
11
31
 
12
- require 'rubygems'
13
- require 'eventmachine'
14
- require 'mongrel'
15
- require 'optparse'
16
32
  require 'uri'
33
+ require 'getoptlong'
34
+ require 'rdoc/ri/ri_paths'
35
+ require 'rdoc/usage'
36
+ require 'logger'
17
37
 
18
38
  require File.dirname(__FILE__) + '/../lib/pdtp/client'
19
39
 
20
- uri = nil
21
- listen_port = 8000
40
+ opts = GetoptLong.new(
41
+ [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
42
+ [ '--output', '-o', GetoptLong::REQUIRED_ARGUMENT ],
43
+ [ '--listen', '-l', GetoptLong::OPTIONAL_ARGUMENT ]
44
+ )
22
45
 
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
46
+ filename = nil
47
+ listen_port = 60860
48
+
49
+ # Display usage information extraced from this file via RDoc
50
+ def display_usage
51
+ File.open(__FILE__) { |file| STDERR.write RDoc.find_comment(file).gsub(/^# ?/, '') }
52
+ exit
53
+ end
54
+
55
+ opts.each do |opt, arg|
56
+ case opt
57
+ when '--help' then display_usage
58
+ when '--output' then filename = arg
59
+ when '--listen' then listen_port = arg.to_i
34
60
  end
35
- end.parse!
61
+ end
62
+
63
+ if ARGV.length != 1
64
+ puts "Missing URI argument (try --help)"
65
+ exit 0
66
+ end
36
67
 
37
- raise "Please specify a URL in the form --url URL" unless uri
68
+ uri = URI.parse ARGV.shift
69
+
70
+ # Validate URI scheme
38
71
  raise "Only pdtp:// URLs are supported" unless uri.scheme == 'pdtp'
39
72
 
40
- options = { :listen_port => listen_port }
41
- options[:port] = uri.port unless uri.port.nil?
73
+ STDERR.write "--#{Time.now.strftime("%H:%M:%S")}-- #{uri}\n"
74
+
75
+ filename ||= File.basename(uri.path)
76
+
77
+ STDERR.write " => '#{filename}'\n"
78
+
79
+ # Open the output file
80
+ output = case filename
81
+ when '-' then STDOUT
82
+ else open(filename, 'w')
83
+ end
84
+
85
+ class Callbacks < PDTP::Client::Callbacks
86
+ attr_accessor :uri
87
+ attr_accessor :output
88
+
89
+ def connected
90
+ STDERR.write "Connected to #{uri.host}\n"
91
+ STDERR.write "Beginning transfer...\n"
92
+
93
+ # Begin fetching the file once we've connected
94
+ client.get uri.path, output
95
+ end
96
+
97
+ def disconnected
98
+ STDERR.write "Disconnected from #{uri.host}\n"
99
+ client.stop
100
+ end
101
+ end
42
102
 
43
- PDTP::Client.get uri.host, uri.path, options
103
+ @@log = Logger.new(STDERR)
104
+ @@log.level=Logger::INFO
105
+
106
+ @client = PDTP::Client.new uri.host, uri.port || 6086, :listen_port => listen_port
107
+ @client.connect(Callbacks) { |c| c.uri, c.output = uri, output }
data/bin/dsseed CHANGED
@@ -14,90 +14,19 @@ require 'rubygems'
14
14
  require 'eventmachine'
15
15
  require 'mongrel'
16
16
 
17
- require File.dirname(__FILE__) + '/../lib/pdtp/client'
17
+ require File.dirname(__FILE__) + '/../lib/pdtp/common/common_init'
18
+ require File.dirname(__FILE__) + '/../lib/pdtp/server/file_service_protocol'
18
19
 
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
20
+ common_init File.basename($0)
96
21
 
97
22
  # Run the EventMachine reactor loop
98
23
  EventMachine::run do
99
24
  host, port, listen_port = @@config[:host], @@config[:port], @@config[:listen_port]
100
- connection = EventMachine::connect host, port, FileServiceProtocol
25
+
26
+ connection = EventMachine::connect(host, port, PDTP::Server::FileService::Protocol) do |c|
27
+ c.instance_eval { @base_path = @@config[:file_root] }
28
+ end
29
+
101
30
  @@log.info "connecting with ev=#{EventMachine::VERSION}"
102
31
  @@log.info "host= #{host} port=#{port}"
103
32
  end
data/bin/dstream ADDED
@@ -0,0 +1,20 @@
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 File.dirname(__FILE__) + '/../lib/pdtp/common/common_init'
13
+ require File.dirname(__FILE__) + '/../lib/pdtp/server'
14
+
15
+ common_init File.basename($0)
16
+
17
+ server = PDTP::Server.new @@config[:host], @@config[:port]
18
+ server.enable_stats_service
19
+ server.enable_file_service @@config[:file_root], :chunk_size => @@config[:chunk_size]
20
+ server.run
data/conf/bigchunk.yml CHANGED
@@ -15,4 +15,4 @@
15
15
  :file_root: /Users/tony/dstest/files
16
16
 
17
17
  # Size of segments to be distributed (in bytes)
18
- :chunk_size: 250000
18
+ :chunk_size: 500000
data/conf/debug.yml CHANGED
@@ -15,4 +15,4 @@
15
15
  :file_root: /Users/tony/dstest/files
16
16
 
17
17
  # Size of segments to be distributed (in bytes)
18
- :chunk_size: 5000
18
+ :chunk_size: 100000
data/conf/example.yml CHANGED
@@ -15,4 +15,4 @@
15
15
  :file_root: /Users/tony/dstest/files
16
16
 
17
17
  # Size of segments to be distributed (in bytes)
18
- :chunk_size: 5000
18
+ :chunk_size: 100000
@@ -2,8 +2,8 @@ require 'rubygems'
2
2
 
3
3
  GEMSPEC = Gem::Specification.new do |s|
4
4
  s.name = "distribustream"
5
- s.version = "0.1.0"
6
- s.date = "2008-10-11"
5
+ s.version = "0.2.0"
6
+ s.date = "2008-10-22"
7
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
8
  s.email = "tony@clickcaster.com"
9
9
  s.homepage = "http://distribustream.rubyforge.org"
@@ -13,7 +13,7 @@ GEMSPEC = Gem::Specification.new do |s|
13
13
  s.extra_rdoc_files = ["COPYING", "README", "CHANGES"]
14
14
  s.authors = ["Tony Arcieri", "Ashvin Mysore", "Galen Pahlke", "James Sanders", "Tom Stapleton"]
15
15
  s.files = Dir.glob("{bin,lib,conf}/**/*") + ['Rakefile', 'distribustream.gemspec']
16
- s.executables = %w{distribustream dsseed dsclient}
16
+ s.executables = %w{dstream dsseed dsclient}
17
17
  s.add_dependency("eventmachine", ">= 0.9.0")
18
18
  s.add_dependency("mongrel", ">= 1.0.1")
19
19
  s.add_dependency("json", ">= 1.1.0")
@@ -0,0 +1,29 @@
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
+ class Client
13
+ # Callbacks
14
+ class Callbacks
15
+ attr_accessor :client
16
+
17
+ def initialize(connection)
18
+ @connection = connection
19
+ end
20
+
21
+ def connected
22
+ end
23
+
24
+ def disconnected
25
+ client.stop
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,114 @@
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 'mongrel'
13
+
14
+ require File.dirname(__FILE__) + '/../common/protocol.rb'
15
+
16
+ module PDTP
17
+ class Client
18
+ # Implementation of the PDTP client protocol
19
+ class Connection < PDTP::Protocol
20
+ attr_accessor :client
21
+ attr_accessor :callbacks
22
+
23
+ # Called after a connection to the server has been established
24
+ def connection_completed
25
+ begin
26
+ #create the client
27
+ #PDTP::Protocol.listener = self
28
+
29
+ # Tell the server about ourself
30
+ send_message(:register,
31
+ :listen_port => @client.listen_port,
32
+ :client_id => @client.client_id
33
+ )
34
+
35
+ callbacks.connected
36
+ rescue Exception => e
37
+ puts "Exception in connection_completed: #{e}"
38
+ puts e.backtrace.join("\n")
39
+ exit
40
+ end
41
+ end
42
+
43
+ # Called when any server message is received. This is the brains of
44
+ # the client's protocol handling.
45
+ def receive_message(command, message)
46
+ case command
47
+ when "tell_info" # Receive and store information for this url
48
+ info = @client.file_service.get_info(message["url"])
49
+ #info = FileInfo.new message["url"].split('/').last
50
+ info.file_size = message["size"]
51
+ info.base_chunk_size = message["chunk_size"]
52
+ info.streaming = message["streaming"]
53
+ #@client.file_service.set_info(message["url"], info)
54
+ when "transfer" # Begin a transfer as a connector
55
+ transfer = Transfer::Connector.new(
56
+ self,
57
+ message,
58
+ @client.file_service
59
+ )
60
+
61
+ @@log.debug "TRANSFER STARTING"
62
+
63
+ # Run each transfer in its own thread and notify the server upon completion
64
+ Thread.new(transfer) do |t|
65
+ begin
66
+ t.run
67
+ rescue Exception=>e
68
+ @@log.info("Exception in dispatch_message: " + e.exception + "\n" + e.backtrace.join("\n"))
69
+ end
70
+ t.send_completed_message(t.hash)
71
+ end
72
+ when "tell_verify"
73
+ # We are a listener, and asked for verification of a transfer from a server.
74
+ # After asking for verification, we stopped running, and must be restarted
75
+ # if verification is successful
76
+
77
+ found=false
78
+ @client.transfers.each do |t|
79
+ if t.matches_message?(message)
80
+ finished(t)
81
+ t.tell_verify(message["authorized"])
82
+ found=true
83
+ break
84
+ end
85
+ end
86
+
87
+ unless found
88
+ puts "BUG: Tell verify sent for an unknown transfer"
89
+ exit!
90
+ end
91
+ when "hash_verify"
92
+ @@log.debug "Hash verified for url=#{message["url"]} range=#{message["range"]} hash_ok=#{message["hash_ok"]}"
93
+ when "protocol_error", "protocol_warn" #ignore
94
+ else raise "Server sent an unknown message type: #{command} "
95
+ end
96
+ end
97
+
98
+ def unbind
99
+ super
100
+ puts 'Disconnected from PDTP server.'
101
+ end
102
+
103
+ #Prints the number of transfers associated with this client
104
+ def print_stats
105
+ @@log.debug "client: num_transfers=#{@client.transfers.size}"
106
+ end
107
+
108
+ #Provides a threadsafe mechanism for transfers to report themselves finished
109
+ def finished(transfer)
110
+ @client.lock.synchronize { @client.transfers.delete(transfer) }
111
+ end
112
+ end
113
+ end
114
+ end