mod_spox 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. data/CHANGELOG +31 -1
  2. data/LICENSE +674 -0
  3. data/README.rdoc +73 -0
  4. data/bin/mod_spox +28 -28
  5. data/data/mod_spox/extras/AOLSpeak.rb +2 -3
  6. data/data/mod_spox/extras/AutoKick.rb +10 -23
  7. data/data/mod_spox/extras/AutoMode.rb +12 -23
  8. data/data/mod_spox/extras/Bash.rb +55 -0
  9. data/data/mod_spox/extras/Bouncer.rb +85 -57
  10. data/data/mod_spox/extras/Bullshit.rb +1 -1
  11. data/data/mod_spox/extras/Bytes.rb +1 -2
  12. data/data/mod_spox/extras/Confess.rb +27 -29
  13. data/data/mod_spox/extras/DCC.rb +11 -20
  14. data/data/mod_spox/extras/DevWatch.rb +21 -23
  15. data/data/mod_spox/extras/DownForEveryoneOrJustMe.rb +47 -0
  16. data/data/mod_spox/extras/EightBall.rb +1 -1
  17. data/data/mod_spox/extras/FML.rb +35 -0
  18. data/data/mod_spox/extras/Headers.rb +31 -50
  19. data/data/mod_spox/extras/Karma.rb +81 -29
  20. data/data/mod_spox/extras/Logger.rb +2 -2
  21. data/data/mod_spox/extras/LolSpeak.rb +1 -2
  22. data/data/mod_spox/extras/PhpCli.rb +138 -8
  23. data/data/mod_spox/extras/PhpFuncLookup.rb +20 -23
  24. data/data/mod_spox/extras/Pinger.rb +1 -1
  25. data/data/mod_spox/extras/Quotes.rb +8 -10
  26. data/data/mod_spox/extras/RegexTracker.rb +2 -4
  27. data/data/mod_spox/extras/Roulette.rb +20 -27
  28. data/data/mod_spox/extras/RubyCli.rb +93 -0
  29. data/data/mod_spox/extras/Search.rb +17 -3
  30. data/data/mod_spox/extras/Seen.rb +150 -0
  31. data/data/mod_spox/extras/SlashdotHeadlineGenerator.rb +500 -0
  32. data/data/mod_spox/extras/Talk.rb +2 -4
  33. data/data/mod_spox/extras/Topten.rb +10 -12
  34. data/data/mod_spox/extras/TracTicket.rb +3 -5
  35. data/data/mod_spox/extras/Translate.rb +20 -22
  36. data/data/mod_spox/extras/Twitter.rb +118 -33
  37. data/data/mod_spox/extras/UrbanDictionary.rb +8 -17
  38. data/data/mod_spox/extras/Weather.rb +1 -2
  39. data/data/mod_spox/plugins/Authenticator.rb +93 -98
  40. data/data/mod_spox/plugins/Banner.rb +26 -56
  41. data/data/mod_spox/plugins/Helper.rb +5 -6
  42. data/data/mod_spox/plugins/Initializer.rb +4 -14
  43. data/data/mod_spox/plugins/Joiner.rb +1 -1
  44. data/data/mod_spox/plugins/Nicker.rb +13 -0
  45. data/data/mod_spox/plugins/Parter.rb +2 -2
  46. data/data/mod_spox/plugins/Permissions.rb +60 -0
  47. data/data/mod_spox/plugins/PluginLoader.rb +7 -12
  48. data/data/mod_spox/plugins/Ponger.rb +51 -0
  49. data/data/mod_spox/plugins/Quitter.rb +1 -2
  50. data/data/mod_spox/plugins/Servers.rb +57 -0
  51. data/data/mod_spox/plugins/Status.rb +3 -2
  52. data/data/mod_spox/plugins/Triggers.rb +9 -9
  53. data/lib/mod_spox/Bot.rb +109 -33
  54. data/lib/mod_spox/BotConfig.rb +2 -2
  55. data/lib/mod_spox/ConfigurationWizard.rb +12 -12
  56. data/lib/mod_spox/Database.rb +1 -4
  57. data/lib/mod_spox/Exceptions.rb +26 -0
  58. data/lib/mod_spox/Helpers.rb +29 -68
  59. data/lib/mod_spox/Loader.rb +23 -24
  60. data/lib/mod_spox/Logger.rb +19 -17
  61. data/lib/mod_spox/MessageFactory.rb +50 -24
  62. data/lib/mod_spox/Pipeline.rb +21 -7
  63. data/lib/mod_spox/Plugin.rb +27 -3
  64. data/lib/mod_spox/PluginManager.rb +28 -15
  65. data/lib/mod_spox/PriorityQueue.rb +69 -0
  66. data/lib/mod_spox/Socket.rb +93 -51
  67. data/lib/mod_spox/Sockets.rb +76 -63
  68. data/lib/mod_spox/Timer.rb +21 -141
  69. data/lib/mod_spox/Version.rb +14 -0
  70. data/lib/mod_spox/handlers/BadNick.rb +1 -1
  71. data/lib/mod_spox/handlers/Bounce.rb +5 -5
  72. data/lib/mod_spox/handlers/Created.rb +13 -5
  73. data/lib/mod_spox/handlers/Handler.rb +12 -3
  74. data/lib/mod_spox/handlers/Invite.rb +14 -8
  75. data/lib/mod_spox/handlers/Join.rb +24 -20
  76. data/lib/mod_spox/handlers/Kick.rb +22 -13
  77. data/lib/mod_spox/handlers/Mode.rb +42 -36
  78. data/lib/mod_spox/handlers/Motd.rb +4 -0
  79. data/lib/mod_spox/handlers/Names.rb +66 -39
  80. data/lib/mod_spox/handlers/Nick.rb +20 -14
  81. data/lib/mod_spox/handlers/Part.rb +25 -8
  82. data/lib/mod_spox/handlers/Ping.rb +11 -5
  83. data/lib/mod_spox/handlers/Pong.rb +9 -5
  84. data/lib/mod_spox/handlers/Privmsg.rb +25 -17
  85. data/lib/mod_spox/handlers/Quit.rb +13 -8
  86. data/lib/mod_spox/handlers/Topic.rb +4 -0
  87. data/lib/mod_spox/handlers/Welcome.rb +16 -24
  88. data/lib/mod_spox/handlers/Who.rb +64 -48
  89. data/lib/mod_spox/handlers/Whois.rb +92 -60
  90. data/lib/mod_spox/messages/incoming/Nick.rb +2 -2
  91. data/lib/mod_spox/messages/incoming/Privmsg.rb +1 -1
  92. data/lib/mod_spox/messages/incoming/Whois.rb +1 -0
  93. data/lib/mod_spox/messages/internal/EstablishConnection.rb +1 -1
  94. data/lib/mod_spox/messages/internal/PluginsReady.rb +10 -0
  95. data/lib/mod_spox/messages/internal/QueueSocket.rb +8 -0
  96. data/lib/mod_spox/messages/internal/Reconnect.rb +8 -0
  97. data/lib/mod_spox/messages/internal/UnqueueSocket.rb +8 -0
  98. data/lib/mod_spox/migrations/002_persistent_sigs.rb +14 -0
  99. data/lib/mod_spox/migrations/003_auth_restructure.rb +31 -0
  100. data/lib/mod_spox/migrations/004_mode_index_fix.rb +18 -0
  101. data/lib/mod_spox/migrations/005_nick_mode_nopark.rb +18 -0
  102. data/lib/mod_spox/models/Auth.rb +16 -46
  103. data/lib/mod_spox/models/AuthMask.rb +13 -0
  104. data/lib/mod_spox/models/Channel.rb +46 -27
  105. data/lib/mod_spox/models/Config.rb +10 -19
  106. data/lib/mod_spox/models/Group.rb +20 -8
  107. data/lib/mod_spox/models/Models.rb +1 -1
  108. data/lib/mod_spox/models/Nick.rb +105 -113
  109. data/lib/mod_spox/models/NickMode.rb +23 -9
  110. data/lib/mod_spox/models/Server.rb +12 -1
  111. data/lib/mod_spox/models/Setting.rb +12 -16
  112. data/lib/mod_spox/models/Signature.rb +28 -8
  113. data/tests/BotHolder.rb +24 -0
  114. data/tests/handlers/tc_BadNick.rb +21 -0
  115. data/tests/handlers/tc_Created.rb +24 -0
  116. data/tests/handlers/tc_Invite.rb +50 -0
  117. data/tests/handlers/tc_Join.rb +33 -0
  118. data/tests/handlers/tc_Kick.rb +32 -0
  119. data/tests/handlers/tc_Mode.rb +85 -0
  120. data/tests/handlers/tc_Names.rb +35 -0
  121. data/tests/handlers/tc_Nick.rb +55 -0
  122. data/tests/handlers/tc_Part.rb +44 -0
  123. data/tests/handlers/tc_Ping.rb +40 -0
  124. data/tests/handlers/tc_Pong.rb +28 -0
  125. data/tests/handlers/tc_Privmsg.rb +85 -0
  126. data/tests/handlers/tc_Quit.rb +40 -0
  127. data/tests/handlers/tc_Who.rb +50 -0
  128. data/tests/handlers/tc_Whois.rb +61 -0
  129. data/tests/models/tc_Auth.rb +34 -0
  130. data/tests/models/tc_Channel.rb +52 -0
  131. data/tests/models/tc_Config.rb +19 -0
  132. data/tests/models/tc_Nick.rb +142 -0
  133. data/tests/models/tc_NickMode.rb +40 -0
  134. data/tests/models/tc_Setting.rb +21 -0
  135. data/tests/models/tc_Signature.rb +14 -0
  136. data/tests/run_tests.rb +4 -0
  137. metadata +284 -212
  138. data/README +0 -36
  139. data/lib/mod_spox/Cache.rb +0 -57
  140. data/lib/mod_spox/Monitors.rb +0 -84
  141. data/lib/mod_spox/Pool.rb +0 -164
  142. data/lib/mod_spox/models/AuthGroup.rb +0 -16
  143. data/lib/mod_spox/models/ChannelMode.rb +0 -14
  144. data/lib/mod_spox/models/NickChannel.rb +0 -45
  145. data/lib/mod_spox/models/NickGroup.rb +0 -16
@@ -1,13 +1,13 @@
1
1
  ['mod_spox/models/Models.rb',
2
2
  'mod_spox/Logger',
3
- 'mod_spox/Pool',
4
3
  'mod_spox/Exceptions'].each{|f|require f}
5
4
  module ModSpox
6
5
 
7
6
  class Pipeline
8
7
 
9
8
  # Create a new Pipeline
10
- def initialize
9
+ def initialize(pool)
10
+ @pool = pool
11
11
  @hooks = Hash.new
12
12
  @plugins = Hash.new
13
13
  @admin = Models::Group.filter(:name => 'admin').first
@@ -93,6 +93,9 @@ module ModSpox
93
93
  def populate_signatures(m=nil)
94
94
  @populate_lock.synchronize do
95
95
  @signatures = {}
96
+ a = Models::Signature.filter(:enabled => false)
97
+ Logger.warn("Killing #{a.count} signatures")
98
+ a.destroy
96
99
  Models::Signature.all.each do |s|
97
100
  c = s.signature[0].chr.downcase
98
101
  if(c =~ /^[a-z]$/)
@@ -117,7 +120,7 @@ module ModSpox
117
120
  begin
118
121
  Logger.info("Pipeline is processing a message: #{message}")
119
122
  parse(message)
120
- type = message.class.to_s.gsub(/^(ModSpox::Messages::|#<.+?>::)/, '').gsub(/::/, '_').to_sym
123
+ type = message.class.to_s.gsub(/^(ModSpox::Messages::|#<.+?>::)/, '').gsub('::', '_').to_sym
121
124
  mod = type.to_s.gsub(/_.+$/, '').to_sym
122
125
  Logger.info("Pipeline determines that #{message} is of type: #{type}")
123
126
  [type, mod, :all].each do |type|
@@ -125,7 +128,18 @@ module ModSpox
125
128
  @hooks[type].each_value do |objects|
126
129
  begin
127
130
  objects.each do |v|
128
- Pool << lambda{ v[:object].send(v[:method].to_s, message) }
131
+ @pool.process do
132
+ begin
133
+ v[:object].send(v[:method].to_s, message)
134
+ rescue Object => boom
135
+ if(boom.class.to_s == 'SQLite3::BusyException')
136
+ Database.reset_connections
137
+ retry
138
+ else
139
+ raise boom
140
+ end
141
+ end
142
+ end
129
143
  end
130
144
  rescue Object => boom
131
145
  Logger.warn("Plugin threw exception while attempting to process message: #{boom}\n#{boom.backtrace.join("\n")}")
@@ -145,7 +159,7 @@ module ModSpox
145
159
  def parse(message)
146
160
  return unless message.kind_of?(Messages::Incoming::Privmsg) || message.kind_of?(Messages::Incoming::Notice)
147
161
  trigger = nil
148
- @triggers.each{|t| trigger = t if message.message =~ /^#{Regexp.escape(t)}/}
162
+ @triggers.each{|t| trigger = t if message.message[0..t.size-1] == t}
149
163
  if(!trigger.nil? || message.addressed?)
150
164
  return if !trigger.nil? && message.message.length == trigger.length
151
165
  Logger.info("Message has matched against a known trigger")
@@ -164,7 +178,7 @@ module ModSpox
164
178
  esc_trig = trigger.nil? ? '' : Regexp.escape(trigger)
165
179
  res = message.message.scan(/^#{esc_trig}#{sig.signature}$/)
166
180
  if(res.size > 0)
167
- next unless message.source.auth_groups.include?(sig.group) || message.source.auth_groups.include?(@admin) || sig.group.nil?
181
+ next unless message.source.in_group?(sig.group) || message.source.in_group?(@admin) || sig.group.nil?
168
182
  next if sig.requirement == 'private' && message.is_public?
169
183
  next if sig.requirement == 'public' && message.is_private?
170
184
  params = Hash.new
@@ -174,7 +188,7 @@ module ModSpox
174
188
  end
175
189
  if(@plugins.has_key?(sig.plugin.to_sym))
176
190
  begin
177
- Pool << lambda{ @plugins[sig.plugin.to_sym].send(sig.values[:method], message, params) }
191
+ @pool.process{ @plugins[sig.plugin.to_sym].send(sig.values[:method], message, params) }
178
192
  rescue Object => boom
179
193
  Logger.warn("Plugin threw exception while attempting to process message: #{boom}\n#{boom.backtrace.join("\n")}")
180
194
  end
@@ -58,15 +58,39 @@ module ModSpox
58
58
  end
59
59
  end
60
60
 
61
+ # Adds a new signature for the given plugin
62
+ # Required args: :sig and :method
63
+ # Optional args: :group, :req, :desc, and :params
61
64
  def add_sig(args)
62
65
  raise ModSpox::Exceptions::InvalidType.new('You must provide a hash for creating new signatures') unless args.is_a?(Hash)
63
- sig = Signature.find_or_create(:signature => args[:sig], :plugin => name, :method => args[:method].to_s)
66
+ args[:params] = nil unless args[:params]
67
+ sig = Signature.find_or_create(:signature => args[:sig], :plugin => name, :method => args[:method].to_s,
68
+ :params => args[:params], :group_id => args[:group].nil? ? nil : args[:group].pk)
64
69
  sig.description = args[:desc] if args.has_key?(:desc)
65
- sig.group_id = args[:group].pk if args.has_key?(:group)
66
70
  sig.requirement = args[:req] if args.has_key?(:req)
67
- sig.params = args[:params] if args.has_key?(:params)
68
71
  sig.save
69
72
  end
73
+
74
+ # to:: Where message is going
75
+ # message:: message
76
+ # Send an information message to target
77
+ def information(to, message)
78
+ reply to, "\2#{name} (info):\2 #{message}"
79
+ end
80
+
81
+ # to:: Where message is going
82
+ # message:: message
83
+ # Send an warning message to target
84
+ def warning(to, message)
85
+ reply to, "\2#{name} (warn):\2 #{message}"
86
+ end
87
+
88
+ # to:: Where message is going
89
+ # message:: message
90
+ # Send an error message to target
91
+ def error(to, message)
92
+ reply to, "\2#{name} (error):\2 #{message}"
93
+ end
70
94
 
71
95
  end
72
96
  end
@@ -30,18 +30,24 @@ module ModSpox
30
30
  # message:: Messages::Internal::PluginReload
31
31
  # Destroys and reinitializes plugins
32
32
  def reload_plugins(message=nil)
33
- @plugin_lock.synchronize do
34
- if(message.fresh && message.stale)
35
- do_unload(message.stale)
36
- FileUtils.remove_file(message.stale)
37
- FileUtils.copy(message.fresh, BotConfig[:userpluginpath])
38
- do_load(message.stale)
39
- Logger.info("Completed reload of plugin: #{message.stale}")
40
- else
41
- unload_plugins
42
- load_plugins
33
+ @pipeline << Messages::Internal::QueueSocket.new
34
+ begin
35
+ @plugin_lock.synchronize do
36
+ if(!message.nil? && (message.fresh && message.stale))
37
+ do_unload(message.stale)
38
+ FileUtils.remove_file(message.stale)
39
+ FileUtils.copy(message.fresh, BotConfig[:userpluginpath])
40
+ do_load(message.stale)
41
+ Logger.info("Completed reload of plugin: #{message.stale}")
42
+ else
43
+ unload_plugins
44
+ load_plugins
45
+ end
43
46
  end
44
- @pipeline << Messages::Internal::SignaturesUpdate.new
47
+ rescue Object => boom
48
+ Logger.error("PluginManager caught error on plugin reload: #{boom}")
49
+ ensure
50
+ @pipeline << Messages::Internal::UnqueueSocket.new
45
51
  end
46
52
  end
47
53
 
@@ -53,6 +59,7 @@ module ModSpox
53
59
  # message:: Messages::Internal::PluginLoadRequest
54
60
  # Loads a plugin
55
61
  def load_plugin(message)
62
+ @pipeline << Messages::Internal::QueueSocket.new
56
63
  begin
57
64
  path = !message.name ? "#{BotConfig[:userpluginpath]}/#{message.path.gsub(/^.+\//, '')}" : "#{BotConfig[:userpluginpath]}/#{message.name}"
58
65
  begin
@@ -66,13 +73,16 @@ module ModSpox
66
73
  rescue Object => boom
67
74
  Logger.warn("Failed to load plugin: #{message.path} Reason: #{boom}")
68
75
  @pipeline << Messages::Internal::PluginLoadResponse.new(message.requester, false)
76
+ ensure
77
+ @pipeline << Messages::Internal::SignaturesUpdate.new
78
+ @pipeline << Messages::Internal::UnqueueSocket.new
69
79
  end
70
- @pipeline << Messages::Internal::SignaturesUpdate.new
71
80
  end
72
81
 
73
82
  # message:: Messages::Internal::PluginUnloadRequest
74
83
  # Unloads a plugin
75
84
  def unload_plugin(message)
85
+ @pipeline << Messages::Internal::QueueSocket.new
76
86
  begin
77
87
  do_unload(message.path)
78
88
  unless(File.symlink?(message.path))
@@ -86,8 +96,10 @@ module ModSpox
86
96
  rescue Object => boom
87
97
  Logger.warn("Failed to unload plugin: #{message.path} Reason: #{boom}")
88
98
  @pipeline << Messages::Internal::PluginUnloadResponse.new(message.requester, false)
99
+ ensure
100
+ @pipeline << Messages::Internal::UnqueueSocket.new
101
+ @pipeline << Messages::Internal::SignaturesUpdate.new
89
102
  end
90
- @pipeline << Messages::Internal::SignaturesUpdate.new
91
103
  end
92
104
 
93
105
  # message:: Messages::Internal::PluginModuleRequest
@@ -116,7 +128,7 @@ module ModSpox
116
128
  # Loads and initializes plugins
117
129
  def load_plugins
118
130
  @pipeline << Messages::Internal::TimerClear.new
119
- Models::Signature.destroy_all
131
+ Models::Signature.set(:enabled => false)
120
132
  [BotConfig[:pluginpath], BotConfig[:userpluginpath]].each do |path|
121
133
  Dir.new(path).each do |file|
122
134
  if(file =~ /^[^\.].+\.rb$/)
@@ -129,11 +141,11 @@ module ModSpox
129
141
  end
130
142
  end
131
143
  @pipeline << Messages::Internal::SignaturesUpdate.new
144
+ @pipeline << Messages::Internal::PluginsReady.new
132
145
  end
133
146
 
134
147
  # Destroys plugins
135
148
  def unload_plugins
136
- Models::Signature.destroy_all
137
149
  @plugins.each_pair do |sym, holder|
138
150
  begin
139
151
  holder.plugin.destroy unless holder.plugin.nil?
@@ -162,6 +174,7 @@ module ModSpox
162
174
  @plugins[plugin.to_sym] = PluginHolder.new(klass.new({:pipeline => @pipeline, :plugin_module => @plugins_module}))
163
175
  end
164
176
  Logger.info("Properly initialized new plugin: #{plugin}")
177
+ Database.reset_connections
165
178
  end
166
179
  Logger.info("All plugins found at: #{path} have been loaded")
167
180
  rescue Object => boom
@@ -0,0 +1,69 @@
1
+ module ModSpox
2
+ # This class provides some simple logic for message output. It
3
+ # is basically a priority based queue with some round robin
4
+ # thrown in, just to keep things interesting. This queue provides
5
+ # protection on message output from extreme lag to one target when
6
+ # another target is expecting large quantities out output
7
+ # NOTE: Design help from the great Ryan "pizza_" Flynn
8
+ class PriorityQueue
9
+
10
+ # Create a priority queue
11
+ def initialize
12
+ @target_queues = {}
13
+ @queues = {:PRIORITY => Queue.new, :NEW => Queue.new, :NORMAL => Queue.new, :WHOCARES => Queue.new}
14
+ @lock = Mutex.new
15
+ end
16
+
17
+ # target:: message target (targets starting with * will be labelled WHOCARES
18
+ # message:: message to send
19
+ # This prioritizes output to help reduce lag when lots of output
20
+ # is being sent to another target. This will automatically decide
21
+ # how to queue the message based on the target
22
+ def priority_queue(target, message)
23
+ @lock.synchronize do
24
+ target.downcase!
25
+ @target_queues[target] = Queue.new unless @target_queues[target]
26
+ if(target[0].chr == '*')
27
+ @target_queues[target] << message
28
+ @queues[:WHOCARES] << @target_queues[target]
29
+ else
30
+ @target_queues[target] << message
31
+ if(@target_queues[target].size < 2)
32
+ @queues[:NEW] << @target_queues[target]
33
+ else
34
+ @queues[:NORMAL] << @target_queues[target]
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ # message:: message to send
41
+ # This will add messages to the PRIORITY queue which gets
42
+ # sent before all other messages.
43
+ def direct_queue(message)
44
+ @lock.synchronize do
45
+ @target_queues[:general] = Queue.new unless @target_queues[:general]
46
+ @target_queues[:general] << message
47
+ @queues[:PRIORITY] << @target_queues[:general]
48
+ end
49
+ end
50
+
51
+ # Returns the next message to send. This method decides what
52
+ # message to send based on the priority of the message. It
53
+ # will throw an Exceptions::EmptyQueue when there are no messages
54
+ # left.
55
+ def pop
56
+ m = nil
57
+ @lock.synchronize do
58
+ [:PRIORITY, :NEW, :NORMAL, :WHOCARES].each do |k|
59
+ unless(@queues[k].empty?)
60
+ m = @queues[k].pop.pop
61
+ break
62
+ end
63
+ end
64
+ end
65
+ raise Exceptions::EmptyQueue.new if m.nil?
66
+ return m
67
+ end
68
+ end
69
+ end
@@ -1,10 +1,10 @@
1
1
  ['iconv',
2
2
  'mod_spox/Logger',
3
- 'mod_spox/Pool',
4
3
  'mod_spox/Exceptions',
5
4
  'mod_spox/messages/Messages',
6
5
  'mod_spox/models/Models',
7
- 'mod_spox/Pipeline'].each{|f|require f}
6
+ 'mod_spox/Pipeline',
7
+ 'mod_spox/PriorityQueue'].each{|f|require f}
8
8
 
9
9
  module ModSpox
10
10
 
@@ -18,6 +18,7 @@ module ModSpox
18
18
  attr_reader :server
19
19
  attr_reader :port
20
20
  attr_reader :socket
21
+ attr_reader :connected_at
21
22
 
22
23
  # factory:: MessageFactory to parse messages
23
24
  # server:: Server to connect to
@@ -26,12 +27,13 @@ module ModSpox
26
27
  # burst_in:: Number of seconds allowed to burst
27
28
  # burst:: Number of lines allowed to be sent within the burst_in time limit
28
29
  # Create a new Socket
29
- def initialize(bot, server, port, delay=2, burst_in=2, burst=4)
30
+ def initialize(bot, server=nil, port=nil, delay=2, burst_in=2, burst=4)
31
+ @pool = bot.pool
30
32
  @factory = bot.factory
31
33
  @pipeline = bot.pipeline
32
34
  @dcc = bot.dcc_sockets
33
35
  @server = server
34
- @port = port.to_i
36
+ @port = port
35
37
  @sent = 0
36
38
  @received = 0
37
39
  @delay = delay.to_f > 0 ? delay.to_f : 2.0
@@ -41,19 +43,35 @@ module ModSpox
41
43
  @time_check = nil
42
44
  @check_burst = 0
43
45
  @pause = false
44
- @sendq = Queue.new
46
+ @sendq = PriorityQueue.new
45
47
  @lock = Mutex.new
46
48
  @ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
49
+ @connected_at = nil
50
+ @empty_lines = 0
51
+ @max_empty = 5
52
+ @servers = Array.new
53
+ @connect_locker = Mutex.new
47
54
  end
48
55
 
49
56
  # Connects to the IRC server
50
57
  def connect
51
- Logger.info("Establishing connection to #{@server}:#{@port}")
52
- @socket = TCPSocket.new(@server, @port)
53
- server = Models::Server.find_or_create(:host => @server, :port => @port)
54
- server.connected = true
55
- server.save
56
- Logger.info("Created new send queue: #{@sendq}")
58
+ return unless @connect_locker.try_lock
59
+ begin
60
+ populate_servers if @servers.empty?
61
+ s = @servers.pop
62
+ @server = s.host
63
+ @port = s.port.to_i
64
+ Logger.info("Establishing connection to #{@server}:#{@port}")
65
+ @socket = TCPSocket.new(@server, @port)
66
+ @socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true)
67
+ @empty_lines = 0
68
+ s.connected = true
69
+ s.save
70
+ @connected_at = Time.now
71
+ @pipeline << Messages::Internal::Connected.new(@server, @port)
72
+ ensure
73
+ @connect_locker.unlock
74
+ end
57
75
  end
58
76
 
59
77
  # new_delay:: Seconds to delay between bursts
@@ -87,65 +105,81 @@ module ModSpox
87
105
  # Sends a string to the IRC server
88
106
  def write(message)
89
107
  return if message.nil?
90
- @socket.puts(message + "\n")
91
- Logger.info("<< #{message}")
92
- @last_send = Time.new
93
- @sent += 1
94
- @check_burst += 1
95
- @time_check = Time.now.to_i if @time_check.nil?
108
+ begin
109
+ @socket.puts(message + "\n")
110
+ @socket.flush
111
+ Logger.info("<< #{message}")
112
+ @last_send = Time.new
113
+ @sent += 1
114
+ @check_burst += 1
115
+ @time_check = Time.now.to_i if @time_check.nil?
116
+ rescue Object => boom
117
+ Logger.warn("Failed to write message to server. #{boom}")
118
+ @pipeline << Messages::Internal::Disconnected.new
119
+ raise Exceptions::Disconnected.new
120
+ end
96
121
  end
97
-
98
- # Retrieves a string from the server
99
- def read
100
- tainted_message = @socket.gets
101
- if(tainted_message.nil? || @socket.closed?) # || message =~ /^ERROR/)
122
+
123
+ # string:: string to be processed
124
+ # Process a string
125
+ def process(string)
126
+ string.strip!
127
+ Logger.info(">> #{string}")
128
+ if(string[0,5] == 'ERROR')
102
129
  @pipeline << Messages::Internal::Disconnected.new
103
- shutdown
104
- server = Models::Server.find_or_create(:host => @server, :port => @port)
105
- server.connected = false
106
- server.save
107
- elsif(tainted_message.length > 0)
108
- message = @ic.iconv(tainted_message + ' ')[0..-2]
109
- message.strip!
110
- Logger.info(">> #{message}")
111
- @received += 1
112
- begin
113
- message.strip!
114
- rescue Object => boom
115
- #do nothing#
116
- ensure
117
- @factory << message
118
- end
130
+ raise Exceptions::Disconnected.new
119
131
  end
132
+ @received += 1
133
+ @factory << string
120
134
  end
121
135
 
122
136
  # message:: String to be sent to server
123
137
  # Queues a message up to be sent to the IRC server
124
138
  def <<(message)
125
- @sendq << message
126
- Pool << lambda{ processor }
139
+ @sendq.direct_queue(message)
140
+ @pool.process{ processor }
141
+ end
142
+
143
+ # target:: Target for outgoing message
144
+ # message:: Message to send
145
+ # This queues a message to be sent out of a prioritized
146
+ # queue. This allows for even message distribution rather
147
+ # than only on target at a time being flooded.
148
+ def prioritize_message(target, message)
149
+ @sendq.priority_queue(target, message)
150
+ @pool.process{ processor }
127
151
  end
128
152
 
129
153
  # Starts the thread for sending messages to the server
130
154
  def processor
131
- @lock.synchronize do
132
- write(@sendq.pop(true))
133
- if((Time.now.to_i - @time_check) > @burst_in)
134
- @time_check = nil
135
- @check_burst = 0
136
- elsif((Time.now.to_i - @time_check) <= @burst_in && @check_burst >= @burst)
137
- Logger.warn("Burst limit hit. Output paused for: #{@delay} seconds")
138
- sleep(@delay)
139
- @time_check = nil
140
- @check_burst = 0
155
+ return unless @lock.try_lock
156
+ did_write = false
157
+ begin
158
+ loop do
159
+ write(@sendq.pop)
160
+ did_write = true
161
+ if((Time.now.to_i - @time_check) > @burst_in)
162
+ @time_check = nil
163
+ @check_burst = 0
164
+ elsif((Time.now.to_i - @time_check) <= @burst_in && @check_burst >= @burst)
165
+ Logger.warn("Burst limit hit. Output paused for: #{@delay} seconds")
166
+ sleep(@delay)
167
+ @time_check = nil
168
+ @check_burst = 0
169
+ end
141
170
  end
171
+ rescue Exceptions::EmptyQueue => boom
172
+ Logger.info('Socket reached an empty queue.')
173
+ ensure
174
+ @lock.unlock
175
+ @pool.process{ processor } if did_write
142
176
  end
143
177
  end
144
178
 
145
179
  # restart:: Reconnect after closing connection
146
180
  # Closes connection to IRC server
147
181
  def shutdown(restart=false)
148
- @socket.close unless @socket.closed?
182
+ @socket.close unless @socket.nil? || @socket.closed?
149
183
  @kill = true
150
184
  server = Models::Server.find_or_create(:host => @server, :port => @port)
151
185
  server.connected = false
@@ -154,6 +188,14 @@ module ModSpox
154
188
  connect if restart
155
189
  end
156
190
 
191
+ private
192
+
193
+ def populate_servers
194
+ Models::Server.reverse_order(:priority).each{|s|
195
+ @servers << s
196
+ }
197
+ end
198
+
157
199
  end
158
200
 
159
201
  end