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 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