distribustream 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,3 +1,16 @@
1
+ Version 0.4.1
2
+ * Add -v / --version flags to dsclient and dstream
3
+
4
+ * Add additional flags to dstream for specifying listen addr / port
5
+
6
+ * Status page now factored into its own ERb template in status/
7
+
8
+ * Added Mongrel handlers to serve images/stylesheets for status page
9
+
10
+ * Added StatusHelper module to provide helpers for status page
11
+
12
+ * New status page
13
+
1
14
  Version 0.4.0
2
15
  * Change PDTP::Client#run to take an instance of PDTP::Client::Callbacks rather
3
16
  than the name of the class to instantiate
data/README CHANGED
@@ -34,7 +34,7 @@ You can see what's going on through the web interface, which runs one port
34
34
  above the port you configured the server to listen on. If the server is
35
35
  listening on the default port of 6086, you can reach the web interface at:
36
36
 
37
- http://myserver.url:6087/stats
37
+ http://myserver.url:6087/status/
38
38
 
39
39
  To test your server, use the DistribuStream client:
40
40
 
data/bin/dsclient CHANGED
@@ -51,6 +51,7 @@ require File.dirname(__FILE__) + '/../lib/pdtp/client'
51
51
 
52
52
  opts = GetoptLong.new(
53
53
  [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
54
+ [ '--version', '-v', GetoptLong::NO_ARGUMENT],
54
55
  [ '--output', '-o', GetoptLong::REQUIRED_ARGUMENT ],
55
56
  [ '--listen', '-l', GetoptLong::OPTIONAL_ARGUMENT ]
56
57
  )
@@ -64,11 +65,19 @@ def display_usage
64
65
  exit
65
66
  end
66
67
 
68
+ # Display version information
69
+ def display_version
70
+ STDERR.write "#{File.basename $0} #{PDTP::VERSION} - DistribuStream client application\n"
71
+ STDERR.write "Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)\n"
72
+ exit
73
+ end
74
+
67
75
  opts.each do |opt, arg|
68
76
  case opt
69
- when '--help' then display_usage
70
- when '--output' then filename = arg
71
- when '--listen' then listen_port = arg.to_i
77
+ when '--help' then display_usage
78
+ when '--version' then display_version
79
+ when '--output' then filename = arg
80
+ when '--listen' then listen_port = arg.to_i
72
81
  end
73
82
  end
74
83
 
data/bin/dstream CHANGED
@@ -26,20 +26,33 @@ require File.dirname(__FILE__) + '/../lib/pdtp/server'
26
26
 
27
27
  program_name = File.basename($0)
28
28
  config_filename = nil
29
+ listen_addr = nil
30
+ listen_port = nil
29
31
 
30
32
  OptionParser.new do |opts|
31
33
  opts.banner = "Usage: #{program_name} [options]"
32
34
  opts.on("--config CONFIGFILE", "Load specified config file.") do |c|
33
35
  config_filename = c
34
36
  end
37
+ opts.on("--listen ADDR", "Listen at the specified host address.") do |h|
38
+ listen_addr = h
39
+ end
40
+ opts.on("--port PORT", "Listen on the specified port number.") do |p|
41
+ listen_port = p
42
+ end
35
43
  opts.on("--help", "Prints this usage info.") do
36
44
  puts opts
37
45
  exit
38
46
  end
47
+ opts.on("--version", "Print version information.") do
48
+ puts "#{program_name} #{PDTP::VERSION} - DistribuStream server application"
49
+ puts "Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)"
50
+ exit
51
+ end
39
52
  end.parse!
40
53
 
41
54
  config = {
42
- :host => '0.0.0.0',
55
+ :host => '127.0.0.1',
43
56
  :port => 6086, #server port
44
57
  :listen_port => 8000, #client listen port
45
58
  :file_root => '.',
@@ -68,7 +81,10 @@ else
68
81
  end
69
82
  end
70
83
 
71
- puts "#{program_name} starting. Run '#{program_name} --help' for more info."
84
+ config[:host] = listen_addr unless listen_addr.nil?
85
+ config[:port] = listen_port unless listen_port.nil?
86
+
87
+ puts "#{program_name} #{PDTP::VERSION} starting. Run '#{program_name} --help' for more info."
72
88
 
73
89
  server = PDTP::Server.new config[:host], config[:port]
74
90
 
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
 
3
3
  GEMSPEC = Gem::Specification.new do |s|
4
4
  s.name = "distribustream"
5
- s.version = "0.4.0"
5
+ s.version = "0.4.1"
6
6
  s.date = "2008-11-15"
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"
@@ -12,7 +12,7 @@ GEMSPEC = Gem::Specification.new do |s|
12
12
  s.rdoc_options = ["--exclude", "definitions", "--exclude", "indexes"]
13
13
  s.extra_rdoc_files = ["COPYING", "README", "CHANGES", "pdtp-specification.xml"]
14
14
  s.authors = ["Tony Arcieri", "Ashvin Mysore", "Galen Pahlke", "James Sanders", "Tom Stapleton"]
15
- s.files = Dir.glob("{bin,lib,conf}/**/*") + ['Rakefile', 'distribustream.gemspec']
15
+ s.files = Dir.glob("{bin,lib,conf,status}/**/*") + ['Rakefile', 'distribustream.gemspec']
16
16
  s.executables = %w{dstream dsclient}
17
17
  s.add_dependency("eventmachine", ">= 0.9.0")
18
18
  s.add_dependency("mongrel", ">= 1.0.2")
data/lib/pdtp/common.rb CHANGED
@@ -22,7 +22,7 @@
22
22
 
23
23
  # Namespace for all PDTP components
24
24
  module PDTP
25
- PDTP::VERSION = '0.4.0' unless defined? PDTP::VERSION
25
+ PDTP::VERSION = '0.4.1' unless defined? PDTP::VERSION
26
26
  def self.version() VERSION end
27
27
 
28
28
  PDTP::DEFAULT_PORT = 6086 unless defined? PDTP::DEFAULT_PORT
@@ -88,34 +88,33 @@ module PDTP
88
88
 
89
89
  # Callback for processing incoming frames
90
90
  def receive_data(data)
91
- # Read data and append it to the size prefix unless it's already been read
92
- data = @prefix.append(data) unless @prefix.read?
93
- return if data.nil?
94
-
95
- # If we've read the prefix, append the data
96
- @buffer << data
97
-
98
- # Don't do anything until we receive the specified amount of data
99
- return unless @buffer.length >= @prefix.length
100
-
101
- # Extract the specified amount of data and process it
102
- data = @buffer[0..(@prefix.length - 1)]
103
-
104
- # Store any remaining data
105
- remainder = @buffer[@prefix.length..@buffer.length]
106
-
107
- # Invoke receive_packet and allow the user to process the data
108
- receive_packet data
109
-
110
- # Reset the prefix and buffer since we've received a whole frame
111
- @prefix.reset!
112
- @buffer = ''
113
-
114
- # Don't do anything if there's no more data
115
- return if remainder.nil? or remainder.empty?
116
-
117
- # Otherwise continue processing the data
118
- receive_data remainder
91
+ begin
92
+ # Read data and append it to the size prefix unless it's already been read
93
+ data = @prefix.append(data) unless @prefix.read?
94
+ return if data.nil?
95
+
96
+ # If we've read the prefix, append the data
97
+ @buffer << data
98
+
99
+ # Don't do anything until we receive the specified amount of data
100
+ return unless @buffer.length >= @prefix.length
101
+
102
+ # Extract the specified amount of data and process it
103
+ data = @buffer[0..(@prefix.length - 1)]
104
+
105
+ # Store any remaining data
106
+ remainder = @buffer[@prefix.length..@buffer.length]
107
+
108
+ # Invoke receive_packet and allow the user to process the data
109
+ receive_packet data
110
+
111
+ # Reset the prefix and buffer since we've received a whole frame
112
+ @prefix.reset!
113
+ @buffer = ''
114
+
115
+ # Store data for next iteration of the loop
116
+ data = remainder
117
+ end until data.nil? or data.empty?
119
118
  end
120
119
 
121
120
  # Stub for receiving a frame
data/lib/pdtp/server.rb CHANGED
@@ -50,8 +50,24 @@ module PDTP
50
50
 
51
51
  # Run a web server to display statistics on the given address and port
52
52
  def enable_status_page(path = '/status')
53
- log "status at http://#{@addr}:#{@http_port}#{path}"
54
- http_server.register path, StatusHandler.new(@dispatcher), true
53
+ # Strip trailing slash (if present)
54
+ path.sub!(/\/$/, '')
55
+
56
+ log "status at http://#{@addr}:#{@http_port}#{path}/"
57
+
58
+ # Redirect requests without a trailing slash
59
+ http_server.register path, Mongrel::RedirectHandler.new(path + '/')
60
+
61
+ # Register the status handler
62
+ http_server.register path + '/', StatusHandler.new(@orig_addr, @dispatcher), true
63
+
64
+ # Serve status images
65
+ images = Mongrel::DirHandler.new(File.dirname(__FILE__) + '/../../status/images', false)
66
+ http_server.register path + '/images', images
67
+
68
+ # Serve status stylesheets
69
+ styles = Mongrel::DirHandler.new(File.dirname(__FILE__) + '/../../status/stylesheets', false)
70
+ http_server.register path + '/stylesheets', styles
55
71
  end
56
72
 
57
73
  # Serve files from the given directory
@@ -25,79 +25,38 @@ require 'mongrel'
25
25
  require 'erb'
26
26
 
27
27
  require File.dirname(__FILE__) + '/../common/http_server'
28
+ require File.dirname(__FILE__) + '/status_helper'
28
29
 
29
30
  module PDTP
30
31
  class Server
31
- #set up the mongrel server for serving the stats page
32
+ # A Mongrel::HttpHandler for generating the status page
32
33
  class StatusHandler < Mongrel::HttpHandler
33
- def initialize(dispatcher)
34
- @dispatcher = dispatcher
34
+ include StatusHelper
35
+
36
+ def initialize(vhost, dispatcher)
37
+ @vhost, @dispatcher = vhost, dispatcher
35
38
  end
36
39
 
37
- # process an incoming request to the admin page
40
+ # Process an incoming request to generate the status page
38
41
  def process(request, response)
39
42
  response.start(200) do |head, out|
40
43
  out.write begin
41
- generate_html_stats
44
+ # Read the status page ERb template
45
+ erb_data = File.read(File.dirname(__FILE__) + '/../../../status/index.erb')
46
+
47
+ # Render the status ERb template
48
+ html = ERB.new(erb_data).result(binding)
49
+
50
+ # Call reset_cycle from the StatusHelper to reset cycle() calls
51
+ reset_cycle
52
+
53
+ # Return output html
54
+ html
42
55
  rescue Exception => e
43
56
  "Exception: #{e}\n#{e.backtrace.join("\n")}"
44
57
  end
45
58
  end
46
- end
47
-
48
- #builds an html page with information about the server's internal workings
49
- def generate_html_stats
50
- s = ERB.new <<EOF
51
- <html><head><title>DistribuStream Status</title></head>
52
- <body>
53
- <h1>DistribuStream Status</h1>
54
- Time=<%= Time.now %><br> Connected Clients=<%= @dispatcher.connections.size %>
55
- <center><table border="1">
56
- <tr><th>Client</th><th>Transfers</th><th>Files</th></tr>
57
- <% @dispatcher.connections.each do |c| %>
58
- <tr><td>
59
- <% if c.file_service? %>
60
- <b>File Service</b>
61
- <% else %>
62
- <%= @dispatcher.connection_name(c) %>
63
- <% end %>
64
- <% host, port = c.get_peer_info %>
65
- <br><%= host %>:<%= port %>
66
- </td>
67
- <td>
68
- <%
69
- @dispatcher.client_info(c).transfers.each do |key,t|
70
- if c==t.giver
71
- type="UP: "
72
- peer=t.taker
73
- else
74
- type="DOWN: "
75
- peer=t.giver
76
- end
77
- %>
78
- <%= type %> id=<%= t.transfer_id %><br>
79
- <%
80
- end
81
- %>
82
- </td>
83
- <td>
84
- <%
85
- @dispatcher.client_info(c).chunk_info.get_file_stats.each do |fs|
86
- %>
87
- <%= fs.url %> size=<%= fs.file_chunks %> req=<%= fs.chunks_requested %>
88
- prov=<%= fs.chunks_provided %> transf=<%= fs.chunks_transferring %><br>
89
- <%
90
- end
91
- %>
92
- </td></tr>
93
- <%
94
- end
95
- %>
96
- </table>
97
- </body></html>
98
- EOF
99
- s.result binding
100
- end
59
+ end
101
60
  end
102
61
  end
103
- end
62
+ end
@@ -0,0 +1,155 @@
1
+ #--
2
+ # Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ #
17
+ # This source file is distributed as part of the
18
+ # DistribuStream file transfer system.
19
+ #
20
+ # See http://distribustream.org/
21
+ #++
22
+
23
+ module PDTP
24
+ class Server
25
+ # Helpers to be called from the DistribuStream status page ERb
26
+ module StatusHelper
27
+ # Number of clients presently connected
28
+ def client_count
29
+ clients = @dispatcher.connections.size - 1
30
+ clients == 1 ? "1 client" : "#{clients} clients"
31
+ end
32
+
33
+ # Server's virtual host name
34
+ def vhost
35
+ @vhost
36
+ end
37
+
38
+ # Iterate over all connected peers
39
+ def each_peer(&block)
40
+ @dispatcher.connections.each(&block)
41
+ end
42
+
43
+ # Iterate over all of a peer's active transfers
44
+ def each_transfer(peer)
45
+ raise ArgumentError, "no block given" unless block_given?
46
+ @dispatcher.client_info(peer).transfers.each { |_, transfer| yield transfer }
47
+ end
48
+
49
+ # Iterate over all of a peer's active files
50
+ def each_file(peer, &block)
51
+ @dispatcher.client_info(peer).chunk_info.get_file_stats.each(&block)
52
+ end
53
+
54
+ # Name of a peer (client ID or file service)
55
+ def peer_name(peer)
56
+ if peer.file_service?
57
+ "<b>File Service</b>"
58
+ else
59
+ @dispatcher.connection_name(peer)
60
+ end
61
+ end
62
+
63
+ # IP address and port of a peer
64
+ def peer_address(peer)
65
+ host, port = peer.get_peer_info
66
+ "#{host}:#{port}"
67
+ end
68
+
69
+ # Information about an active transfer
70
+ def transfer_info(peer, transfer)
71
+ "#{peer == transfer.giver ? 'UP' : 'DOWN'}: id=#{transfer.transfer_id.split('$')[2..3].join(':')}"
72
+ end
73
+
74
+ # Path to a file
75
+ def file_path(file)
76
+ file.url.sub(/.+?:\/\/.+?\//, '/')
77
+ end
78
+
79
+ # Number of requested chunks that have been completed
80
+ def chunks_completed(file)
81
+ file.chunks_provided
82
+ end
83
+
84
+ # Number of requested or completed chunks
85
+ def chunks_active(file)
86
+ file.file_chunks
87
+ end
88
+
89
+ # Percent of a file that has been transferred
90
+ def percent_complete(file)
91
+ (chunks_completed(file).to_f / chunks_active(file) * 100).to_i
92
+ end
93
+
94
+ #
95
+ # Cycle code excerpted from ActionPack and released under the MIT license
96
+ # Copyright (c) 2004-2006 David Heinemeier Hansson
97
+ #
98
+
99
+ def cycle(first_value, *values)
100
+ if (values.last.instance_of? Hash)
101
+ params = values.pop
102
+ name = params[:name]
103
+ else
104
+ name = "default"
105
+ end
106
+ values.unshift(first_value)
107
+
108
+ cycle = get_cycle(name)
109
+ if (cycle.nil? || cycle.values != values)
110
+ cycle = set_cycle(name, Cycle.new(*values))
111
+ end
112
+ return cycle.to_s
113
+ end
114
+
115
+ def reset_cycle(name = "default")
116
+ cycle = get_cycle(name)
117
+ cycle.reset unless cycle.nil?
118
+ end
119
+
120
+ #######
121
+ private
122
+ #######
123
+
124
+ class Cycle #:nodoc:
125
+ attr_reader :values
126
+
127
+ def initialize(first_value, *values)
128
+ @values = values.unshift(first_value)
129
+ reset
130
+ end
131
+
132
+ def reset
133
+ @index = 0
134
+ end
135
+
136
+ def to_s
137
+ value = @values[@index].to_s
138
+ @index = (@index + 1) % @values.size
139
+ return value
140
+ end
141
+ end
142
+
143
+
144
+ def get_cycle(name)
145
+ @_cycles = Hash.new unless defined?(@_cycles)
146
+ return @_cycles[name]
147
+ end
148
+
149
+ def set_cycle(name, cycle_object)
150
+ @_cycles = Hash.new unless defined?(@_cycles)
151
+ @_cycles[name] = cycle_object
152
+ end
153
+ end
154
+ end
155
+ end
Binary file
data/status/index.erb ADDED
@@ -0,0 +1,44 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
3
+ <head>
4
+ <title>DistribuStream Status</title>
5
+ <link href="stylesheets/style.css" rel="stylesheet" type="text/css" />
6
+ </head>
7
+ <body>
8
+ <h1><img src="images/logo.png" alt="DistribuStream Status" /></h1>
9
+ <div id="server_info">
10
+ Host: <%= vhost %><br />
11
+ <%= client_count %>
12
+ </div>
13
+ <center>
14
+ <table border="0" cellpadding="0" cellspacing="0">
15
+ <tr><th>Client</th><th>Transfers</th><th>Files</th></tr>
16
+ <% each_peer do |peer| %>
17
+ <tr class="row<%= cycle(' row_alternate', '') %>"><td>
18
+ <%= peer_name(peer) %><br />
19
+ <%= peer_address(peer) %>
20
+ </td>
21
+ <td>
22
+ <% each_transfer(peer) do |transfer| %>
23
+ <%= transfer_info(peer, transfer) %><br />
24
+ <% end %>
25
+ </td>
26
+ <td>
27
+ <% each_file(peer) do |file| %>
28
+ <%= file_path(file) %>
29
+ (<%= chunks_completed(file) %>/<%= chunks_active(file) %>,
30
+ <%= percent_complete(file) %>%)<br />
31
+ <% end %>
32
+ </td></tr>
33
+ <% end %>
34
+ </table>
35
+ </center>
36
+ <hr />
37
+ <div id="footer">
38
+ <%= vhost %> -
39
+ <a href="http://distribustream.org/">DistribuStream</a> <%= PDTP::VERSION %>
40
+ &copy;2006-07 <a href="http://www.clickcaster.com/">ClickCaster, Inc.</a><br />
41
+ <%= Time.now %>
42
+ </div>
43
+ </body>
44
+ </html>
@@ -0,0 +1,42 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ }
5
+
6
+ html, body {
7
+ font-family: Verdana, Helvetica, Arial, sans-serif;
8
+ font-size: 12px;
9
+ color: black;
10
+ background: white;
11
+ padding: 0.5em;
12
+ }
13
+
14
+ hr {
15
+ margin: 0.5em;
16
+ }
17
+
18
+ th {
19
+ text-align: left;
20
+ font-size: 1.2em;
21
+ padding: 0.2em 1em;
22
+ }
23
+
24
+ td {
25
+ text-align: left;
26
+ padding: 0.5em;
27
+ }
28
+
29
+ #server_info {
30
+ position: absolute;
31
+ top: 1em;
32
+ right: 1em;
33
+ font-size: 1.2em;
34
+ }
35
+
36
+ #footer {
37
+ font-style: italic;
38
+ }
39
+
40
+ .row_alternate {
41
+ background: #EEEEEE;
42
+ }
metadata CHANGED
@@ -1,9 +1,9 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.4
2
+ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: distribustream
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.4.0
6
+ version: 0.4.1
7
7
  date: 2008-11-15 00:00:00 -07:00
8
8
  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
9
9
  require_paths:
@@ -33,37 +33,43 @@ authors:
33
33
  - James Sanders
34
34
  - Tom Stapleton
35
35
  files:
36
- - bin/dsclient
37
36
  - bin/dstream
37
+ - bin/dsclient
38
38
  - lib/pdtp
39
+ - lib/pdtp/server
40
+ - lib/pdtp/common.rb
39
41
  - lib/pdtp/client
40
- - lib/pdtp/client/callbacks.rb
41
- - lib/pdtp/client/connection.rb
42
+ - lib/pdtp/common
43
+ - lib/pdtp/client.rb
44
+ - lib/pdtp/server.rb
45
+ - lib/pdtp/server/file_service_protocol.rb
46
+ - lib/pdtp/server/status_handler.rb
47
+ - lib/pdtp/server/trust.rb
48
+ - lib/pdtp/server/status_helper.rb
49
+ - lib/pdtp/server/transfer.rb
50
+ - lib/pdtp/server/client_info.rb
51
+ - lib/pdtp/server/connection.rb
52
+ - lib/pdtp/server/file_service.rb
53
+ - lib/pdtp/server/dispatcher.rb
42
54
  - lib/pdtp/client/file_buffer.rb
43
- - lib/pdtp/client/file_service.rb
44
55
  - lib/pdtp/client/http_client.rb
45
56
  - lib/pdtp/client/http_handler.rb
57
+ - lib/pdtp/client/callbacks.rb
46
58
  - lib/pdtp/client/transfer.rb
47
- - lib/pdtp/client.rb
48
- - lib/pdtp/common
49
- - lib/pdtp/common/file_service.rb
50
- - lib/pdtp/common/http_server.rb
59
+ - lib/pdtp/client/connection.rb
60
+ - lib/pdtp/client/file_service.rb
51
61
  - lib/pdtp/common/length_prefix_protocol.rb
62
+ - lib/pdtp/common/http_server.rb
52
63
  - lib/pdtp/common/protocol.rb
53
- - lib/pdtp/common.rb
54
- - lib/pdtp/server
55
- - lib/pdtp/server/client_info.rb
56
- - lib/pdtp/server/connection.rb
57
- - lib/pdtp/server/dispatcher.rb
58
- - lib/pdtp/server/file_service.rb
59
- - lib/pdtp/server/file_service_protocol.rb
60
- - lib/pdtp/server/status_handler.rb
61
- - lib/pdtp/server/transfer.rb
62
- - lib/pdtp/server/trust.rb
63
- - lib/pdtp/server.rb
64
+ - lib/pdtp/common/file_service.rb
65
+ - conf/example.yml
64
66
  - conf/bigchunk.yml
65
67
  - conf/debug.yml
66
- - conf/example.yml
68
+ - status/stylesheets
69
+ - status/images
70
+ - status/index.erb
71
+ - status/stylesheets/style.css
72
+ - status/images/logo.png
67
73
  - Rakefile
68
74
  - distribustream.gemspec
69
75
  - COPYING