syndi 0.0.1 → 0.1.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.
- checksums.yaml +4 -4
- data/.yardopts +12 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +8 -0
- data/INSTALL.md +86 -0
- data/LICENSE +28 -0
- data/README.md +104 -0
- data/Rakefile +26 -0
- data/WINDOWS.md +64 -0
- data/bin/syndi +102 -0
- data/bin/syndi-conf +47 -0
- data/conf/example.yml +101 -0
- data/docs/Events.md +103 -0
- data/docs/Upgrade.md +16 -0
- data/ext/csyndi/events.c +50 -0
- data/ext/csyndi/extconf.rb +20 -0
- data/ext/csyndi/integer.c +53 -0
- data/ext/csyndi/libauto.c +37 -0
- data/ext/csyndi/logger.c +228 -0
- data/include/syndi/csyndi.h +38 -0
- data/include/syndi/events.h +19 -0
- data/include/syndi/integer.h +17 -0
- data/include/syndi/logger.h +57 -0
- data/include/syndi.h +22 -0
- data/lib/syndi/actress.rb +12 -0
- data/lib/syndi/api/events.rb +170 -0
- data/lib/syndi/api/object.rb +29 -0
- data/lib/syndi/api/plugin.rb +155 -0
- data/lib/syndi/api.rb +7 -0
- data/lib/syndi/bot.rb +270 -0
- data/lib/syndi/config.rb +113 -0
- data/lib/syndi/configure/cli.rb +23 -0
- data/lib/syndi/configure/generator.rb +410 -0
- data/lib/syndi/configure.rb +19 -0
- data/lib/syndi/dsl/base.rb +74 -0
- data/lib/syndi/dsl/irc.rb +13 -0
- data/lib/syndi/events.rb +114 -0
- data/lib/syndi/irc/common.rb +63 -0
- data/lib/syndi/irc/library.rb +89 -0
- data/lib/syndi/irc/object/channel.rb +21 -0
- data/lib/syndi/irc/object/entity.rb +90 -0
- data/lib/syndi/irc/object/message.rb +99 -0
- data/lib/syndi/irc/object/user.rb +139 -0
- data/lib/syndi/irc/protocol/numerics.rb +60 -0
- data/lib/syndi/irc/protocol.rb +164 -0
- data/lib/syndi/irc/sasl/diffie_hellman.rb +36 -0
- data/lib/syndi/irc/sasl/mech/dh_blowfish.rb +83 -0
- data/lib/syndi/irc/sasl/mech/plain.rb +39 -0
- data/lib/syndi/irc/sasl/mech.rb +15 -0
- data/lib/syndi/irc/server.rb +301 -0
- data/lib/syndi/irc/state/channel_manager.rb +6 -0
- data/lib/syndi/irc/state/support.rb +142 -0
- data/lib/syndi/irc/state/user_manager.rb +6 -0
- data/lib/syndi/irc/std/commands.rb +99 -0
- data/lib/syndi/irc/std/numerics.rb +216 -0
- data/lib/syndi/irc.rb +8 -0
- data/lib/syndi/jewel/specification.rb +121 -0
- data/lib/syndi/jewel/util.rb +27 -0
- data/lib/syndi/jewel.rb +5 -0
- data/lib/syndi/rubyext/string.rb +10 -0
- data/lib/syndi/verbosity.rb +10 -0
- data/lib/syndi/version.rb +38 -0
- data/lib/syndi.rb +129 -0
- data/spec/helper.rb +32 -0
- data/spec/syndi/events_spec.rb +43 -0
- data/tasks/compile.rake +15 -0
- data/tasks/install.rake +10 -0
- data/tasks/package.rake +13 -0
- data/tasks/spec.rake +12 -0
- metadata +101 -13
@@ -0,0 +1,170 @@
|
|
1
|
+
# Copyright (c) 2013, Autumn Perrault, et al. All rights reserved.
|
2
|
+
# This free software is distributed under the FreeBSD license (see LICENSE).
|
3
|
+
|
4
|
+
require 'thread'
|
5
|
+
require 'syndi/verbosity'
|
6
|
+
require 'syndi/api/object'
|
7
|
+
|
8
|
+
# Entering namespace: Syndi
|
9
|
+
module Syndi
|
10
|
+
|
11
|
+
# Entering namespace: API
|
12
|
+
module API
|
13
|
+
|
14
|
+
# A class which provides the the fundamental event system, upon which
|
15
|
+
# much of the API is based, and which follows a simple model of broadcasting
|
16
|
+
# and hooking onto such broadcasts.
|
17
|
+
#
|
18
|
+
# Plugin writers may be rather interested in {Syndi::DSL::Base}, since that
|
19
|
+
# provides a simpler interface to Syndi's instances of this class.
|
20
|
+
#
|
21
|
+
# @api Syndi
|
22
|
+
# @since 4.0.0
|
23
|
+
# @author noxgirl
|
24
|
+
#
|
25
|
+
# @see Syndi::DSL::Base
|
26
|
+
#
|
27
|
+
# @!attribute [r] threads
|
28
|
+
# @return [Array] An array of threads used by {#call}.
|
29
|
+
class Events < Syndi::API::Object
|
30
|
+
|
31
|
+
attr_reader :events, :threads
|
32
|
+
|
33
|
+
# Create a new instance of Syndi::API::Events.
|
34
|
+
def initialize
|
35
|
+
@events = {}
|
36
|
+
@threads = []
|
37
|
+
end
|
38
|
+
|
39
|
+
# Listen for (hook onto) an event.
|
40
|
+
#
|
41
|
+
# @param [Symbol] event The name of the event for which to listen.
|
42
|
+
# @param [Integer] priority The priority of the event from 1-5, 1 being utmost priority.
|
43
|
+
#
|
44
|
+
# @yield [...] The arguments that will be yielded to the block vary by event.
|
45
|
+
# Please consult with the {file:docs/Events.md events specification} for details by event.
|
46
|
+
#
|
47
|
+
# @return [Array(Symbol, Integer, String)] Identification data including a unique string. Keep
|
48
|
+
# this if you need to destroy the hook later.
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# events.on :disconnect do |irc|
|
52
|
+
# puts "I'm dying!"
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# @see Syndi::DSL::Base#on
|
56
|
+
# @see file:docs/Events.md
|
57
|
+
def on(event, priority=3, &cb)
|
58
|
+
|
59
|
+
# Priority must be in the range of 1-5.
|
60
|
+
unless (1..5).include? priority
|
61
|
+
return 0
|
62
|
+
end
|
63
|
+
|
64
|
+
# If the event does not exist, create it.
|
65
|
+
@events[event] ||= {1 => {}, 2 => {}, 3 => {}, 4 => {}, 5 => {}}
|
66
|
+
|
67
|
+
# Generate a unique pseudorandom ID for this hook.
|
68
|
+
id = ''
|
69
|
+
10.times { id += get_rand_char }
|
70
|
+
while @events[event][priority].has_key? id
|
71
|
+
id = ''
|
72
|
+
10.times { id += get_rand_char }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Create the hook in memory.
|
76
|
+
@events[event][priority][id] = cb
|
77
|
+
|
78
|
+
[event, priority, id]
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
# Broadcast an event and associated arguments.
|
83
|
+
#
|
84
|
+
# The arguments are globbed into an array from the list passed to the
|
85
|
+
# method, so be sure to format your call correctly.
|
86
|
+
#
|
87
|
+
# If a hook returns +false+, all subsequent hook executions will be
|
88
|
+
# forestalled from occurring.
|
89
|
+
#
|
90
|
+
# @param [Symbol] event The event being broadcasted.
|
91
|
+
# @param [Array] args A list of arguments which should be passed to
|
92
|
+
# the listeners. (splat)
|
93
|
+
#
|
94
|
+
# @example
|
95
|
+
# events.call(:cow_moo, "the cows", "go moo", [1, 3, 5])
|
96
|
+
#
|
97
|
+
# @see Syndi::DSL::Base#emit
|
98
|
+
def call(event, *args)
|
99
|
+
# Check if any hooks exist for this event.
|
100
|
+
if @events.include? event
|
101
|
+
|
102
|
+
$m.verbose("A thread is spawning for an event broadcast (:#{event}).", VNOISY) do
|
103
|
+
@threads << Thread.new(event) do |evnt|
|
104
|
+
|
105
|
+
status = nil
|
106
|
+
|
107
|
+
begin # catch exceptions
|
108
|
+
# Iterate through the hooks.
|
109
|
+
@events[evnt].each_key do |priority|
|
110
|
+
@events[evnt][priority].each_value do |prc|
|
111
|
+
status = prc.call(*args) unless status == false
|
112
|
+
end # each hook
|
113
|
+
end # each priority
|
114
|
+
rescue => e
|
115
|
+
$m.error "An exception occurred within the thread of :#{event}: #{e}", false, e.backtrace
|
116
|
+
end # begin
|
117
|
+
|
118
|
+
end # thread
|
119
|
+
end # verbose
|
120
|
+
|
121
|
+
end # whether this event exists
|
122
|
+
end
|
123
|
+
|
124
|
+
# Delete a hook or listener.
|
125
|
+
#
|
126
|
+
# @param [Array(Symbol, Integer, String)] id The identification data of the hook,
|
127
|
+
# as provided by #on.
|
128
|
+
#
|
129
|
+
# @see Syndi::DSL::Base#undo_on
|
130
|
+
def del(id)
|
131
|
+
event, priority, hook = id
|
132
|
+
|
133
|
+
if @events.has_key? event
|
134
|
+
if @events[event][priority].has_key? hook
|
135
|
+
@events[event][priority].delete(hook)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
tidy
|
140
|
+
end
|
141
|
+
|
142
|
+
# Terminate all active threads.
|
143
|
+
def die
|
144
|
+
@threads.each { |thr| thr.kill }
|
145
|
+
end
|
146
|
+
|
147
|
+
#######
|
148
|
+
private
|
149
|
+
#######
|
150
|
+
|
151
|
+
# Tidy up.
|
152
|
+
def tidy
|
153
|
+
@events.each do |name, lists|
|
154
|
+
empty = true
|
155
|
+
empty = lists.each_value { |v| break false if not v.empty? }
|
156
|
+
if empty
|
157
|
+
# Drop the event.
|
158
|
+
@events.delete name
|
159
|
+
next
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
end # class Events
|
165
|
+
|
166
|
+
end # module API
|
167
|
+
|
168
|
+
end # module Syndi
|
169
|
+
|
170
|
+
# vim: set ts=4 sts=2 sw=2 et:
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Copyright (c) 2013, Autumn Perrault, et al. All rights reserved.
|
2
|
+
# This free software is distributed under the FreeBSD license (see LICENSE).
|
3
|
+
|
4
|
+
# Namespace: Syndi
|
5
|
+
module Syndi
|
6
|
+
|
7
|
+
# Namespace: API
|
8
|
+
module API
|
9
|
+
|
10
|
+
# A superclass for {Syndi::API::Timers} and {Syndi::API::Events}.
|
11
|
+
class Object
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# Get a random character.
|
16
|
+
#
|
17
|
+
# @return [String] A random character.
|
18
|
+
def get_rand_char
|
19
|
+
chrs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".split(//)
|
20
|
+
chrs[rand(chrs.length)]
|
21
|
+
end
|
22
|
+
|
23
|
+
end # class Object
|
24
|
+
|
25
|
+
end # module API
|
26
|
+
|
27
|
+
end # module Syndi
|
28
|
+
|
29
|
+
# vim: set ts=4 sts=2 sw=2 et:
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# Copyright (c) 2013, Autumn Perrault, et al. All rights reserved.
|
2
|
+
# This free software is distributed under the FreeBSD license (see LICENSE).
|
3
|
+
|
4
|
+
require 'ostruct'
|
5
|
+
require 'libsyndi'
|
6
|
+
require 'syndi/bot'
|
7
|
+
|
8
|
+
module Syndi
|
9
|
+
|
10
|
+
module API
|
11
|
+
|
12
|
+
# A basic superclass for plugins.
|
13
|
+
#
|
14
|
+
# @api Syndi
|
15
|
+
# @since 4.0.0
|
16
|
+
# @author noxgirl
|
17
|
+
# @author swarley
|
18
|
+
#
|
19
|
+
# @!attribute [r] name
|
20
|
+
# @return [String] Name of the plugin.
|
21
|
+
#
|
22
|
+
# @!attribute [r] summary
|
23
|
+
# @return [String] Summary of the plugin.
|
24
|
+
#
|
25
|
+
# @!attribute [r] version
|
26
|
+
# @return [String] Version of the plugin.
|
27
|
+
#
|
28
|
+
# @!attribute [r] library
|
29
|
+
# @return [String] The library upon which the plugin is based.
|
30
|
+
#
|
31
|
+
# @!attribute [r] author
|
32
|
+
# @return [String] Author of the plugin.
|
33
|
+
#
|
34
|
+
# @!attribute [r] syndi
|
35
|
+
# @return [String] Version of Syndi required by the plugin.
|
36
|
+
class Plugin
|
37
|
+
|
38
|
+
attr_reader :name, :summary, :version, :library, :author, :syndi
|
39
|
+
|
40
|
+
# Configure the plugin.
|
41
|
+
#
|
42
|
+
# @yieldparam [OpenStruct] conf Configuration structure.
|
43
|
+
#
|
44
|
+
# - +conf.name+: Name of the plugin (String).
|
45
|
+
# - +conf.summary+: Summary of the plugin (String).
|
46
|
+
# - +conf.version+: Version of the plugin (String).
|
47
|
+
# - +conf.library+: Library upon which the plugin is based. (String).
|
48
|
+
# - +conf.author+: Author of the plugin (String).
|
49
|
+
# - +conf.syndi+: Required version of Syndi (String). Should be in the format of
|
50
|
+
# +'~> version'+ or +'>= version'+. +~>+ means at least +version+ but no later
|
51
|
+
# than the minor version (e.g. +'~> 4.0'+ will allow +4.0.2+ but not +4.1.0+).
|
52
|
+
# +>=+ means at at least +version+.
|
53
|
+
#
|
54
|
+
# Additionally, it should be noted that +conf.library+ should be either of the
|
55
|
+
# core libraries, *or* 'multi' if it uses multiple libraries.
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# configure do |c|
|
59
|
+
# c.name = 'MagicPlugin'
|
60
|
+
# c.summary = 'A magical extension.'
|
61
|
+
# c.version = '1.00'
|
62
|
+
# c.library = 'irc'
|
63
|
+
# c.author = 'noxgirl'
|
64
|
+
# c.syndi = '~> 4.0'
|
65
|
+
# end
|
66
|
+
def configure
|
67
|
+
|
68
|
+
# Prepare an open structure.
|
69
|
+
conf = OpenStruct.new
|
70
|
+
|
71
|
+
# Yield it to the configuration block.
|
72
|
+
yield conf if block_given?
|
73
|
+
|
74
|
+
# Check for sufficient configuration.
|
75
|
+
[:name,:summary,:version,:library,:author,:syndi].each do |s|
|
76
|
+
if conf.send(s).nil?
|
77
|
+
raise PluginError, "Plugin #{self.inspect} provided insufficient configuration (#{s} is nil)."
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
@name = conf.name
|
82
|
+
@summary = conf.summary
|
83
|
+
@version = conf.version
|
84
|
+
@library = conf.library
|
85
|
+
@author = conf.author
|
86
|
+
|
87
|
+
# Check for compatibility.
|
88
|
+
if conf.syndi =~ /^(~>\s*)((?:[\dabcr])(?:\.[\dabcr]))+$/ # ~>
|
89
|
+
|
90
|
+
ver = $2
|
91
|
+
if Gem::Version.new(ver.dup) >= Gem::Version.new(Syndi::VERSION.dup) # must be later than or equal to current
|
92
|
+
|
93
|
+
# Split current version and plugin-demanded version by '.'.
|
94
|
+
verarr = Syndi::VERSION.split(/\./)
|
95
|
+
pverarr = ver.split(/\./)
|
96
|
+
|
97
|
+
# Must be no later than the current minor version
|
98
|
+
unless verarr[1] <= pverarr[1]
|
99
|
+
raise PluginError, "Plugin #@name v#@version demands Syndi #{conf.syndi}; current version is #{Syndi::VERSION}. Incompatible! Aborting load!"
|
100
|
+
end
|
101
|
+
|
102
|
+
@syndi = conf.syndi
|
103
|
+
|
104
|
+
else
|
105
|
+
raise PluginError, "Plugin #@name v#@version demands Syndi #{conf.syndi}; current version is #{Syndi::VERSION}. Incompatible! Aborting load!"
|
106
|
+
end # if ver >=
|
107
|
+
|
108
|
+
elsif conf.syndi =~ /^(>=\s*)((?:[\dabcr])(?:\.[\dabcr]))+$/ # >=
|
109
|
+
|
110
|
+
ver = $2
|
111
|
+
unless ver >= Syndi::VERSION # must be later than or equal to current
|
112
|
+
raise PluginError, "Plugin #@name v#@version demands Syndi #{conf.syndi}; current version is #{Syndi::VERSION}. Incompatible! Aborting load!"
|
113
|
+
end
|
114
|
+
|
115
|
+
@syndi = conf.syndi
|
116
|
+
|
117
|
+
else
|
118
|
+
raise PluginError, "Plugin #@name v#@version cannot be checked for compatibility. Aborting load!"
|
119
|
+
end # compat check
|
120
|
+
|
121
|
+
# If we've made it this far, it's sufficiently compatible with the API.
|
122
|
+
# Now, we need to extend this plugin with our domain-specific language (DSL).
|
123
|
+
self.extend Syndi::DSL::Base # this is the base
|
124
|
+
case @library
|
125
|
+
|
126
|
+
when 'irc'
|
127
|
+
self.extend Syndi::DSL::IRC
|
128
|
+
when 'multi'
|
129
|
+
# the specifications for multilib DSL functionality are yet undecided
|
130
|
+
else
|
131
|
+
# If it's a library we don't comprehend, exception time, it is.
|
132
|
+
raise PluginError, "Plugin #@name v#@version demands '#{@library}' library, which I do not understand. Aborting load!"
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end # def configure
|
137
|
+
|
138
|
+
#########
|
139
|
+
protected
|
140
|
+
#########
|
141
|
+
|
142
|
+
# Inheritance event.
|
143
|
+
#
|
144
|
+
# @param [Class] subklass The subclass which has inherited {self}.
|
145
|
+
def self.inherited(subklass)
|
146
|
+
$m.debug("[plugin] Syndi::API::Plugin inherited by #{subklass}")
|
147
|
+
end
|
148
|
+
|
149
|
+
end # class Plugin
|
150
|
+
|
151
|
+
end # module API
|
152
|
+
|
153
|
+
end # module Syndi
|
154
|
+
|
155
|
+
# vim: set ts=4 sts=2 sw=2 et:
|
data/lib/syndi/api.rb
ADDED
data/lib/syndi/bot.rb
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
# Copyright (c) 2013, Autumn Perrault, et al. All rights reserved.
|
2
|
+
# This free software is distributed under the FreeBSD license (see LICENSE).
|
3
|
+
|
4
|
+
syndiload :Redis, 'redis'
|
5
|
+
syndiload :FileKV, 'filekv'
|
6
|
+
|
7
|
+
require 'syndi/config'
|
8
|
+
require 'syndi/api'
|
9
|
+
|
10
|
+
# Namespace: Syndi
|
11
|
+
module Syndi
|
12
|
+
|
13
|
+
# This is the central class of Syndi, providing all core functionality.
|
14
|
+
#
|
15
|
+
# It should be additionally noted that for each loaded core library, a readable
|
16
|
+
# instance attribute of the library's name will exist, typically pointing to
|
17
|
+
# an instance of its respective Library class. (e.g. @irc = <Syndi::IRC::Library>)
|
18
|
+
#
|
19
|
+
# @!attribute [r] opts
|
20
|
+
# @return [Slop] The options object.
|
21
|
+
#
|
22
|
+
#
|
23
|
+
# @!attribute [r] log
|
24
|
+
# @return [Syndi::Logger] The logging instance.
|
25
|
+
#
|
26
|
+
# @!attribute [r] conf
|
27
|
+
# @return [Syndi::Config] The configuration instance.
|
28
|
+
#
|
29
|
+
# @!attribute [r] events
|
30
|
+
# @return [Syndi::API::Events] The event system instance.
|
31
|
+
#
|
32
|
+
# @!attribute [r] clock
|
33
|
+
# @return [Syndi::API::Timers] The timer system instance.
|
34
|
+
#
|
35
|
+
# @!attribute [r] db
|
36
|
+
# @return [Redis] The Redis database.
|
37
|
+
#
|
38
|
+
# @!attribute [r] libs
|
39
|
+
# @return [Array<String>] List of loaded core libraries.
|
40
|
+
#
|
41
|
+
# @!attribute [r] netloop
|
42
|
+
# @return [Thread] The thread in which #main_loop is running.
|
43
|
+
#
|
44
|
+
# @!attribute [r] sockets
|
45
|
+
# @return [Array<Object>] A list of socket objects.
|
46
|
+
class Bot
|
47
|
+
|
48
|
+
attr_reader :opts, :log, :conf, :events, :clock, :db, :libs,
|
49
|
+
:netloop, :sockets
|
50
|
+
|
51
|
+
# Create a new instance of Syndi.
|
52
|
+
#
|
53
|
+
# @param [Hash{String => Object}] opts A hash of options.
|
54
|
+
def initialize opts
|
55
|
+
# Save options.
|
56
|
+
@opts = opts
|
57
|
+
end
|
58
|
+
|
59
|
+
# Initialize this instance.
|
60
|
+
def init
|
61
|
+
|
62
|
+
# Load configuration
|
63
|
+
load_config
|
64
|
+
|
65
|
+
# Initialize the central event system
|
66
|
+
@events = Syndi::API::Events.new
|
67
|
+
|
68
|
+
# Start the timer system.
|
69
|
+
@clock = Syndi::API::Timers.new
|
70
|
+
|
71
|
+
# Prepare for sockets.
|
72
|
+
@sockets = []
|
73
|
+
|
74
|
+
# Initialize the database
|
75
|
+
@db = load_database
|
76
|
+
|
77
|
+
# Load core libraries.
|
78
|
+
load_libraries
|
79
|
+
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
# Start the bot.
|
84
|
+
def start
|
85
|
+
|
86
|
+
# Call the start event.
|
87
|
+
@events.call :start
|
88
|
+
|
89
|
+
# Throw the program into the main loop.
|
90
|
+
@events.threads.each { |thr| thr.join } # block until we're ready to go
|
91
|
+
$log.verbose("Producing a thread and entering the main loop...", VUSEFUL) do
|
92
|
+
@netloop = Thread.new { main_loop }
|
93
|
+
@netloop.join
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
# Daemonize the bot.
|
99
|
+
def daemonize
|
100
|
+
$log.info "Forking into the background. . . ."
|
101
|
+
|
102
|
+
# Direct all incoming data on STDIN and outgoing data on STDOUT/STDERR to /dev/null.
|
103
|
+
$stdin = File.open '/dev/null'
|
104
|
+
$stdout = $stderr = File.open '/dev/null', 'w'
|
105
|
+
|
106
|
+
# Fork and retrieve the PID.
|
107
|
+
pid = fork
|
108
|
+
|
109
|
+
# Save it to syndi.pid.
|
110
|
+
unless pid.nil?
|
111
|
+
File.open('syndi.pid', 'w') { |io| io.puts pid }
|
112
|
+
exit 0
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Main loop.
|
117
|
+
def main_loop
|
118
|
+
loop do
|
119
|
+
# Build a list of sockets.
|
120
|
+
sockets = []
|
121
|
+
assoc_objects = {}
|
122
|
+
@sockets.each do |o|
|
123
|
+
unless o.socket.nil? or o.socket.closed?
|
124
|
+
sockets << o.socket
|
125
|
+
assoc_objects[o.socket] = o
|
126
|
+
end
|
127
|
+
end
|
128
|
+
next if sockets.empty?
|
129
|
+
|
130
|
+
# Call #select.
|
131
|
+
ready_read, _, _ = IO.select(sockets, [], [], nil)
|
132
|
+
|
133
|
+
# Iterate through sockets ready for reading.
|
134
|
+
ready_read.each do |socket|
|
135
|
+
@events.call :net_receive, assoc_objects[socket]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Terminate the bot.
|
141
|
+
#
|
142
|
+
# @param [String] reason The reason for termination.
|
143
|
+
def terminate reason = 'Terminating'
|
144
|
+
info "Syndi is terminating owing to thus: #{reason}"
|
145
|
+
|
146
|
+
# Call :die
|
147
|
+
@events.call :die, reason
|
148
|
+
|
149
|
+
# Close the database.
|
150
|
+
@db.disconnect
|
151
|
+
|
152
|
+
# When dying, allow about three seconds for hooks to execute before
|
153
|
+
# fully terminating.
|
154
|
+
sleep 3
|
155
|
+
|
156
|
+
# Delete syndi.pid
|
157
|
+
File.delete 'syndi.pid' unless @opts.foreground?
|
158
|
+
|
159
|
+
exit 0
|
160
|
+
end
|
161
|
+
|
162
|
+
#######
|
163
|
+
private
|
164
|
+
#######
|
165
|
+
|
166
|
+
# Load the configuration.
|
167
|
+
def load_config
|
168
|
+
|
169
|
+
# Try to find the file
|
170
|
+
# if we're a gem, we'll try ~/.syndi/syndi.yml
|
171
|
+
# else we'll try ./conf/syndi.yml
|
172
|
+
confpath = nil
|
173
|
+
if Syndi.gem?
|
174
|
+
confpath = File.join(SYNDI_DIR, 'syndi.yml')
|
175
|
+
else
|
176
|
+
confpath = File.join('conf', 'syndi.yml')
|
177
|
+
end
|
178
|
+
confpath = @opts[:conf] if @opts.conf? # --conf=FILE has supreme precedence
|
179
|
+
|
180
|
+
$log.info "Reading the configuration file #{confpath}..."
|
181
|
+
@conf = Syndi::Config.new File.expand_path(confpath)
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
# Load Syndi libraries.
|
186
|
+
def load_libraries
|
187
|
+
|
188
|
+
$log.info 'Loading core libraries...'
|
189
|
+
@libs = []
|
190
|
+
|
191
|
+
# Iterate through each configured library.
|
192
|
+
@conf['libraries'].each do |lib|
|
193
|
+
lib.dc!
|
194
|
+
|
195
|
+
if @libs.include? lib
|
196
|
+
# Don't load a library more than once!
|
197
|
+
$log.error "Cannot load library twice (#{lib})! Please fix your configuration."
|
198
|
+
next
|
199
|
+
end
|
200
|
+
|
201
|
+
begin
|
202
|
+
load_library lib
|
203
|
+
@libs.push lib
|
204
|
+
rescue => e
|
205
|
+
$log.error_bt "Failed to load core library '#{lib}': #{e}", e.backtrace
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
# Load a core library.
|
213
|
+
def load_library lib
|
214
|
+
# here is where magic occurs to load a library
|
215
|
+
require "syndi/#{lib}"
|
216
|
+
instance_variable_set "@#{lib}".to_sym, Object.const_get("LIBRARY_#{lib.uc}")
|
217
|
+
define_singleton_method(lib.to_sym) { self.instance_variable_get("@#{__method__}".to_sym) }
|
218
|
+
end
|
219
|
+
|
220
|
+
# Load the Redis database.
|
221
|
+
def load_database
|
222
|
+
|
223
|
+
driver = @conf['database']['driver'] || 'redis'
|
224
|
+
|
225
|
+
case driver
|
226
|
+
when 'redis'
|
227
|
+
load_db_redis
|
228
|
+
when 'flatfile'
|
229
|
+
load_db_flatfile
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
# Initializes Redis.
|
235
|
+
def load_db_redis
|
236
|
+
|
237
|
+
config = Hash.new
|
238
|
+
if host = @conf['database']['address']
|
239
|
+
config[:host] = host
|
240
|
+
end
|
241
|
+
if port = @conf['database']['port']
|
242
|
+
config[:port] = port
|
243
|
+
end
|
244
|
+
if path = @conf['database']['path']
|
245
|
+
config[:path] = path
|
246
|
+
end
|
247
|
+
|
248
|
+
redis = Redis.new config
|
249
|
+
|
250
|
+
if passwd = @conf['database']['password']
|
251
|
+
redis.auth passwd
|
252
|
+
end
|
253
|
+
if id = @conf['database']['number']
|
254
|
+
redis.select id
|
255
|
+
end
|
256
|
+
|
257
|
+
redis
|
258
|
+
|
259
|
+
end
|
260
|
+
|
261
|
+
# Initializes Flatfile.
|
262
|
+
def load_db_flatfile
|
263
|
+
FileKV.new 'syndi.db'
|
264
|
+
end
|
265
|
+
|
266
|
+
end # class Bot
|
267
|
+
|
268
|
+
end # module Syndi
|
269
|
+
|
270
|
+
# vim: set ts=4 sts=2 sw=2 et:
|