fargo 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/fargo/client.rb CHANGED
@@ -1,16 +1,15 @@
1
- require 'active_support/callbacks'
2
- require 'active_support/configurable'
1
+ require 'socket'
3
2
  require 'active_support/core_ext/object/try'
3
+ require 'active_support/callbacks'
4
4
 
5
5
  module Fargo
6
6
  class Client
7
7
 
8
- include ActiveSupport::Callbacks
9
8
  include ActiveSupport::Configurable
9
+ include ActiveSupport::Callbacks
10
10
 
11
- define_callbacks :setup
11
+ define_callbacks :initialization
12
12
 
13
- include Fargo::Publisher
14
13
  include Fargo::Supports::Chat
15
14
  include Fargo::Supports::Uploads
16
15
  include Fargo::Supports::NickList
@@ -20,141 +19,71 @@ module Fargo
20
19
  include Fargo::Supports::Timeout
21
20
  include Fargo::Supports::FileList
22
21
 
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
22
  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'
23
+ config.download_dir = '/tmp/fargo/downloads'
24
+ config.address = IPSocket.getaddress(Socket.gethostname)
25
+ config.passive = false
26
+ config.nick = 'fargo'
27
+ config.hub_address = '127.0.0.1'
28
+ config.hub_port = 7314
29
+ config.active_port = 7315
30
+ config.search_port = 7316
31
+ config.download_slots = 4
32
+ config.upload_slots = 4
33
+ config.password = ''
34
+ config.speed = 'DSL'
35
+ config.email = nil
43
36
  end
44
37
 
45
- attr_reader :hub, :searcher, :active_server
38
+ attr_reader :hub, :channel
46
39
 
47
40
  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
41
+ run_callbacks :initialization do
42
+ @channel = EventMachine::Channel.new
43
+ @connection_timeouts = {}
70
44
 
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}"
45
+ @channel.subscribe do |type, hash|
46
+ Fargo.logger.debug "Channel received: #{type} - #{hash.inspect}"
47
+ end
92
48
  end
93
49
  end
94
50
 
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
51
  def connect
101
- setup if hub.nil?
102
-
103
- # connect all our associated servers
104
- hub.connect
52
+ EventMachine.error_handler{ |e|
53
+ Fargo.logger.debug "Error raised during event loop: #{e.message}"
54
+ Fargo.logger.debug e.backtrace.join("\n")
55
+ }
56
+
57
+ EventMachine.connect config.hub_address, config.hub_port,
58
+ Fargo::Protocol::Hub do |conn|
59
+ @hub = conn
60
+ @hub.client = self
61
+ end
105
62
 
106
63
  unless config.passive
107
- searcher.connect
108
- active_server.connect
64
+ EventMachine.start_server '0.0.0.0', config.active_port,
65
+ Fargo::Protocol::Download do |conn|
66
+ conn.client = self
67
+ end
68
+
69
+ EventMachine.open_datagram_socket '0.0.0.0', config.search_port,
70
+ Fargo::Protocol::DC do |conn|
71
+ conn.client = self
72
+ end
109
73
  end
110
-
111
- true
112
74
  end
113
75
 
114
76
  def connected?
115
- hub.try :connected?
77
+ EventMachine.reactor_running?
116
78
  end
117
79
 
118
80
  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
- []
81
+ Fargo.logger.info 'Disconnecting from hub.'
82
+ EventMachine.stop_event_loop
147
83
  end
148
84
 
149
85
  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
86
+ "<fargo V:#{Fargo::VERSION},M:#{config.passive ? 'P' : 'A'},H:1/0/0,S:#{open_slots},Dt:1.2.6/W>"
158
87
  end
159
88
 
160
89
  end
@@ -0,0 +1,52 @@
1
+ module Fargo
2
+ module Protocol
3
+ module DC
4
+
5
+ include Fargo::Parser
6
+ attr_accessor :client
7
+
8
+ def post_init
9
+ @received_data = ''
10
+ end
11
+
12
+ def receive_message type, message
13
+ client.channel << [type, message] if client
14
+ end
15
+
16
+ def send_message method, args = nil
17
+ if args
18
+ data = "$#{method} #{args}|"
19
+ else
20
+ data = "$#{method}|"
21
+ end
22
+
23
+ Fargo.logger.debug "#{self} Sending: #{data.inspect}"
24
+ send_data data
25
+ end
26
+
27
+ def receive_data data
28
+ @received_data << data
29
+
30
+ while message = @received_data.slice!(/[^\|]+\|/)
31
+ message.chomp! '|'
32
+ Fargo.logger.debug "#{self}: Received: #{message.inspect}"
33
+ hash = parse_message message
34
+ receive_message hash[:type], hash
35
+ end
36
+ end
37
+
38
+ def publish_args
39
+ {}
40
+ end
41
+
42
+ def unbind
43
+ if client
44
+ connection_type = self.class.name.split('::').last.downcase
45
+ args = [:"#{connection_type}_disconnected", publish_args]
46
+ client.channel << args
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -1,44 +1,36 @@
1
1
  require 'zlib'
2
2
 
3
3
  module Fargo
4
- module Connection
5
- class Download < Base
4
+ module Protocol
5
+ class Download < EventMachine::Connection
6
6
 
7
7
  include Fargo::Utils
8
- include Fargo::Parser
8
+ include Fargo::Protocol::DC
9
9
 
10
- set_callback :listen, :before, :pre_listen
11
- set_callback :listen, :after do |connection|
12
- send_lock if connection.config.first
13
- end
10
+ attr_accessor :download, :client
14
11
 
15
- attr_accessor :download
12
+ def post_init
13
+ super
16
14
 
17
- def pre_listen
18
- Fargo.logger.debug "Initiating connection on: #{config.address}:#{config.port}"
15
+ set_comm_inactivity_timeout 20
19
16
 
20
- config.quit_on_disconnect = false
21
- @lock, @pk = generate_lock
17
+ @lock, @pk = generate_lock
22
18
  @handshake_step = 0
23
-
24
- @buffer_size = (2 << 12).freeze
25
19
  end
26
20
 
27
21
  def send_lock
28
- write "$MyNick #{@client.config.nick}|$Lock #{@lock} Pk=#{@pk}"
22
+ @lock_sent = true
23
+ send_message 'MyNick', @client.config.nick
24
+ send_message 'Lock', "#{@lock} Pk=#{@pk}"
29
25
  end
30
26
 
31
- def read_data
32
- # only download if we're at the correct time
27
+ def receive_data data
28
+ # only download if we're at the correct handshake step
33
29
  return super if @handshake_step != 6
34
30
 
35
- @exit_time = 20 # reset our timeout time
36
-
37
- data = @socket.readpartial @buffer_size
38
-
39
31
  if @zlib
40
- @zs = Zlib::Inflate.new if @zs.nil?
41
- data = @zs.inflate data
32
+ @inflator = Zlib::Inflate.new if @inflator.nil?
33
+ data = @inflator.inflate data
42
34
  end
43
35
 
44
36
  @file << data
@@ -51,33 +43,29 @@ module Fargo
51
43
  percent = @recvd.to_f / @length
52
44
  if percent - @last_published > 0.05
53
45
  @file.flush
54
- publish :download_progress, :percent => percent,
55
- :file => download_path,
56
- :nick => @other_nick,
57
- :download => @download,
58
- :size => @recvd,
59
- :compressed => @zlib
46
+ @client.channel << [:download_progress, {:percent => percent,
47
+ :file => download_path,
48
+ :nick => @other_nick,
49
+ :download => @download,
50
+ :size => @recvd,
51
+ :compressed => @zlib}]
60
52
 
61
53
  @last_published = percent
62
54
  end
63
55
 
64
56
  download_finished! if @recvd == @length
65
57
  end
66
- rescue IOError => e
67
- error "#{self}: IOError, disconnecting #{e}"
68
58
  end
69
59
 
70
- def receive data
71
- message = parse_message data
72
-
73
- case message[:type]
60
+ def receive_message type, message
61
+ case type
74
62
  when :mynick
75
63
  if @handshake_step == 0
76
64
  @handshake_step = 1
77
65
  @other_nick = message[:nick]
78
66
 
79
- @client.connected_with! @other_nick
80
- @client.lock_connection_with! @other_nick, self
67
+ client.channel << [:download_opened,
68
+ publish_args.merge(:connection => self)]
81
69
  @download = @client.lock_next_download! @other_nick, self
82
70
 
83
71
  if @download.try(:file).nil?
@@ -91,12 +79,12 @@ module Fargo
91
79
  if @handshake_step == 1
92
80
  @remote_lock = message[:lock]
93
81
  @handshake_step = 2
94
- send_lock unless config.first
95
- out = ''
96
- out << '$Supports TTHF ADCGet ZLIG|'
97
- out << "$Direction Download #{@my_num = rand(10000)}|"
98
- out << "$Key #{generate_key @remote_lock}|"
99
- write out
82
+
83
+ send_lock unless @lock_sent
84
+
85
+ send_message 'Supports', 'TTHF ADCGet ZLIG'
86
+ send_message 'Direction', "Download #{@my_num = rand(10000)}"
87
+ send_message 'Key', generate_key(@remote_lock)
100
88
  else
101
89
  error 'Premature disconnect when lock received'
102
90
  end
@@ -136,18 +124,18 @@ module Fargo
136
124
  @zlib = message[:zlib]
137
125
  @length = message[:size]
138
126
 
139
- write "$Send" unless @client_extensions.include? 'ADCGet'
127
+ send_message 'Send' unless @client_extensions.include? 'ADCGet'
140
128
 
141
- publish :download_started, :file => download_path,
142
- :download => @download,
143
- :nick => @other_nick
129
+ @client.channel << [:download_started, {:file => download_path,
130
+ :download => @download,
131
+ :nick => @other_nick}]
144
132
  else
145
133
  error "Premature disconnect when #{message[:type]} received"
146
134
  end
147
135
 
148
136
  when :noslots
149
137
  if @download
150
- Fargo.logger.debug "#{self}: No Slots for #{self[:download]}"
138
+ Fargo.logger.debug "#{self}: No Slots for #{@download}"
151
139
 
152
140
  download_failed! 'No Slots'
153
141
  end
@@ -158,7 +146,7 @@ module Fargo
158
146
 
159
147
  # This wasn't handled by us, proxy it on up to the client
160
148
  else
161
- @client.publish message[:type], message
149
+ super
162
150
 
163
151
  end
164
152
  end
@@ -167,8 +155,6 @@ module Fargo
167
155
  @file = File.open download_path, 'wb'
168
156
 
169
157
  @file.seek @download.offset
170
- @file.sync = true
171
- @socket.sync = true
172
158
  @handshake_step = 5
173
159
  @last_published = 0
174
160
 
@@ -194,24 +180,11 @@ module Fargo
194
180
  Fargo.logger.debug "Enabling zlib compression on: #{@download.file}"
195
181
  end
196
182
 
197
- write "$ADCGET file #{download_query} #{@download.offset} #{@download.size} #{zlig}"
183
+ send_message 'ADCGET', "file #{download_query} #{@download.offset} #{@download.size} #{zlig}"
198
184
  else
199
- write "$Get #{@download.file}$#{@download.offset + 1}"
185
+ send_message 'Get', "#{@download.file}$#{@download.offset + 1}"
200
186
  end
201
187
 
202
- # This is the thread for the timeout of a connection. The @exit_time
203
- # variable is reset to 20 after every bit of information is received.
204
- @exit_time = 20
205
- @exit_thread = Thread.start {
206
- while @exit_time > 0
207
- sleep 1
208
- @exit_time -= 1
209
- Fargo.logger.debug "#{self} time out in #{@exit_time} seconds"
210
- end
211
-
212
- download_failed! 'Download timeout!'
213
- }
214
-
215
188
  Fargo.logger.debug "#{self}: Beginning download of #{@download}"
216
189
  end
217
190
 
@@ -224,12 +197,10 @@ module Fargo
224
197
 
225
198
  reset_download
226
199
 
227
- publish :download_failed, opts.merge(:nick => @other_nick,
200
+ @client.channel << [:download_failed, opts.merge(:nick => @other_nick,
228
201
  :download => download,
229
202
  :file => path,
230
- :last_error => msg)
231
-
232
- @exit_thread = nil
203
+ :last_error => msg)]
233
204
  end
234
205
 
235
206
  def download_finished!
@@ -241,16 +212,21 @@ module Fargo
241
212
 
242
213
  reset_download
243
214
 
244
- publish :download_finished, :file => path, :download => download,
245
- :nick => @other_nick
246
- disconnect if download.file_list?
215
+ @client.channel << [:download_finished,
216
+ {:file => path, :download => download, :nick => @other_nick}]
217
+
218
+ close_connection_after_writing if download.file_list?
247
219
  end
248
220
 
249
- def disconnect
250
- Fargo.logger.debug "#{self} Disconnecting from: #{@other_nick}"
221
+ def publish_args
222
+ {:nick => @other_nick}
223
+ end
251
224
 
225
+ def unbind
252
226
  super
253
227
 
228
+ Fargo.logger.debug "#{self} Disconnected from: #{@other_nick}"
229
+
254
230
  if @download
255
231
  download_failed! @last_error, :recvd => @recvd, :length => @length
256
232
  end
@@ -259,25 +235,16 @@ module Fargo
259
235
  end
260
236
 
261
237
  private
238
+
262
239
  def reset_download
263
240
  @file.close unless @file.nil? || @file.closed?
241
+
264
242
  if @file_path && File.exists?(@file_path) && File.size(@file_path) == 0
265
243
  File.delete(@file_path)
266
244
  end
267
245
 
268
- if @socket
269
- @socket.sync = false
270
- @socket.flush
271
- end
272
-
273
- # If this was called from exit thread, don't kill it
274
- if @exit_thread != Thread.current
275
- @exit_thread.exit if @exit_thread && @exit_thread.alive?
276
- @exit_thread = nil
277
- end
278
-
279
246
  # clear out these variables
280
- @zs = @file_path = @zlib = @download = @length = @recvd = nil
247
+ @inflator = @file_path = @zlib = @download = @length = @recvd = nil
281
248
 
282
249
  # Go back to the get step
283
250
  @handshake_step = 5
@@ -305,7 +272,8 @@ module Fargo
305
272
 
306
273
  def error message
307
274
  Fargo.logger.warn @last_error = message
308
- disconnect
275
+
276
+ close_connection
309
277
  end
310
278
 
311
279
  end
@@ -0,0 +1,81 @@
1
+ module Fargo
2
+ module Protocol
3
+ class Hub < EventMachine::Connection
4
+
5
+ include Fargo::Protocol::DC
6
+ include Fargo::Utils
7
+
8
+ attr_reader :hubname
9
+
10
+ # See <http://www.teamfair.info/DC-Protocol.htm> for specifics on
11
+ # the DC protocol
12
+ def receive_message type, message
13
+ case type
14
+ when :lock
15
+ @validated = false
16
+ send_message 'Key', generate_key(message[:lock])
17
+ when :hubname
18
+ @hubname = message[:name]
19
+ send_message 'ValidateNick', @client.config.nick unless @validated
20
+ when :getpass
21
+ send_message 'MyPass', @client.config.password
22
+ when :badpass, :hubfull
23
+ Fargo.logger.warn "Disconnecting because of: #{message.inspect}"
24
+ close_connection_after_writing
25
+ when :hello
26
+ if message[:who] == @client.config.nick
27
+ Fargo.logger.info "Connected to DC Hub #{@hubname}"
28
+ @validated = true
29
+
30
+ send_message 'Version', '1,0091'
31
+ send_message 'MyINFO', "$ALL #{@client.config.nick} " +
32
+ "#{@client.description}$ $#{@client.config.speed}" +
33
+ "#{@status || 1.chr}$#{@client.config.email}" +
34
+ "$#{@client.share_size}$"
35
+ send_message 'GetNickList'
36
+ end
37
+
38
+ when :connect_to_me
39
+ if !@client.nicks.include?(message[:nick])
40
+ Fargo.logger.info "Invalid connect_to_me request from: #{message[:nick]}"
41
+ return
42
+ end
43
+
44
+ EventMachine.connect message[:address], message[:port],
45
+ Fargo::Protocol::Download do |conn|
46
+ conn.client = @client
47
+ conn.send_lock # We connect first, we send lock first
48
+ end
49
+
50
+ when :search
51
+ # Let the client handle the results
52
+ @results = @client.search_files message
53
+
54
+ # Send all the results to the peer. Take care of active/passive
55
+ # connections
56
+ @results.each { |r|
57
+ if message[:address]
58
+ r.active_send @client.config.nick, message[:ip], message[:port]
59
+ else
60
+ send_message 'SR', "#{@client.config.nick} #{r}"
61
+ end
62
+ }
63
+
64
+ when :revconnect
65
+ if @client.config.passive
66
+ send_message 'RevConnectToMe',
67
+ "#{@client.config.nick} #{message[:who]}"
68
+ else
69
+ send_message 'ConnectToMe', "#{@client.config.nick} #{@client.config.address}:#{@client.config.extport}"
70
+ end
71
+
72
+ # proxy this message on up the stack if we don't handle it
73
+ else
74
+ super
75
+
76
+ end
77
+ end
78
+
79
+ end
80
+ end
81
+ end
data/lib/fargo/search.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Fargo
2
2
  class Search
3
-
3
+
4
4
  ANY = 1
5
5
  AUDIO = 2
6
6
  COMPRESSED = 3
@@ -8,38 +8,43 @@ module Fargo
8
8
  EXECUTABLE = 5
9
9
  VIDEO = 7
10
10
  FOLDER = 8
11
-
11
+
12
12
  attr_accessor :size_restricted, :is_minimum_size, :size, :filetype, :pattern
13
-
13
+
14
14
  def initialize opts = {}
15
- self.size_restricted = opts[:size_restricted]
16
- self.is_minimum_size = opts[:is_minimum_size]
17
- self.size = opts[:size]
18
- self.filetype = opts[:filetype] || ANY
15
+ @size_restricted = opts[:size_restricted]
16
+ @is_minimum_size = opts[:is_minimum_size]
17
+ @size = opts[:size]
18
+ @filetype = opts[:filetype] || ANY
19
+
19
20
  if opts[:pattern]
20
- self.pattern = opts[:pattern]
21
+ @pattern = opts[:pattern]
21
22
  elsif opts[:query]
22
23
  self.query = opts[:query]
23
24
  end
24
25
  end
25
-
26
+
26
27
  def query= query
27
28
  @pattern = query.split(' ').join('$')
28
29
  end
29
-
30
+
30
31
  def queries
31
- pattern.split("$")
32
+ pattern.split('$')
32
33
  end
33
34
 
34
35
  def query
35
36
  pattern.gsub('$', ' ')
36
37
  end
37
-
38
+
38
39
  def matches_result? map
39
40
  file = map[:file].downcase
40
- matches_query = queries.inject(true) { |last, word| last && file.index(word.downcase) }
41
+
42
+ matches_query = queries.inject(true) do |last, word|
43
+ last && file.index(word.downcase)
44
+ end
45
+
41
46
  if size_restricted == 'T'
42
- if is_minimum_size
47
+ if is_minimum_size
43
48
  matches_query && map[:size] > size
44
49
  else
45
50
  matches_query && map[:size] < size
@@ -51,11 +56,11 @@ module Fargo
51
56
 
52
57
  def to_s
53
58
  if size_restricted
54
- "#{size_restricted ? 'T' : 'F' }?#{!size_restricted || is_minimum_size ? 'T' : 'F'}?#{size || 0}?#{filetype}?#{pattern}"
59
+ "#{size_restricted ? 'T' : 'F' }?#{!size_restricted || is_minimum_size ? 'T' : 'F'}?#{size || 0}?#{filetype}?#{pattern}"
55
60
  else
56
61
  "F?T?#{size || 0}?#{filetype}?#{pattern}"
57
62
  end
58
63
  end
59
-
64
+
60
65
  end
61
66
  end