distribustream 0.5.0 → 0.5.1
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 +8 -0
- data/bin/dstream +16 -3
- data/conf/bigchunk.yml +7 -1
- data/conf/debug.yml +6 -0
- data/conf/example.yml +6 -0
- data/distribustream.gemspec +3 -2
- data/lib/pdtp/common.rb +4 -1
- data/lib/pdtp/common/length_prefix_protocol.rb +10 -10
- data/lib/pdtp/common/range_map.rb +138 -0
- data/lib/pdtp/server/bandwidth_estimator.rb +5 -3
- data/lib/pdtp/server/chunk_info.rb +46 -39
- data/lib/pdtp/server/connection.rb +92 -44
- data/lib/pdtp/server/dispatcher.rb +34 -26
- data/lib/pdtp/server/file_info.rb +58 -0
- data/lib/pdtp/server/file_service.rb +5 -35
- data/lib/pdtp/server/file_service_connection.rb +44 -0
- data/lib/pdtp/server/status_handler.rb +3 -2
- data/lib/pdtp/server/transfer_manager.rb +45 -31
- data/lib/pdtp/server/trust.rb +5 -8
- data/status/index.erb +2 -2
- metadata +40 -28
data/CHANGES
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
Version 0.5.1
|
2
|
+
|
3
|
+
* Add #score method to peers to compare ideal transfer candidates
|
4
|
+
|
5
|
+
* Convert PDTP::Server::ChunkInfo to store chunk data in RangeMaps
|
6
|
+
|
7
|
+
* Add daemonization support
|
8
|
+
|
1
9
|
Version 0.5.0
|
2
10
|
|
3
11
|
* Factor traffic routing code into PDTP::Server::TransferManager
|
data/bin/dstream
CHANGED
@@ -21,6 +21,7 @@
|
|
21
21
|
# See http://distribustream.org/
|
22
22
|
#++
|
23
23
|
|
24
|
+
require 'rubygems'
|
24
25
|
require 'optparse'
|
25
26
|
require File.dirname(__FILE__) + '/../lib/pdtp/server'
|
26
27
|
|
@@ -28,6 +29,7 @@ program_name = File.basename($0)
|
|
28
29
|
config_filename = nil
|
29
30
|
listen_addr = nil
|
30
31
|
listen_port = nil
|
32
|
+
daemonize = true
|
31
33
|
|
32
34
|
OptionParser.new do |opts|
|
33
35
|
opts.banner = "Usage: #{program_name} [options]"
|
@@ -40,11 +42,14 @@ OptionParser.new do |opts|
|
|
40
42
|
opts.on("--port PORT", "Listen on the specified port number.") do |p|
|
41
43
|
listen_port = p
|
42
44
|
end
|
45
|
+
opts.on("--foreground", "Run in the foreground instead of daemonizing.") do
|
46
|
+
daemonize = false
|
47
|
+
end
|
43
48
|
opts.on("--help", "Prints this usage info.") do
|
44
49
|
puts opts
|
45
50
|
exit
|
46
51
|
end
|
47
|
-
|
52
|
+
opts.on("--version", "Print version information.") do
|
48
53
|
puts "#{program_name} #{PDTP::VERSION} - DistribuStream server application"
|
49
54
|
puts "Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)"
|
50
55
|
exit
|
@@ -57,7 +62,9 @@ config = {
|
|
57
62
|
:listen_port => 8000, #client listen port
|
58
63
|
:file_root => '.',
|
59
64
|
:chunk_size => 5000,
|
60
|
-
:quiet => true
|
65
|
+
:quiet => true,
|
66
|
+
:logfile => nil,
|
67
|
+
:daemonize => true
|
61
68
|
}
|
62
69
|
|
63
70
|
if config_filename.nil?
|
@@ -96,4 +103,10 @@ logger.level = Logger::INFO if config[:quiet]
|
|
96
103
|
server.enable_logging logger
|
97
104
|
server.enable_status_page
|
98
105
|
server.enable_file_service config[:file_root], :chunk_size => config[:chunk_size]
|
99
|
-
|
106
|
+
|
107
|
+
if daemonize and config[:daemonize]
|
108
|
+
require 'daemons/daemonize'
|
109
|
+
Daemonize.daemonize config[:logfile], program_name
|
110
|
+
end
|
111
|
+
|
112
|
+
server.run
|
data/conf/bigchunk.yml
CHANGED
data/conf/debug.yml
CHANGED
data/conf/example.yml
CHANGED
data/distribustream.gemspec
CHANGED
@@ -2,10 +2,10 @@ require 'rubygems'
|
|
2
2
|
|
3
3
|
GEMSPEC = Gem::Specification.new do |s|
|
4
4
|
s.name = "distribustream"
|
5
|
-
s.version = "0.5.
|
5
|
+
s.version = "0.5.1"
|
6
6
|
s.authors = ["Tony Arcieri", "Ashvin Mysore", "Galen Pahlke", "James Sanders", "Tom Stapleton"]
|
7
7
|
s.email = "tony@clickcaster.com"
|
8
|
-
s.date = "2008-
|
8
|
+
s.date = "2008-12-04"
|
9
9
|
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"
|
10
10
|
s.platform = Gem::Platform::RUBY
|
11
11
|
|
@@ -17,6 +17,7 @@ GEMSPEC = Gem::Specification.new do |s|
|
|
17
17
|
s.add_dependency("eventmachine", ">= 0.9.0")
|
18
18
|
s.add_dependency("mongrel", ">= 1.0.2")
|
19
19
|
s.add_dependency("json", ">= 1.1.0")
|
20
|
+
s.add_dependency("activesupport", ">= 1.4.0")
|
20
21
|
|
21
22
|
# RubyForge info
|
22
23
|
s.homepage = "http://distribustream.org"
|
data/lib/pdtp/common.rb
CHANGED
@@ -20,9 +20,12 @@
|
|
20
20
|
# See http://distribustream.org/
|
21
21
|
#++
|
22
22
|
|
23
|
+
require 'rubygems'
|
24
|
+
require 'active_support'
|
25
|
+
|
23
26
|
# Namespace for all PDTP components
|
24
27
|
module PDTP
|
25
|
-
PDTP::VERSION = '0.5.
|
28
|
+
PDTP::VERSION = '0.5.1' unless defined? PDTP::VERSION
|
26
29
|
def self.version() VERSION end
|
27
30
|
|
28
31
|
PDTP::DEFAULT_PORT = 6086 unless defined? PDTP::DEFAULT_PORT
|
@@ -50,10 +50,10 @@ module PDTP
|
|
50
50
|
new_data = data[0..(toread - 1)]
|
51
51
|
|
52
52
|
@data << new_data
|
53
|
-
@read += new_data.
|
53
|
+
@read += new_data.size
|
54
54
|
return nil unless @read == @size
|
55
55
|
|
56
|
-
@
|
56
|
+
@payload_length = @data.unpack(@size == 2 ? 'n' : 'N').first
|
57
57
|
result = data[toread..data.length]
|
58
58
|
|
59
59
|
return nil if result.nil? or result.empty?
|
@@ -61,13 +61,13 @@ module PDTP
|
|
61
61
|
end
|
62
62
|
|
63
63
|
# Length of the payload extracted from the prefix
|
64
|
-
def
|
65
|
-
raise RuntimeError, '
|
66
|
-
@
|
64
|
+
def payload_length
|
65
|
+
raise RuntimeError, 'payload_length called before prefix extracted' unless read?
|
66
|
+
@payload_length
|
67
67
|
end
|
68
68
|
|
69
69
|
def reset!
|
70
|
-
@
|
70
|
+
@payload_length = nil
|
71
71
|
@read = 0
|
72
72
|
@data = ''
|
73
73
|
end
|
@@ -97,13 +97,13 @@ module PDTP
|
|
97
97
|
@buffer << data
|
98
98
|
|
99
99
|
# Don't do anything until we receive the specified amount of data
|
100
|
-
return unless @buffer.
|
100
|
+
return unless @buffer.size >= @prefix.payload_length
|
101
101
|
|
102
102
|
# Extract the specified amount of data and process it
|
103
|
-
data = @buffer[0..(@prefix.
|
103
|
+
data = @buffer[0..(@prefix.payload_length - 1)]
|
104
104
|
|
105
105
|
# Store any remaining data
|
106
|
-
remainder = @buffer[@prefix.
|
106
|
+
remainder = @buffer[@prefix.payload_length..@buffer.length]
|
107
107
|
|
108
108
|
# Invoke receive_packet and allow the user to process the data
|
109
109
|
receive_packet data
|
@@ -127,4 +127,4 @@ module PDTP
|
|
127
127
|
send_data [data.size].pack(@prefix.size == 2 ? 'n' : 'N') << data
|
128
128
|
end
|
129
129
|
end
|
130
|
-
end
|
130
|
+
end
|
@@ -0,0 +1,138 @@
|
|
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
|
+
# Structure which maps non-overlapping ranges to objects
|
25
|
+
class RangeMap
|
26
|
+
include Enumerable
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@ranges = []
|
30
|
+
end
|
31
|
+
|
32
|
+
# Insert a range into the RangeMap
|
33
|
+
def []=(range, obj)
|
34
|
+
range = range..range if range.is_a?(Integer) or range.is_a?(Float)
|
35
|
+
raise ArgumentError, 'key must be a number or range' unless range.is_a?(Range)
|
36
|
+
|
37
|
+
index = binary_search range.begin
|
38
|
+
|
39
|
+
# Correct total overlap
|
40
|
+
[index, index + 1].each do |i|
|
41
|
+
while @ranges[i] and @ranges[i][0].begin >= range.begin and @ranges[i][0].end <= range.end
|
42
|
+
@ranges.delete_at i
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Correct overlap with leftmost member
|
47
|
+
if @ranges[index] and @ranges[index][0].begin <= range.begin
|
48
|
+
if range.end < @ranges[index][0].end
|
49
|
+
@ranges[index][0] = (range.end + 1)..(@ranges[index][0].end)
|
50
|
+
else
|
51
|
+
@ranges[index][0] = (@ranges[index][0].begin)..(range.begin - 1)
|
52
|
+
index += 1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Correct overlap with rightmost member
|
57
|
+
if @ranges[index] and @ranges[index][0].begin <= range.end
|
58
|
+
@ranges[index][0] = (range.end + 1)..(@ranges[index][0].end)
|
59
|
+
end
|
60
|
+
|
61
|
+
@ranges.insert(index, [range, obj])
|
62
|
+
obj
|
63
|
+
end
|
64
|
+
|
65
|
+
# Find a value in the RangeMap
|
66
|
+
def [](value)
|
67
|
+
return nil if empty?
|
68
|
+
range, obj = @ranges[binary_search(value)]
|
69
|
+
return nil if range.nil?
|
70
|
+
|
71
|
+
case value
|
72
|
+
when Integer, Float
|
73
|
+
return nil unless range.include?(value)
|
74
|
+
else raise ArgumentError, 'key must be a number'
|
75
|
+
end
|
76
|
+
|
77
|
+
obj
|
78
|
+
end
|
79
|
+
|
80
|
+
# Iterate over all ranges and objects
|
81
|
+
def each(&block)
|
82
|
+
@ranges.each(&block)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Number of entries in the RangeMap
|
86
|
+
def size
|
87
|
+
@ranges.size
|
88
|
+
end
|
89
|
+
|
90
|
+
# First range
|
91
|
+
def first
|
92
|
+
@ranges.first[0]
|
93
|
+
end
|
94
|
+
|
95
|
+
# Last range
|
96
|
+
def last
|
97
|
+
@ranges.last[0]
|
98
|
+
end
|
99
|
+
|
100
|
+
# Is the RangeMap empty?
|
101
|
+
def empty?
|
102
|
+
@ranges.empty?
|
103
|
+
end
|
104
|
+
|
105
|
+
# Remove all elements from the RangeMap
|
106
|
+
def clear
|
107
|
+
@ranges.clear
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
# Inspect the RangeMap
|
112
|
+
def inspect
|
113
|
+
"#<PDTP::RangeMap {#{@ranges.map { |r| "#{r.first}=>#{r.last.inspect}" }.join(", ")}}>"
|
114
|
+
end
|
115
|
+
|
116
|
+
#########
|
117
|
+
protected
|
118
|
+
#########
|
119
|
+
|
120
|
+
# Find the index of the range nearest the given value
|
121
|
+
def binary_search(value, a = 0, b = @ranges.size)
|
122
|
+
pivot = (a + b) / 2
|
123
|
+
range, _ = @ranges[pivot]
|
124
|
+
|
125
|
+
return b if range.nil?
|
126
|
+
|
127
|
+
if value < range.begin
|
128
|
+
return a if a == pivot
|
129
|
+
binary_search(value, a, pivot)
|
130
|
+
elsif value > range.end
|
131
|
+
return b if b == pivot
|
132
|
+
binary_search(value, pivot + 1, b)
|
133
|
+
else
|
134
|
+
pivot
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -20,6 +20,8 @@
|
|
20
20
|
# See http://distribustream.org/
|
21
21
|
#++
|
22
22
|
|
23
|
+
require File.dirname(__FILE__) + '/../common'
|
24
|
+
|
23
25
|
module PDTP
|
24
26
|
class Server
|
25
27
|
# BandwidthEstimator logs the durations of chunk transfers over time,
|
@@ -76,7 +78,7 @@ module PDTP
|
|
76
78
|
return 0 if pruned.size.zero?
|
77
79
|
|
78
80
|
# Calculate the mean of the pruned estimates
|
79
|
-
@estimate = pruned.
|
81
|
+
@estimate = pruned.sum / pruned.size
|
80
82
|
end
|
81
83
|
|
82
84
|
#########
|
@@ -95,12 +97,12 @@ module PDTP
|
|
95
97
|
end
|
96
98
|
|
97
99
|
# Sum the overlapping transfer rates and store them as estimates
|
98
|
-
@estimates ||= overlapping_transfers.
|
100
|
+
@estimates ||= overlapping_transfers.sum
|
99
101
|
end
|
100
102
|
|
101
103
|
# Compute the mean of the bandwidth estimates
|
102
104
|
def estimates_mean
|
103
|
-
@mean ||= estimates.
|
105
|
+
@mean ||= estimates.sum / estimates.size
|
104
106
|
end
|
105
107
|
|
106
108
|
# Compute the variance of the bandwidth estimates
|
@@ -20,77 +20,84 @@
|
|
20
20
|
# See http://distribustream.org/
|
21
21
|
#++
|
22
22
|
|
23
|
+
require File.dirname(__FILE__) + '/../common/range_map'
|
24
|
+
|
23
25
|
module PDTP
|
24
26
|
class Server
|
25
27
|
# Stores information about the chunks requested or provided by a client
|
26
|
-
class
|
28
|
+
# FIXME this entire class is in need of a rewrite
|
29
|
+
class ChunkInfo
|
27
30
|
def initialize
|
28
31
|
@files = {}
|
29
32
|
end
|
30
33
|
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
def unrequest(filename,range); set(filename,range, :none); end
|
37
|
-
def transfer(filename,range); set(filename,range, :transfer); end
|
34
|
+
# Set the state for a given chunk range
|
35
|
+
def set(state, filename, range)
|
36
|
+
chunks = @files[filename] ||= RangeMap.new
|
37
|
+
chunks[range] = state
|
38
|
+
end
|
38
39
|
|
39
|
-
def provided?(filename,
|
40
|
-
|
40
|
+
def provided?(filename, chunk)
|
41
|
+
get(filename, chunk) == :provided
|
42
|
+
end
|
43
|
+
|
44
|
+
def requested?(filename,chunk)
|
45
|
+
get(filename, chunk) == :requested
|
46
|
+
end
|
41
47
|
|
42
48
|
#returns a high priority requested chunk
|
43
49
|
def high_priority_chunk
|
44
50
|
#right now return any chunk
|
45
|
-
@files.each do |name,
|
46
|
-
|
47
|
-
|
48
|
-
|
51
|
+
@files.each do |name, chunkmap|
|
52
|
+
range, _ = chunkmap.find { |_, state| state == :requested }
|
53
|
+
next unless range
|
54
|
+
|
55
|
+
return name, range.begin
|
49
56
|
end
|
50
57
|
|
51
58
|
nil
|
52
59
|
end
|
53
|
-
|
54
|
-
class FileStats
|
55
|
-
attr_accessor :file_chunks, :chunks_requested, :url
|
56
|
-
attr_accessor :chunks_provided, :chunks_transferring
|
57
|
-
|
58
|
-
def initialize
|
59
|
-
@url = ""
|
60
|
-
@file_chunks = 0
|
61
|
-
@chunks_requested = 0
|
62
|
-
@chunks_provided = 0
|
63
|
-
@chunks_transferring = 0
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
60
|
+
|
67
61
|
# Returns an array of FileStats objects for debug output
|
68
62
|
def get_file_stats
|
69
|
-
@files.map do |name,
|
63
|
+
@files.map do |name, chunkmap|
|
70
64
|
fs = FileStats.new
|
71
|
-
fs.file_chunks =
|
65
|
+
fs.file_chunks = chunkmap.last.end + 1
|
72
66
|
fs.url = name
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
67
|
+
|
68
|
+
chunkmap.each do |range, state|
|
69
|
+
length = range.end - range.begin + 1
|
70
|
+
|
71
|
+
case state
|
72
|
+
when :requested then fs.chunks_requested += length
|
73
|
+
when :provided then fs.chunks_provided += length
|
74
|
+
when :transferring then fs.chunks_transferring += length
|
75
|
+
end
|
77
76
|
end
|
78
77
|
|
79
78
|
fs
|
80
79
|
end
|
81
80
|
end
|
82
|
-
|
81
|
+
|
83
82
|
#########
|
84
83
|
protected
|
85
84
|
#########
|
86
85
|
|
87
|
-
def get(filename,chunk)
|
86
|
+
def get(filename, chunk)
|
88
87
|
@files[filename][chunk] rescue :neither
|
89
88
|
end
|
89
|
+
|
90
|
+
class FileStats
|
91
|
+
attr_accessor :file_chunks, :chunks_requested, :url
|
92
|
+
attr_accessor :chunks_provided, :chunks_transferring
|
90
93
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
+
def initialize
|
95
|
+
@url = ""
|
96
|
+
@file_chunks = 0
|
97
|
+
@chunks_requested = 0
|
98
|
+
@chunks_provided = 0
|
99
|
+
@chunks_transferring = 0
|
100
|
+
end
|
94
101
|
end
|
95
102
|
end
|
96
103
|
end
|
@@ -20,6 +20,7 @@
|
|
20
20
|
# See http://distribustream.org/
|
21
21
|
#++
|
22
22
|
|
23
|
+
require File.dirname(__FILE__) + '/../common'
|
23
24
|
require File.dirname(__FILE__) + '/../common/protocol'
|
24
25
|
require File.dirname(__FILE__) + '/chunk_info'
|
25
26
|
require File.dirname(__FILE__) + '/trust'
|
@@ -57,11 +58,41 @@ module PDTP
|
|
57
58
|
|
58
59
|
super
|
59
60
|
end
|
61
|
+
|
62
|
+
# Address prefix of this peer
|
63
|
+
def prefix(mask = 24)
|
64
|
+
raise ArgumentError, "mask must be 8, 16, or 24" unless [8, 16, 24].include?(mask)
|
65
|
+
addr, _ = @cached_peer_info
|
66
|
+
|
67
|
+
octets = mask / 8
|
68
|
+
addr.split('.')[0..(octets - 1)].join('.')
|
69
|
+
end
|
70
|
+
|
71
|
+
# Calculate a score from 0 to 10 for two clients being a good match
|
72
|
+
def score(peer)
|
73
|
+
s = 0.0
|
74
|
+
|
75
|
+
# One point for each matching prefix
|
76
|
+
[8, 16, 24].each { |mask| s += 1 if prefix(mask) == peer.prefix(mask) }
|
77
|
+
|
78
|
+
# Up to three points for having ample bandwidth compared to us
|
79
|
+
[0.5, 1, 2].each { |divisor| s += 1 if peer.upstream_bandwidth > downstream_bandwidth / divisor }
|
80
|
+
|
81
|
+
# 0 - 4 points for trust
|
82
|
+
s += 4 * @trust.weight(peer.trust)
|
83
|
+
|
84
|
+
#puts "#{peer} trust: #{@trust.weight(peer.trust)}"
|
85
|
+
|
86
|
+
# Deprioritize file service by cutting its score in half
|
87
|
+
s /= 2 if peer.file_service?
|
88
|
+
|
89
|
+
s
|
90
|
+
end
|
60
91
|
|
61
92
|
# Log a completed transfer and update internal data
|
62
93
|
def success(transfer)
|
63
94
|
if transfer.taker == self
|
64
|
-
@chunk_info.
|
95
|
+
@chunk_info.set :provided, transfer.url, transfer.chunkid..transfer.chunkid
|
65
96
|
@trust.success transfer.giver.trust
|
66
97
|
bandwidth_estimator = @downstream
|
67
98
|
else
|
@@ -91,65 +122,82 @@ module PDTP
|
|
91
122
|
def downstream_bandwidth
|
92
123
|
@downstream.estimate rescue nil
|
93
124
|
end
|
94
|
-
|
95
|
-
#
|
125
|
+
|
126
|
+
# Number of concurrent downloads desired desired
|
127
|
+
# FIXME hardcoded, should probably be computed or client-specified
|
128
|
+
def max_concurrent_downloads; 8; end
|
129
|
+
|
130
|
+
# For now, keep concurrent_downloads equal to uploads
|
131
|
+
alias_method :max_concurrent_uploads, :max_concurrent_downloads
|
132
|
+
|
133
|
+
# Maximum number of "half open" transfers allowed
|
134
|
+
# FIXME hardcoded, should probably be computed
|
135
|
+
def max_half_open; 8; end
|
136
|
+
|
137
|
+
# Are we below the limit on half-open transfer slots?
|
138
|
+
def empty_transfer_slots?
|
139
|
+
@transfers.select { |_, t| not t.verification_asked }.size < max_half_open
|
140
|
+
end
|
141
|
+
|
142
|
+
# List of all active downloads
|
143
|
+
def downloads
|
144
|
+
@transfers.select { |_, t| t.taker == self and t.verification_asked }
|
145
|
+
end
|
146
|
+
|
147
|
+
# List of all active uplaods
|
148
|
+
def uploads
|
149
|
+
@transfers.select { |_, t| t.giver == self and t.verification_asked }
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns true if this client wants the server to spawn a download for it
|
96
153
|
def wants_download?
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
154
|
+
return false unless @chunk_info.high_priority_chunk
|
155
|
+
return false unless empty_transfer_slots?
|
156
|
+
|
157
|
+
# Are we at our concurrent download limit?
|
158
|
+
downloads.size < max_concurrent_downloads
|
159
|
+
end
|
160
|
+
|
161
|
+
# Returns true if this client wants the server to spawn an uplaod for it
|
162
|
+
def wants_upload?
|
163
|
+
return false unless empty_transfer_slots?
|
164
|
+
|
165
|
+
# Are we at our concurrent upload limit?
|
166
|
+
uploads.size < max_concurrent_uploads
|
167
|
+
end
|
110
168
|
|
111
169
|
# Returns a list of all the stalled transfers this client is a part of
|
112
170
|
def stalled_transfers
|
113
|
-
stalled = []
|
114
171
|
timeout = 20.0
|
115
172
|
now = Time.now
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
stalled << t
|
173
|
+
|
174
|
+
@transfers.inject([]) do |stalled, (_, t)|
|
175
|
+
# only delete if we are the acceptor to prevent race conditions
|
176
|
+
unless t.acceptor == self
|
177
|
+
stalled << t if now - t.creation_time > timeout and not t.verification_asked
|
121
178
|
end
|
179
|
+
|
180
|
+
stalled
|
122
181
|
end
|
123
|
-
stalled
|
124
182
|
end
|
125
183
|
|
126
184
|
# Is this connection the file service?
|
127
185
|
def file_service?
|
128
|
-
|
186
|
+
false
|
129
187
|
end
|
130
|
-
|
131
|
-
# Mark this connection as being a file service
|
132
|
-
def mark_as_file_service
|
133
|
-
@file_service = true
|
134
|
-
end
|
135
|
-
|
188
|
+
|
136
189
|
#
|
137
|
-
#
|
190
|
+
# Delegate PDTP::Protocol callbacks to the dispatcher
|
138
191
|
#
|
139
|
-
|
140
|
-
def connection_completed
|
141
|
-
raise(RuntimeError, 'server was never initialized') unless @dispatcher
|
142
|
-
@dispatcher.connection_created self
|
143
|
-
end
|
144
|
-
|
145
|
-
def connection_destroyed
|
146
|
-
raise(RuntimeError, 'server was never initialized') unless @dispatcher
|
147
|
-
@dispatcher.connection_destroyed self
|
148
|
-
end
|
149
192
|
|
150
|
-
|
151
|
-
|
152
|
-
|
193
|
+
%w{connection_completed connection_destroyed receive_message}.each do |method|
|
194
|
+
module_eval <<-EOD
|
195
|
+
def #{method}(*args)
|
196
|
+
raise(RuntimeError, 'server was never initialized') unless @dispatcher
|
197
|
+
args << self
|
198
|
+
@dispatcher.#{method}(*args)
|
199
|
+
end
|
200
|
+
EOD
|
153
201
|
end
|
154
202
|
end
|
155
203
|
end
|
@@ -21,12 +21,13 @@
|
|
21
21
|
#++
|
22
22
|
|
23
23
|
require File.dirname(__FILE__) + '/transfer_manager'
|
24
|
+
require File.dirname(__FILE__) + '/file_service_connection'
|
24
25
|
|
25
26
|
module PDTP
|
26
27
|
class Server
|
27
28
|
# Core dispatching and control logic for PDTP servers
|
28
29
|
class Dispatcher
|
29
|
-
attr_reader :connections
|
30
|
+
attr_reader :connections, :server
|
30
31
|
|
31
32
|
def initialize(server, file_service)
|
32
33
|
@server = server
|
@@ -37,16 +38,22 @@ module PDTP
|
|
37
38
|
end
|
38
39
|
|
39
40
|
# Register a PDTP::Server::Connection with the Dispatcher
|
40
|
-
def
|
41
|
+
def connection_completed(connection)
|
41
42
|
addr, port = connection.get_peer_info
|
42
43
|
|
44
|
+
# FIXME hacked file service registration. There really ought to be a better way
|
45
|
+
# to both register and authenticate file services
|
43
46
|
if @server.file_service_enabled? and not @seen_file_service and addr == @server.addr
|
44
|
-
#
|
47
|
+
# Display file service greeting when we see it connect
|
45
48
|
@server.log "file service running at #{addr}:#{@server.instance_eval { @http_port }}"
|
46
49
|
@seen_file_service = true
|
47
|
-
|
50
|
+
|
51
|
+
# Extend connection with file service-specific method implementations
|
52
|
+
connection.extend FileServiceConnection
|
53
|
+
elsif not @seen_file_service
|
54
|
+
raise RuntimeError, "File service failed to initialize. Ensure listen address is correct."
|
48
55
|
else
|
49
|
-
#
|
56
|
+
# Display greeting for normal client
|
50
57
|
@server.log "client connected: #{connection.get_peer_info.inspect}"
|
51
58
|
end
|
52
59
|
|
@@ -59,16 +66,8 @@ module PDTP
|
|
59
66
|
@connections.delete connection
|
60
67
|
end
|
61
68
|
|
62
|
-
# This function removes all stalled transfers from the list
|
63
|
-
# and spawns new transfers as appropriate
|
64
|
-
# It must be called periodically by EventMachine
|
65
|
-
def clear_all_stalled_transfers
|
66
|
-
@connections.each { |connection| clear_stalled_transfers_for_client connection }
|
67
|
-
@transfer_manager.spawn_all_transfers
|
68
|
-
end
|
69
|
-
|
70
69
|
# Handles all incoming messages from clients
|
71
|
-
def
|
70
|
+
def receive_message(command, message, connection)
|
72
71
|
# Store the command in the message hash
|
73
72
|
message["type"] = command
|
74
73
|
|
@@ -98,7 +97,7 @@ module PDTP
|
|
98
97
|
end
|
99
98
|
connection.send_message :tell_info, response
|
100
99
|
when "request", "provide", "unrequest", "unprovide"
|
101
|
-
handle_requestprovide connection, message
|
100
|
+
handle_requestprovide connection, command, message
|
102
101
|
when "ask_verify"
|
103
102
|
#check if the specified transfer is a real one
|
104
103
|
my_id = connection.client_id
|
@@ -136,6 +135,14 @@ module PDTP
|
|
136
135
|
# Process all clients that are in need of new transfers
|
137
136
|
@transfer_manager.spawn_all_transfers
|
138
137
|
end
|
138
|
+
|
139
|
+
# This function removes all stalled transfers from the list
|
140
|
+
# and spawns new transfers as appropriate
|
141
|
+
# It must be called periodically by EventMachine
|
142
|
+
def clear_all_stalled_transfers
|
143
|
+
@connections.each { |connection| clear_stalled_transfers_for_client connection }
|
144
|
+
@transfer_manager.spawn_all_transfers
|
145
|
+
end
|
139
146
|
|
140
147
|
#########
|
141
148
|
protected
|
@@ -154,13 +161,8 @@ module PDTP
|
|
154
161
|
local_hash = @file_service.get_chunk_hash transfer.url, transfer.chunkid
|
155
162
|
|
156
163
|
if connection == transfer.taker
|
157
|
-
success = (chunk_hash == local_hash)
|
158
|
-
|
159
|
-
if success
|
160
|
-
transfer.success
|
161
|
-
else
|
162
|
-
transfer.failure
|
163
|
-
end
|
164
|
+
success = (chunk_hash == local_hash) # Capture for hash_verify message
|
165
|
+
success ? transfer.success : transfer.failure
|
164
166
|
|
165
167
|
transfer.taker.send_message(:hash_verify,
|
166
168
|
:url => transfer.url,
|
@@ -174,18 +176,24 @@ module PDTP
|
|
174
176
|
@transfer_manager.process_client(connection)
|
175
177
|
end
|
176
178
|
|
179
|
+
CHUNK_STATES = {
|
180
|
+
'request' => :requested,
|
181
|
+
'provide' => :provided,
|
182
|
+
'unrequest' => :none,
|
183
|
+
'unprovide' => :none,
|
184
|
+
} unless defined? CHUNK_STATES
|
185
|
+
|
177
186
|
# Handles the request, provide, unrequest, unprovide messages
|
178
|
-
def handle_requestprovide(connection, message)
|
179
|
-
type = message["type"]
|
187
|
+
def handle_requestprovide(connection, command, message)
|
180
188
|
url = message["url"]
|
181
189
|
info = @file_service.get_info(url) rescue nil
|
182
190
|
raise ProtocolWarn, "Requested URL: '#{url}' not found" if info.nil?
|
183
191
|
|
184
|
-
exclude_partial = (
|
192
|
+
exclude_partial = (command=="provide") #only exclude partial chunks from provides
|
185
193
|
range = info.chunk_range_from_byte_range(message["range"],exclude_partial)
|
186
194
|
|
187
195
|
#call request, provide, unrequest, or unprovide
|
188
|
-
connection.chunk_info.
|
196
|
+
connection.chunk_info.set(CHUNK_STATES[command], url, range)
|
189
197
|
@transfer_manager.process_client(connection)
|
190
198
|
end
|
191
199
|
end
|
@@ -0,0 +1,58 @@
|
|
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
|
+
require File.dirname(__FILE__) + '/../common/file_service'
|
24
|
+
|
25
|
+
module PDTP
|
26
|
+
class Server
|
27
|
+
#The server specific file utilities
|
28
|
+
class FileInfo < PDTP::FileInfo
|
29
|
+
attr_accessor :path
|
30
|
+
|
31
|
+
#Return a raw string of chunk data. The range parameter is local to this chunk
|
32
|
+
#and zero based
|
33
|
+
def chunk_data(chunkid, range = nil)
|
34
|
+
begin
|
35
|
+
range = 0..chunk_size(chunkid) - 1 if range.nil? # full range of chunk if range isnt specified
|
36
|
+
raise if range.first < 0 or range.last >= chunk_size(chunkid)
|
37
|
+
start = range.first + chunkid * @base_chunk_size
|
38
|
+
size = range.last - range.first + 1
|
39
|
+
file = open @path
|
40
|
+
file.pos = start
|
41
|
+
file.read size
|
42
|
+
rescue nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
#reads the specified byte range from the file and returns it as a string
|
47
|
+
def read(range)
|
48
|
+
#puts "READING: range=#{range}"
|
49
|
+
begin
|
50
|
+
file = open @path
|
51
|
+
file.pos = range.first
|
52
|
+
file.read range.last - range.first + 1
|
53
|
+
rescue nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -20,44 +20,14 @@
|
|
20
20
|
# See http://distribustream.org/
|
21
21
|
#++
|
22
22
|
|
23
|
-
require
|
24
|
-
require
|
25
|
-
require
|
26
|
-
require File.dirname(__FILE__) + '/../common/file_service
|
23
|
+
require 'uri'
|
24
|
+
require 'pathname'
|
25
|
+
require 'digest/sha2'
|
26
|
+
require File.dirname(__FILE__) + '/../common/file_service'
|
27
|
+
require File.dirname(__FILE__) + '/file_info'
|
27
28
|
|
28
29
|
module PDTP
|
29
30
|
class Server
|
30
|
-
#The server specific file utilities
|
31
|
-
class FileInfo < PDTP::FileInfo
|
32
|
-
attr_accessor :path
|
33
|
-
|
34
|
-
#Return a raw string of chunk data. The range parameter is local to this chunk
|
35
|
-
#and zero based
|
36
|
-
def chunk_data(chunkid, range = nil)
|
37
|
-
begin
|
38
|
-
range = 0..chunk_size(chunkid) - 1 if range.nil? # full range of chunk if range isnt specified
|
39
|
-
raise if range.first < 0 or range.last >= chunk_size(chunkid)
|
40
|
-
start = range.first + chunkid * @base_chunk_size
|
41
|
-
size = range.last - range.first + 1
|
42
|
-
file = open @path
|
43
|
-
file.pos = start
|
44
|
-
file.read size
|
45
|
-
rescue nil
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
#reads the specified byte range from the file and returns it as a string
|
50
|
-
def read(range)
|
51
|
-
#puts "READING: range=#{range}"
|
52
|
-
begin
|
53
|
-
file = open @path
|
54
|
-
file.pos = range.first
|
55
|
-
file.read range.last - range.first + 1
|
56
|
-
rescue nil
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
31
|
#The file service provides utilities for determining various information about files.
|
62
32
|
class FileService < PDTP::FileService
|
63
33
|
attr_accessor :root, :default_chunk_size
|
@@ -0,0 +1,44 @@
|
|
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
|
+
require File.dirname(__FILE__) + '/../common'
|
24
|
+
require File.dirname(__FILE__) + '/../common/protocol'
|
25
|
+
require File.dirname(__FILE__) + '/chunk_info'
|
26
|
+
require File.dirname(__FILE__) + '/trust'
|
27
|
+
require File.dirname(__FILE__) + '/bandwidth_estimator'
|
28
|
+
|
29
|
+
module PDTP
|
30
|
+
class Server
|
31
|
+
# Mixin for PDTP::Server::Connection to give it file service capibility
|
32
|
+
module FileServiceConnection
|
33
|
+
# We are the file service
|
34
|
+
def file_service?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
# The file service does not explicitly want uploads
|
39
|
+
#def wants_upload?
|
40
|
+
# false
|
41
|
+
#end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -35,6 +35,7 @@ module PDTP
|
|
35
35
|
|
36
36
|
def initialize(vhost, dispatcher)
|
37
37
|
@vhost, @dispatcher = vhost, dispatcher
|
38
|
+
@status_erb = File.expand_path(File.dirname(__FILE__) + '/../../../status/index.erb')
|
38
39
|
end
|
39
40
|
|
40
41
|
# Process an incoming request to generate the status page
|
@@ -42,7 +43,7 @@ module PDTP
|
|
42
43
|
response.start(200) do |head, out|
|
43
44
|
out.write begin
|
44
45
|
# Read the status page ERb template
|
45
|
-
erb_data = File.read
|
46
|
+
erb_data = File.read @status_erb
|
46
47
|
|
47
48
|
# Render the status ERb template
|
48
49
|
html = ERB.new(erb_data).result(binding)
|
@@ -59,4 +60,4 @@ module PDTP
|
|
59
60
|
end
|
60
61
|
end
|
61
62
|
end
|
62
|
-
end
|
63
|
+
end
|
@@ -30,22 +30,22 @@ module PDTP
|
|
30
30
|
@connections, @file_service = connections, file_service
|
31
31
|
@updated_clients = []
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
# Add client to list of ones needing updates
|
35
35
|
def process_client(client)
|
36
36
|
@updated_clients << client
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
# Creates new transfers for all clients that have been updated
|
40
40
|
def spawn_all_transfers
|
41
41
|
@updated_clients.each { |client| spawn_transfers_for_client(client) }
|
42
42
|
@updated_clients.clear
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
#########
|
46
46
|
protected
|
47
47
|
#########
|
48
|
-
|
48
|
+
|
49
49
|
# Spawns uploads and downloads for this client.
|
50
50
|
# Should be called every time there is a change that would affect
|
51
51
|
# what this client has or wants
|
@@ -70,48 +70,62 @@ module PDTP
|
|
70
70
|
return false
|
71
71
|
end
|
72
72
|
|
73
|
-
@connections.each do |
|
74
|
-
next if connection ==
|
75
|
-
next unless
|
76
|
-
if
|
77
|
-
feasible_peers << c2
|
78
|
-
break if feasible_peers.size > 5
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# we now have a list of clients that have the requested chunk.
|
83
|
-
# pick one and start the transfer
|
84
|
-
if feasible_peers.size > 0
|
85
|
-
#FIXME base this on the trust model
|
86
|
-
giver = feasible_peers[rand(feasible_peers.size)]
|
87
|
-
return begin_transfer(connection,giver,url,chunkid)
|
88
|
-
#FIXME should we try again if begin_transfer fails?
|
73
|
+
@connections.each do |peer|
|
74
|
+
next if connection == peer
|
75
|
+
next unless peer.wants_upload?
|
76
|
+
feasible_peers << peer if peer.chunk_info.provided?(url, chunkid)
|
89
77
|
end
|
90
|
-
|
78
|
+
|
79
|
+
# debug info
|
80
|
+
# puts "Feasible peers: " + feasible_peers.map { |peer| peer.to_s }.join(", ")
|
81
|
+
|
82
|
+
giver = optimal_peer(connection, feasible_peers)
|
83
|
+
return begin_transfer(connection, giver, url, chunkid) if giver
|
84
|
+
|
85
|
+
#FIXME should we try again if begin_transfer fails?
|
91
86
|
false
|
92
87
|
end
|
88
|
+
|
89
|
+
# Apply constraints to the list of potential peers to select
|
90
|
+
# the optimal one
|
91
|
+
def optimal_peer(connection, peer_list)
|
92
|
+
# We can't do anything if there's zero or one candidates
|
93
|
+
return peer_list.first if peer_list.size <= 1
|
94
|
+
|
95
|
+
# Build a list of peers and their respective scores
|
96
|
+
scored_peers = peer_list.map { |peer| [peer, connection.score(peer)] }
|
97
|
+
|
98
|
+
# Sort the peers by score
|
99
|
+
sorted_peers = scored_peers.sort { |(_, a), (_, b)| b <=> a }
|
100
|
+
|
101
|
+
# debug info
|
102
|
+
#puts "Sorted peers: " + sorted_peers.map { |peer, score| "#{peer}(#{score})" }.join(", ")
|
103
|
+
|
104
|
+
# Return the top choice (from a nested array)
|
105
|
+
sorted_peers.first.first
|
106
|
+
end
|
93
107
|
|
94
108
|
# Creates a single upload for the specified client
|
95
109
|
# Returns true on success, false on failure
|
96
110
|
def spawn_upload_for_client(connection)
|
97
|
-
@connections.each do |
|
98
|
-
next if connection ==
|
99
|
-
next unless
|
111
|
+
@connections.each do |peer|
|
112
|
+
next if connection == peer
|
113
|
+
next unless peer.wants_download?
|
100
114
|
|
101
115
|
begin
|
102
|
-
url, chunkid =
|
116
|
+
url, chunkid = peer.chunk_info.high_priority_chunk
|
103
117
|
rescue
|
104
118
|
next
|
105
119
|
end
|
106
120
|
|
107
121
|
if connection.chunk_info.provided?(url, chunkid)
|
108
|
-
return begin_transfer(
|
122
|
+
return begin_transfer(peer, connection, url, chunkid)
|
109
123
|
end
|
110
124
|
end
|
111
125
|
|
112
126
|
false
|
113
127
|
end
|
114
|
-
|
128
|
+
|
115
129
|
# Creates a new transfer between two peers
|
116
130
|
# Returns true on success, or false if the specified transfer is already in progress
|
117
131
|
def begin_transfer(taker, giver, url, chunkid)
|
@@ -123,13 +137,13 @@ module PDTP
|
|
123
137
|
t2 = giver.transfers[t.transfer_id]
|
124
138
|
return false unless t1.nil? and t2.nil?
|
125
139
|
|
126
|
-
taker.chunk_info.transfer
|
140
|
+
taker.chunk_info.set(:transfer, url, chunkid..chunkid)
|
127
141
|
taker.transfers[t.transfer_id] = t
|
128
142
|
giver.transfers[t.transfer_id] = t
|
129
143
|
|
130
144
|
#send transfer message to the connector
|
131
145
|
addr, port = t.acceptor.get_peer_info
|
132
|
-
|
146
|
+
|
133
147
|
t.connector.send_message(:transfer,
|
134
148
|
:host => addr,
|
135
149
|
:port => t.acceptor.listen_port,
|
@@ -138,9 +152,9 @@ module PDTP
|
|
138
152
|
:range => byte_range,
|
139
153
|
:peer_id => t.acceptor.client_id
|
140
154
|
)
|
141
|
-
|
155
|
+
|
142
156
|
true
|
143
157
|
end
|
144
158
|
end
|
145
159
|
end
|
146
|
-
end
|
160
|
+
end
|
data/lib/pdtp/server/trust.rb
CHANGED
@@ -81,18 +81,15 @@ module PDTP
|
|
81
81
|
@outgoing.each { |_, link| link.trust = link.success / total_transfers }
|
82
82
|
@outgoing.each do |target, link|
|
83
83
|
[target.outgoing, target.implicit].each do |links|
|
84
|
-
links.each do |
|
85
|
-
nextlinktarget = nextlinkedge[0]
|
86
|
-
nextlink = nextlinkedge[1]
|
84
|
+
links.each do |nextlinktarget, nextlink|
|
87
85
|
next unless outgoing[nextlinktarget].nil?
|
88
|
-
|
89
|
-
|
90
|
-
|
86
|
+
next unless implicit[nextlinktarget].nil? or implicit[nextlinktarget].trust < link.trust * nextlink.trust
|
87
|
+
|
88
|
+
implicit[nextlinktarget] = Edge.new(
|
91
89
|
link.trust * nextlink.trust,
|
92
90
|
nextlink.success,
|
93
91
|
nextlink.transfers
|
94
|
-
|
95
|
-
end
|
92
|
+
)
|
96
93
|
end
|
97
94
|
end
|
98
95
|
end
|
data/status/index.erb
CHANGED
@@ -22,8 +22,8 @@
|
|
22
22
|
<% each_peer do |peer| %>
|
23
23
|
<tr class="row<%= cycle(' row_alternate', '') %>">
|
24
24
|
<td>
|
25
|
-
|
26
|
-
|
25
|
+
<%= peer_name(peer) %><br />
|
26
|
+
<%= peer_address(peer) unless peer.file_service? %>
|
27
27
|
</td>
|
28
28
|
<td>
|
29
29
|
Upstream: <%= upstream_bandwidth(peer) %><br />
|
metadata
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.
|
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.5.
|
7
|
-
date: 2008-
|
6
|
+
version: 0.5.1
|
7
|
+
date: 2008-12-04 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:
|
10
10
|
- lib
|
@@ -33,45 +33,48 @@ 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/
|
41
|
-
- lib/pdtp/client
|
42
|
+
- lib/pdtp/common
|
43
|
+
- lib/pdtp/client.rb
|
44
|
+
- lib/pdtp/server.rb
|
45
|
+
- lib/pdtp/server/file_info.rb
|
46
|
+
- lib/pdtp/server/file_service_protocol.rb
|
47
|
+
- lib/pdtp/server/chunk_info.rb
|
48
|
+
- lib/pdtp/server/transfer_manager.rb
|
49
|
+
- lib/pdtp/server/status_handler.rb
|
50
|
+
- lib/pdtp/server/trust.rb
|
51
|
+
- lib/pdtp/server/status_helper.rb
|
52
|
+
- lib/pdtp/server/bandwidth_estimator.rb
|
53
|
+
- lib/pdtp/server/file_service_connection.rb
|
54
|
+
- lib/pdtp/server/transfer.rb
|
55
|
+
- lib/pdtp/server/connection.rb
|
56
|
+
- lib/pdtp/server/file_service.rb
|
57
|
+
- lib/pdtp/server/dispatcher.rb
|
42
58
|
- lib/pdtp/client/file_buffer.rb
|
43
|
-
- lib/pdtp/client/file_service.rb
|
44
59
|
- lib/pdtp/client/http_client.rb
|
45
60
|
- lib/pdtp/client/http_handler.rb
|
61
|
+
- lib/pdtp/client/callbacks.rb
|
46
62
|
- lib/pdtp/client/transfer.rb
|
47
|
-
- lib/pdtp/client.rb
|
48
|
-
- lib/pdtp/
|
49
|
-
- lib/pdtp/common/file_service.rb
|
50
|
-
- lib/pdtp/common/http_server.rb
|
63
|
+
- lib/pdtp/client/connection.rb
|
64
|
+
- lib/pdtp/client/file_service.rb
|
51
65
|
- lib/pdtp/common/length_prefix_protocol.rb
|
66
|
+
- lib/pdtp/common/http_server.rb
|
52
67
|
- lib/pdtp/common/protocol.rb
|
53
|
-
- lib/pdtp/common.rb
|
54
|
-
- lib/pdtp/
|
55
|
-
-
|
56
|
-
- lib/pdtp/server/chunk_info.rb
|
57
|
-
- lib/pdtp/server/connection.rb
|
58
|
-
- lib/pdtp/server/dispatcher.rb
|
59
|
-
- lib/pdtp/server/file_service.rb
|
60
|
-
- lib/pdtp/server/file_service_protocol.rb
|
61
|
-
- lib/pdtp/server/status_handler.rb
|
62
|
-
- lib/pdtp/server/status_helper.rb
|
63
|
-
- lib/pdtp/server/transfer.rb
|
64
|
-
- lib/pdtp/server/transfer_manager.rb
|
65
|
-
- lib/pdtp/server/trust.rb
|
66
|
-
- lib/pdtp/server.rb
|
68
|
+
- lib/pdtp/common/range_map.rb
|
69
|
+
- lib/pdtp/common/file_service.rb
|
70
|
+
- conf/example.yml
|
67
71
|
- conf/bigchunk.yml
|
68
72
|
- conf/debug.yml
|
69
|
-
-
|
73
|
+
- status/stylesheets
|
70
74
|
- status/images
|
71
|
-
- status/images/logo.png
|
72
75
|
- status/index.erb
|
73
|
-
- status/stylesheets
|
74
76
|
- status/stylesheets/style.css
|
77
|
+
- status/images/logo.png
|
75
78
|
- Rakefile
|
76
79
|
- distribustream.gemspec
|
77
80
|
- COPYING
|
@@ -126,3 +129,12 @@ dependencies:
|
|
126
129
|
- !ruby/object:Gem::Version
|
127
130
|
version: 1.1.0
|
128
131
|
version:
|
132
|
+
- !ruby/object:Gem::Dependency
|
133
|
+
name: activesupport
|
134
|
+
version_requirement:
|
135
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: 1.4.0
|
140
|
+
version:
|