fargo 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,17 +15,17 @@ module Fargo
15
15
  end
16
16
 
17
17
  s << sprintf(" %d/%d\005%s (%s:%d)", @client.open_slots,
18
- @client.slots,
18
+ @client.config.upload_slots,
19
19
  @client.hub.hubname,
20
- @client.hub.config.ip,
21
- @client.hub.config.port)
20
+ @client.config.hub_address,
21
+ @client.config.hub_port)
22
22
  s << "\005#{@target}" if @client.config.passive
23
23
  end
24
24
 
25
25
  def active_send nick, ip, port
26
- socket = UDPSocket.new
27
- socket.send "$SR #{nick} #{to_s}", 0, ip, port
28
- socket.close
26
+ socket = EventMachine.open_datagram_socket '0.0.0.0', 0
27
+ socket.send_datagram "$SR #{nick} #{to_s}", ip, port
28
+ socket.close_connection_after_writing
29
29
  end
30
30
  end
31
31
  end
@@ -2,9 +2,9 @@ module Fargo
2
2
  module Supports
3
3
  module Chat
4
4
  extend ActiveSupport::Concern
5
-
5
+
6
6
  included do
7
- set_callback :setup, :after, :subscribe_to_chats
7
+ set_callback :initialization, :after, :initialize_chats
8
8
  end
9
9
 
10
10
  def messages
@@ -12,14 +12,16 @@ module Fargo
12
12
  end
13
13
 
14
14
  def messages_with nick
15
- @chats[nick] if @chats
15
+ @chats[nick]
16
16
  end
17
17
 
18
- def subscribe_to_chats
18
+ protected
19
+
20
+ def initialize_chats
19
21
  @public_chats = []
20
- @chats = Hash.new{ |h, k| h[k] = [] }
22
+ @chats = Hash.new{ |h, k| h[k] = [] }
21
23
 
22
- subscribe do |type, map|
24
+ channel.subscribe do |type, map|
23
25
  if type == :chat
24
26
  @public_chats << map
25
27
  elsif type == :privmsg
@@ -30,6 +32,7 @@ module Fargo
30
32
  end
31
33
  end
32
34
  end
35
+
33
36
  end
34
37
  end
35
- end
38
+ end
@@ -11,12 +11,15 @@ module Fargo
11
11
  end
12
12
  end
13
13
 
14
+ included do
15
+ set_callback :initialization, :after, :initialize_download_lists
16
+ end
17
+
14
18
  attr_reader :current_downloads, :finished_downloads, :queued_downloads,
15
- :failed_downloads, :open_download_slots, :trying, :timed_out,
16
- :download_slots
19
+ :failed_downloads, :trying, :timed_out
17
20
 
18
- included do
19
- set_callback :setup, :after, :initialize_queues
21
+ def has_download_slot?
22
+ @current_downloads.size + @trying.size < config.download_slots
20
23
  end
21
24
 
22
25
  def clear_failed_downloads
@@ -56,34 +59,45 @@ module Fargo
56
59
  download.percent = 0
57
60
  download.status = 'idle'
58
61
 
59
- # Append it to the queue of things to download. This will be processed
60
- # elsewhere
61
- @to_download << download
62
+ # Using the mutex can be expensive in start_download, defer this
63
+ if @timed_out.include? download.nick
64
+ download.status = 'timeout'
65
+ @failed_downloads[download.nick] << download
66
+ else
67
+ unless @queued_downloads[download.nick].include?(download) ||
68
+ @current_downloads[download.nick] == download
69
+ @queued_downloads[download.nick] << download
70
+
71
+ # This uses the lock and could be expensive, defer this for later
72
+ EventMachine.defer{ start_download }
73
+ end
74
+ end
75
+
62
76
  true
63
77
  end
64
78
 
65
79
  def retry_download nick, file
66
- dl = (@failed_downloads[nick] ||= []).detect{ |h| h.file == file }
80
+ dl = @failed_downloads[nick].detect{ |h| h.file == file }
67
81
 
68
- if dl.nil?
69
- Fargo.logger.warn "#{file} isn't a failed download for: #{nick}!"
70
- return
71
- end
82
+ return if dl.nil?
72
83
 
73
84
  @failed_downloads[nick].delete dl
74
- download dl.nick, dl.file, dl.tth, dl.size
85
+ download dl
75
86
  end
76
87
 
77
88
  def remove_download nick, file
78
89
  # We need to synchronize this access, so append these arguments to a
79
90
  # queue to be processed later
80
- @to_remove << [nick, file]
81
- true
91
+ EventMachine.defer do
92
+ @downloading_lock.synchronize {
93
+ @queued_downloads[nick].delete_if{ |h| h.file == file }
94
+ }
95
+ end
82
96
  end
83
97
 
84
98
  def lock_next_download! user, connection
85
99
  @downloading_lock.synchronize {
86
- return get_next_download_with_lock! user, connection
100
+ get_next_download_with_lock! user, connection
87
101
  }
88
102
  end
89
103
 
@@ -94,16 +108,17 @@ module Fargo
94
108
  @timed_out.delete nick
95
109
  downloads = @failed_downloads[nick].dup
96
110
  @failed_downloads[nick].clear
111
+ # Reschedule all the failed downloads again
97
112
  downloads.each{ |d| download nick, d.file, d.tth, d.size }
98
113
 
99
114
  true
100
115
  end
101
116
 
102
- private
117
+ protected
103
118
 
104
119
  # Finds the next queued up download and begins downloading it.
105
120
  def start_download
106
- return false if open_download_slots == 0 || @current_downloads.size + @trying.size > download_slots
121
+ return false unless has_download_slot?
107
122
 
108
123
  arr = nil
109
124
 
@@ -114,7 +129,7 @@ module Fargo
114
129
  !@current_downloads.has_key?(nick) &&
115
130
  !@trying.include?(nick) &&
116
131
  !@timed_out.include?(nick) &&
117
- (connection_for(nick) || has_slot?(nick))
132
+ (connection_for(nick) || nick_has_slot?(nick))
118
133
  }
119
134
 
120
135
  return false if arr.nil? || arr.size == 0
@@ -127,6 +142,7 @@ module Fargo
127
142
  if connection
128
143
  Fargo.logger.debug "Requesting previous connection downloads: #{arr[1].first}"
129
144
  download = get_next_download_with_lock! dl_nick, connection
145
+
130
146
  connection.download = download
131
147
  connection.begin_download!
132
148
  else
@@ -135,18 +151,14 @@ module Fargo
135
151
  connect_with dl_nick
136
152
  end
137
153
  }
138
-
139
- arr
140
154
  end
141
155
 
142
156
  # This method should only be called when synchronized by the mutex
143
157
  def get_next_download_with_lock! user, connection
144
- raise 'No open slots!' if @open_download_slots <= 0
158
+ raise 'No open slots!' unless has_download_slot?
145
159
  raise "Already downloading from #{user}!" if @current_downloads[user]
146
160
 
147
- if @queued_downloads[user].nil? || @queued_downloads[user].size == 0
148
- return nil
149
- end
161
+ return nil if @queued_downloads[user].size == 0
150
162
 
151
163
  download = @queued_downloads[user].shift
152
164
  @current_downloads[user] = download
@@ -154,26 +166,24 @@ module Fargo
154
166
 
155
167
  Fargo.logger.debug "#{self}: Locking download: #{download}"
156
168
 
157
- block = Proc.new{ |type, map|
158
- Fargo.logger.debug "#{connection}: received: #{type.inspect} - #{map.inspect}"
159
-
160
- if type == :download_progress
161
- download.percent = map[:percent]
162
- elsif type == :download_started
163
- download.status = 'downloading'
164
- elsif type == :download_finished
165
- connection.unsubscribe &block
166
- download.percent = 1
167
- download.status = 'finished'
168
- download_finished! user, false
169
- elsif type == :download_failed || type == :download_disconnected
170
- connection.unsubscribe &block
171
- download.status = 'failed'
172
- download_finished! user, true
169
+ subscribed_id = channel.subscribe do |type, map|
170
+ if map[:nick] == user
171
+ if type == :download_progress
172
+ download.percent = map[:percent]
173
+ elsif type == :download_started
174
+ download.status = 'downloading'
175
+ elsif type == :download_finished
176
+ channel.unsubscribe subscribed_id
177
+ download.percent = 1
178
+ download.status = 'finished'
179
+ download_finished! user, false
180
+ elsif type == :download_failed || type == :download_disconnected
181
+ channel.unsubscribe subscribed_id
182
+ download.status = 'failed'
183
+ download_finished! user, true
184
+ end
173
185
  end
174
- }
175
-
176
- connection.subscribe &block
186
+ end
177
187
 
178
188
  download
179
189
  end
@@ -182,104 +192,50 @@ module Fargo
182
192
  download = nil
183
193
  @downloading_lock.synchronize{
184
194
  download = @current_downloads.delete user
185
- @open_download_slots += 1
186
-
187
- # connection_for(user).disconnect if @queued_downloads[user].size == 0
188
195
  }
189
196
 
190
197
  if failed
191
- (@failed_downloads[user] ||= []) << download
198
+ @failed_downloads[user] << download
192
199
  else
193
- (@finished_downloads[user] ||= []) << download
200
+ @finished_downloads[user] << download
194
201
  end
195
202
 
196
- start_download # Start another download if possible
203
+ # Start another download if possible
204
+ EventMachine.defer{ start_download }
197
205
  end
198
206
 
199
207
  def connection_failed_with! nick
200
- @trying.delete nick
201
- @timed_out << nick
202
-
203
208
  @downloading_lock.synchronize {
209
+ @trying.delete nick
210
+ @timed_out << nick
211
+
204
212
  @queued_downloads[nick].each{ |d| d.status = 'timeout' }
205
- @failed_downloads[nick] ||= []
206
- @failed_downloads[nick] = @failed_downloads[nick] | @queued_downloads[nick]
207
- @queued_downloads[nick].clear
213
+ @failed_downloads[nick] |= @queued_downloads.delete(nick)
208
214
  }
209
215
 
210
- start_download # This one failed, try the next one
216
+ # This one failed, try the next one
217
+ EventMachine.defer{ start_download }
211
218
  end
212
219
 
213
- def initialize_queues
214
- @download_slots ||= 4
215
-
220
+ def initialize_download_lists
216
221
  FileUtils.mkdir_p config.download_dir, :mode => 0755
217
222
 
218
223
  @downloading_lock = Mutex.new
219
224
 
220
- # Don't use Hash.new{} because this can't be dumped by Marshal
221
- @queued_downloads = {}
225
+ @queued_downloads = Hash.new{ |h, k| h[k] = [] }
222
226
  @current_downloads = {}
223
- @failed_downloads = {}
224
- @finished_downloads = {}
227
+ @failed_downloads = Hash.new{ |h, k| h[k] = [] }
228
+ @finished_downloads = Hash.new{ |h, k| h[k] = [] }
225
229
  @trying = []
226
230
  @timed_out = []
227
231
 
228
- @open_download_slots = download_slots
229
-
230
- subscribe { |type, hash|
232
+ channel.subscribe do |type, hash|
231
233
  if type == :connection_timeout
232
234
  connection_failed_with! hash[:nick] if @trying.include?(hash[:nick])
233
- elsif type == :hub_disconnected
234
- exit_download_queue_threads
235
- elsif type == :hub_connection_opened
236
- start_download_queue_threads
237
235
  end
238
- }
239
- end
240
-
241
- def exit_download_queue_threads
242
- @download_starter_thread.exit
243
- @download_removal_thread.exit
244
- end
245
-
246
- # Both of these need access to the synchronization lock, so we use
247
- # separate threads to do these processes.
248
- def start_download_queue_threads
249
- @to_download = Queue.new
250
- @download_starter_thread = Thread.start {
251
- loop {
252
- download = @to_download.pop
253
-
254
- if @timed_out.include? download.nick
255
- download.status = 'timeout'
256
- (@failed_downloads[download.nick] ||= []) << download
257
- else
258
- @queued_downloads[download.nick] ||= []
259
-
260
- unless @queued_downloads[download.nick].include?(download) ||
261
- @current_downloads[download.nick] == download
262
- @queued_downloads[download.nick] << download
263
- start_download
264
- end
265
- end
266
- }
267
- }
268
-
269
- @to_remove = Queue.new
270
- @download_removal_thread = Thread.start {
271
- loop {
272
- user, file = @to_remove.pop
273
-
274
- @downloading_lock.synchronize {
275
- @queued_downloads[user] ||= []
276
- download = @queued_downloads[user].detect{ |h| h.file == file }
277
- @queued_downloads[user].delete download unless download.nil?
278
- }
279
- }
280
- }
236
+ end
281
237
  end
282
238
 
283
- end # Downloads
284
- end # Supports
285
- end # Fargo
239
+ end
240
+ end
241
+ end
@@ -6,31 +6,35 @@ module Fargo
6
6
  module FileList
7
7
  class Listing < Struct.new(:tth, :size, :name, :nick); end
8
8
 
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ set_callback :initialization, :after, :initialize_file_lists
13
+ end
14
+
9
15
  # Lazily load the file list for the nick. Subscribe to the client for the
10
16
  # event :file_list to get notified.
11
17
  def file_list nick
12
- @file_list ||= {}
13
- @getting_file_list ||= {}
14
-
15
18
  if @file_list.has_key?(nick)
16
19
  return parse_file_list(@file_list[nick], nick)
17
20
  elsif @getting_file_list[nick]
18
21
  return true
19
22
  end
20
23
 
21
- file_gotten = lambda{ |type, map|
24
+ subscription_id = channel.subscribe do |type, map|
22
25
  case type
23
26
  when :download_finished, :download_failed, :connection_timeout
24
27
  if map[:nick] == nick
25
28
  @file_list[nick] = map[:file]
26
- unsubscribe &file_gotten
27
- publish :file_list, :nick => nick, :list => @file_list[nick]
29
+
30
+ channel.unsubscribe subscription_id
31
+ channel << [:file_list,
32
+ {:nick => nick, :list => @file_list[nick]}]
33
+
28
34
  @getting_file_list.delete nick
29
35
  end
30
36
  end
31
- }
32
-
33
- subscribe &file_gotten
37
+ end
34
38
 
35
39
  @getting_file_list[nick] = true
36
40
  download nick, 'files.xml.bz2'
@@ -38,7 +42,6 @@ module Fargo
38
42
 
39
43
  # Wait for the results to arrive, timed out after some time
40
44
  def file_list! nick, timeout = 10
41
- @file_list ||= {}
42
45
  if @file_list.has_key?(nick)
43
46
  return parse_file_list(@file_list[nick], nick)
44
47
  end
@@ -91,6 +94,14 @@ module Fargo
91
94
 
92
95
  list
93
96
  end
97
+
98
+ protected
99
+
100
+ def initialize_file_lists
101
+ @file_list = {}
102
+ @getting_file_list = {}
103
+ end
104
+
94
105
  end
95
106
  end
96
107
  end
@@ -2,31 +2,38 @@ module Fargo
2
2
  module Supports
3
3
  module NickList
4
4
  extend ActiveSupport::Concern
5
-
5
+
6
6
  included do
7
- set_callback :setup, :after, :subscribe_to_nicks
7
+ set_callback :initialization, :after, :initialize_nick_lists
8
8
  end
9
-
9
+
10
10
  attr_accessor :nicks
11
-
11
+
12
+ def get_info nick
13
+ hub.send_message 'GetINFO', "#{nick} #{config.nick}"
14
+ end
15
+
16
+ def get_ip *nicks
17
+ hub.send_message 'UserIP', nicks.flatten.join('$$')
18
+ end
19
+
12
20
  def info nick
13
- return nil unless @nick_info
14
21
  if @nick_info.has_key?(nick) || !connected? || !@nicks.include?(nick)
15
- return @nick_info[nick]
22
+ return @nick_info[nick]
16
23
  end
17
24
 
18
25
  # If we're connected and we don't already have this user's info, ask the
19
26
  # server. We'll wait for 5 second to respond, otherwise we'll just
20
27
  # return nil and be done with it
21
- info_gotten = lambda{ |type, map|
22
- map[:type] == :myinfo && map[:nick].to_s == nick.to_s
28
+ info_gotten = lambda{ |type, map|
29
+ type == :myinfo && map[:nick].to_s == nick.to_s
23
30
  }
24
31
  timeout_response(5, info_gotten){ get_info nick }
25
32
 
26
33
  @nick_info[nick]
27
34
  end
28
-
29
- def has_slot? nick
35
+
36
+ def nick_has_slot? nick
30
37
  # This query must be up to date so remove any cached information we have
31
38
  # about the nick so we can get a fresh copy
32
39
  @nick_info.try :delete, nick
@@ -41,12 +48,14 @@ module Fargo
41
48
  Fargo.logger.debug "#{self} User: #{nick} has #{match[1]} open slots"
42
49
  match[1].to_i > 0
43
50
  end
44
-
45
- def subscribe_to_nicks
51
+
52
+ protected
53
+
54
+ def initialize_nick_lists
46
55
  @nicks = []
47
56
  @nick_info = Hash.new{ |h, k| h[k] = {} }
48
57
 
49
- subscribe do |type, map|
58
+ channel.subscribe do |type, map|
50
59
  case type
51
60
  when :hello
52
61
  @nicks << map[:who] unless @nicks.include? map[:who]
@@ -65,7 +74,7 @@ module Fargo
65
74
  end
66
75
  end
67
76
  end
68
-
77
+
69
78
  end
70
79
  end
71
80
  end
@@ -4,50 +4,55 @@ module Fargo
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
- set_callback :setup, :after, :setup_connection_cache
7
+ set_callback :initialization, :after, :initialize_connection_caches
8
8
  end
9
9
 
10
- def lock_connection_with! nick, connection
11
- @connection_cache[nick] = connection
12
- end
10
+ def connect_with nick
11
+ @connection_timeouts[nick] = EventMachine::Timer.new(10) do
12
+ @connection_timeouts.delete(nick)
13
+ channel.push [:connection_timeout, {:nick => nick}]
14
+ end
13
15
 
14
- def connection_for nick
15
- c = @connection_cache.try :[], nick
16
- if c.nil? || c.connected?
17
- Fargo.logger.debug "#{self} has connection with: #{nick}: #{c}"
18
- return c
16
+ if config.passive
17
+ hub.send_message 'RevConnectToMe', "#{self.config.nick} #{nick}"
18
+ else
19
+ hub.send_message 'ConnectToMe',
20
+ "#{nick} #{config.address}:#{config.active_port}"
19
21
  end
22
+ end
20
23
 
21
- # If it's present and not connected, remove it from the cache
22
- @connection_cache.try :delete, nick
23
- nil
24
+ def connection_for nick
25
+ @connection_cache[nick]
24
26
  end
25
27
 
26
28
  def connected_with? nick
27
- c = @connection_cache.try :[], nick
28
- c.connected? unless c.nil?
29
+ @connection_cache.has_key? nick
29
30
  end
30
31
 
31
32
  def disconnect_from nick
32
- c = @connection_cache.try :delete, nick
33
- c.disconnect unless c.nil?
33
+ c = @connection_cache.delete nick
34
+ c.try :close_connection_after_writing
34
35
  end
35
36
 
36
37
  def nicks_connected_with
37
- return [] if @connection_cache.nil?
38
-
39
- nicks = @connection_cache.keys
40
- nicks.reject{ |n| !connected_with? n }
38
+ @connection_cache.keys
41
39
  end
42
40
 
43
- def setup_connection_cache
41
+ protected
42
+
43
+ def initialize_connection_caches
44
44
  @connection_cache = {}
45
45
 
46
- subscribe { |type, hash|
46
+ channel.subscribe do |type, hash|
47
47
  if type == :hub_disconnected
48
48
  nicks_connected_with.each{ |n| disconnect_from n }
49
+ elsif type == :download_disconnected
50
+ @connection_cache.delete hash[:nick]
51
+ elsif type == :download_opened
52
+ @connection_timeouts.delete(hash[:nick]).try(:cancel)
53
+ @connection_cache[hash[:nick]] = hash[:connection]
49
54
  end
50
- }
55
+ end
51
56
  end
52
57
 
53
58
  end
@@ -2,9 +2,30 @@ module Fargo
2
2
  module Supports
3
3
  module Searches
4
4
  extend ActiveSupport::Concern
5
-
5
+
6
6
  included do
7
- set_callback :setup, :after, :subscribe_to_searches
7
+ set_callback :initialization, :after, :initialize_search_caches
8
+ end
9
+
10
+ # see parser#@@search for what's passed in
11
+ #
12
+ # searches this client's files based on those options and returns an array
13
+ # of SearchResult(s)
14
+ def search_files options
15
+ # TODO: implement me
16
+ []
17
+ end
18
+
19
+ def search_hub query
20
+ raise ConnectionError.new('Not connected Yet!') unless connected?
21
+
22
+ if config.passive
23
+ location = "Hub:#{config.nick}"
24
+ else
25
+ location = "#{config.address}:#{config.search_port}"
26
+ end
27
+
28
+ hub.send_message 'Search', "#{location} #{query.to_s}"
8
29
  end
9
30
 
10
31
  def search search
@@ -17,21 +38,21 @@ module Fargo
17
38
  end
18
39
 
19
40
  def searches
20
- @searches.keys.map { |k| @search_objects[k] } if @searches
41
+ @searches.keys.map { |k| @search_objects[k] }
21
42
  end
22
43
 
23
44
  def search_results search
24
45
  search = normalize search
25
- @searches[search.to_s] if @searches
46
+ @searches[search.to_s]
26
47
  end
27
48
 
28
49
  def remove_search search
29
50
  search = normalize search
30
- @searches.delete search.to_s if @searches
31
- @search_objects.delete search.to_s if @search_objects
51
+ @searches.delete search.to_s
52
+ @search_objects.delete search.to_s
32
53
  end
33
54
 
34
- private
55
+ protected
35
56
 
36
57
  def normalize search
37
58
  unless search.is_a? Fargo::Search
@@ -41,11 +62,11 @@ module Fargo
41
62
  search
42
63
  end
43
64
 
44
- def subscribe_to_searches
65
+ def initialize_search_caches
45
66
  @searches = {}
46
67
  @search_objects = {}
47
68
 
48
- subscribe do |type, map|
69
+ channel.subscribe do |type, map|
49
70
  if type == :search_result
50
71
  @searches.keys.each do |search|
51
72
  if @search_objects[search].matches_result?(map)