distribustream 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +11 -0
- data/Rakefile +14 -10
- data/bin/dsclient +86 -22
- data/bin/dsseed +8 -79
- data/bin/dstream +20 -0
- data/conf/bigchunk.yml +1 -1
- data/conf/debug.yml +1 -1
- data/conf/example.yml +1 -1
- data/distribustream.gemspec +3 -3
- data/lib/pdtp/client/callbacks.rb +29 -0
- data/lib/pdtp/client/connection.rb +114 -0
- data/lib/pdtp/client/file_buffer.rb +93 -103
- data/lib/pdtp/client/file_service.rb +6 -3
- data/lib/pdtp/client/http_handler.rb +77 -0
- data/lib/pdtp/client/transfer.rb +13 -14
- data/lib/pdtp/client.rb +73 -156
- data/lib/pdtp/common/packet.rb +106 -0
- data/lib/pdtp/common/protocol.rb +19 -29
- data/lib/pdtp/server/client_info.rb +109 -107
- data/lib/pdtp/server/connection.rb +35 -0
- data/lib/pdtp/server/dispatcher.rb +370 -0
- data/lib/pdtp/server/file_service.rb +1 -1
- data/lib/pdtp/server/file_service_protocol.rb +116 -0
- data/lib/pdtp/server/stats_handler.rb +23 -0
- data/lib/pdtp/server/trust.rb +60 -58
- data/lib/pdtp/server.rb +45 -346
- metadata +12 -9
- data/bin/distribustream +0 -60
- data/lib/pdtp/client/file_buffer_spec.rb +0 -154
- data/lib/pdtp/client/protocol.rb +0 -66
- data/lib/pdtp/common/file_service_spec.rb +0 -91
- data/lib/pdtp/common/protocol_spec.rb +0 -68
- data/lib/pdtp/server/trust_spec.rb +0 -40
@@ -0,0 +1,370 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
|
3
|
+
# All rights reserved. See COPYING for permissions.
|
4
|
+
#
|
5
|
+
# This source file is distributed as part of the
|
6
|
+
# DistribuStream file transfer system.
|
7
|
+
#
|
8
|
+
# See http://distribustream.rubyforge.org/
|
9
|
+
#++
|
10
|
+
|
11
|
+
require File.dirname(__FILE__) + '/../common/protocol'
|
12
|
+
require File.dirname(__FILE__) + '/../common/common_init'
|
13
|
+
require File.dirname(__FILE__) + '/file_service'
|
14
|
+
require File.dirname(__FILE__) + '/client_info'
|
15
|
+
require File.dirname(__FILE__) + '/transfer'
|
16
|
+
|
17
|
+
require 'thread'
|
18
|
+
require 'erb'
|
19
|
+
|
20
|
+
module PDTP
|
21
|
+
class Server
|
22
|
+
# Core dispatching and control logic for PDTP servers
|
23
|
+
class Dispatcher
|
24
|
+
attr_reader :connections
|
25
|
+
attr_accessor :file_service
|
26
|
+
def initialize
|
27
|
+
@connections = []
|
28
|
+
@used_client_ids = {} #keeps a list of client ids in use, they must be unique
|
29
|
+
@updated_clients = {} #a set of clients that have been modified and need transfers spawned
|
30
|
+
@stats_mutex=Mutex.new
|
31
|
+
end
|
32
|
+
|
33
|
+
#called by pdtp_protocol when a connection is created
|
34
|
+
def connection_created(connection)
|
35
|
+
@stats_mutex.synchronize do
|
36
|
+
@@log.info "Client connected: #{connection.get_peer_info.inspect}"
|
37
|
+
connection.user_data = ClientInfo.new
|
38
|
+
@connections << connection
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
#called by pdtp_protocol when a connection is destroyed
|
43
|
+
def connection_destroyed(connection)
|
44
|
+
@stats_mutex.synchronize do
|
45
|
+
@@log.info "Client connection closed: #{connection.get_peer_info.inspect}"
|
46
|
+
@connections.delete connection
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# returns the ClientInfo object associated with this connection
|
51
|
+
def client_info(connection)
|
52
|
+
connection.user_data ||= ClientInfo.new
|
53
|
+
end
|
54
|
+
|
55
|
+
# called when a transfer either finishes, successfully or not
|
56
|
+
def transfer_completed(transfer,connection,chunk_hash,send_response=true)
|
57
|
+
# did the transfer complete successfully?
|
58
|
+
local_hash=@file_service.get_chunk_hash(transfer.url,transfer.chunkid)
|
59
|
+
|
60
|
+
c1=client_info(transfer.taker)
|
61
|
+
c2=client_info(transfer.giver)
|
62
|
+
|
63
|
+
if connection==transfer.taker
|
64
|
+
success= (chunk_hash==local_hash)
|
65
|
+
|
66
|
+
if success
|
67
|
+
#the taker now has the file, so he can provide it
|
68
|
+
client_info(transfer.taker).chunk_info.provide(transfer.url,transfer.chunkid..transfer.chunkid)
|
69
|
+
c1.trust.success(c2.trust)
|
70
|
+
else
|
71
|
+
#transfer failed, the client still wants the chunk
|
72
|
+
client_info(transfer.taker).chunk_info.request(transfer.url,transfer.chunkid..transfer.chunkid)
|
73
|
+
c1.trust.failure(c2.trust)
|
74
|
+
end
|
75
|
+
|
76
|
+
transfer.taker.send_message(:hash_verify,
|
77
|
+
:url => transfer.url,
|
78
|
+
:range => transfer.byte_range,
|
79
|
+
:hash_ok => success
|
80
|
+
) if send_response
|
81
|
+
end
|
82
|
+
|
83
|
+
#outstr="#{@ids[transfer.giver]}->#{@ids[transfer.taker]} transfer completed: #{transfer}"
|
84
|
+
#outstr=outstr+" t->g=#{c1.trust.weight(c2.trust)} g->t=#{c2.trust.weight(c1.trust)}"
|
85
|
+
#outstr=outstr+"sent_by: "+ ( connection==transfer.taker ? "taker" : "giver" )
|
86
|
+
#outstr=outstr+" success=#{success} "
|
87
|
+
#@@log.debug(outstr)
|
88
|
+
|
89
|
+
#remove this transfer from whoever sent it
|
90
|
+
client_info(connection).transfers.delete(transfer.transfer_id)
|
91
|
+
@updated_clients[connection]=true #flag this client for transfer creation
|
92
|
+
end
|
93
|
+
|
94
|
+
#Creates a new transfer between two peers
|
95
|
+
#returns true on success, or false if the specified transfer is already in progress
|
96
|
+
def begin_transfer(taker, giver, url, chunkid)
|
97
|
+
byte_range = @file_service.get_info(url).chunk_range(chunkid)
|
98
|
+
t = Transfer.new(taker, giver, url, chunkid, byte_range)
|
99
|
+
|
100
|
+
#make sure this transfer doesnt already exist
|
101
|
+
t1 = client_info(taker).transfers[t.transfer_id]
|
102
|
+
t2 = client_info(giver).transfers[t.transfer_id]
|
103
|
+
return false unless t1.nil? and t2.nil?
|
104
|
+
|
105
|
+
client_info(taker).chunk_info.transfer(url, chunkid..chunkid)
|
106
|
+
client_info(taker).transfers[t.transfer_id] = t
|
107
|
+
client_info(giver).transfers[t.transfer_id] = t
|
108
|
+
|
109
|
+
#send transfer message to the connector
|
110
|
+
addr, port = t.acceptor.get_peer_info
|
111
|
+
|
112
|
+
t.connector.send_message(:transfer,
|
113
|
+
:host => addr,
|
114
|
+
:port => t.acceptor.user_data.listen_port,
|
115
|
+
:method => t.connector == t.taker ? "get" : "put",
|
116
|
+
:url => url,
|
117
|
+
:range => byte_range,
|
118
|
+
:peer_id => client_info(t.acceptor).client_id
|
119
|
+
)
|
120
|
+
true
|
121
|
+
end
|
122
|
+
|
123
|
+
#this function removes all stalled transfers from the list
|
124
|
+
#and spawns new transfers as appropriate
|
125
|
+
#it must be called periodically by EventMachine
|
126
|
+
def clear_all_stalled_transfers
|
127
|
+
@connections.each { |connection| clear_stalled_transfers_for_client connection }
|
128
|
+
spawn_all_transfers
|
129
|
+
end
|
130
|
+
|
131
|
+
#removes all stalled transfers that this client is a part of
|
132
|
+
def clear_stalled_transfers_for_client(client_connection)
|
133
|
+
client_info(client_connection).get_stalled_transfers.each do |transfer|
|
134
|
+
transfer_completed transfer, client_connection, nil, false
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
#spawns uploads and downloads for this client.
|
139
|
+
#should be called every time there is a change that would affect
|
140
|
+
#what this client has or wants
|
141
|
+
def spawn_transfers_for_client(client_connection)
|
142
|
+
info = client_info client_connection
|
143
|
+
|
144
|
+
while info.wants_download? do
|
145
|
+
break if spawn_download_for_client(client_connection) == false
|
146
|
+
end
|
147
|
+
|
148
|
+
while info.wants_upload? do
|
149
|
+
break if spawn_upload_for_client(client_connection) == false
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
#creates a single download for the specified client
|
154
|
+
#returns true on success, false on failure
|
155
|
+
def spawn_download_for_client(client_connection)
|
156
|
+
feasible_peers=[]
|
157
|
+
|
158
|
+
c1info=client_info(client_connection)
|
159
|
+
begin
|
160
|
+
url,chunkid=c1info.chunk_info.high_priority_chunk
|
161
|
+
rescue
|
162
|
+
return false
|
163
|
+
end
|
164
|
+
|
165
|
+
@connections.each do |c2|
|
166
|
+
next if client_connection==c2
|
167
|
+
next if client_info(c2).wants_upload? == false
|
168
|
+
if client_info(c2).chunk_info.provided?(url,chunkid)
|
169
|
+
feasible_peers << c2
|
170
|
+
break if feasible_peers.size > 5
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# we now have a list of clients that have the requested chunk.
|
175
|
+
# pick one and start the transfer
|
176
|
+
if feasible_peers.size > 0
|
177
|
+
#FIXME base this on the trust model
|
178
|
+
giver=feasible_peers[rand(feasible_peers.size)]
|
179
|
+
return begin_transfer(client_connection,giver,url,chunkid)
|
180
|
+
#FIXME should we try again if begin_transfer fails?
|
181
|
+
end
|
182
|
+
|
183
|
+
false
|
184
|
+
end
|
185
|
+
|
186
|
+
#creates a single upload for the specified client
|
187
|
+
#returns true on success, false on failure
|
188
|
+
def spawn_upload_for_client(client_connection)
|
189
|
+
c1info=client_info(client_connection)
|
190
|
+
|
191
|
+
@connections.each do |c2|
|
192
|
+
next if client_connection==c2
|
193
|
+
next if client_info(c2).wants_download? == false
|
194
|
+
|
195
|
+
begin
|
196
|
+
url,chunkid=client_info(c2).chunk_info.high_priority_chunk
|
197
|
+
rescue
|
198
|
+
next
|
199
|
+
end
|
200
|
+
|
201
|
+
if c1info.chunk_info.provided?(url,chunkid)
|
202
|
+
return begin_transfer(c2,client_connection,url,chunkid)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
false
|
207
|
+
end
|
208
|
+
|
209
|
+
#called by pdtp_protocol for each message that comes in from the wire
|
210
|
+
def dispatch_message(command, message, connection)
|
211
|
+
@stats_mutex.synchronize do
|
212
|
+
dispatch_message_needslock command, message, connection
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
#creates new transfers for all clients that have been updated
|
217
|
+
def spawn_all_transfers
|
218
|
+
while @updated_clients.size > 0 do
|
219
|
+
tmp=@updated_clients
|
220
|
+
@updated_clients=Hash.new
|
221
|
+
tmp.each do |client,true_key|
|
222
|
+
spawn_transfers_for_client(client)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
#handles the request, provide, unrequest, unprovide messages
|
228
|
+
def handle_requestprovide(connection,message)
|
229
|
+
type=message["type"]
|
230
|
+
url=message["url"]
|
231
|
+
info=@file_service.get_info(url) rescue nil
|
232
|
+
raise ProtocolWarn.new("Requested URL: '#{url}' not found") if info.nil?
|
233
|
+
|
234
|
+
exclude_partial= (type=="provide") #only exclude partial chunks from provides
|
235
|
+
range=info.chunk_range_from_byte_range(message["range"],exclude_partial)
|
236
|
+
|
237
|
+
#call request, provide, unrequest, or unprovide
|
238
|
+
client_info(connection).chunk_info.send( type.to_sym, url, range)
|
239
|
+
@updated_clients[connection]=true #add to the list of client that need new transfers
|
240
|
+
end
|
241
|
+
|
242
|
+
#handles all incoming messages from clients
|
243
|
+
def dispatch_message_needslock(command, message, connection)
|
244
|
+
# store the command in the message hash
|
245
|
+
message["type"] = command
|
246
|
+
|
247
|
+
#require the client to register their client id and listen port before doing anything
|
248
|
+
if command != "register" and client_info(connection).client_id.nil?
|
249
|
+
raise ProtocolError.new("You need to send a 'client_info' message first")
|
250
|
+
end
|
251
|
+
|
252
|
+
case command
|
253
|
+
when "register"
|
254
|
+
cid = message["client_id"]
|
255
|
+
#make sure this id isnt in use
|
256
|
+
if @used_client_ids[cid]
|
257
|
+
raise ProtocolError.new("Your client id: #{cid} is already in use.")
|
258
|
+
end
|
259
|
+
|
260
|
+
@used_client_ids[cid] = true
|
261
|
+
client_info(connection).listen_port = message["listen_port"]
|
262
|
+
client_info(connection).client_id = cid
|
263
|
+
when "ask_info"
|
264
|
+
info = file_service.get_info(message["url"])
|
265
|
+
response = { :url => message["url"] }
|
266
|
+
unless info.nil?
|
267
|
+
response[:size] = info.file_size
|
268
|
+
response[:chunk_size] = info.base_chunk_size
|
269
|
+
response[:streaming] = info.streaming
|
270
|
+
end
|
271
|
+
connection.send_message :tell_info, response
|
272
|
+
when "request", "provide", "unrequest", "unprovide"
|
273
|
+
handle_requestprovide connection, message
|
274
|
+
when "ask_verify"
|
275
|
+
#check if the specified transfer is a real one
|
276
|
+
my_id = client_info(connection).client_id
|
277
|
+
transfer_id=Transfer.gen_transfer_id(my_id,message["peer_id"],message["url"],message["range"])
|
278
|
+
ok = !!client_info(connection).transfers[transfer_id]
|
279
|
+
client_info(connection).transfers[transfer_id].verification_asked=true if ok
|
280
|
+
@@log.debug "AskVerify not ok: id=#{transfer_id}" unless ok
|
281
|
+
connection.send_message(:tell_verify,
|
282
|
+
:url => message["url"],
|
283
|
+
:peer_id => message["peer_id"],
|
284
|
+
:range => message["range"],
|
285
|
+
:peer => message["peer"],
|
286
|
+
:authorized=>ok
|
287
|
+
)
|
288
|
+
when "completed"
|
289
|
+
my_id = client_info(connection).client_id
|
290
|
+
transfer_id= Transfer::gen_transfer_id(
|
291
|
+
my_id,message["peer_id"],
|
292
|
+
message["url"],
|
293
|
+
message["range"]
|
294
|
+
)
|
295
|
+
transfer=client_info(connection).transfers[transfer_id]
|
296
|
+
@@log.debug("Completed: id=#{transfer_id} ok=#{transfer != nil}" )
|
297
|
+
if transfer
|
298
|
+
transfer_completed(transfer,connection,message["hash"])
|
299
|
+
else
|
300
|
+
raise ProtocolWarn.new("You sent me a transfer completed message for unknown transfer: #{transfer_id}")
|
301
|
+
end
|
302
|
+
when 'protocol_error', 'protocol_warn' #ignore
|
303
|
+
else raise ProtocolError.new("Unhandled message type: #{command}")
|
304
|
+
end
|
305
|
+
|
306
|
+
spawn_all_transfers
|
307
|
+
end
|
308
|
+
|
309
|
+
#returns a string representing the specified connection
|
310
|
+
def connection_name(c)
|
311
|
+
#host,port=c.get_peer_info
|
312
|
+
#return "#{get_id(c)}: #{host}:#{port}"
|
313
|
+
client_info(c).client_id
|
314
|
+
end
|
315
|
+
|
316
|
+
def generate_html_stats
|
317
|
+
@stats_mutex.synchronize { generate_html_stats_needslock }
|
318
|
+
end
|
319
|
+
|
320
|
+
#builds an html page with information about the server's internal workings
|
321
|
+
def generate_html_stats_needslock
|
322
|
+
s = ERB.new <<EOF
|
323
|
+
<html><head><title>DistribuStream Statistics</title></head>
|
324
|
+
<body>
|
325
|
+
<h1>DistribuStream Statistics</h1>
|
326
|
+
Time=<%= Time.new %><br> Connected Clients=<%= @connections.size %>
|
327
|
+
<center><table border=1>
|
328
|
+
<tr><th>Client</th><th>Transfers</th><th>Files</th></tr>
|
329
|
+
<% @connections.each do |c| %>
|
330
|
+
<tr><td>
|
331
|
+
<% host, port = c.get_peer_info %>
|
332
|
+
<%= connection_name(c) %><br><%= host %>:<%= port %>
|
333
|
+
</td>
|
334
|
+
<td>
|
335
|
+
<%
|
336
|
+
client_info(c).transfers.each do |key,t|
|
337
|
+
if c==t.giver
|
338
|
+
type="UP: "
|
339
|
+
peer=t.taker
|
340
|
+
else
|
341
|
+
type="DOWN: "
|
342
|
+
peer=t.giver
|
343
|
+
end
|
344
|
+
%>
|
345
|
+
<%= type %> id=<%= t.transfer_id %><br>
|
346
|
+
<%
|
347
|
+
end
|
348
|
+
%>
|
349
|
+
</td>
|
350
|
+
<td>
|
351
|
+
<%
|
352
|
+
client_info(c).chunk_info.get_file_stats.each do |fs|
|
353
|
+
%>
|
354
|
+
<%= fs.url %> size=<%= fs.file_chunks %> req=<%= fs.chunks_requested %>
|
355
|
+
prov=<%= fs.chunks_provided %> transf=<%= fs.chunks_transferring %><br>
|
356
|
+
<%
|
357
|
+
end
|
358
|
+
%>
|
359
|
+
</td></tr>
|
360
|
+
<%
|
361
|
+
end
|
362
|
+
%>
|
363
|
+
</table>
|
364
|
+
</body></html>
|
365
|
+
EOF
|
366
|
+
s.result binding
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
@@ -48,7 +48,7 @@ module PDTP
|
|
48
48
|
|
49
49
|
#The file service provides utilities for determining various information about files.
|
50
50
|
class FileService < PDTP::FileService
|
51
|
-
attr_accessor :root
|
51
|
+
attr_accessor :root, :default_chunk_size
|
52
52
|
|
53
53
|
def initialize
|
54
54
|
@root = ''
|
@@ -0,0 +1,116 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#--
|
3
|
+
# Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
|
4
|
+
# All rights reserved. See COPYING for permissions.
|
5
|
+
#
|
6
|
+
# This source file is distributed as part of the
|
7
|
+
# DistribuStream file transfer system.
|
8
|
+
#
|
9
|
+
# See http://distribustream.rubyforge.org/
|
10
|
+
#++
|
11
|
+
|
12
|
+
require 'digest/md5'
|
13
|
+
require 'thread'
|
14
|
+
|
15
|
+
require File.dirname(__FILE__) + '/../common/protocol'
|
16
|
+
require File.dirname(__FILE__) + '/../server/file_service'
|
17
|
+
require File.dirname(__FILE__) + '/../client/connection'
|
18
|
+
require File.dirname(__FILE__) + '/../client/http_handler'
|
19
|
+
|
20
|
+
module PDTP
|
21
|
+
class Server
|
22
|
+
class FileService
|
23
|
+
# Implements the file service for the pdtp protocol
|
24
|
+
class Protocol < PDTP::Client::Connection
|
25
|
+
attr_reader :client, :connection, :file_service, :client_id, :transfers, :lock
|
26
|
+
|
27
|
+
def initialize(data)
|
28
|
+
@transfers = []
|
29
|
+
@lock = Mutex.new
|
30
|
+
@client = self
|
31
|
+
@connection = self
|
32
|
+
@client_id = Digest::MD5.hexdigest "#{Time.now.to_f}#{$$}"
|
33
|
+
|
34
|
+
super data
|
35
|
+
end
|
36
|
+
|
37
|
+
# Called after a connection to the server has been established
|
38
|
+
def connection_completed
|
39
|
+
begin
|
40
|
+
@listen_addr = '0.0.0.0'
|
41
|
+
@listen_port = @@config[:listen_port]
|
42
|
+
|
43
|
+
#create the client
|
44
|
+
#client = PDTP::Client.new
|
45
|
+
#PDTP::Protocol.listener = client
|
46
|
+
#client.server_connection = self
|
47
|
+
#client.generate_client_id listen_port
|
48
|
+
|
49
|
+
# Start a mongrel server on the specified port. If it isnt available, keep trying higher ports
|
50
|
+
begin
|
51
|
+
mongrel_server = Mongrel::HttpServer.new @listen_addr, @listen_port
|
52
|
+
rescue Exception => e
|
53
|
+
@listen_port += 1
|
54
|
+
retry
|
55
|
+
end
|
56
|
+
|
57
|
+
#@@log.info "listening on port #{@listen_port}"
|
58
|
+
@http_handler = Client::HttpHandler.new(self)
|
59
|
+
|
60
|
+
@@log.info "listening on port #{@listen_port}"
|
61
|
+
mongrel_server.register "/", @http_handler
|
62
|
+
mongrel_server.run
|
63
|
+
|
64
|
+
# Register our client_id and listen_port
|
65
|
+
send_message :register, :listen_port => @listen_port, :client_id => @client_id
|
66
|
+
|
67
|
+
@@log.info 'This client is providing'
|
68
|
+
@file_service = PDTP::Server::FileService.new
|
69
|
+
@file_service.root = @base_path
|
70
|
+
#client.file_service = sfs #give this client access to all data
|
71
|
+
|
72
|
+
hostname = @@config[:vhost]
|
73
|
+
|
74
|
+
# Provide all the files in the root directory
|
75
|
+
files = find_files @base_path
|
76
|
+
files.each { |file| send_message :provide, :url => "http://#{hostname}/#{file}" }
|
77
|
+
rescue Exception => e
|
78
|
+
puts "Exception in connection_completed: #{e}"
|
79
|
+
puts e.backtrace.join("\n")
|
80
|
+
exit
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def unbind
|
85
|
+
super
|
86
|
+
puts "Disconnected from PDTP server."
|
87
|
+
end
|
88
|
+
|
89
|
+
#########
|
90
|
+
protected
|
91
|
+
#########
|
92
|
+
|
93
|
+
# Fine all suitable files in the give path
|
94
|
+
def find_files(base_path)
|
95
|
+
require 'find'
|
96
|
+
|
97
|
+
found = []
|
98
|
+
excludes = %w{.svn CVS}
|
99
|
+
base_full = File.expand_path(base_path)
|
100
|
+
|
101
|
+
Find.find(base_full) do |path|
|
102
|
+
if FileTest.directory?(path)
|
103
|
+
next unless excludes.include?(File.basename(path))
|
104
|
+
Find.prune
|
105
|
+
else
|
106
|
+
filename = path[(base_path.size - path.size + 1)..-1] #the entire file path after the base_path
|
107
|
+
found << filename
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
found
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'mongrel'
|
3
|
+
|
4
|
+
module PDTP
|
5
|
+
class Server
|
6
|
+
#set up the mongrel server for serving the stats page
|
7
|
+
class StatsHandler < Mongrel::HttpHandler
|
8
|
+
def initialize(server)
|
9
|
+
@server = server
|
10
|
+
end
|
11
|
+
|
12
|
+
def process(request,response)
|
13
|
+
response.start(200) do |head, out|
|
14
|
+
out.write begin
|
15
|
+
outstr = @server.generate_html_stats
|
16
|
+
rescue Exception=>e
|
17
|
+
outstr = "Exception: #{e}\n#{e.backtrace.join("\n")}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/pdtp/server/trust.rb
CHANGED
@@ -9,76 +9,78 @@
|
|
9
9
|
#++
|
10
10
|
|
11
11
|
module PDTP
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
class Server
|
13
|
+
#maintains trust information for a single client
|
14
|
+
class Trust
|
15
|
+
#struct for storing a single trust relationship
|
16
|
+
class Edge
|
17
|
+
attr_accessor :trust, :success, :transfers
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
def initialize(trust = 1.0, success = 1.0, transfers = 1.0)
|
20
|
+
@trust = trust
|
21
|
+
@success = success
|
22
|
+
@transfers = transfers
|
23
|
+
end
|
22
24
|
end
|
23
|
-
end
|
24
25
|
|
25
|
-
|
26
|
+
attr_reader :outgoing, :implicit
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
def initialize(incoming = {}, outgoing = {}, implicit = {})
|
29
|
+
@incoming = incoming
|
30
|
+
@outgoing = outgoing
|
31
|
+
@implicit = implicit
|
32
|
+
end
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
34
|
+
#I have successfully downloaded a chunk from 'node'
|
35
|
+
def success(node)
|
36
|
+
if @outgoing[node].nil?
|
37
|
+
@outgoing[node] = Edge.new
|
38
|
+
else
|
39
|
+
@outgoing[node].success += 1.0
|
40
|
+
@outgoing[node].transfers += 1.0
|
41
|
+
end
|
42
|
+
normalize
|
40
43
|
end
|
41
|
-
normalize
|
42
|
-
end
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
45
|
+
#I have failed to download a chunk from 'node'
|
46
|
+
def failure(node)
|
47
|
+
@outgoing[node] = Edge.new if @outgoing[node].nil?
|
48
|
+
@outgoing[node].transfers += 1.0
|
49
|
+
normalize
|
50
|
+
end
|
50
51
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
52
|
+
#returns a number from 0 to 1 saying how much I trust 'node'
|
53
|
+
def weight(node)
|
54
|
+
return @outgoing[node].trust unless @outgoing[node].nil?
|
55
|
+
return @implicit[node].trust unless @implicit[node].nil?
|
56
|
+
0
|
57
|
+
end
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
# brings all trust values between 0 and 1
|
60
|
+
def normalize
|
61
|
+
total_success = 0
|
62
|
+
total_transfers = 0
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
64
|
+
@outgoing.each do |_, link|
|
65
|
+
total_success += link.success
|
66
|
+
total_transfers += link.transfers
|
67
|
+
end
|
67
68
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
69
|
+
@outgoing.each { |_, link| link.trust = link.success / total_transfers }
|
70
|
+
@outgoing.each do |target, link|
|
71
|
+
[target.outgoing, target.implicit].each do |links|
|
72
|
+
links.each do |nextlinkedge|
|
73
|
+
nextlinktarget = nextlinkedge[0]
|
74
|
+
nextlink = nextlinkedge[1]
|
75
|
+
next unless outgoing[nextlinktarget].nil?
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
77
|
+
if implicit[nextlinktarget].nil? || implicit[nextlinktarget].trust < (link.trust * nextlink.trust)
|
78
|
+
implicit[nextlinktarget] = Edge.new(
|
79
|
+
link.trust * nextlink.trust,
|
80
|
+
nextlink.success,
|
81
|
+
nextlink.transfers
|
82
|
+
)
|
83
|
+
end
|
82
84
|
end
|
83
85
|
end
|
84
86
|
end
|