distribustream 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|