mod_spox 0.0.2 → 0.0.3

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 (40) hide show
  1. data/CHANGELOG +1 -1
  2. data/README +6 -2
  3. data/bin/mod_spox +15 -9
  4. data/data/mod_spox/extras/AOLSpeak.rb +271 -0
  5. data/data/mod_spox/extras/AutoKick.rb +119 -0
  6. data/data/mod_spox/extras/AutoRejoin.rb +14 -0
  7. data/data/mod_spox/extras/Bullshit.rb +19 -0
  8. data/data/mod_spox/extras/Confess.rb +166 -0
  9. data/data/mod_spox/extras/DevWatch.rb +154 -0
  10. data/data/mod_spox/extras/EightBall.rb +31 -0
  11. data/data/mod_spox/extras/Headers.rb +50 -0
  12. data/data/mod_spox/extras/Karma.rb +16 -17
  13. data/data/mod_spox/extras/LolSpeak.rb +22 -0
  14. data/data/mod_spox/extras/PhpCli.rb +146 -0
  15. data/data/mod_spox/extras/PhpFuncLookup.rb +261 -0
  16. data/data/mod_spox/extras/Quotes.rb +80 -0
  17. data/data/mod_spox/extras/Roulette.rb +24 -3
  18. data/data/mod_spox/extras/Talk.rb +41 -0
  19. data/data/mod_spox/extras/Translate.rb +95 -0
  20. data/data/mod_spox/extras/Weather.rb +55 -0
  21. data/data/mod_spox/plugins/Authenticator.rb +12 -0
  22. data/data/mod_spox/plugins/Banner.rb +3 -3
  23. data/data/mod_spox/plugins/Helper.rb +37 -0
  24. data/lib/mod_spox/Bot.rb +1 -1
  25. data/lib/mod_spox/ConfigurationWizard.rb +12 -5
  26. data/lib/mod_spox/MessageFactory.rb +1 -2
  27. data/lib/mod_spox/Monitors.rb +26 -8
  28. data/lib/mod_spox/Pipeline.rb +13 -13
  29. data/lib/mod_spox/PluginManager.rb +12 -1
  30. data/lib/mod_spox/Pool.rb +212 -29
  31. data/lib/mod_spox/Socket.rb +6 -6
  32. data/lib/mod_spox/Timer.rb +6 -8
  33. data/lib/mod_spox/handlers/Handler.rb +1 -1
  34. data/lib/mod_spox/handlers/Privmsg.rb +1 -0
  35. data/lib/mod_spox/handlers/Who.rb +1 -0
  36. data/lib/mod_spox/messages/internal/PluginRequest.rb +1 -1
  37. data/lib/mod_spox/messages/outgoing/Privmsg.rb +9 -1
  38. data/lib/mod_spox/models/Setting.rb +3 -3
  39. data/lib/mod_spox/models/Signature.rb +1 -1
  40. metadata +18 -20
@@ -0,0 +1,37 @@
1
+ class Helper < ModSpox::Plugin
2
+
3
+ include Models
4
+
5
+ def initialize(pipeline)
6
+ super(pipeline)
7
+ Signature.find_or_create(:signature => 'help', :plugin => name, :method => 'default_help',
8
+ :description => 'Display default help information')
9
+ Signature.find_or_create(:signature => 'help (\S+)', :plugin => name, :method => 'plugin_help',
10
+ :description => 'Display help information from given plugin').params = [:plugin]
11
+ end
12
+
13
+ def default_help(message, params)
14
+ plugins = Signature.map(:plugin)
15
+ plugins.uniq!
16
+ reply message.replyto, "Plugins currently available for help: #{plugins.join(', ')}"
17
+ reply message.replyto, "Request help on a plugin: !help Plugin"
18
+ end
19
+
20
+ def plugin_help(message, params)
21
+ sigs = Signature.filter(:plugin => params[:plugin])
22
+ if(sigs.count > 0)
23
+ reply message.source, "Available triggers for plugin: \2#{params[:plugin]}\2"
24
+ sigs.each do |sig|
25
+ output = []
26
+ output << "\2Pattern:\2 #{sig.signature}"
27
+ output << "\2Parameters:\2 [#{sig.params.join(' | ')}]" if sig.params
28
+ output << "\2Auth Group:\2 #{Group[sig.group_id].name}" if sig.group_id
29
+ output << "\2Description:\2 #{sig.description}" if sig.description
30
+ reply message.source, output.join(' ')
31
+ end
32
+ else
33
+ reply message.replyto, "\2Error:\2 No triggers found for plugin named: #{params[:plugin]}"
34
+ end
35
+ end
36
+
37
+ end
data/lib/mod_spox/Bot.rb CHANGED
@@ -272,7 +272,7 @@ module ModSpox
272
272
  target = message.target.name if message.target.is_a?(Models::Channel)
273
273
  target = message.target.nick if message.target.is_a?(Models::Nick)
274
274
  target = message.target unless target
275
- @socket << "PRIVMSG #{target} :#{message.message}"
275
+ @socket << "PRIVMSG #{target} :#{message.is_action? ? "\cAACTION #{message.message}\cA" : message.message}"
276
276
  end
277
277
 
278
278
  # message:: Messages::Outgoing::Notice message
@@ -1,4 +1,4 @@
1
- ['etc', 'termios', 'mod_spox/Loader',
1
+ ['etc', 'mod_spox/Loader',
2
2
  'mod_spox/BotConfig', 'mod_spox/BaseConfig'].each{|f|require f}
3
3
 
4
4
 
@@ -28,6 +28,12 @@ module ModSpox
28
28
  @config << {:id => :admin_password, :string => 'Administrator password: ', :regex => '.+', :default => nil, :value => nil, :echo => false}
29
29
  @config << {:id => :plugin_directory, :string => 'Plugin directory (bot must have write priviliges): ', :regex => '.+', :default => nil, :echo => true}
30
30
  @config << {:id => :trigger, :string => 'Trigger character for plugins: ', :regex => '.', :default => '!', :value => nil, :echo => true}
31
+ @stuck_visible = true
32
+ begin
33
+ require 'termios'
34
+ @stuck_visible = false
35
+ rescue Object => boom
36
+ end
31
37
  end
32
38
 
33
39
  # Run the configuration wizard
@@ -83,7 +89,7 @@ module ModSpox
83
89
  # echo:: echo user input
84
90
  # Turns echoing of user input on or off
85
91
  def input_echo(echo=true)
86
- return if echo == @echo
92
+ return if echo == @echo || @stuck_visible
87
93
  term = Termios::getattr($stdin)
88
94
  term.c_lflag |= Termios::ECHO if echo
89
95
  term.c_lflag &= ~Termios::ECHO unless echo
@@ -137,11 +143,12 @@ module ModSpox
137
143
  Database.db << "CREATE TABLE IF NOT EXISTS nick_modes (id integer primary key auto_increment not null, mode varchar(255) not null, nick_id int not null references nicks, channel_id int references channels, unique index nick_modes_nick_id_channel_id_index (nick_id, channel_id))"
138
144
  Database.db << "CREATE TABLE IF NOT EXISTS servers (id int not null auto_increment primary key, host varchar(255) not null, port int not null default 6667, priority int not null default 0, connected boolean not null default false, unique index servers_server_port_index (server, port))"
139
145
  Database.db << "CREATE TABLE IF NOT EXISTS settings (id int not null auto_increment primary key, name varchar(255) not null unique, value text)"
140
- Database.db << "CREATE TABLE IF NOT EXISTS signatures (id int not null auto_increment primary key, signature varchar(255) not null, params varchar(255), group_id int default null references groups, method varchar(255) not null, plugin varchar(255) not null, description text)"
146
+ Database.db << "CREATE TABLE IF NOT EXISTS signatures (id int not null auto_increment primary key, signature varchar(255) not null, params varchar(255), group_id int default null references groups, method varchar(255) not null, plugin varchar(255) not null, description text, requirement enum('public', 'private', 'both') default 'both' not null)"
141
147
  Database.db << "CREATE TABLE IF NOT EXISTS triggers (id int not null auto_increment primary key, `trigger` varchar(255) unique not null, active boolean not null default false)"
142
148
  Database.db << "CREATE TABLE IF NOT EXISTS groups (id int not null auto_increment primary key, name varchar(255) not null unique)"
143
149
  Database.db << "CREATE TABLE IF NOT EXISTS auth_groups (auth_id int not null references auths, group_id int not null references groups, primary key(auth_id, group_id))"
144
150
  when :pgsql
151
+ Database.db << "CREATE TYPE sig_requirement AS ENUM ('public', 'private', 'both')"
145
152
  Database.db << "CREATE TABLE nicks (id serial not null primary key, nick text unique not null, username text, real_name text, address text, source text, connected_at timestamp, connected_to text, seconds_idle integer, visible boolean not null default false, away boolean not null default false, botnick boolean not null default false)"
146
153
  Database.db << "CREATE TABLE channels (id serial not null primary key, name text unique not null, password text, autojoin boolean not null default false, topic text, quiet boolean not null default false, parked boolean not null default false)"
147
154
  Database.db << "CREATE TABLE auths (id serial not null primary key, password text, services boolean not null default false, mask text unique, authed boolean not null default false, nick_id integer unique references nicks)"
@@ -151,7 +158,7 @@ module ModSpox
151
158
  Database.db << "CREATE TABLE nick_channels (channel_id integer not null references channels, nick_id integer not null references nicks, primary key(nick_id, channel_id))"
152
159
  Database.db << "CREATE TABLE nick_modes (id serial not null primary key, mode text not null, nick_id integer not null references nicks, channel_id integer references channels, unique (nick_id, channel_id))"
153
160
  Database.db << "CREATE TABLE servers (id serial not null primary key, host text not null, port integer not null default 6667, priority integer not null default 0, connected boolean not null default false, unique (host, port))"
154
- Database.db << "CREATE TABLE signatures (id serial not null primary key, signature text not null, params text, group_id integer default null references groups, method text not null, plugin text not null, description text)"
161
+ Database.db << "CREATE TABLE signatures (id serial not null primary key, signature text not null, params text, group_id integer default null references groups, method text not null, plugin text not null, description text, requirement sig_requirement default 'both' not null)"
155
162
  Database.db << "CREATE TABLE settings (id serial not null primary key, name text unique not null, value text)"
156
163
  Database.db << "CREATE TABLE triggers (id serial not null primary key, trigger text unique not null, active boolean not null default false)"
157
164
  Database.db << "CREATE TABLE auth_groups (auth_id integer not null references auths, group_id integer not null references groups, primary key (auth_id, group_id))"
@@ -167,7 +174,7 @@ module ModSpox
167
174
  Database.db << "CREATE TABLE if not exists servers (id integer primary key autoincrement, host string NOT NULL, port integer NOT NULL DEFAULT 6667, priority integer NOT NULL DEFAULT 0, connected boolean NOT NULL DEFAULT 'f')"
168
175
  Database.db << "CREATE UNIQUE INDEX if not exists servers_host_port_index on servers (host, port)"
169
176
  Database.db << "CREATE TABLE if not exists settings (id integer PRIMARY KEY AUTOINCREMENT, name string UNIQUE NOT NULL, value text)"
170
- Database.db << "CREATE TABLE if not exists signatures (id integer PRIMARY KEY AUTOINCREMENT, signature string NOT NULL UNIQUE, params string, group_id integer DEFAULT NULL REFERENCES groups, method string NOT NULL, plugin string NOT NULL, description text)"
177
+ Database.db << "CREATE TABLE if not exists signatures (id integer PRIMARY KEY AUTOINCREMENT, signature string NOT NULL UNIQUE, params string, group_id integer DEFAULT NULL REFERENCES groups, method string NOT NULL, plugin string NOT NULL, description text, requirement text not null default 'both')"
171
178
  Database.db << "CREATE TABLE if not exists triggers (id integer PRIMARY KEY AUTOINCREMENT, trigger string UNIQUE NOT NULL, active boolean NOT NULL DEFAULT 'f')"
172
179
  Database.db << "CREATE TABLE if not exists groups (id integer PRIMARY KEY AUTOINCREMENT, name string UNIQUE NOT NULL COLLATE NOCASE)"
173
180
  Database.db << "CREATE TABLE if not exists auth_groups(auth_id integer REFERENCES auths, group_id integer REFERENCES groups)"
@@ -5,10 +5,9 @@ module ModSpox
5
5
  # pipeline:: Message pipeline
6
6
  # Create a new MessageFactory
7
7
  def initialize(pipeline)
8
- super(1)
8
+ super()
9
9
  @pipeline = pipeline
10
10
  @handlers = Hash.new
11
- @queue = Queue.new
12
11
  Logger.log("Created new factory queue: #{@queue}", 15)
13
12
  build_handlers
14
13
  start_pool
@@ -34,24 +34,42 @@ module ModSpox
34
34
 
35
35
  # Create a new Boolean Monitor
36
36
  def initialize
37
- @thread = nil
37
+ @threads = []
38
38
  end
39
39
 
40
40
  # Stop waiting
41
41
  def wakeup
42
- Logger.log("Sending wakeup for thread: #{@thread}", 5)
43
- @thread.run unless @thread == nil
44
- Logger.log("Status of thread to wakeup: #{@thread.status}", 5)
45
- Logger.log("Calling thread is: #{Thread.current}", 5)
46
- @thread = nil
42
+ return if @threads.empty?
43
+ @threads.each do |thread|
44
+ if(thread.status == 'sleep')
45
+ Logger.log("Sending wakeup for thread: #{thread}", 5)
46
+ thread.run
47
+ Logger.log("Status of thread to wakeup: #{thread.status}", 5)
48
+ Logger.log("Calling thread is: #{Thread.current}", 5)
49
+ else
50
+ Logger.log("Thread to wakeup has been killed: #{thread}")
51
+ end
52
+ @threads.delete(thread)
53
+ end
47
54
  end
48
55
 
49
56
  # Start waiting
50
57
  def wait
51
- @thread = Thread.current
52
- Logger.log("Stopping execution of thread: #{@thread}", 5)
58
+ @threads << Thread.current
59
+ Logger.log("Stopping execution of thread: #{Thread.current}", 5)
53
60
  Thread.stop
54
61
  end
62
+
63
+ # Returns if a thread is currently waiting in this monitor
64
+ def thread_waiting?(thread)
65
+ return @threads.include?(thread)
66
+ end
67
+
68
+ # Removes a thread from the list of waiting. Will be removed automatically
69
+ # if thread has been killed on next call to wakeup
70
+ def remove_thread(thread)
71
+ @threads.delete(thread) if @threads.include?(thread)
72
+ end
55
73
 
56
74
  end
57
75
  end
@@ -1,14 +1,12 @@
1
1
  module ModSpox
2
2
 
3
3
  class Pipeline < Pool
4
-
5
- # procs:: number of threads running in Pool
4
+
6
5
  # Create a new Pipeline
7
- def initialize(procs=3)
8
- super(procs)
6
+ def initialize
7
+ super()
9
8
  @timeout = 20 # Anything over 20 seconds and we assume a plugin locked up the thread
10
- @messageq = Queue.new
11
- Logger.log("Created queue #{@messageq} in pipeline", 10)
9
+ Logger.log("Created queue #{@queue} in pipeline", 10)
12
10
  @hooks = Hash.new
13
11
  @plugins = Hash.new
14
12
  @admin = Models::Group.filter(:name => 'admin').first
@@ -23,7 +21,7 @@ module ModSpox
23
21
  # Queues a message to send down pipeline
24
22
  def <<(message)
25
23
  Logger.log("Message added to pipeline queue: #{message}", 5)
26
- @messageq << message
24
+ @queue << message
27
25
  end
28
26
 
29
27
  # plugin:: Plugin to hook to pipeline
@@ -65,9 +63,9 @@ module ModSpox
65
63
  def unhook(object, method, type)
66
64
  Logger.log("Object #{object.class.to_s} unhooking from messages of type: #{type}", 10)
67
65
  type = type.gsub(/::/, '_').to_sym unless type.is_a?(Symbol)
68
- name = object.class.gsub(/^.+:/, '').to_sym
69
- raise Exception::InvalidValue.new("Unknown hook type given: #{type.to_s}") unless @hooks.has_key?(type)
70
- raise Exception::InvalidValue.new("Unknown object hooked: #{name.to_s}") unless @hooks[type].has_key?(name)
66
+ name = object.class.to_s.gsub(/^.+:/, '').to_sym
67
+ raise Exceptions::InvalidValue.new("Unknown hook type given: #{type.to_s}") unless @hooks.has_key?(type)
68
+ raise Exceptions::InvalidValue.new("Unknown object hooked: #{name.to_s}") unless @hooks[type].has_key?(name)
71
69
  @hooks[type][name].each{|hook|
72
70
  @hooks[type][name].delete(hook) if hook[:method] == method
73
71
  }
@@ -98,8 +96,8 @@ module ModSpox
98
96
 
99
97
  # Processes messages
100
98
  def processor
99
+ message = @queue.pop
101
100
  begin
102
- message = @messageq.pop
103
101
  Logger.log("Pipeline is processing a message: #{message}", 10)
104
102
  parse(message)
105
103
  type = message.class.to_s.gsub(/^ModSpox::Messages::/, '').gsub(/::/, '_').to_sym
@@ -140,10 +138,12 @@ module ModSpox
140
138
  res = message.message.scan(/^#{trigger}#{sig.signature}$/)
141
139
  if(res.size > 0)
142
140
  next unless message.source.auth_groups.include?(sig.group) || message.source.auth_groups.include?(@admin) ||sig.group.nil?
141
+ next if sig.requirement == 'private' && message.is_public?
142
+ next if sig.requirement == 'public' && message.is_private?
143
143
  params = Hash.new
144
144
  sig.params.size.times do |i|
145
- params[sig.params[i - 1].to_sym] = res[0][i]
146
- Logger.log("Signature params: #{sig.params[i - 1].to_sym} = #{res[0][i]}")
145
+ params[sig.params[i].to_sym] = res[0][i]
146
+ Logger.log("Signature params: #{sig.params[i].to_sym} = #{res[0][i]}")
147
147
  end
148
148
  if(@plugins.has_key?(sig.plugin.to_sym))
149
149
  begin
@@ -14,6 +14,7 @@ module ModSpox
14
14
  @pipeline.hook(self, :unload_plugin, :Internal_PluginUnloadRequest)
15
15
  @pipeline.hook(self, :reload_plugins, :Internal_PluginReload)
16
16
  @pipeline.hook(self, :send_modules, :Internal_PluginModuleRequest)
17
+ @pipeline.hook(self, :plugin_request, :Internal_PluginRequest)
17
18
  @plugins_module = Module.new
18
19
  load_plugins
19
20
  end
@@ -34,7 +35,6 @@ module ModSpox
34
35
  # Loads a plugin
35
36
  def load_plugin(message)
36
37
  begin
37
- Logger.log("THE MESSAGE NAME IS: #{message.name}")
38
38
  path = !message.name ? BotConfig[:userpluginpath] : "#{BotConfig[:userpluginpath]}/#{message.name}"
39
39
  FileUtils.copy(message.path, path)
40
40
  reload_plugins
@@ -69,6 +69,17 @@ module ModSpox
69
69
  @pipeline << Messages::Internal::PluginModuleResponse.new(message.requester, @plugins_module)
70
70
  end
71
71
 
72
+ # message:: Messages::Internal::PluginRequest
73
+ # Returns a plugin to requesting object
74
+ def plugin_request(message)
75
+ if(@plugins.has_key?(message.plugin))
76
+ response = Messages::Internal::PluginResponse.new(message.requester, @plugins[message.plugin])
77
+ else
78
+ response = Messages::Internal::PluginResponse.new(message.requester, nil)
79
+ end
80
+ @pipeline << response
81
+ end
82
+
72
83
  private
73
84
 
74
85
  # Loads and initializes plugins
data/lib/mod_spox/Pool.rb CHANGED
@@ -1,49 +1,232 @@
1
1
  module ModSpox
2
2
 
3
- # The Pool class is used to reduce thread creation. When
4
- # used in conjuntion with a PoolQueue, it provides an easy
5
- # way to process many objects in an asynchronous manner
3
+ # The Pool class is used to reduce thread creation. It provides
4
+ # an easy way to process many objects in an asynchronous manner.
6
5
  class Pool
7
6
 
8
- # num_procs:: Number of threads to use
7
+ #
8
+ def Pool.max_threads(num=nil)
9
+ if(num.nil?)
10
+ return @@max_threads
11
+ else
12
+ num = num.to_i
13
+ raise Exceptions::InvalidValue.new('Maximum threads setting must be a positive integer') if num < 1
14
+ @@max_threads = num
15
+ end
16
+ end
17
+
18
+ def Pool.max_thread_life(num=nil)
19
+ if(num.nil?)
20
+ return @@max_thread_life
21
+ else
22
+ num = num.to_i
23
+ raise Exceptions::InvalidValue.new('Maximum thread life setting must be a positive integer') if num < 1
24
+ @@max_thread_life = num
25
+ end
26
+ end
27
+
28
+ # Action thread is to perform
29
+ attr_reader :proc
30
+ # Storage space that the pool will be processing
31
+ attr_reader :queue
32
+
9
33
  # Create a new Pool
10
- def initialize(num_procs=2)
11
- @num_threads = num_procs
12
- @threads = Array.new
13
- @kill = false
34
+ def initialize
35
+ @proc = Proc.new{ run_processor }
36
+ @queue = PoolQueue.new
14
37
  end
15
38
 
16
- # Stop all the running threads
39
+ # Destroys this pool
17
40
  def destroy
18
- @kill = true
19
- @threads.each{|t|
20
- Logger.log("Shutting down thread: #{t} in #{self.class.to_s}", 10)
21
- t.exit
22
- }
23
- sleep(0.1)
41
+ Pool.remove_pool(self)
24
42
  end
25
43
 
26
44
  # Starts the pool
27
45
  def start_pool
28
- @num_threads.times do
29
- @threads << Thread.new{
30
- until @kill do
31
- processor
32
- end
33
- }
34
- end
46
+ Pool.add_pool(self)
35
47
  end
36
48
 
37
- # Method the pool uses to do stuff.
38
- # (It is important to note that using this can
39
- # very easily eat up all your CPU time. The Processor
40
- # method must yield at some point, otherwise it will
41
- # just continue to loop, even if it is doing nothing.
42
- # This is the reason for the PoolQueue as this Pool
43
- # was created as a way to quickly process messages)
49
+ private
50
+
51
+ def run_processor
52
+ begin
53
+ processor
54
+ rescue Object => boom
55
+ Logger.log("Pool encountered an error processing code block: #{boom}")
56
+ end
57
+ end
58
+
44
59
  def processor
45
60
  raise Exceptions::NotImplemented.new('Processor method has not been implemented')
46
61
  end
62
+
63
+ # Running pools
64
+ @@pools = Array.new
65
+ # Threads running in pool
66
+ @@threads = Hash.new
67
+ # Lock for thread safety
68
+ @@lock = Mutex.new
69
+ # Maximum number of threads in pool
70
+ @@max_threads = 10
71
+ # Maximum number of seconds a thread may spend waiting for an action
72
+ @@max_thread_life = 60
73
+ # Thread to tend to pool actions
74
+ @@pool_thread = nil
75
+ # Informs threads to halt
76
+ @@kill = false
77
+ # Place for threads to wait for actions
78
+ @@thread_stopper = Monitors::Boolean.new
79
+ # Timer for the Pool thread
80
+ @@watcher_timer = Monitors::Timer.new
81
+
82
+ # Adds a new thread to the pool
83
+ def Pool.add_thread
84
+ if(@@threads.size < @@max_threads)
85
+ thread = Thread.new do
86
+ sleep(0.01)
87
+ until(@@kill) do
88
+ Pool.schedule_thread
89
+ end
90
+ end
91
+ @@threads[thread] = Time.now
92
+ Logger.log("New thread added to pool: #{thread}")
93
+ else
94
+ raise Exceptions::BotException.new("Reached maximum thread pool size: #{@@max_threads} threads")
95
+ end
96
+ end
97
+
98
+ # thread:: Thread to delete
99
+ # Removes thread from pool
100
+ def Pool.delete_thread(thread)
101
+ @@lock.synchronize do
102
+ @@threads.delete(thread) if @@threads.has_key?(thread)
103
+ @@thread_stopper.remove_thread(thread)
104
+ thread.kill
105
+ Logger.log("Thread removed from thread pool: #{thread}")
106
+ end
107
+ end
108
+
109
+ # Returns the largest queue size of all available pools
110
+ def Pool.max_queue_size
111
+ size = 0
112
+ @@pools.each do |pool|
113
+ size = pool.queue.size if pool.queue.size > size
114
+ end
115
+ return size
116
+ end
117
+
118
+ # Schedules a thread within the pool
119
+ def Pool.schedule_thread
120
+ run_pool = nil
121
+ @@lock.synchronize do
122
+ @@pools.each do |pool|
123
+ run_pool = pool if (run_pool.nil? && pool.queue.size > 0) || (!run_pool.nil? && (run_pool.queue.size < pool.queue.size))
124
+ end
125
+ end
126
+ unless(run_pool.nil?)
127
+ @@threads[Thread.current] = Time.now
128
+ run_pool.proc.call
129
+ @@threads[Thread.current] = Time.now
130
+ else
131
+ @@thread_stopper.wait
132
+ end
133
+ end
134
+
135
+ # Starts the master thread for Pool maintanence
136
+ def Pool.start_watcher
137
+ @@pool_thread = Thread.new{
138
+ until(@@kill) do
139
+ sleep_time = 0
140
+ begin
141
+ waiters = Pool.waiting_threads
142
+ unless(waiters.empty?)
143
+ waiters.each do |thread|
144
+ time = Time.now.to_i - @@threads[thread].to_i
145
+ sleep_time = (@@max_thread_life - time).to_i if sleep_time = 0 || (@@max_thread_life - time).to_i < sleep_time
146
+ if(time > @@max_thread_life)
147
+ Pool.delete_thread(thread)
148
+ elsif(!['sleep', 'run'].include?(thread.status))
149
+ Pool.delete_thread(thread)
150
+ end
151
+ end
152
+ else
153
+ if(Pool.max_queue_size > 0)
154
+ Pool.add_thread
155
+ end
156
+ end
157
+ rescue Object => boom
158
+ Logger.log("Pool watcher caught an error (ignoring): #{boom}")
159
+ end
160
+ sleep_time = 1 if sleep_time == 0
161
+ sleep_time = nil if sleep_time < 0
162
+ Logger.log("Pool watcher thread is now sleeping for: #{sleep_time.nil? ? 'forever' : "#{sleep_time} seconds"}")
163
+ @@watcher_timer.wait(sleep_time)
164
+ end
165
+ } if @@pool_thread.nil?
166
+ end
167
+
168
+ # Forces sleeping threads to wake up
169
+ def Pool.wakeup_threads
170
+ @@watcher_timer.wakeup
171
+ @@thread_stopper.wakeup
172
+ end
173
+
174
+ # Returns list of threads currently waiting for an action to process
175
+ def Pool.waiting_threads
176
+ waiting = []
177
+ @@threads.each_pair do |thread,time|
178
+ waiting << thread if @@thread_stopper.thread_waiting?(thread)
179
+ end
180
+ return waiting
181
+ end
182
+
183
+ # Adds a Pool to the master Pool list
184
+ def Pool.add_pool(pool)
185
+ @@pools << pool
186
+ @@lock.synchronize do
187
+ Pool.start_watcher
188
+ end
189
+ end
190
+
191
+ # Removes a Pool from the master Pool list
192
+ def Pool.remove_pool(pool)
193
+ @@pools.delete(pool)
194
+ @@kill = true if @@pools.empty?
195
+ end
196
+
197
+ # Modified Queue to properly interact with Pool
198
+ class PoolQueue < Queue
199
+
200
+ def initialize
201
+ super
202
+ @lock = Mutex.new
203
+ end
204
+
205
+ def <<(val)
206
+ @lock.synchronize do
207
+ super
208
+ Pool.wakeup_threads
209
+ end
210
+ end
211
+
212
+ def push(val)
213
+ @lock.synchronize do
214
+ super
215
+ Pool.wakeup_threads
216
+ end
217
+ end
218
+
219
+ def pop
220
+ @lock.synchronize do
221
+ if(size > 0)
222
+ super
223
+ else
224
+ raise Exceptions::BotException.new("Queue is currently empty")
225
+ end
226
+ end
227
+ end
228
+
229
+ end
47
230
 
48
231
  end
49
232
 
@@ -39,6 +39,7 @@ module ModSpox
39
39
  def connect
40
40
  Logger.log("Establishing connection to #{@server}:#{@port}", 10)
41
41
  @socket = TCPSocket.new(@server, @port)
42
+ @socket.sync = true
42
43
  server = Models::Server.find_or_create(:host => @server, :port => @port)
43
44
  server.connected = true
44
45
  server.save
@@ -58,7 +59,7 @@ module ModSpox
58
59
  # new_burst:: Number of lines allowed in burst
59
60
  # Resets the burst
60
61
  def burst=(new_burst)
61
- raise Exceptions::InvalidValue('Burst value must be a positive number') unless new_busrt.to_i > 0
62
+ raise Exceptions::InvalidValue('Burst value must be a positive number') unless new_burst.to_i > 0
62
63
  @burst = new_burst
63
64
  end
64
65
 
@@ -84,7 +85,7 @@ module ModSpox
84
85
  @last_send = Time.new
85
86
  @sent += 1
86
87
  @check_burst += 1
87
- @check_time = Time.now if @check_time.nil?
88
+ @check_time = Time.now.to_i if @check_time.nil?
88
89
  end
89
90
 
90
91
  # Retrieves a string from the server
@@ -126,11 +127,11 @@ module ModSpox
126
127
  @writer_thread = Thread.new{
127
128
  until @kill do
128
129
  write(@sendq.pop)
129
- if((Time.now - @check_time) > @burst_in && @check_burst > @burst)
130
- sleep(@delay)
130
+ if((Time.now.to_i - @check_time) > @burst_in)
131
131
  @check_time = nil
132
132
  @check_burst = 0
133
- elsif((Time.now - @check_time) > @burst_in && @check_burst < @burst)
133
+ elsif((Time.now.to_i - @check_time) >= @burst_in && @check_burst >= @burst)
134
+ sleep(@delay)
134
135
  @check_time = nil
135
136
  @check_burst = 0
136
137
  end
@@ -145,7 +146,6 @@ module ModSpox
145
146
  Kernel.select([@socket], nil, nil, nil)
146
147
  @factory << read
147
148
  end
148
- @sendq.clear
149
149
  }
150
150
  end
151
151
 
@@ -3,14 +3,12 @@ module ModSpox
3
3
  class Timer < Pool
4
4
 
5
5
  # pipeline:: message pipeline
6
- # procs:: number of threads in the pool
7
6
  # Create a new Timer
8
- def initialize(pipeline, procs=2)
9
- super(procs)
7
+ def initialize(pipeline)
8
+ super()
10
9
  @pipeline = pipeline
11
10
  @timers = Array.new
12
- @actions = Queue.new
13
- Logger.log("Created queue: #{@actions} in timer", 10)
11
+ Logger.log("Created queue: #{@queue} in timer", 10)
14
12
  @monitor = Monitors::Timer.new
15
13
  @thread = nil
16
14
  @stop_timer = false
@@ -105,7 +103,7 @@ module ModSpox
105
103
 
106
104
  # Clears all actions in the timer's queue
107
105
  def clear
108
- @actions.clear
106
+ @queue.clear
109
107
  @timers.clear
110
108
  end
111
109
 
@@ -117,14 +115,14 @@ module ModSpox
117
115
  for action in @timers do
118
116
  action.tick(time_passed)
119
117
  if(action.due?)
120
- @actions << action.schedule
118
+ @queue << action.schedule
121
119
  end
122
120
  end
123
121
  end
124
122
 
125
123
  # Process the actions
126
124
  def processor
127
- action = @actions.pop
125
+ action = @queue.pop
128
126
  begin
129
127
  action.run
130
128
  remove(action) if action.is_complete?
@@ -10,7 +10,7 @@ module ModSpox
10
10
  protected
11
11
 
12
12
  def find_model(string)
13
- if(string =~ /^[A-Za-z\|\\\{\}\[\]\^\`~\_\-]+$/)
13
+ if(string =~ /^[A-Za-z\|\\\{\}\[\]\^\`~\_\-]+[A-Za-z0-9\|\\\{\}\[\]\^\`~\_\-]*$/)
14
14
  Logger.log("Model: #{string} -> Nick")
15
15
  return Models::Nick.find_or_create(:nick => string)
16
16
  elsif(string =~ /^[&#+!]/)
@@ -17,6 +17,7 @@ module ModSpox
17
17
  source.source = base_source
18
18
  source.save
19
19
  end
20
+ Models::NickChannel.find_or_create(:channel_id => target.pk, :nick_id => source.pk) if target.is_a?(ModSpox::Models::Channel)
20
21
  return Messages::Incoming::Privmsg.new(string, source, target, message)
21
22
  else
22
23
  Logger.log('Failed to match PRIVMSG message')
@@ -35,6 +35,7 @@ module ModSpox
35
35
  @raw_cache[location] << string
36
36
  if(location[0].chr !~ /[A-Za-z]/)
37
37
  channel = find_model(location)
38
+ Models::NickChannel.find_or_create(:channel_id => channel.pk, :nick_id => nick.pk)
38
39
  if(info.include?('+'))
39
40
  Models::NickMode.find_or_create(:channel_id => channel.pk, :nick_id => nick.pk, :mode => 'v')
40
41
  elsif(info.include?('@'))