fargo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/fargo/client.rb +161 -0
- data/lib/fargo/connection/base.rb +133 -0
- data/lib/fargo/connection/download.rb +302 -0
- data/lib/fargo/connection/hub.rb +120 -0
- data/lib/fargo/connection/search.rb +19 -0
- data/lib/fargo/connection/upload.rb +85 -0
- data/lib/fargo/parser.rb +121 -0
- data/lib/fargo/publisher.rb +26 -0
- data/lib/fargo/search.rb +61 -0
- data/lib/fargo/search_result.rb +31 -0
- data/lib/fargo/server.rb +52 -0
- data/lib/fargo/supports/chat.rb +35 -0
- data/lib/fargo/supports/downloads.rb +261 -0
- data/lib/fargo/supports/file_list.rb +84 -0
- data/lib/fargo/supports/nick_list.rb +71 -0
- data/lib/fargo/supports/persistence.rb +52 -0
- data/lib/fargo/supports/searches.rb +64 -0
- data/lib/fargo/supports/timeout.rb +19 -0
- data/lib/fargo/supports/uploads.rb +16 -0
- data/lib/fargo/utils.rb +35 -0
- data/lib/fargo/version.rb +3 -0
- data/lib/fargo.rb +48 -0
- metadata +127 -0
data/lib/fargo/client.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'active_support/callbacks'
|
2
|
+
require 'active_support/configurable'
|
3
|
+
require 'active_support/core_ext/object/try'
|
4
|
+
|
5
|
+
module Fargo
|
6
|
+
class Client
|
7
|
+
|
8
|
+
include ActiveSupport::Callbacks
|
9
|
+
include ActiveSupport::Configurable
|
10
|
+
|
11
|
+
define_callbacks :setup
|
12
|
+
|
13
|
+
include Fargo::Publisher
|
14
|
+
include Fargo::Supports::Chat
|
15
|
+
include Fargo::Supports::Uploads
|
16
|
+
include Fargo::Supports::NickList
|
17
|
+
include Fargo::Supports::Searches
|
18
|
+
include Fargo::Supports::Downloads
|
19
|
+
include Fargo::Supports::Persistence
|
20
|
+
include Fargo::Supports::Timeout
|
21
|
+
include Fargo::Supports::FileList
|
22
|
+
|
23
|
+
# Options
|
24
|
+
# :hub_port
|
25
|
+
# :hub_address
|
26
|
+
# :search_port
|
27
|
+
# :active_port
|
28
|
+
# :nick
|
29
|
+
# :password
|
30
|
+
# :email
|
31
|
+
# :speed
|
32
|
+
# :passive
|
33
|
+
# :download_slots
|
34
|
+
# :download_dir
|
35
|
+
# :slots
|
36
|
+
configure do |config|
|
37
|
+
config.download_dir = '/tmp/fargo/downloads'
|
38
|
+
config.version = '0.75'
|
39
|
+
# default the address to the address of this machine
|
40
|
+
config.address = IPSocket.getaddress(Socket.gethostname)
|
41
|
+
config.passive = true
|
42
|
+
config.nick = 'fargo'
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :hub, :searcher, :active_server
|
46
|
+
|
47
|
+
def initialize
|
48
|
+
@connection_timeout_threads = {}
|
49
|
+
end
|
50
|
+
|
51
|
+
# Don't do this in initialization so we have time to set all the options
|
52
|
+
def setup
|
53
|
+
@hub = Fargo::Connection::Hub.new self
|
54
|
+
@hub.config.port = config.hub_port if config.hub_port
|
55
|
+
@hub.config.address = config.hub_address if config.hub_address
|
56
|
+
|
57
|
+
unless config.passive
|
58
|
+
# TODO: get this working again
|
59
|
+
# Always create a search connection for this.
|
60
|
+
# searcher_options = new_options.merge :port => search_port,
|
61
|
+
# :connection => Fargo::Connection::Search
|
62
|
+
# @searcher = Fargo::Server.new searcher_options
|
63
|
+
#
|
64
|
+
# # For now, being active means that you can only download things. Always make a
|
65
|
+
# # connection which downloads things.
|
66
|
+
# active_options = new_options.merge :port => active_port,
|
67
|
+
# :connection => Fargo::Connection::Download
|
68
|
+
# @active_server = Fargo::Server.new active_options
|
69
|
+
end
|
70
|
+
|
71
|
+
run_callbacks :setup
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_info nick
|
75
|
+
hub.write "$GetINFO #{nick} #{config.nick}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_ip *nicks
|
79
|
+
hub.write "$UserIP #{nicks.flatten.join '$$'}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def connect_with nick
|
83
|
+
@connection_timeout_threads[nick] = Thread.start do
|
84
|
+
sleep 10
|
85
|
+
connection_timeout! nick
|
86
|
+
end
|
87
|
+
|
88
|
+
if config.passive
|
89
|
+
hub.write "$RevConnectToMe #{self.config.nick} #{nick}"
|
90
|
+
else
|
91
|
+
hub.write "$ConnectToMe #{nick} #{config.address}:#{config.active_port}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def connected_with! nick
|
96
|
+
return unless @connection_timeout_threads.has_key?(nick)
|
97
|
+
@connection_timeout_threads.delete(nick).exit
|
98
|
+
end
|
99
|
+
|
100
|
+
def connect
|
101
|
+
setup if hub.nil?
|
102
|
+
|
103
|
+
# connect all our associated servers
|
104
|
+
hub.connect
|
105
|
+
|
106
|
+
unless config.passive
|
107
|
+
searcher.connect
|
108
|
+
active_server.connect
|
109
|
+
end
|
110
|
+
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
def connected?
|
115
|
+
hub.try :connected?
|
116
|
+
end
|
117
|
+
|
118
|
+
def disconnect
|
119
|
+
return if hub.nil?
|
120
|
+
|
121
|
+
Fargo.logger.info "Disconnecting from hub."
|
122
|
+
hub.disconnect
|
123
|
+
unless config.passive
|
124
|
+
searcher.disconnect
|
125
|
+
active_server.disconnect
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def search_hub query
|
130
|
+
raise ConnectionError.new('Not connected Yet!') if hub.nil?
|
131
|
+
|
132
|
+
if config.passive
|
133
|
+
location = "Hub:#{config.nick}"
|
134
|
+
else
|
135
|
+
location = "#{config.address}:#{config.search_port}"
|
136
|
+
end
|
137
|
+
|
138
|
+
hub.write "$Search #{location} #{query.to_s}"
|
139
|
+
end
|
140
|
+
|
141
|
+
# see hub/parser#@@search for what's passed in
|
142
|
+
# searches this client's files based on those options and returns an array
|
143
|
+
# of SearchResult(s)
|
144
|
+
def search_files options
|
145
|
+
# TODO: implement me
|
146
|
+
[]
|
147
|
+
end
|
148
|
+
|
149
|
+
def description
|
150
|
+
"<++ V:#{config.version},M:#{config.passive ? 'P' : 'A'},H:1/0/0,S:#{open_slots},Dt:1.2.0/W>"
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def connection_timeout! nick
|
156
|
+
@connection_timeout_threads.delete(nick)
|
157
|
+
publish :connection_timeout, :nick => nick
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'active_support/configurable'
|
2
|
+
require 'active_support/callbacks'
|
3
|
+
|
4
|
+
module Fargo
|
5
|
+
class ConnectionError < RuntimeError; end
|
6
|
+
|
7
|
+
module Connection
|
8
|
+
class Base
|
9
|
+
|
10
|
+
include ActiveSupport::Configurable
|
11
|
+
include ActiveSupport::Callbacks
|
12
|
+
include Fargo::Publisher
|
13
|
+
|
14
|
+
attr_accessor :socket
|
15
|
+
define_callbacks :listen
|
16
|
+
|
17
|
+
def initialize client
|
18
|
+
@outgoing = Queue.new
|
19
|
+
@client = client
|
20
|
+
config.quit_on_disconnect = true
|
21
|
+
end
|
22
|
+
|
23
|
+
def connect
|
24
|
+
Fargo.logger.info(
|
25
|
+
"#{self}: Opening connection with #{config.address}, #{config.port}"
|
26
|
+
)
|
27
|
+
|
28
|
+
open_socket
|
29
|
+
listen
|
30
|
+
|
31
|
+
connection_type = self.class.name.split('::').last.downcase
|
32
|
+
@client.publish :"#{connection_type}_connection_opened"
|
33
|
+
end
|
34
|
+
|
35
|
+
def receive
|
36
|
+
raise 'Implement me!'
|
37
|
+
end
|
38
|
+
|
39
|
+
def open_socket
|
40
|
+
@socket ||= TCPSocket.open config.address, config.port
|
41
|
+
rescue Errno::ECONNREFUSED
|
42
|
+
raise Fargo::ConnectionError.new "Couldn't open a connection to #{config.address}:#{config.port}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def connected?
|
46
|
+
!@socket.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
def listen
|
50
|
+
return unless @threads.nil? || @threads.size == 0
|
51
|
+
|
52
|
+
run_callbacks :listen do
|
53
|
+
@threads = []
|
54
|
+
|
55
|
+
# Start a thread to read the socket
|
56
|
+
@threads << Thread.start { loop { read_data } }
|
57
|
+
|
58
|
+
# Start a thread to send information from the queue
|
59
|
+
@threads << Thread.start { loop { write_data @outgoing.pop } }
|
60
|
+
|
61
|
+
@threads.each { |t| t.abort_on_exception = true }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def disconnect
|
66
|
+
Fargo.logger.debug "#{self}: Disconnecting connection"
|
67
|
+
|
68
|
+
write "$Quit #{@client.config.nick}" if config.quit_on_disconnect
|
69
|
+
|
70
|
+
if @threads
|
71
|
+
@threads.each &:exit
|
72
|
+
@threads.clear
|
73
|
+
end
|
74
|
+
|
75
|
+
if @socket
|
76
|
+
begin
|
77
|
+
@socket.close
|
78
|
+
rescue => e
|
79
|
+
Fargo.logger.error "Error closing socket: #{e}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
@socket = nil
|
84
|
+
@outgoing.clear
|
85
|
+
|
86
|
+
connection_type = self.class.name.split('::').last.downcase
|
87
|
+
@client.publish :"#{connection_type}_disconnected"
|
88
|
+
end
|
89
|
+
|
90
|
+
def write string
|
91
|
+
string << '|' unless string.end_with?('|')
|
92
|
+
@outgoing << string # append this to the queue of things to be written
|
93
|
+
true
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def read_data
|
99
|
+
if @socket.closed?
|
100
|
+
Fargo.logger.debug 'When reading data, socket was already closed!'
|
101
|
+
disconnect
|
102
|
+
else
|
103
|
+
begin
|
104
|
+
data = @socket.gets '|'
|
105
|
+
raise ConnectionError.new("Received nil data!") if data.nil?
|
106
|
+
rescue => e
|
107
|
+
Fargo.logger.warn "#{self}: Error reading data, disconnecting: #{e}"
|
108
|
+
disconnect
|
109
|
+
end
|
110
|
+
|
111
|
+
Fargo.logger.debug "#{self} Received: #{data.inspect}"
|
112
|
+
receive data.chomp('|')
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def write_data data
|
117
|
+
if @socket.closed?
|
118
|
+
Fargo.logger.debug "When writing data, socket was already closed!"
|
119
|
+
disconnect
|
120
|
+
else
|
121
|
+
begin
|
122
|
+
Fargo.logger.debug "#{self} Sending: #{data.inspect}"
|
123
|
+
@socket << data
|
124
|
+
rescue
|
125
|
+
@client.publish :write_error
|
126
|
+
disconnect
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,302 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
module Fargo
|
4
|
+
module Connection
|
5
|
+
class Download < Base
|
6
|
+
|
7
|
+
include Fargo::Utils
|
8
|
+
include Fargo::Parser
|
9
|
+
|
10
|
+
set_callback :listen, :before, :pre_listen
|
11
|
+
set_callback :listen, :after do |connection|
|
12
|
+
send_lock if connection.config.first
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :download
|
16
|
+
|
17
|
+
def pre_listen
|
18
|
+
Fargo.logger.debug "Initiating connection on: #{config.address}:#{config.port}"
|
19
|
+
|
20
|
+
config.quit_on_disconnect = false
|
21
|
+
@lock, @pk = generate_lock
|
22
|
+
@handshake_step = 0
|
23
|
+
|
24
|
+
@buffer_size = (2 << 12).freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_lock
|
28
|
+
write "$MyNick #{@client.config.nick}|$Lock #{@lock} Pk=#{@pk}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def read_data
|
32
|
+
# only download if we're at the correct time
|
33
|
+
return super if @handshake_step != 6
|
34
|
+
|
35
|
+
@exit_time = 20 # reset our timeout time
|
36
|
+
|
37
|
+
data = @socket.readpartial @buffer_size
|
38
|
+
|
39
|
+
if @zlib
|
40
|
+
@zs = Zlib::Inflate.new if @zs.nil?
|
41
|
+
data = @zs.inflate data
|
42
|
+
end
|
43
|
+
|
44
|
+
@file << data
|
45
|
+
@recvd += data.length
|
46
|
+
|
47
|
+
if @recvd == @length
|
48
|
+
download_finished!
|
49
|
+
elsif @recvd > @length
|
50
|
+
error "#{self} #{@recvd} > #{@length}!!!"
|
51
|
+
download_finished!
|
52
|
+
else
|
53
|
+
publish :download_progress, :percent => @recvd.to_f / @length,
|
54
|
+
:file => download_path,
|
55
|
+
:nick => @other_nick,
|
56
|
+
:download => @download,
|
57
|
+
:size => @length,
|
58
|
+
:compressed => @zlib
|
59
|
+
end
|
60
|
+
rescue IOError => e
|
61
|
+
error "#{self}: IOError, disconnecting #{e}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def receive data
|
65
|
+
message = parse_message data
|
66
|
+
|
67
|
+
case message[:type]
|
68
|
+
when :mynick
|
69
|
+
if @handshake_step == 0
|
70
|
+
@handshake_step = 1
|
71
|
+
@other_nick = message[:nick]
|
72
|
+
|
73
|
+
@client.connected_with! @other_nick
|
74
|
+
@client.lock_connection_with! @other_nick, self
|
75
|
+
@download = @client.lock_next_download! @other_nick, self
|
76
|
+
|
77
|
+
if @download.try(:file).nil?
|
78
|
+
error "Nothing to download from:#{@other_nick}!"
|
79
|
+
end
|
80
|
+
else
|
81
|
+
error 'Premature disconnect when mynick received'
|
82
|
+
end
|
83
|
+
|
84
|
+
when :lock
|
85
|
+
if @handshake_step == 1
|
86
|
+
@remote_lock = message[:lock]
|
87
|
+
@handshake_step = 2
|
88
|
+
send_lock unless config.first
|
89
|
+
out = ''
|
90
|
+
out << '$Supports TTHF ADCGet ZLIG|'
|
91
|
+
out << "$Direction Download #{@my_num = rand(10000)}|"
|
92
|
+
out << "$Key #{generate_key @remote_lock}|"
|
93
|
+
write out
|
94
|
+
else
|
95
|
+
error 'Premature disconnect when lock received'
|
96
|
+
end
|
97
|
+
|
98
|
+
when :supports
|
99
|
+
if @handshake_step == 2
|
100
|
+
@client_extensions = message[:extensions]
|
101
|
+
@handshake_step = 3
|
102
|
+
else
|
103
|
+
error 'Premature disconnect when supports received'
|
104
|
+
end
|
105
|
+
|
106
|
+
when :direction
|
107
|
+
if @handshake_step == 3 && message[:direction] == 'upload'
|
108
|
+
@client_num = message[:number]
|
109
|
+
@handshake_step = 4
|
110
|
+
else
|
111
|
+
error 'Premature disconnect when direction received'
|
112
|
+
end
|
113
|
+
|
114
|
+
when :key
|
115
|
+
if @handshake_step == 4 && generate_key(@lock) == message[:key]
|
116
|
+
|
117
|
+
FileUtils.mkdir_p File.dirname(download_path), :mode => 0755
|
118
|
+
|
119
|
+
begin_download!
|
120
|
+
|
121
|
+
else
|
122
|
+
error 'Premature disconnect when key received'
|
123
|
+
end
|
124
|
+
|
125
|
+
when :file_length, :adcsnd
|
126
|
+
if @handshake_step == 5
|
127
|
+
@recvd = 0
|
128
|
+
@handshake_step = 6
|
129
|
+
|
130
|
+
@zlib = message[:zlib]
|
131
|
+
@length = message[:size]
|
132
|
+
|
133
|
+
write "$Send" unless @client_extensions.include? 'ADCGet'
|
134
|
+
|
135
|
+
publish :download_started, :file => download_path,
|
136
|
+
:download => @download,
|
137
|
+
:nick => @other_nick
|
138
|
+
else
|
139
|
+
error "Premature disconnect when #{message[:type]} received"
|
140
|
+
end
|
141
|
+
|
142
|
+
when :noslots
|
143
|
+
if @download
|
144
|
+
Fargo.logger.debug "#{self}: No Slots for #{self[:download]}"
|
145
|
+
|
146
|
+
download_failed! 'No Slots'
|
147
|
+
end
|
148
|
+
|
149
|
+
when :error
|
150
|
+
error "#{self}: Error! #{message[:message]}"
|
151
|
+
|
152
|
+
# This wasn't handled by us, proxy it on up to the client
|
153
|
+
else
|
154
|
+
@client.publish message[:type], message
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def begin_download!
|
160
|
+
@file = File.new download_path, File::CREAT | File::WRONLY
|
161
|
+
|
162
|
+
@file.seek @download.offset
|
163
|
+
@file.sync = true
|
164
|
+
@socket.sync = true
|
165
|
+
@handshake_step = 5
|
166
|
+
|
167
|
+
if @download.file_list?
|
168
|
+
if @client_extensions.include? 'XmlBZList'
|
169
|
+
@download.file = 'files.xml.bz2'
|
170
|
+
elsif @client_extensions.include? 'BZList'
|
171
|
+
@download.file = 'MyList.bz2'
|
172
|
+
else
|
173
|
+
@download.file = 'MyList.DcLst' # TODO: support this?
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
if @client_extensions.include? 'ADCGet'
|
178
|
+
download_query = @download.file
|
179
|
+
if @download.tth && @client_extensions.include?('TTHF')
|
180
|
+
download_query = @download.tth.gsub ':', '/'
|
181
|
+
end
|
182
|
+
|
183
|
+
zlig = ''
|
184
|
+
if @client_extensions.include? 'ZLIG'
|
185
|
+
zlig = 'ZL1'
|
186
|
+
Fargo.logger.debug "Enabling zlib compression on: #{@download.file}"
|
187
|
+
end
|
188
|
+
|
189
|
+
write "$ADCGET file #{download_query} #{@download.offset} #{@download.size} #{zlig}"
|
190
|
+
else
|
191
|
+
write "$Get #{@download.file}$#{@download.offset + 1}"
|
192
|
+
end
|
193
|
+
|
194
|
+
# This is the thread for the timeout of a connection. The @exit_time
|
195
|
+
# variable is reset to 20 after every bit of information is received.
|
196
|
+
@exit_time = 20
|
197
|
+
@exit_thread = Thread.start {
|
198
|
+
while @exit_time > 0
|
199
|
+
sleep 1
|
200
|
+
@exit_time -= 1
|
201
|
+
Fargo.logger.debug "#{self} time out in #{@exit_time} seconds"
|
202
|
+
end
|
203
|
+
|
204
|
+
download_failed! 'Download timeout!'
|
205
|
+
}
|
206
|
+
|
207
|
+
Fargo.logger.debug "#{self}: Beginning download of #{@download}"
|
208
|
+
end
|
209
|
+
|
210
|
+
def download_failed! msg, opts = {}
|
211
|
+
Fargo.logger.debug "#{self}: #{msg} #{@download}"
|
212
|
+
|
213
|
+
# cache because publishing must be at end of method and we're about to
|
214
|
+
# clear these
|
215
|
+
path, download = download_path, @download
|
216
|
+
|
217
|
+
reset_download
|
218
|
+
|
219
|
+
publish :download_failed, opts.merge(:nick => @other_nick,
|
220
|
+
:download => download,
|
221
|
+
:file => path,
|
222
|
+
:last_error => msg)
|
223
|
+
|
224
|
+
@exit_thread = nil
|
225
|
+
end
|
226
|
+
|
227
|
+
def download_finished!
|
228
|
+
Fargo.logger.debug "#{self}: Finished download of #{@download}"
|
229
|
+
|
230
|
+
# cache because publishing must be at end of method and we're about to
|
231
|
+
# clear these
|
232
|
+
path, download = download_path, @download
|
233
|
+
|
234
|
+
reset_download
|
235
|
+
|
236
|
+
publish :download_finished, :file => path, :download => download,
|
237
|
+
:nick => @other_nick
|
238
|
+
end
|
239
|
+
|
240
|
+
def disconnect
|
241
|
+
Fargo.logger.debug "#{self} Disconnecting from: #{@other_nick}"
|
242
|
+
|
243
|
+
super
|
244
|
+
|
245
|
+
if @download
|
246
|
+
download_failed! @last_error, :recvd => @recvd, :length => @length
|
247
|
+
end
|
248
|
+
|
249
|
+
reset_download
|
250
|
+
end
|
251
|
+
|
252
|
+
private
|
253
|
+
def reset_download
|
254
|
+
@file.close unless @file.nil? || @file.closed?
|
255
|
+
if @file_path && File.exists?(@file_path) && File.size(@file_path) == 0
|
256
|
+
File.delete(@file_path)
|
257
|
+
end
|
258
|
+
|
259
|
+
if @socket
|
260
|
+
@socket.sync = false
|
261
|
+
@socket.flush
|
262
|
+
end
|
263
|
+
|
264
|
+
# If this was called from exit thread, don't kill it
|
265
|
+
if @exit_thread != Thread.current
|
266
|
+
@exit_thread.exit if @exit_thread && @exit_thread.alive?
|
267
|
+
@exit_thread = nil
|
268
|
+
end
|
269
|
+
|
270
|
+
# clear out these variables
|
271
|
+
@zs = @file_path = @zlib = @download = @length = @recvd = nil
|
272
|
+
|
273
|
+
# Go back to the get step
|
274
|
+
@handshake_step = 5
|
275
|
+
end
|
276
|
+
|
277
|
+
def download_path
|
278
|
+
return nil if @download.try(:file).nil?
|
279
|
+
|
280
|
+
@file_path ||= begin
|
281
|
+
prefix = @client.config.download_dir
|
282
|
+
filename = File.basename @download.file.gsub("\\", '/')
|
283
|
+
path = File.join(prefix, @other_nick, filename)
|
284
|
+
|
285
|
+
i = 0
|
286
|
+
while File.exists?(path)
|
287
|
+
i += 1
|
288
|
+
path = File.join(prefix, @other_nick, "#{i}-#{filename}")
|
289
|
+
end
|
290
|
+
|
291
|
+
path
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def error message
|
296
|
+
Fargo.logger.warn @last_error = message
|
297
|
+
disconnect
|
298
|
+
end
|
299
|
+
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Fargo
|
2
|
+
module Connection
|
3
|
+
class Hub < Base
|
4
|
+
|
5
|
+
include Fargo::Utils
|
6
|
+
include Fargo::Parser
|
7
|
+
|
8
|
+
attr_reader :hubname
|
9
|
+
|
10
|
+
configure do |config|
|
11
|
+
config.address = '127.0.0.1'
|
12
|
+
config.port = 7314
|
13
|
+
end
|
14
|
+
|
15
|
+
# See <http://www.teamfair.info/DC-Protocol.htm> for specifics on
|
16
|
+
# the DC protocol
|
17
|
+
def receive data
|
18
|
+
message = parse_message data
|
19
|
+
|
20
|
+
case message[:type]
|
21
|
+
when :lock
|
22
|
+
@validated = false
|
23
|
+
write "$Key #{generate_key message[:lock]}"
|
24
|
+
when :hubname
|
25
|
+
@hubname = message[:name]
|
26
|
+
write "$ValidateNick #{@client.config.nick}" unless @validated
|
27
|
+
when :getpass
|
28
|
+
write "$MyPass #{@client.password}"
|
29
|
+
when :badpass, :hubfull
|
30
|
+
Fargo.logger.warn "Disconnecting because of: #{message.inspect}"
|
31
|
+
disconnect
|
32
|
+
when :hello
|
33
|
+
if message[:who] == @client.config.nick
|
34
|
+
Fargo.logger.info "Connected to DC Hub #{@hubname} (#{config.address}:#{config.port})"
|
35
|
+
@validated = true
|
36
|
+
|
37
|
+
write '$Version 1,0091'
|
38
|
+
write '$GetNickList'
|
39
|
+
write "$MyINFO $ALL #{@client.config.nick} " +
|
40
|
+
"#{@client.description}$ $#{@client.config.speed || 'DSL'}" +
|
41
|
+
"#{@status || 1.chr}$#{@client.config.email}" +
|
42
|
+
"$#{@client.share_size}$"
|
43
|
+
end
|
44
|
+
|
45
|
+
when :connect_to_me
|
46
|
+
if !@client.nicks.include?(message[:nick])
|
47
|
+
Fargo.logger.info "Invalid connect_to_me request from: #{message[:nick]}"
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
@client_connections ||= []
|
52
|
+
|
53
|
+
connection = Fargo::Connection::Download.new @client
|
54
|
+
connection.config.address = message[:address]
|
55
|
+
connection.config.port = message[:port]
|
56
|
+
# we're going to initiate the download
|
57
|
+
connection.config.first = true
|
58
|
+
|
59
|
+
# proxy all messages from them back to the client and delete the
|
60
|
+
# connection if necessary
|
61
|
+
connection.subscribe { |*args|
|
62
|
+
@client.publish *args
|
63
|
+
@client_connections.delete connection unless connection.connected?
|
64
|
+
}
|
65
|
+
|
66
|
+
# establish the connection. This will also listen for data to be
|
67
|
+
# read/written
|
68
|
+
connection.connect
|
69
|
+
|
70
|
+
# keep track of who we're downloading from
|
71
|
+
@client_connections << connection
|
72
|
+
|
73
|
+
when :search
|
74
|
+
# Make sure we received a valid search request
|
75
|
+
if message[:searcher].nil? || !@client.nicks.include?(message[:searcher])
|
76
|
+
Fargo.logger.info "Invalid search request: #{message.inspect}"
|
77
|
+
return
|
78
|
+
end
|
79
|
+
|
80
|
+
# Let the client handle the results
|
81
|
+
@results = @client.search_files message
|
82
|
+
|
83
|
+
# Send all the results to the peer. Take care of active/passive
|
84
|
+
# connections
|
85
|
+
@results.each { |r|
|
86
|
+
if message[:address]
|
87
|
+
r.active_send @client.config.nick, message[:ip], message[:port]
|
88
|
+
else
|
89
|
+
write "$SR #{@client.config.nick} #{r}"
|
90
|
+
end
|
91
|
+
}
|
92
|
+
|
93
|
+
when :revconnect
|
94
|
+
# TODO: Don't send RevConnectToMe when we're passive and
|
95
|
+
# receiving is passive
|
96
|
+
if @client.config.passive
|
97
|
+
write "$RevConnectToMe #{@client.config.nick} #{message[:who]}"
|
98
|
+
else
|
99
|
+
write "$ConnectToMe #{@client.config.nick} #{@client.config.address}:#{@client.config.extport}"
|
100
|
+
end
|
101
|
+
|
102
|
+
# proxy this message on up the stack if we don't handle it
|
103
|
+
else
|
104
|
+
@client.publish message[:type], message
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def disconnect
|
110
|
+
if @client_connections
|
111
|
+
@client_connections.each &:disconnect
|
112
|
+
@client_connections.clear
|
113
|
+
end
|
114
|
+
|
115
|
+
super
|
116
|
+
end
|
117
|
+
|
118
|
+
end # Hub
|
119
|
+
end # Connection
|
120
|
+
end # Fargo
|