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
@@ -8,120 +8,110 @@
|
|
8
8
|
# See http://distribustream.rubyforge.org/
|
9
9
|
#++
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def initialize(io = nil)
|
15
|
-
@io = io
|
16
|
-
@written = 0
|
17
|
-
@entries = []
|
18
|
-
end
|
11
|
+
require 'rubygems'
|
12
|
+
require 'mongrel'
|
13
|
+
require 'tempfile'
|
19
14
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
15
|
+
module PDTP
|
16
|
+
class Client
|
17
|
+
# Handle a file buffer, which may be written to and read from randomly
|
18
|
+
class FileBuffer
|
19
|
+
def initialize(output = nil)
|
20
|
+
# The output stream. Treated as non-seekable since it may be a pipe
|
21
|
+
@output = output
|
22
|
+
@cursor = 0
|
23
|
+
|
24
|
+
# The tempfile backbuffer. This will always be seekable
|
25
|
+
@buffer = Tempfile.new 'distribustream'
|
26
|
+
@populated = []
|
27
|
+
end
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
# Write to the backbuffer. Write contiguous data to the output stream
|
30
|
+
def write(position, data)
|
31
|
+
length = data.size
|
32
|
+
return 0 if length.zero?
|
33
|
+
|
34
|
+
range = position..(position + length - 1)
|
35
|
+
|
36
|
+
# Insert the chunk into the buffer and return a new range of contiguous data
|
37
|
+
writeable_range = insert_chunk range, data
|
38
|
+
|
39
|
+
# If this chunk begins at the cursor, write it out
|
40
|
+
if @output and writeable_range.begin == @cursor
|
41
|
+
@cursor += @output.write read(writeable_range)
|
36
42
|
end
|
43
|
+
|
44
|
+
# Return the length we just wrote
|
45
|
+
length
|
37
46
|
end
|
38
|
-
|
39
|
-
# Add entry to the local store
|
40
|
-
@entries << new_entry
|
41
47
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
48
|
+
def read(range)
|
49
|
+
chunk = containing_chunk range.begin
|
50
|
+
|
51
|
+
unless chunk and chunk.end >= range.end
|
52
|
+
raise RuntimeError, "#{chunk.inspect} cannot satisfy range #{range.inspect}"
|
53
|
+
end
|
54
|
+
|
55
|
+
@buffer.pos = range.begin
|
56
|
+
@buffer.read(range.end - range.begin + 1)
|
49
57
|
end
|
50
58
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
found=false
|
66
|
-
@entries.each do |e|
|
67
|
-
if e.range.include?(current_byte)
|
68
|
-
internal_start=current_byte-e.start_pos #start position inside this entry's data
|
69
|
-
internal_end=(range.last<e.end_pos ? range.last : e.end_pos) - e.start_pos
|
70
|
-
buffer << e.data[internal_start..internal_end]
|
71
|
-
current_byte+=internal_end-internal_start+1
|
72
|
-
found=true
|
73
|
-
break if current_byte>range.last
|
59
|
+
#######
|
60
|
+
private
|
61
|
+
#######
|
62
|
+
|
63
|
+
def insert_chunk(range, data)
|
64
|
+
i = 0
|
65
|
+
|
66
|
+
# Find the first slot in the populated array that this range exceeds
|
67
|
+
i += 1 while i < @populated.size and range.begin > @populated[i].end
|
68
|
+
|
69
|
+
if i == 0
|
70
|
+
unless @populated.empty? or @populated.first.begin > range.end
|
71
|
+
raise RuntimeError, "chunks in output buffer overlap"
|
74
72
|
end
|
73
|
+
|
74
|
+
@populated = [range] + @populated
|
75
|
+
elsif i < @populated.size
|
76
|
+
head = @populated[0..(i - 1)]
|
77
|
+
tail = @populated[i..(@populated.size - 1)]
|
78
|
+
|
79
|
+
raise RuntimeError, "chunks in output buffer overlap" if tail.first.begin <= range.end
|
80
|
+
|
81
|
+
@populated = head + [range] + tail
|
82
|
+
else
|
83
|
+
@populated << range
|
75
84
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
first.end_pos + 1 >= last.start_pos
|
86
|
-
end
|
87
|
-
|
88
|
-
# Takes two Entries
|
89
|
-
# Returns nil if there is no intersection
|
90
|
-
# Returns the union if they intersect
|
91
|
-
def combine(old_entry, new_entry)
|
92
|
-
start = old_entry.start_pos < new_entry.start_pos ? old_entry.start_pos: new_entry.start_pos
|
93
|
-
|
94
|
-
stringio = StringIO.new
|
95
|
-
stringio.seek(old_entry.start_pos - start)
|
96
|
-
stringio.write(old_entry.data)
|
97
|
-
stringio.seek(new_entry.start_pos - start)
|
98
|
-
stringio.write(new_entry.data)
|
99
|
-
return Entry.new(start, stringio.string)
|
100
|
-
end
|
101
|
-
|
102
|
-
# Return number of bytes currently in the buffer
|
103
|
-
def bytes_stored
|
104
|
-
bytes=0
|
105
|
-
@entries.each do |e|
|
106
|
-
bytes=bytes+e.data.size
|
107
|
-
end
|
108
|
-
return bytes
|
109
|
-
end
|
110
|
-
|
111
|
-
# Container for an entry in the buffer
|
112
|
-
class Entry
|
113
|
-
def initialize(start_pos,data)
|
114
|
-
@start_pos,@data=start_pos,data
|
85
|
+
|
86
|
+
@buffer.pos = range.begin
|
87
|
+
@buffer.write data
|
88
|
+
|
89
|
+
compact_chunks
|
90
|
+
position = range.begin
|
91
|
+
|
92
|
+
# Find the range beginning at the current position and ending at the last contiguous chunk
|
93
|
+
position..(containing_chunk(position).end)
|
115
94
|
end
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
95
|
+
|
96
|
+
def compact_chunks
|
97
|
+
output = []
|
98
|
+
current_range = @populated.shift
|
99
|
+
|
100
|
+
@populated.each do |next_range|
|
101
|
+
if current_range.end + 1 == next_range.begin
|
102
|
+
current_range = current_range.begin..next_range.end
|
103
|
+
else
|
104
|
+
output << current_range
|
105
|
+
current_range = next_range
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
output << current_range
|
110
|
+
@populated = output
|
121
111
|
end
|
122
|
-
|
123
|
-
def
|
124
|
-
|
112
|
+
|
113
|
+
def containing_chunk(position)
|
114
|
+
@populated.detect { |range| range.include? position }
|
125
115
|
end
|
126
116
|
end
|
127
117
|
end
|
@@ -8,18 +8,21 @@
|
|
8
8
|
# See http://distribustream.rubyforge.org/
|
9
9
|
#++
|
10
10
|
|
11
|
+
require 'rubygems'
|
12
|
+
require 'mongrel'
|
11
13
|
require 'uri'
|
12
14
|
require 'pathname'
|
15
|
+
|
13
16
|
require File.dirname(__FILE__) + '/../common/file_service.rb'
|
14
17
|
require File.dirname(__FILE__) + '/file_buffer.rb'
|
15
18
|
|
16
19
|
module PDTP
|
17
|
-
class Client
|
20
|
+
class Client
|
18
21
|
# The client specific file utilities. Most importantly, handling
|
19
22
|
# the data buffer.
|
20
23
|
class FileInfo < PDTP::FileInfo
|
21
|
-
def initialize(filename)
|
22
|
-
@buffer = FileBuffer.new open(filename, 'w')
|
24
|
+
def initialize(filename, io = nil)
|
25
|
+
@buffer = FileBuffer.new io || open(filename, 'w')
|
23
26
|
@lock = Mutex.new
|
24
27
|
end
|
25
28
|
|
@@ -0,0 +1,77 @@
|
|
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 'rubygems'
|
12
|
+
require 'eventmachine'
|
13
|
+
require 'mongrel'
|
14
|
+
|
15
|
+
require File.dirname(__FILE__) + '/transfer'
|
16
|
+
|
17
|
+
module PDTP
|
18
|
+
class Client
|
19
|
+
# Mongrel::HttpHandler to handle incoming HTTP chunk transfers
|
20
|
+
class HttpHandler < Mongrel::HttpHandler
|
21
|
+
attr_accessor :client
|
22
|
+
|
23
|
+
def initialize(client)
|
24
|
+
@client = client
|
25
|
+
end
|
26
|
+
|
27
|
+
# This method is called after a connection to the server
|
28
|
+
# has been successfully established.
|
29
|
+
def connection_created(connection)
|
30
|
+
@@log.debug("[mongrel] Opened connection...");
|
31
|
+
end
|
32
|
+
|
33
|
+
# This method is called when the server connection is destroyed
|
34
|
+
def connection_destroyed(connection)
|
35
|
+
@@log.debug("[mongrel] Closed connection...")
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns a transfer object if the given connection is a peer associated with
|
39
|
+
# that transfer. Otherwise returns nil.
|
40
|
+
def get_transfer(connection)
|
41
|
+
@client.transfers.each { |t| return t if t.peer == connection }
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# This method is called when an HTTP request is received. It is called in
|
46
|
+
# a separate thread, one for each request.
|
47
|
+
def process(request,response)
|
48
|
+
begin
|
49
|
+
@@log.debug "Creating Transfer::Listener"
|
50
|
+
transfer = Transfer::Listener.new(
|
51
|
+
@client.connection,
|
52
|
+
request,
|
53
|
+
response,
|
54
|
+
client.file_service
|
55
|
+
)
|
56
|
+
|
57
|
+
#Needs to be locked because multiple threads could attempt to append a transfer at once
|
58
|
+
@client.lock.synchronize { @client.transfers << transfer }
|
59
|
+
transfer.handle_header
|
60
|
+
rescue Exception => e
|
61
|
+
raise e if transfer.nil?
|
62
|
+
transfer.write_http_exception(e)
|
63
|
+
end
|
64
|
+
|
65
|
+
transfer.send_completed_message transfer.hash
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns true if the given message refers to the given transfer
|
69
|
+
def transfer_matches?(transfer, message)
|
70
|
+
transfer.peer == message["peer"] and
|
71
|
+
transfer.url == message["url"] and
|
72
|
+
transfer.byte_range == message["range"] and
|
73
|
+
transfer.peer_id == message["peer_id"]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/pdtp/client/transfer.rb
CHANGED
@@ -15,7 +15,7 @@ require "uri"
|
|
15
15
|
require "digest/sha2"
|
16
16
|
|
17
17
|
module PDTP
|
18
|
-
class Client
|
18
|
+
class Client
|
19
19
|
module Transfer
|
20
20
|
# Generic HTTP Exception to be used on error
|
21
21
|
class HTTPException < Exception
|
@@ -29,8 +29,7 @@ module PDTP
|
|
29
29
|
# The base information and methods needed by client transfers
|
30
30
|
class Base
|
31
31
|
attr_reader :peer, :peer_id, :url, :byte_range
|
32
|
-
attr_reader :
|
33
|
-
attr_reader :method, :client, :hash
|
32
|
+
attr_reader :file_service, :method, :connection, :hash
|
34
33
|
|
35
34
|
# Returns true if a server message matches this transfer
|
36
35
|
def matches_message?(message)
|
@@ -52,7 +51,7 @@ module PDTP
|
|
52
51
|
# Notify the server of transfer completion.
|
53
52
|
# Hash field is used to denote success or failure
|
54
53
|
def send_completed_message(hash)
|
55
|
-
@
|
54
|
+
@connection.send_message(:completed,
|
56
55
|
:url => @url,
|
57
56
|
:peer => @peer,
|
58
57
|
:range => @byte_range,
|
@@ -62,7 +61,7 @@ module PDTP
|
|
62
61
|
end
|
63
62
|
|
64
63
|
def send_ask_verify_message
|
65
|
-
@
|
64
|
+
@connection.send_message(:ask_verify,
|
66
65
|
:url => @url,
|
67
66
|
:peer => @peer,
|
68
67
|
:range => @byte_range,
|
@@ -76,11 +75,11 @@ module PDTP
|
|
76
75
|
attr :request, :response
|
77
76
|
|
78
77
|
# Called with the request and response parameters given by Mongrel
|
79
|
-
def initialize(request,response,
|
80
|
-
@request
|
81
|
-
@
|
82
|
-
@authorized=false
|
83
|
-
@
|
78
|
+
def initialize(connection, request, response, file_service)
|
79
|
+
@request, @response = request,response
|
80
|
+
@file_service = file_service
|
81
|
+
@authorized = false
|
82
|
+
@connection = connection
|
84
83
|
end
|
85
84
|
|
86
85
|
# Send an HTTP error response to requester
|
@@ -177,14 +176,14 @@ module PDTP
|
|
177
176
|
|
178
177
|
# Implements http transfer between two peers from the connector's (client) perspective
|
179
178
|
class Connector < Base
|
180
|
-
def initialize(message,
|
181
|
-
@
|
179
|
+
def initialize(connection, message, file_service)
|
180
|
+
@file_service=file_service
|
182
181
|
@peer,@port=message["host"],message["port"]
|
183
182
|
@method = message["method"]
|
184
183
|
@url=message["url"]
|
185
184
|
@byte_range=message["range"]
|
186
185
|
@peer_id=message["peer_id"]
|
187
|
-
@
|
186
|
+
@connection=connection
|
188
187
|
end
|
189
188
|
|
190
189
|
# Perform the transfer
|
@@ -211,7 +210,7 @@ module PDTP
|
|
211
210
|
|
212
211
|
req.add_field("Range", "bytes=#{@byte_range.begin}-#{@byte_range.end}")
|
213
212
|
req.add_field("Host",vhost)
|
214
|
-
req.add_field("X-PDTP-Peer-Id"
|
213
|
+
req.add_field("X-PDTP-Peer-Id", @connection.client.client_id)
|
215
214
|
res = Net::HTTP.start(@peer,@port) {|http| http.request(req,body) }
|
216
215
|
|
217
216
|
if res.code == '206' and @method == 'get'
|
data/lib/pdtp/client.rb
CHANGED
@@ -10,186 +10,103 @@
|
|
10
10
|
|
11
11
|
require 'rubygems'
|
12
12
|
require 'eventmachine'
|
13
|
-
require 'mongrel'
|
14
|
-
require 'net/http'
|
15
13
|
require 'thread'
|
16
14
|
require 'digest/md5'
|
17
15
|
|
18
|
-
require File.dirname(__FILE__) + '/
|
19
|
-
require File.dirname(__FILE__) + '/
|
20
|
-
require File.dirname(__FILE__) + '/client/protocol'
|
16
|
+
require File.dirname(__FILE__) + '/client/connection'
|
17
|
+
require File.dirname(__FILE__) + '/client/callbacks'
|
21
18
|
require File.dirname(__FILE__) + '/client/file_service'
|
22
|
-
require File.dirname(__FILE__) + '/client/
|
23
|
-
require File.dirname(__FILE__) + '/server/file_service'
|
19
|
+
require File.dirname(__FILE__) + '/client/http_handler'
|
24
20
|
|
25
21
|
module PDTP
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
attr_accessor :my_id
|
38
|
-
|
39
|
-
def self.get(host, path, options = {})
|
40
|
-
path = '/' + path unless path[0] == ?/
|
41
|
-
|
42
|
-
opts = {
|
43
|
-
:host => host,
|
44
|
-
:port => 6086,
|
45
|
-
:file_root => '.',
|
46
|
-
:quiet => true,
|
47
|
-
:listen_port => 8000,
|
48
|
-
:request_url => "http://#{host}#{path}"
|
49
|
-
}.merge(options)
|
50
|
-
|
51
|
-
common_init $0, opts
|
22
|
+
# PDTP::Client provides an interface for accessing resources on PDTP servers
|
23
|
+
class Client
|
24
|
+
# None of these should be publically accessible, and will be factored away in time
|
25
|
+
attr_reader :connection, :client_id, :listen_port, :file_service, :transfers, :lock
|
26
|
+
|
27
|
+
# Create a PDTP::Client object for accessing the PDTP server at the given host and port
|
28
|
+
def initialize(host, port = 6086, options = {})
|
29
|
+
@host, @port = host, port
|
30
|
+
|
31
|
+
@listen_addr = options[:listen_addr] || '0.0.0.0'
|
32
|
+
@listen_port = options[:listen_port] || 60860
|
52
33
|
|
53
|
-
|
54
|
-
EventMachine::run do
|
55
|
-
connection = EventMachine::connect host, opts[:port], Client::Protocol
|
56
|
-
@@log.info "connecting with ev=#{EventMachine::VERSION}"
|
57
|
-
@@log.info "host= #{host} port=#{opts[:port]}"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def initialize
|
34
|
+
@file_service = PDTP::Client::FileService.new
|
62
35
|
@transfers = []
|
63
|
-
@
|
64
|
-
end
|
65
|
-
|
66
|
-
# This method is called after a connection to the server
|
67
|
-
# has been successfully established.
|
68
|
-
def connection_created(connection)
|
69
|
-
@@log.debug("[mongrel] Opened connection...");
|
70
|
-
end
|
71
|
-
|
72
|
-
# This method is called when the server connection is destroyed
|
73
|
-
def connection_destroyed(connection)
|
74
|
-
@@log.debug("[mongrel] Closed connection...")
|
75
|
-
end
|
36
|
+
@lock = Mutex.new
|
76
37
|
|
77
|
-
|
78
|
-
# that transfer. Otherwise returns nil.
|
79
|
-
def get_transfer(connection)
|
80
|
-
@transfers.each { |t| return t if t.peer == connection }
|
81
|
-
nil
|
82
|
-
end
|
83
|
-
|
84
|
-
# This method is called when an HTTP request is received. It is called in
|
85
|
-
# a separate thread, one for each request.
|
86
|
-
def process(request,response)
|
38
|
+
# Start a Mongrel server on the specified port. If it isnt available, keep trying higher ports
|
87
39
|
begin
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
@server_connection,
|
93
|
-
@file_service,
|
94
|
-
self
|
95
|
-
)
|
96
|
-
|
97
|
-
#Needs to be locked because multiple threads could attempt to append a transfer at once
|
98
|
-
@mutex.synchronize { @transfers << transfer }
|
99
|
-
transfer.handle_header
|
100
|
-
rescue Exception=>e
|
101
|
-
transfer.write_http_exception(e)
|
40
|
+
@http_server = Mongrel::HttpServer.new @listen_addr, @listen_port
|
41
|
+
rescue Exception => e
|
42
|
+
@listen_port += 1
|
43
|
+
retry
|
102
44
|
end
|
103
45
|
|
104
|
-
|
105
|
-
end
|
46
|
+
@client_id = Digest::MD5.hexdigest "#{Time.now.to_f}#{$$}"
|
106
47
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
transfer.byte_range == message["range"] and
|
112
|
-
transfer.peer_id == message["peer_id"]
|
48
|
+
#@@log.info "listening on port #{@listen_port}"
|
49
|
+
@http_handler = HttpHandler.new(self)
|
50
|
+
@http_server.register '/', @http_handler
|
51
|
+
@http_server.run
|
113
52
|
end
|
114
53
|
|
115
|
-
#
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
end
|
137
|
-
t.send_completed_message(t.hash)
|
138
|
-
end
|
139
|
-
when "tell_verify"
|
140
|
-
# We are a listener, and asked for verification of a transfer from a server.
|
141
|
-
# After asking for verification, we stopped running, and must be restarted
|
142
|
-
# if verification is successful
|
143
|
-
|
144
|
-
found=false
|
145
|
-
@transfers.each do |t|
|
146
|
-
if t.matches_message?(message)
|
147
|
-
finished(t)
|
148
|
-
t.tell_verify(message["is_authorized"])
|
149
|
-
found=true
|
150
|
-
break
|
151
|
-
end
|
54
|
+
# Connect to the PDTP server. This is a blocking call which runs the client event loop
|
55
|
+
def connect(callbacks = nil)
|
56
|
+
klass = if(callbacks and callbacks.is_a?(Class))
|
57
|
+
callbacks
|
58
|
+
else
|
59
|
+
Class.new(Callbacks) {callbacks and include callbacks}
|
60
|
+
end
|
61
|
+
|
62
|
+
# Run the EventMachine reactor loop
|
63
|
+
EventMachine.run do
|
64
|
+
EventMachine.connect(@host, @port, Connection) do |c|
|
65
|
+
# Store reference to the connection
|
66
|
+
@connection = c
|
67
|
+
|
68
|
+
# Set connection variables and configure callbacks
|
69
|
+
c.client = self
|
70
|
+
c.callbacks = klass.new(self)
|
71
|
+
c.callbacks.client = self
|
72
|
+
|
73
|
+
# Allow users to populate instance variables in their callback class
|
74
|
+
yield c.callbacks if block_given?
|
152
75
|
end
|
153
76
|
|
154
|
-
|
155
|
-
|
156
|
-
exit!
|
157
|
-
end
|
158
|
-
when "hash_verify"
|
159
|
-
@@log.debug "Hash verified for url=#{message["url"]} range=#{message["range"]} hash_ok=#{message["hash_ok"]}"
|
160
|
-
when "protocol_error", "protocol_warn" #ignore
|
161
|
-
else raise "Server sent an unknown message type: #{command} "
|
77
|
+
#@@log.info "connecting with ev=#{EventMachine::VERSION}"
|
78
|
+
#@@log.info "host= #{host} port=#{opts[:port]}"
|
162
79
|
end
|
163
80
|
end
|
164
81
|
|
165
|
-
#
|
166
|
-
def
|
167
|
-
|
82
|
+
# Are we currently connected to a server?
|
83
|
+
def connected?
|
84
|
+
!!@connection
|
168
85
|
end
|
169
86
|
|
170
|
-
#
|
171
|
-
def
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
87
|
+
# Retrieve the resource at the given path and write it to the given IO object
|
88
|
+
def get(path, io, options = {})
|
89
|
+
raise RuntimeError, "not connected to server yet" unless connected?
|
90
|
+
|
91
|
+
path = '/' + path unless path[0] == ?/
|
92
|
+
url = "http://#{@host}#{path}"
|
93
|
+
filename = path.split('/').last
|
94
|
+
|
95
|
+
# Register the file and its IO object with the local file service
|
96
|
+
file_service.set_info url, FileInfo.new(filename, io)
|
97
|
+
|
98
|
+
# Ask the server for some information on the file we want
|
99
|
+
@connection.send_message :ask_info, :url => url
|
181
100
|
|
182
|
-
|
183
|
-
|
184
|
-
md5 = Digest::MD5::new
|
185
|
-
now = Time::now
|
186
|
-
md5.update now.to_s
|
187
|
-
md5.update String(now.usec)
|
188
|
-
md5.update String(rand(0))
|
189
|
-
md5.update String($$)
|
101
|
+
# Request the file (should probably be done after receiving :tell_info)
|
102
|
+
@connection.send_message :request, :url => url
|
190
103
|
|
191
|
-
|
192
|
-
|
104
|
+
#@@log.info "This client is requesting"
|
105
|
+
end
|
106
|
+
|
107
|
+
# Stop the client event loop. This only works within callbacks given to the #connect method
|
108
|
+
def stop
|
109
|
+
EventMachine.stop_event_loop
|
193
110
|
end
|
194
111
|
end
|
195
112
|
end
|