fargo 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/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
|