fargo 0.1.1 → 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.
@@ -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)