mod_spox 0.2.0 → 0.3.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.
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