distribustream 0.1.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 +2 -0
- data/COPYING +674 -0
- data/README +107 -0
- data/Rakefile +44 -0
- data/bin/distribustream +60 -0
- data/bin/dsclient +43 -0
- data/bin/dsseed +103 -0
- data/conf/bigchunk.yml +18 -0
- data/conf/debug.yml +18 -0
- data/conf/example.yml +18 -0
- data/distribustream.gemspec +20 -0
- data/lib/pdtp/client.rb +195 -0
- data/lib/pdtp/client/file_buffer.rb +128 -0
- data/lib/pdtp/client/file_buffer_spec.rb +154 -0
- data/lib/pdtp/client/file_service.rb +60 -0
- data/lib/pdtp/client/protocol.rb +66 -0
- data/lib/pdtp/client/transfer.rb +229 -0
- data/lib/pdtp/common/common_init.rb +122 -0
- data/lib/pdtp/common/file_service.rb +69 -0
- data/lib/pdtp/common/file_service_spec.rb +91 -0
- data/lib/pdtp/common/protocol.rb +346 -0
- data/lib/pdtp/common/protocol_spec.rb +68 -0
- data/lib/pdtp/server.rb +368 -0
- data/lib/pdtp/server/client_info.rb +140 -0
- data/lib/pdtp/server/file_service.rb +89 -0
- data/lib/pdtp/server/transfer.rb +62 -0
- data/lib/pdtp/server/trust.rb +88 -0
- data/lib/pdtp/server/trust_spec.rb +40 -0
- metadata +114 -0
@@ -0,0 +1,68 @@
|
|
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__) + '/protocol'
|
12
|
+
|
13
|
+
describe PDTP::Protocol, 'obj_matches_type?' do
|
14
|
+
it "identifies :url objects" do
|
15
|
+
PDTP::Protocol.obj_matches_type?("http://bla.com/test3.mp3",:url).should == true
|
16
|
+
PDTP::Protocol.obj_matches_type?(4,:url).should == false
|
17
|
+
end
|
18
|
+
|
19
|
+
it "identifies :range objects" do
|
20
|
+
PDTP::Protocol.obj_matches_type?(0..4,:range).should == true
|
21
|
+
PDTP::Protocol.obj_matches_type?(4,:range).should == false
|
22
|
+
PDTP::Protocol.obj_matches_type?( {"min"=>0,"max"=>4} , :range ).should == true
|
23
|
+
end
|
24
|
+
|
25
|
+
it "identifies :ip objects" do
|
26
|
+
PDTP::Protocol.obj_matches_type?("127.0.0.1", :ip).should == true
|
27
|
+
PDTP::Protocol.obj_matches_type?("127.0.0.1.1", :ip).should == false
|
28
|
+
end
|
29
|
+
|
30
|
+
it "identifies :int objects" do
|
31
|
+
PDTP::Protocol.obj_matches_type?(4,:int).should == true
|
32
|
+
PDTP::Protocol.obj_matches_type?("hi",:int).should == false
|
33
|
+
end
|
34
|
+
|
35
|
+
it "identifies :bool objects" do
|
36
|
+
PDTP::Protocol.obj_matches_type?(true, :bool).should == true
|
37
|
+
PDTP::Protocol.obj_matches_type?(0,:bool).should == false
|
38
|
+
end
|
39
|
+
|
40
|
+
it "identifies :string objects" do
|
41
|
+
PDTP::Protocol.obj_matches_type?("hi", :string).should == true
|
42
|
+
PDTP::Protocol.obj_matches_type?(6, :string).should == false
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
describe PDTP::Protocol, 'validate_message' do
|
48
|
+
it "supports optional parameters" do
|
49
|
+
msg1 = ["request", {"url"=>"pdtp://bla.com/test.txt", "range"=>0..4 }]
|
50
|
+
msg2 = ["request", {"url"=>"pdtp://bla.com/test.txt" }]
|
51
|
+
msg3 = ["request", {"range"=> "hi", "url"=>"pdtp://bla.com/test.txt" }]
|
52
|
+
|
53
|
+
proc { PDTP::Protocol.validate_message(msg1)}.should_not raise_error
|
54
|
+
proc { PDTP::Protocol.validate_message(msg2)}.should_not raise_error
|
55
|
+
proc { PDTP::Protocol.validate_message(msg3)}.should raise_error
|
56
|
+
end
|
57
|
+
|
58
|
+
it "validates required parameters" do
|
59
|
+
msg1 = ["ask_info"]
|
60
|
+
msg2 = ["ask_info", {"url"=>"pdtp://bla.com/test.txt"}]
|
61
|
+
msg3 = ["ask_info", {"url"=>42 }]
|
62
|
+
|
63
|
+
proc { PDTP::Protocol.validate_message(msg1)}.should raise_error
|
64
|
+
proc { PDTP::Protocol.validate_message(msg2)}.should_not raise_error
|
65
|
+
proc { PDTP::Protocol.validate_message(msg3)}.should raise_error
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
data/lib/pdtp/server.rb
ADDED
@@ -0,0 +1,368 @@
|
|
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__) + '/server/file_service'
|
14
|
+
require File.dirname(__FILE__) + '/server/client_info'
|
15
|
+
require File.dirname(__FILE__) + '/server/transfer'
|
16
|
+
|
17
|
+
require 'thread'
|
18
|
+
require 'erb'
|
19
|
+
|
20
|
+
module PDTP
|
21
|
+
# PDTP server implementation
|
22
|
+
class Server
|
23
|
+
attr_reader :connections
|
24
|
+
attr_accessor :file_service
|
25
|
+
def initialize
|
26
|
+
@connections = Array.new
|
27
|
+
@stats_mutex=Mutex.new
|
28
|
+
@used_client_ids=Hash.new #keeps a list of client ids in use, they must be unique
|
29
|
+
@updated_clients=Hash.new #a set of clients that have been modified and need transfers spawned
|
30
|
+
end
|
31
|
+
|
32
|
+
#called by pdtp_protocol when a connection is created
|
33
|
+
def connection_created(connection)
|
34
|
+
@stats_mutex.synchronize do
|
35
|
+
@@log.info "Client connected: #{connection.get_peer_info.inspect}"
|
36
|
+
connection.user_data = ClientInfo.new
|
37
|
+
@connections << connection
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#called by pdtp_protocol when a connection is destroyed
|
42
|
+
def connection_destroyed(connection)
|
43
|
+
@stats_mutex.synchronize do
|
44
|
+
@@log.info "Client connection closed: #{connection.get_peer_info.inspect}"
|
45
|
+
@connections.delete connection
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# returns the ClientInfo object associated with this connection
|
50
|
+
def client_info(connection)
|
51
|
+
connection.user_data ||= ClientInfo.new
|
52
|
+
end
|
53
|
+
|
54
|
+
# called when a transfer either finishes, successfully or not
|
55
|
+
def transfer_completed(transfer,connection,chunk_hash,send_response=true)
|
56
|
+
# did the transfer complete successfully?
|
57
|
+
local_hash=@file_service.get_chunk_hash(transfer.url,transfer.chunkid)
|
58
|
+
|
59
|
+
c1=client_info(transfer.taker)
|
60
|
+
c2=client_info(transfer.giver)
|
61
|
+
|
62
|
+
if connection==transfer.taker
|
63
|
+
success= (chunk_hash==local_hash)
|
64
|
+
|
65
|
+
if success
|
66
|
+
#the taker now has the file, so he can provide it
|
67
|
+
client_info(transfer.taker).chunk_info.provide(transfer.url,transfer.chunkid..transfer.chunkid)
|
68
|
+
c1.trust.success(c2.trust)
|
69
|
+
else
|
70
|
+
#transfer failed, the client still wants the chunk
|
71
|
+
client_info(transfer.taker).chunk_info.request(transfer.url,transfer.chunkid..transfer.chunkid)
|
72
|
+
c1.trust.failure(c2.trust)
|
73
|
+
end
|
74
|
+
|
75
|
+
transfer.taker.send_message(:hash_verify,
|
76
|
+
:url => transfer.url,
|
77
|
+
:range => transfer.byte_range,
|
78
|
+
:hash_ok => success
|
79
|
+
) if send_response
|
80
|
+
end
|
81
|
+
|
82
|
+
#outstr="#{@ids[transfer.giver]}->#{@ids[transfer.taker]} transfer completed: #{transfer}"
|
83
|
+
#outstr=outstr+" t->g=#{c1.trust.weight(c2.trust)} g->t=#{c2.trust.weight(c1.trust)}"
|
84
|
+
#outstr=outstr+"sent_by: "+ ( connection==transfer.taker ? "taker" : "giver" )
|
85
|
+
#outstr=outstr+" success=#{success} "
|
86
|
+
#@@log.debug(outstr)
|
87
|
+
|
88
|
+
#remove this transfer from whoever sent it
|
89
|
+
client_info(connection).transfers.delete(transfer.transfer_id)
|
90
|
+
@updated_clients[connection]=true #flag this client for transfer creation
|
91
|
+
end
|
92
|
+
|
93
|
+
#Creates a new transfer between two peers
|
94
|
+
#returns true on success, or false if the specified transfer is already in progress
|
95
|
+
def begin_transfer(taker, giver, url, chunkid)
|
96
|
+
byte_range = @file_service.get_info(url).chunk_range(chunkid)
|
97
|
+
t = Transfer.new(taker, giver, url, chunkid, byte_range)
|
98
|
+
|
99
|
+
#make sure this transfer doesnt already exist
|
100
|
+
t1 = client_info(taker).transfers[t.transfer_id]
|
101
|
+
t2 = client_info(giver).transfers[t.transfer_id]
|
102
|
+
return false unless t1.nil? and t2.nil?
|
103
|
+
|
104
|
+
client_info(taker).chunk_info.transfer(url, chunkid..chunkid)
|
105
|
+
client_info(taker).transfers[t.transfer_id] = t
|
106
|
+
client_info(giver).transfers[t.transfer_id] = t
|
107
|
+
|
108
|
+
#send transfer message to the connector
|
109
|
+
addr, port = t.acceptor.get_peer_info
|
110
|
+
|
111
|
+
t.connector.send_message(:transfer,
|
112
|
+
:host => addr,
|
113
|
+
:port => t.acceptor.user_data.listen_port,
|
114
|
+
:method => t.connector == t.taker ? "get" : "put",
|
115
|
+
:url => url,
|
116
|
+
:range => byte_range,
|
117
|
+
:peer_id => client_info(t.acceptor).client_id
|
118
|
+
)
|
119
|
+
true
|
120
|
+
end
|
121
|
+
|
122
|
+
#this function removes all stalled transfers from the list
|
123
|
+
#and spawns new transfers as appropriate
|
124
|
+
#it must be called periodically by EventMachine
|
125
|
+
def clear_all_stalled_transfers
|
126
|
+
@connections.each { |connection| clear_stalled_transfers_for_client connection }
|
127
|
+
spawn_all_transfers
|
128
|
+
end
|
129
|
+
|
130
|
+
#removes all stalled transfers that this client is a part of
|
131
|
+
def clear_stalled_transfers_for_client(client_connection)
|
132
|
+
client_info(client_connection).get_stalled_transfers.each do |transfer|
|
133
|
+
transfer_completed transfer, client_connection, nil, false
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
#spawns uploads and downloads for this client.
|
138
|
+
#should be called every time there is a change that would affect
|
139
|
+
#what this client has or wants
|
140
|
+
def spawn_transfers_for_client(client_connection)
|
141
|
+
info = client_info client_connection
|
142
|
+
|
143
|
+
while info.wants_download? do
|
144
|
+
break if spawn_download_for_client(client_connection) == false
|
145
|
+
end
|
146
|
+
|
147
|
+
while info.wants_upload? do
|
148
|
+
break if spawn_upload_for_client(client_connection) == false
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
#creates a single download for the specified client
|
153
|
+
#returns true on success, false on failure
|
154
|
+
def spawn_download_for_client(client_connection)
|
155
|
+
feasible_peers=[]
|
156
|
+
|
157
|
+
c1info=client_info(client_connection)
|
158
|
+
begin
|
159
|
+
url,chunkid=c1info.chunk_info.high_priority_chunk
|
160
|
+
rescue
|
161
|
+
return false
|
162
|
+
end
|
163
|
+
|
164
|
+
@connections.each do |c2|
|
165
|
+
next if client_connection==c2
|
166
|
+
next if client_info(c2).wants_upload? == false
|
167
|
+
if client_info(c2).chunk_info.provided?(url,chunkid)
|
168
|
+
feasible_peers << c2
|
169
|
+
break if feasible_peers.size > 5
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# we now have a list of clients that have the requested chunk.
|
174
|
+
# pick one and start the transfer
|
175
|
+
if feasible_peers.size > 0
|
176
|
+
#FIXME base this on the trust model
|
177
|
+
giver=feasible_peers[rand(feasible_peers.size)]
|
178
|
+
return begin_transfer(client_connection,giver,url,chunkid)
|
179
|
+
#FIXME should we try again if begin_transfer fails?
|
180
|
+
end
|
181
|
+
|
182
|
+
false
|
183
|
+
end
|
184
|
+
|
185
|
+
#creates a single upload for the specified client
|
186
|
+
#returns true on success, false on failure
|
187
|
+
def spawn_upload_for_client(client_connection)
|
188
|
+
c1info=client_info(client_connection)
|
189
|
+
|
190
|
+
@connections.each do |c2|
|
191
|
+
next if client_connection==c2
|
192
|
+
next if client_info(c2).wants_download? == false
|
193
|
+
|
194
|
+
begin
|
195
|
+
url,chunkid=client_info(c2).chunk_info.high_priority_chunk
|
196
|
+
rescue
|
197
|
+
next
|
198
|
+
end
|
199
|
+
|
200
|
+
if c1info.chunk_info.provided?(url,chunkid)
|
201
|
+
return begin_transfer(c2,client_connection,url,chunkid)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
false
|
206
|
+
end
|
207
|
+
|
208
|
+
#called by pdtp_protocol for each message that comes in from the wire
|
209
|
+
def dispatch_message(command, message, connection)
|
210
|
+
@stats_mutex.synchronize do
|
211
|
+
dispatch_message_needslock command, message, connection
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
#creates new transfers for all clients that have been updated
|
216
|
+
def spawn_all_transfers
|
217
|
+
while @updated_clients.size > 0 do
|
218
|
+
tmp=@updated_clients
|
219
|
+
@updated_clients=Hash.new
|
220
|
+
tmp.each do |client,true_key|
|
221
|
+
spawn_transfers_for_client(client)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
#handles the request, provide, unrequest, unprovide messages
|
227
|
+
def handle_requestprovide(connection,message)
|
228
|
+
type=message["type"]
|
229
|
+
url=message["url"]
|
230
|
+
info=@file_service.get_info(url) rescue nil
|
231
|
+
raise ProtocolWarn.new("Requested URL: '#{url}' not found") if info.nil?
|
232
|
+
|
233
|
+
exclude_partial= (type=="provide") #only exclude partial chunks from provides
|
234
|
+
range=info.chunk_range_from_byte_range(message["range"],exclude_partial)
|
235
|
+
|
236
|
+
#call request, provide, unrequest, or unprovide
|
237
|
+
client_info(connection).chunk_info.send( type.to_sym, url, range)
|
238
|
+
@updated_clients[connection]=true #add to the list of client that need new transfers
|
239
|
+
end
|
240
|
+
|
241
|
+
#handles all incoming messages from clients
|
242
|
+
def dispatch_message_needslock(command, message, connection)
|
243
|
+
# store the command in the message hash
|
244
|
+
message["type"] = command
|
245
|
+
|
246
|
+
#require the client to be logged in with a client id
|
247
|
+
if command != "client_info" and client_info(connection).client_id.nil?
|
248
|
+
raise ProtocolError.new("You need to send a 'client_info' message first")
|
249
|
+
end
|
250
|
+
|
251
|
+
case command
|
252
|
+
when "client_info"
|
253
|
+
cid = message["client_id"]
|
254
|
+
#make sure this id isnt in use
|
255
|
+
if @used_client_ids[cid]
|
256
|
+
raise ProtocolError.new("Your client id: #{cid} is already in use.")
|
257
|
+
end
|
258
|
+
|
259
|
+
@used_client_ids[cid] = true
|
260
|
+
client_info(connection).listen_port = message["listen_port"]
|
261
|
+
client_info(connection).client_id = cid
|
262
|
+
when "ask_info"
|
263
|
+
info = file_service.get_info(message["url"])
|
264
|
+
response = { :url => message["url"] }
|
265
|
+
unless info.nil?
|
266
|
+
response[:size] = info.file_size
|
267
|
+
response[:chunk_size] = info.base_chunk_size
|
268
|
+
response[:streaming] = info.streaming
|
269
|
+
end
|
270
|
+
connection.send_message :tell_info, response
|
271
|
+
when "request", "provide", "unrequest", "unprovide"
|
272
|
+
handle_requestprovide connection, message
|
273
|
+
when "ask_verify"
|
274
|
+
#check if the specified transfer is a real one
|
275
|
+
my_id = client_info(connection).client_id
|
276
|
+
transfer_id=Transfer.gen_transfer_id(my_id,message["peer_id"],message["url"],message["range"])
|
277
|
+
ok = !!client_info(connection).transfers[transfer_id]
|
278
|
+
client_info(connection).transfers[transfer_id].verification_asked=true if ok
|
279
|
+
@@log.debug "AskVerify not ok: id=#{transfer_id}" unless ok
|
280
|
+
connection.send_message(:tell_verify,
|
281
|
+
:url => message["url"],
|
282
|
+
:peer_id => message["peer_id"],
|
283
|
+
:range => message["range"],
|
284
|
+
:peer => message["peer"],
|
285
|
+
:is_authorized=>ok
|
286
|
+
)
|
287
|
+
when "completed"
|
288
|
+
my_id = client_info(connection).client_id
|
289
|
+
transfer_id= Transfer::gen_transfer_id(
|
290
|
+
my_id,message["peer_id"],
|
291
|
+
message["url"],
|
292
|
+
message["range"]
|
293
|
+
)
|
294
|
+
transfer=client_info(connection).transfers[transfer_id]
|
295
|
+
@@log.debug("Completed: id=#{transfer_id} ok=#{transfer != nil}" )
|
296
|
+
if transfer
|
297
|
+
transfer_completed(transfer,connection,message["hash"])
|
298
|
+
else
|
299
|
+
raise ProtocolWarn.new("You sent me a transfer completed message for unknown transfer: #{transfer_id}")
|
300
|
+
end
|
301
|
+
when 'protocol_error', 'protocol_warn' #ignore
|
302
|
+
else raise ProtocolError.new("Unhandled message type: #{command}")
|
303
|
+
end
|
304
|
+
|
305
|
+
spawn_all_transfers
|
306
|
+
end
|
307
|
+
|
308
|
+
#returns a string representing the specified connection
|
309
|
+
def connection_name(c)
|
310
|
+
#host,port=c.get_peer_info
|
311
|
+
#return "#{get_id(c)}: #{host}:#{port}"
|
312
|
+
client_info(c).client_id
|
313
|
+
end
|
314
|
+
|
315
|
+
def generate_html_stats
|
316
|
+
@stats_mutex.synchronize { generate_html_stats_needslock }
|
317
|
+
end
|
318
|
+
|
319
|
+
#builds an html page with information about the server's internal workings
|
320
|
+
def generate_html_stats_needslock
|
321
|
+
s = ERB.new <<EOF
|
322
|
+
<html><head><title>DistribuStream Statistics</title></head>
|
323
|
+
<body>
|
324
|
+
<h1>DistribuStream Statistics</h1>
|
325
|
+
Time=<%= Time.new %><br> Connected Clients=<%= @connections.size %>
|
326
|
+
<center><table border=1>
|
327
|
+
<tr><th>Client</th><th>Transfers</th><th>Files</th></tr>
|
328
|
+
<% @connections.each do |c| %>
|
329
|
+
<tr><td>
|
330
|
+
<% host, port = c.get_peer_info %>
|
331
|
+
<%= connection_name(c) %><br><%= host %>:<%= port %>
|
332
|
+
</td>
|
333
|
+
<td>
|
334
|
+
<%
|
335
|
+
client_info(c).transfers.each do |key,t|
|
336
|
+
if c==t.giver
|
337
|
+
type="UP: "
|
338
|
+
peer=t.taker
|
339
|
+
else
|
340
|
+
type="DOWN: "
|
341
|
+
peer=t.giver
|
342
|
+
end
|
343
|
+
%>
|
344
|
+
<%= type %> id=<%= t.transfer_id %><br>
|
345
|
+
<%
|
346
|
+
end
|
347
|
+
%>
|
348
|
+
</td>
|
349
|
+
<td>
|
350
|
+
<%
|
351
|
+
client_info(c).chunk_info.get_file_stats.each do |fs|
|
352
|
+
%>
|
353
|
+
<%= fs.url %> size=<%= fs.file_chunks %> req=<%= fs.chunks_requested %>
|
354
|
+
prov=<%= fs.chunks_provided %> transf=<%= fs.chunks_transferring %><br>
|
355
|
+
<%
|
356
|
+
end
|
357
|
+
%>
|
358
|
+
</td></tr>
|
359
|
+
<%
|
360
|
+
end
|
361
|
+
%>
|
362
|
+
</table>
|
363
|
+
</body></html>
|
364
|
+
EOF
|
365
|
+
s.result binding
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
@@ -0,0 +1,140 @@
|
|
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__)+'/trust.rb'
|
12
|
+
|
13
|
+
module PDTP
|
14
|
+
#stores information about a single connected client
|
15
|
+
class ClientInfo
|
16
|
+
attr_accessor :chunk_info, :trust
|
17
|
+
attr_accessor :listen_port, :client_id
|
18
|
+
attr_accessor :transfers
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@chunk_info=ChunkInfo.new
|
22
|
+
@listen_port=6000 #default
|
23
|
+
@trust=Trust.new
|
24
|
+
@transfers=Hash.new
|
25
|
+
end
|
26
|
+
|
27
|
+
# returns true if this client wants the server to spawn a transfer for it
|
28
|
+
def wants_download?
|
29
|
+
transfer_state_allowed=5
|
30
|
+
total_allowed=10
|
31
|
+
transferring=0
|
32
|
+
@transfers.each do |key, t|
|
33
|
+
transferring=transferring+1 if t.verification_asked
|
34
|
+
return false if transferring >= transfer_state_allowed
|
35
|
+
end
|
36
|
+
|
37
|
+
@transfers.size < total_allowed
|
38
|
+
end
|
39
|
+
|
40
|
+
#this could have a different definition, but it works fine to use wants_download?
|
41
|
+
alias_method :wants_upload?, :wants_download?
|
42
|
+
|
43
|
+
#returns a list of all the stalled transfers this client is a part of
|
44
|
+
def get_stalled_transfers
|
45
|
+
stalled=[]
|
46
|
+
timeout=20.0
|
47
|
+
now=Time.now
|
48
|
+
@transfers.each do |key,t|
|
49
|
+
#only delete if we are the acceptor to prevent race conditions
|
50
|
+
next if t.acceptor.user_data != self
|
51
|
+
if now-t.creation_time > timeout and not t.verification_asked
|
52
|
+
stalled << t
|
53
|
+
end
|
54
|
+
end
|
55
|
+
stalled
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
#stores information about the chunks requested or provided by a client
|
60
|
+
class ChunkInfo
|
61
|
+
def initialize
|
62
|
+
@files={}
|
63
|
+
end
|
64
|
+
|
65
|
+
#each chunk can either be provided, requested, transfer, or none
|
66
|
+
def provide(filename,range); set(filename,range,:provided) ; end
|
67
|
+
def unprovide(filename,range); set(filename,range, :none); end
|
68
|
+
def request(filename,range); set(filename,range, :requested); end
|
69
|
+
def unrequest(filename,range); set(filename,range, :none); end
|
70
|
+
def transfer(filename,range); set(filename,range, :transfer); end
|
71
|
+
|
72
|
+
def provided?(filename,chunk); get(filename,chunk) == :provided; end
|
73
|
+
def requested?(filename,chunk); get(filename,chunk) == :requested; end
|
74
|
+
|
75
|
+
#returns a high priority requested chunk
|
76
|
+
def high_priority_chunk
|
77
|
+
#right now return any chunk
|
78
|
+
@files.each do |name,file|
|
79
|
+
file.each_index do |i|
|
80
|
+
return [name,i] if file[i]==:requested
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
#calls a block for each chunk of the specified type
|
88
|
+
def each_chunk_of_type(type)
|
89
|
+
@files.each do |name,file|
|
90
|
+
file.each_index do |i|
|
91
|
+
yield(name,i) if file[i]==type
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class FileStats
|
97
|
+
attr_accessor :file_chunks, :chunks_requested,:url
|
98
|
+
attr_accessor :chunks_provided, :chunks_transferring
|
99
|
+
|
100
|
+
def initialize
|
101
|
+
@url=""
|
102
|
+
@file_chunks=0
|
103
|
+
@chunks_requested=0
|
104
|
+
@chunks_provided=0
|
105
|
+
@chunks_transferring=0
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
#returns an array of FileStats objects for debug output
|
110
|
+
def get_file_stats
|
111
|
+
stats=[]
|
112
|
+
@files.each do |name,file|
|
113
|
+
fs=FileStats.new
|
114
|
+
fs.file_chunks=file.size
|
115
|
+
fs.url=name
|
116
|
+
file.each do |chunk|
|
117
|
+
fs.chunks_requested+=1 if chunk==:requested
|
118
|
+
fs.chunks_provided+=1 if chunk==:provided
|
119
|
+
fs.chunks_transferring+=1 if chunk==:transfer
|
120
|
+
end
|
121
|
+
stats << fs
|
122
|
+
end
|
123
|
+
|
124
|
+
stats
|
125
|
+
end
|
126
|
+
|
127
|
+
#########
|
128
|
+
protected
|
129
|
+
#########
|
130
|
+
|
131
|
+
def get(filename,chunk)
|
132
|
+
@files[filename][chunk] rescue :neither
|
133
|
+
end
|
134
|
+
|
135
|
+
def set(filename,range,state)
|
136
|
+
chunks=@files[filename]||=Array.new
|
137
|
+
range.each { |i| chunks[i]=state }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|