Class: Syndi::Bot

Inherits:
Object
  • Object
show all
Defined in:
lib/syndi/bot.rb

Overview

This is the central class of Syndi, providing all core functionality.

It should be additionally noted that for each loaded core library, a readable instance attribute of the library's name will exist, typically pointing to an instance of its respective Library class. (e.g. @irc = Syndi::IRC::Library)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts) ⇒ Bot

Create a new instance of Syndi.

Parameters:

  • opts (Hash{String => Object})

    A hash of options.



50
51
52
53
# File 'lib/syndi/bot.rb', line 50

def initialize opts
  # Save options.
  @opts = opts
end

Instance Attribute Details

#clockSyndi::API::Timers (readonly)

Returns The timer system instance.

Returns:

  • (Syndi::API::Timers)

    The timer system instance.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/syndi/bot.rb', line 42

class Bot

  attr_reader :opts, :log, :conf, :events, :clock, :db, :libs,
              :netloop, :sockets

  # Create a new instance of Syndi.
  #
  # @param [Hash{String => Object}] opts A hash of options.
  def initialize opts
    # Save options.
    @opts = opts
  end      

  # Initialize this instance.
  def init
  
    # Load configuration
    load_config

    # Initialize the central event system
    @events = Syndi::API::Events.new

    # Start the timer system.
    @clock = Syndi::API::Timers.new

    # Prepare for sockets.
    @sockets = []

    # Initialize the database
    @db = load_database

    # Load core libraries.
    load_libraries

    true
  end

  # Start the bot.
  def start
    
    # Call the start event.
    @events.call :start

    # Throw the program into the main loop.
    @events.threads.each { |thr| thr.join } # block until we're ready to go
    $log.verbose("Producing a thread and entering the main loop...", VUSEFUL) do
      @netloop = Thread.new { main_loop }
      @netloop.join
    end

  end

  # Daemonize the bot.
  def daemonize
    $log.info "Forking into the background. . . ."

    # Direct all incoming data on STDIN and outgoing data on STDOUT/STDERR to /dev/null.
    $stdin  =           File.open '/dev/null'
    $stdout = $stderr = File.open '/dev/null', 'w'
    
    # Fork and retrieve the PID.
    pid = fork

    # Save it to syndi.pid.
    unless pid.nil?
      File.open('syndi.pid', 'w') { |io| io.puts pid }
      exit 0
    end
  end

  # Main loop.
  def main_loop
    loop do
      # Build a list of sockets.
      sockets       = []
      assoc_objects = {}
      @sockets.each do |o|
        unless o.socket.nil? or o.socket.closed?
          sockets << o.socket
          assoc_objects[o.socket] = o
        end
      end
      next if sockets.empty?

      # Call #select.
      ready_read, _, _ = IO.select(sockets, [], [], nil)

      # Iterate through sockets ready for reading.
      ready_read.each do |socket|
        @events.call :net_receive, assoc_objects[socket]
      end
    end
  end

  # Terminate the bot.
  #
  # @param [String] reason The reason for termination.
  def terminate reason = 'Terminating'
    info "Syndi is terminating owing to thus: #{reason}"

    # Call :die
    @events.call :die, reason
  
    # Close the database.
    @db.disconnect

    # When dying, allow about three seconds for hooks to execute before
    # fully terminating.
    sleep 3

    # Delete syndi.pid
    File.delete 'syndi.pid' unless @opts.foreground?

    exit 0
  end

  #######
  private
  #######

  # Load the configuration.
  def load_config

    # Try to find the file
    # if we're a gem, we'll try ~/.syndi/syndi.yml
    # else we'll try ./conf/syndi.yml
    confpath = nil
    if Syndi.gem?
      confpath = File.join(SYNDI_DIR, 'syndi.yml')
    else
      confpath = File.join('conf', 'syndi.yml')
    end
    confpath = @opts[:conf] if @opts.conf? # --conf=FILE has supreme precedence

    $log.info "Reading the configuration file #{confpath}..."
    @conf = Syndi::Config.new File.expand_path(confpath)

  end

  # Load Syndi libraries.
  def load_libraries
    
    $log.info 'Loading core libraries...'
    @libs = []

    # Iterate through each configured library.
    @conf['libraries'].each do |lib|
      lib.dc!

      if @libs.include? lib
        # Don't load a library more than once!
        $log.error "Cannot load library twice (#{lib})! Please fix your configuration."
        next
      end

      begin
        load_library lib
        @libs.push lib
      rescue => e
        $log.error_bt "Failed to load core library '#{lib}': #{e}", e.backtrace
      end

    end

  end

  # Load a core library.
  def load_library lib
    # here is where magic occurs to load a library
    require "syndi/#{lib}"
    instance_variable_set "@#{lib}".to_sym, Object.const_get("LIBRARY_#{lib.uc}")
    define_singleton_method(lib.to_sym) { self.instance_variable_get("@#{__method__}".to_sym) }
  end

  # Load the Redis database.
  def load_database

    driver = @conf['database']['driver'] || 'redis'

    case driver
    when 'redis'
      load_db_redis
    when 'flatfile'
      load_db_flatfile
    end

  end

  # Initializes Redis.
  def load_db_redis

    config = Hash.new
    if host = @conf['database']['address']
      config[:host] = host
    end
    if port = @conf['database']['port']
      config[:port] = port
    end
    if path = @conf['database']['path']
      config[:path] = path
    end

    redis = Redis.new config

    if passwd = @conf['database']['password']
      redis.auth passwd
    end
    if id = @conf['database']['number']
      redis.select id
    end

    redis

  end

  # Initializes Flatfile.
  def load_db_flatfile
    FileKV.new 'syndi.db'
  end

end

#confSyndi::Config (readonly)

Returns The configuration instance.

Returns:



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/syndi/bot.rb', line 42

class Bot

  attr_reader :opts, :log, :conf, :events, :clock, :db, :libs,
              :netloop, :sockets

  # Create a new instance of Syndi.
  #
  # @param [Hash{String => Object}] opts A hash of options.
  def initialize opts
    # Save options.
    @opts = opts
  end      

  # Initialize this instance.
  def init
  
    # Load configuration
    load_config

    # Initialize the central event system
    @events = Syndi::API::Events.new

    # Start the timer system.
    @clock = Syndi::API::Timers.new

    # Prepare for sockets.
    @sockets = []

    # Initialize the database
    @db = load_database

    # Load core libraries.
    load_libraries

    true
  end

  # Start the bot.
  def start
    
    # Call the start event.
    @events.call :start

    # Throw the program into the main loop.
    @events.threads.each { |thr| thr.join } # block until we're ready to go
    $log.verbose("Producing a thread and entering the main loop...", VUSEFUL) do
      @netloop = Thread.new { main_loop }
      @netloop.join
    end

  end

  # Daemonize the bot.
  def daemonize
    $log.info "Forking into the background. . . ."

    # Direct all incoming data on STDIN and outgoing data on STDOUT/STDERR to /dev/null.
    $stdin  =           File.open '/dev/null'
    $stdout = $stderr = File.open '/dev/null', 'w'
    
    # Fork and retrieve the PID.
    pid = fork

    # Save it to syndi.pid.
    unless pid.nil?
      File.open('syndi.pid', 'w') { |io| io.puts pid }
      exit 0
    end
  end

  # Main loop.
  def main_loop
    loop do
      # Build a list of sockets.
      sockets       = []
      assoc_objects = {}
      @sockets.each do |o|
        unless o.socket.nil? or o.socket.closed?
          sockets << o.socket
          assoc_objects[o.socket] = o
        end
      end
      next if sockets.empty?

      # Call #select.
      ready_read, _, _ = IO.select(sockets, [], [], nil)

      # Iterate through sockets ready for reading.
      ready_read.each do |socket|
        @events.call :net_receive, assoc_objects[socket]
      end
    end
  end

  # Terminate the bot.
  #
  # @param [String] reason The reason for termination.
  def terminate reason = 'Terminating'
    info "Syndi is terminating owing to thus: #{reason}"

    # Call :die
    @events.call :die, reason
  
    # Close the database.
    @db.disconnect

    # When dying, allow about three seconds for hooks to execute before
    # fully terminating.
    sleep 3

    # Delete syndi.pid
    File.delete 'syndi.pid' unless @opts.foreground?

    exit 0
  end

  #######
  private
  #######

  # Load the configuration.
  def load_config

    # Try to find the file
    # if we're a gem, we'll try ~/.syndi/syndi.yml
    # else we'll try ./conf/syndi.yml
    confpath = nil
    if Syndi.gem?
      confpath = File.join(SYNDI_DIR, 'syndi.yml')
    else
      confpath = File.join('conf', 'syndi.yml')
    end
    confpath = @opts[:conf] if @opts.conf? # --conf=FILE has supreme precedence

    $log.info "Reading the configuration file #{confpath}..."
    @conf = Syndi::Config.new File.expand_path(confpath)

  end

  # Load Syndi libraries.
  def load_libraries
    
    $log.info 'Loading core libraries...'
    @libs = []

    # Iterate through each configured library.
    @conf['libraries'].each do |lib|
      lib.dc!

      if @libs.include? lib
        # Don't load a library more than once!
        $log.error "Cannot load library twice (#{lib})! Please fix your configuration."
        next
      end

      begin
        load_library lib
        @libs.push lib
      rescue => e
        $log.error_bt "Failed to load core library '#{lib}': #{e}", e.backtrace
      end

    end

  end

  # Load a core library.
  def load_library lib
    # here is where magic occurs to load a library
    require "syndi/#{lib}"
    instance_variable_set "@#{lib}".to_sym, Object.const_get("LIBRARY_#{lib.uc}")
    define_singleton_method(lib.to_sym) { self.instance_variable_get("@#{__method__}".to_sym) }
  end

  # Load the Redis database.
  def load_database

    driver = @conf['database']['driver'] || 'redis'

    case driver
    when 'redis'
      load_db_redis
    when 'flatfile'
      load_db_flatfile
    end

  end

  # Initializes Redis.
  def load_db_redis

    config = Hash.new
    if host = @conf['database']['address']
      config[:host] = host
    end
    if port = @conf['database']['port']
      config[:port] = port
    end
    if path = @conf['database']['path']
      config[:path] = path
    end

    redis = Redis.new config

    if passwd = @conf['database']['password']
      redis.auth passwd
    end
    if id = @conf['database']['number']
      redis.select id
    end

    redis

  end

  # Initializes Flatfile.
  def load_db_flatfile
    FileKV.new 'syndi.db'
  end

end

#dbRedis (readonly)

Returns The Redis database.

Returns:

  • (Redis)

    The Redis database.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/syndi/bot.rb', line 42

class Bot

  attr_reader :opts, :log, :conf, :events, :clock, :db, :libs,
              :netloop, :sockets

  # Create a new instance of Syndi.
  #
  # @param [Hash{String => Object}] opts A hash of options.
  def initialize opts
    # Save options.
    @opts = opts
  end      

  # Initialize this instance.
  def init
  
    # Load configuration
    load_config

    # Initialize the central event system
    @events = Syndi::API::Events.new

    # Start the timer system.
    @clock = Syndi::API::Timers.new

    # Prepare for sockets.
    @sockets = []

    # Initialize the database
    @db = load_database

    # Load core libraries.
    load_libraries

    true
  end

  # Start the bot.
  def start
    
    # Call the start event.
    @events.call :start

    # Throw the program into the main loop.
    @events.threads.each { |thr| thr.join } # block until we're ready to go
    $log.verbose("Producing a thread and entering the main loop...", VUSEFUL) do
      @netloop = Thread.new { main_loop }
      @netloop.join
    end

  end

  # Daemonize the bot.
  def daemonize
    $log.info "Forking into the background. . . ."

    # Direct all incoming data on STDIN and outgoing data on STDOUT/STDERR to /dev/null.
    $stdin  =           File.open '/dev/null'
    $stdout = $stderr = File.open '/dev/null', 'w'
    
    # Fork and retrieve the PID.
    pid = fork

    # Save it to syndi.pid.
    unless pid.nil?
      File.open('syndi.pid', 'w') { |io| io.puts pid }
      exit 0
    end
  end

  # Main loop.
  def main_loop
    loop do
      # Build a list of sockets.
      sockets       = []
      assoc_objects = {}
      @sockets.each do |o|
        unless o.socket.nil? or o.socket.closed?
          sockets << o.socket
          assoc_objects[o.socket] = o
        end
      end
      next if sockets.empty?

      # Call #select.
      ready_read, _, _ = IO.select(sockets, [], [], nil)

      # Iterate through sockets ready for reading.
      ready_read.each do |socket|
        @events.call :net_receive, assoc_objects[socket]
      end
    end
  end

  # Terminate the bot.
  #
  # @param [String] reason The reason for termination.
  def terminate reason = 'Terminating'
    info "Syndi is terminating owing to thus: #{reason}"

    # Call :die
    @events.call :die, reason
  
    # Close the database.
    @db.disconnect

    # When dying, allow about three seconds for hooks to execute before
    # fully terminating.
    sleep 3

    # Delete syndi.pid
    File.delete 'syndi.pid' unless @opts.foreground?

    exit 0
  end

  #######
  private
  #######

  # Load the configuration.
  def load_config

    # Try to find the file
    # if we're a gem, we'll try ~/.syndi/syndi.yml
    # else we'll try ./conf/syndi.yml
    confpath = nil
    if Syndi.gem?
      confpath = File.join(SYNDI_DIR, 'syndi.yml')
    else
      confpath = File.join('conf', 'syndi.yml')
    end
    confpath = @opts[:conf] if @opts.conf? # --conf=FILE has supreme precedence

    $log.info "Reading the configuration file #{confpath}..."
    @conf = Syndi::Config.new File.expand_path(confpath)

  end

  # Load Syndi libraries.
  def load_libraries
    
    $log.info 'Loading core libraries...'
    @libs = []

    # Iterate through each configured library.
    @conf['libraries'].each do |lib|
      lib.dc!

      if @libs.include? lib
        # Don't load a library more than once!
        $log.error "Cannot load library twice (#{lib})! Please fix your configuration."
        next
      end

      begin
        load_library lib
        @libs.push lib
      rescue => e
        $log.error_bt "Failed to load core library '#{lib}': #{e}", e.backtrace
      end

    end

  end

  # Load a core library.
  def load_library lib
    # here is where magic occurs to load a library
    require "syndi/#{lib}"
    instance_variable_set "@#{lib}".to_sym, Object.const_get("LIBRARY_#{lib.uc}")
    define_singleton_method(lib.to_sym) { self.instance_variable_get("@#{__method__}".to_sym) }
  end

  # Load the Redis database.
  def load_database

    driver = @conf['database']['driver'] || 'redis'

    case driver
    when 'redis'
      load_db_redis
    when 'flatfile'
      load_db_flatfile
    end

  end

  # Initializes Redis.
  def load_db_redis

    config = Hash.new
    if host = @conf['database']['address']
      config[:host] = host
    end
    if port = @conf['database']['port']
      config[:port] = port
    end
    if path = @conf['database']['path']
      config[:path] = path
    end

    redis = Redis.new config

    if passwd = @conf['database']['password']
      redis.auth passwd
    end
    if id = @conf['database']['number']
      redis.select id
    end

    redis

  end

  # Initializes Flatfile.
  def load_db_flatfile
    FileKV.new 'syndi.db'
  end

end

#eventsSyndi::API::Events (readonly)

Returns The event system instance.

Returns:

  • (Syndi::API::Events)

    The event system instance.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/syndi/bot.rb', line 42

class Bot

  attr_reader :opts, :log, :conf, :events, :clock, :db, :libs,
              :netloop, :sockets

  # Create a new instance of Syndi.
  #
  # @param [Hash{String => Object}] opts A hash of options.
  def initialize opts
    # Save options.
    @opts = opts
  end      

  # Initialize this instance.
  def init
  
    # Load configuration
    load_config

    # Initialize the central event system
    @events = Syndi::API::Events.new

    # Start the timer system.
    @clock = Syndi::API::Timers.new

    # Prepare for sockets.
    @sockets = []

    # Initialize the database
    @db = load_database

    # Load core libraries.
    load_libraries

    true
  end

  # Start the bot.
  def start
    
    # Call the start event.
    @events.call :start

    # Throw the program into the main loop.
    @events.threads.each { |thr| thr.join } # block until we're ready to go
    $log.verbose("Producing a thread and entering the main loop...", VUSEFUL) do
      @netloop = Thread.new { main_loop }
      @netloop.join
    end

  end

  # Daemonize the bot.
  def daemonize
    $log.info "Forking into the background. . . ."

    # Direct all incoming data on STDIN and outgoing data on STDOUT/STDERR to /dev/null.
    $stdin  =           File.open '/dev/null'
    $stdout = $stderr = File.open '/dev/null', 'w'
    
    # Fork and retrieve the PID.
    pid = fork

    # Save it to syndi.pid.
    unless pid.nil?
      File.open('syndi.pid', 'w') { |io| io.puts pid }
      exit 0
    end
  end

  # Main loop.
  def main_loop
    loop do
      # Build a list of sockets.
      sockets       = []
      assoc_objects = {}
      @sockets.each do |o|
        unless o.socket.nil? or o.socket.closed?
          sockets << o.socket
          assoc_objects[o.socket] = o
        end
      end
      next if sockets.empty?

      # Call #select.
      ready_read, _, _ = IO.select(sockets, [], [], nil)

      # Iterate through sockets ready for reading.
      ready_read.each do |socket|
        @events.call :net_receive, assoc_objects[socket]
      end
    end
  end

  # Terminate the bot.
  #
  # @param [String] reason The reason for termination.
  def terminate reason = 'Terminating'
    info "Syndi is terminating owing to thus: #{reason}"

    # Call :die
    @events.call :die, reason
  
    # Close the database.
    @db.disconnect

    # When dying, allow about three seconds for hooks to execute before
    # fully terminating.
    sleep 3

    # Delete syndi.pid
    File.delete 'syndi.pid' unless @opts.foreground?

    exit 0
  end

  #######
  private
  #######

  # Load the configuration.
  def load_config

    # Try to find the file
    # if we're a gem, we'll try ~/.syndi/syndi.yml
    # else we'll try ./conf/syndi.yml
    confpath = nil
    if Syndi.gem?
      confpath = File.join(SYNDI_DIR, 'syndi.yml')
    else
      confpath = File.join('conf', 'syndi.yml')
    end
    confpath = @opts[:conf] if @opts.conf? # --conf=FILE has supreme precedence

    $log.info "Reading the configuration file #{confpath}..."
    @conf = Syndi::Config.new File.expand_path(confpath)

  end

  # Load Syndi libraries.
  def load_libraries
    
    $log.info 'Loading core libraries...'
    @libs = []

    # Iterate through each configured library.
    @conf['libraries'].each do |lib|
      lib.dc!

      if @libs.include? lib
        # Don't load a library more than once!
        $log.error "Cannot load library twice (#{lib})! Please fix your configuration."
        next
      end

      begin
        load_library lib
        @libs.push lib
      rescue => e
        $log.error_bt "Failed to load core library '#{lib}': #{e}", e.backtrace
      end

    end

  end

  # Load a core library.
  def load_library lib
    # here is where magic occurs to load a library
    require "syndi/#{lib}"
    instance_variable_set "@#{lib}".to_sym, Object.const_get("LIBRARY_#{lib.uc}")
    define_singleton_method(lib.to_sym) { self.instance_variable_get("@#{__method__}".to_sym) }
  end

  # Load the Redis database.
  def load_database

    driver = @conf['database']['driver'] || 'redis'

    case driver
    when 'redis'
      load_db_redis
    when 'flatfile'
      load_db_flatfile
    end

  end

  # Initializes Redis.
  def load_db_redis

    config = Hash.new
    if host = @conf['database']['address']
      config[:host] = host
    end
    if port = @conf['database']['port']
      config[:port] = port
    end
    if path = @conf['database']['path']
      config[:path] = path
    end

    redis = Redis.new config

    if passwd = @conf['database']['password']
      redis.auth passwd
    end
    if id = @conf['database']['number']
      redis.select id
    end

    redis

  end

  # Initializes Flatfile.
  def load_db_flatfile
    FileKV.new 'syndi.db'
  end

end

#libsArray<String> (readonly)

Returns List of loaded core libraries.

Returns:

  • (Array<String>)

    List of loaded core libraries.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/syndi/bot.rb', line 42

class Bot

  attr_reader :opts, :log, :conf, :events, :clock, :db, :libs,
              :netloop, :sockets

  # Create a new instance of Syndi.
  #
  # @param [Hash{String => Object}] opts A hash of options.
  def initialize opts
    # Save options.
    @opts = opts
  end      

  # Initialize this instance.
  def init
  
    # Load configuration
    load_config

    # Initialize the central event system
    @events = Syndi::API::Events.new

    # Start the timer system.
    @clock = Syndi::API::Timers.new

    # Prepare for sockets.
    @sockets = []

    # Initialize the database
    @db = load_database

    # Load core libraries.
    load_libraries

    true
  end

  # Start the bot.
  def start
    
    # Call the start event.
    @events.call :start

    # Throw the program into the main loop.
    @events.threads.each { |thr| thr.join } # block until we're ready to go
    $log.verbose("Producing a thread and entering the main loop...", VUSEFUL) do
      @netloop = Thread.new { main_loop }
      @netloop.join
    end

  end

  # Daemonize the bot.
  def daemonize
    $log.info "Forking into the background. . . ."

    # Direct all incoming data on STDIN and outgoing data on STDOUT/STDERR to /dev/null.
    $stdin  =           File.open '/dev/null'
    $stdout = $stderr = File.open '/dev/null', 'w'
    
    # Fork and retrieve the PID.
    pid = fork

    # Save it to syndi.pid.
    unless pid.nil?
      File.open('syndi.pid', 'w') { |io| io.puts pid }
      exit 0
    end
  end

  # Main loop.
  def main_loop
    loop do
      # Build a list of sockets.
      sockets       = []
      assoc_objects = {}
      @sockets.each do |o|
        unless o.socket.nil? or o.socket.closed?
          sockets << o.socket
          assoc_objects[o.socket] = o
        end
      end
      next if sockets.empty?

      # Call #select.
      ready_read, _, _ = IO.select(sockets, [], [], nil)

      # Iterate through sockets ready for reading.
      ready_read.each do |socket|
        @events.call :net_receive, assoc_objects[socket]
      end
    end
  end

  # Terminate the bot.
  #
  # @param [String] reason The reason for termination.
  def terminate reason = 'Terminating'
    info "Syndi is terminating owing to thus: #{reason}"

    # Call :die
    @events.call :die, reason
  
    # Close the database.
    @db.disconnect

    # When dying, allow about three seconds for hooks to execute before
    # fully terminating.
    sleep 3

    # Delete syndi.pid
    File.delete 'syndi.pid' unless @opts.foreground?

    exit 0
  end

  #######
  private
  #######

  # Load the configuration.
  def load_config

    # Try to find the file
    # if we're a gem, we'll try ~/.syndi/syndi.yml
    # else we'll try ./conf/syndi.yml
    confpath = nil
    if Syndi.gem?
      confpath = File.join(SYNDI_DIR, 'syndi.yml')
    else
      confpath = File.join('conf', 'syndi.yml')
    end
    confpath = @opts[:conf] if @opts.conf? # --conf=FILE has supreme precedence

    $log.info "Reading the configuration file #{confpath}..."
    @conf = Syndi::Config.new File.expand_path(confpath)

  end

  # Load Syndi libraries.
  def load_libraries
    
    $log.info 'Loading core libraries...'
    @libs = []

    # Iterate through each configured library.
    @conf['libraries'].each do |lib|
      lib.dc!

      if @libs.include? lib
        # Don't load a library more than once!
        $log.error "Cannot load library twice (#{lib})! Please fix your configuration."
        next
      end

      begin
        load_library lib
        @libs.push lib
      rescue => e
        $log.error_bt "Failed to load core library '#{lib}': #{e}", e.backtrace
      end

    end

  end

  # Load a core library.
  def load_library lib
    # here is where magic occurs to load a library
    require "syndi/#{lib}"
    instance_variable_set "@#{lib}".to_sym, Object.const_get("LIBRARY_#{lib.uc}")
    define_singleton_method(lib.to_sym) { self.instance_variable_get("@#{__method__}".to_sym) }
  end

  # Load the Redis database.
  def load_database

    driver = @conf['database']['driver'] || 'redis'

    case driver
    when 'redis'
      load_db_redis
    when 'flatfile'
      load_db_flatfile
    end

  end

  # Initializes Redis.
  def load_db_redis

    config = Hash.new
    if host = @conf['database']['address']
      config[:host] = host
    end
    if port = @conf['database']['port']
      config[:port] = port
    end
    if path = @conf['database']['path']
      config[:path] = path
    end

    redis = Redis.new config

    if passwd = @conf['database']['password']
      redis.auth passwd
    end
    if id = @conf['database']['number']
      redis.select id
    end

    redis

  end

  # Initializes Flatfile.
  def load_db_flatfile
    FileKV.new 'syndi.db'
  end

end

#logSyndi::Logger (readonly)

Returns The logging instance.

Returns:



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/syndi/bot.rb', line 42

class Bot

  attr_reader :opts, :log, :conf, :events, :clock, :db, :libs,
              :netloop, :sockets

  # Create a new instance of Syndi.
  #
  # @param [Hash{String => Object}] opts A hash of options.
  def initialize opts
    # Save options.
    @opts = opts
  end      

  # Initialize this instance.
  def init
  
    # Load configuration
    load_config

    # Initialize the central event system
    @events = Syndi::API::Events.new

    # Start the timer system.
    @clock = Syndi::API::Timers.new

    # Prepare for sockets.
    @sockets = []

    # Initialize the database
    @db = load_database

    # Load core libraries.
    load_libraries

    true
  end

  # Start the bot.
  def start
    
    # Call the start event.
    @events.call :start

    # Throw the program into the main loop.
    @events.threads.each { |thr| thr.join } # block until we're ready to go
    $log.verbose("Producing a thread and entering the main loop...", VUSEFUL) do
      @netloop = Thread.new { main_loop }
      @netloop.join
    end

  end

  # Daemonize the bot.
  def daemonize
    $log.info "Forking into the background. . . ."

    # Direct all incoming data on STDIN and outgoing data on STDOUT/STDERR to /dev/null.
    $stdin  =           File.open '/dev/null'
    $stdout = $stderr = File.open '/dev/null', 'w'
    
    # Fork and retrieve the PID.
    pid = fork

    # Save it to syndi.pid.
    unless pid.nil?
      File.open('syndi.pid', 'w') { |io| io.puts pid }
      exit 0
    end
  end

  # Main loop.
  def main_loop
    loop do
      # Build a list of sockets.
      sockets       = []
      assoc_objects = {}
      @sockets.each do |o|
        unless o.socket.nil? or o.socket.closed?
          sockets << o.socket
          assoc_objects[o.socket] = o
        end
      end
      next if sockets.empty?

      # Call #select.
      ready_read, _, _ = IO.select(sockets, [], [], nil)

      # Iterate through sockets ready for reading.
      ready_read.each do |socket|
        @events.call :net_receive, assoc_objects[socket]
      end
    end
  end

  # Terminate the bot.
  #
  # @param [String] reason The reason for termination.
  def terminate reason = 'Terminating'
    info "Syndi is terminating owing to thus: #{reason}"

    # Call :die
    @events.call :die, reason
  
    # Close the database.
    @db.disconnect

    # When dying, allow about three seconds for hooks to execute before
    # fully terminating.
    sleep 3

    # Delete syndi.pid
    File.delete 'syndi.pid' unless @opts.foreground?

    exit 0
  end

  #######
  private
  #######

  # Load the configuration.
  def load_config

    # Try to find the file
    # if we're a gem, we'll try ~/.syndi/syndi.yml
    # else we'll try ./conf/syndi.yml
    confpath = nil
    if Syndi.gem?
      confpath = File.join(SYNDI_DIR, 'syndi.yml')
    else
      confpath = File.join('conf', 'syndi.yml')
    end
    confpath = @opts[:conf] if @opts.conf? # --conf=FILE has supreme precedence

    $log.info "Reading the configuration file #{confpath}..."
    @conf = Syndi::Config.new File.expand_path(confpath)

  end

  # Load Syndi libraries.
  def load_libraries
    
    $log.info 'Loading core libraries...'
    @libs = []

    # Iterate through each configured library.
    @conf['libraries'].each do |lib|
      lib.dc!

      if @libs.include? lib
        # Don't load a library more than once!
        $log.error "Cannot load library twice (#{lib})! Please fix your configuration."
        next
      end

      begin
        load_library lib
        @libs.push lib
      rescue => e
        $log.error_bt "Failed to load core library '#{lib}': #{e}", e.backtrace
      end

    end

  end

  # Load a core library.
  def load_library lib
    # here is where magic occurs to load a library
    require "syndi/#{lib}"
    instance_variable_set "@#{lib}".to_sym, Object.const_get("LIBRARY_#{lib.uc}")
    define_singleton_method(lib.to_sym) { self.instance_variable_get("@#{__method__}".to_sym) }
  end

  # Load the Redis database.
  def load_database

    driver = @conf['database']['driver'] || 'redis'

    case driver
    when 'redis'
      load_db_redis
    when 'flatfile'
      load_db_flatfile
    end

  end

  # Initializes Redis.
  def load_db_redis

    config = Hash.new
    if host = @conf['database']['address']
      config[:host] = host
    end
    if port = @conf['database']['port']
      config[:port] = port
    end
    if path = @conf['database']['path']
      config[:path] = path
    end

    redis = Redis.new config

    if passwd = @conf['database']['password']
      redis.auth passwd
    end
    if id = @conf['database']['number']
      redis.select id
    end

    redis

  end

  # Initializes Flatfile.
  def load_db_flatfile
    FileKV.new 'syndi.db'
  end

end

#netloopThread (readonly)

Returns The thread in which #main_loop is running.

Returns:

  • (Thread)

    The thread in which #main_loop is running.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/syndi/bot.rb', line 42

class Bot

  attr_reader :opts, :log, :conf, :events, :clock, :db, :libs,
              :netloop, :sockets

  # Create a new instance of Syndi.
  #
  # @param [Hash{String => Object}] opts A hash of options.
  def initialize opts
    # Save options.
    @opts = opts
  end      

  # Initialize this instance.
  def init
  
    # Load configuration
    load_config

    # Initialize the central event system
    @events = Syndi::API::Events.new

    # Start the timer system.
    @clock = Syndi::API::Timers.new

    # Prepare for sockets.
    @sockets = []

    # Initialize the database
    @db = load_database

    # Load core libraries.
    load_libraries

    true
  end

  # Start the bot.
  def start
    
    # Call the start event.
    @events.call :start

    # Throw the program into the main loop.
    @events.threads.each { |thr| thr.join } # block until we're ready to go
    $log.verbose("Producing a thread and entering the main loop...", VUSEFUL) do
      @netloop = Thread.new { main_loop }
      @netloop.join
    end

  end

  # Daemonize the bot.
  def daemonize
    $log.info "Forking into the background. . . ."

    # Direct all incoming data on STDIN and outgoing data on STDOUT/STDERR to /dev/null.
    $stdin  =           File.open '/dev/null'
    $stdout = $stderr = File.open '/dev/null', 'w'
    
    # Fork and retrieve the PID.
    pid = fork

    # Save it to syndi.pid.
    unless pid.nil?
      File.open('syndi.pid', 'w') { |io| io.puts pid }
      exit 0
    end
  end

  # Main loop.
  def main_loop
    loop do
      # Build a list of sockets.
      sockets       = []
      assoc_objects = {}
      @sockets.each do |o|
        unless o.socket.nil? or o.socket.closed?
          sockets << o.socket
          assoc_objects[o.socket] = o
        end
      end
      next if sockets.empty?

      # Call #select.
      ready_read, _, _ = IO.select(sockets, [], [], nil)

      # Iterate through sockets ready for reading.
      ready_read.each do |socket|
        @events.call :net_receive, assoc_objects[socket]
      end
    end
  end

  # Terminate the bot.
  #
  # @param [String] reason The reason for termination.
  def terminate reason = 'Terminating'
    info "Syndi is terminating owing to thus: #{reason}"

    # Call :die
    @events.call :die, reason
  
    # Close the database.
    @db.disconnect

    # When dying, allow about three seconds for hooks to execute before
    # fully terminating.
    sleep 3

    # Delete syndi.pid
    File.delete 'syndi.pid' unless @opts.foreground?

    exit 0
  end

  #######
  private
  #######

  # Load the configuration.
  def load_config

    # Try to find the file
    # if we're a gem, we'll try ~/.syndi/syndi.yml
    # else we'll try ./conf/syndi.yml
    confpath = nil
    if Syndi.gem?
      confpath = File.join(SYNDI_DIR, 'syndi.yml')
    else
      confpath = File.join('conf', 'syndi.yml')
    end
    confpath = @opts[:conf] if @opts.conf? # --conf=FILE has supreme precedence

    $log.info "Reading the configuration file #{confpath}..."
    @conf = Syndi::Config.new File.expand_path(confpath)

  end

  # Load Syndi libraries.
  def load_libraries
    
    $log.info 'Loading core libraries...'
    @libs = []

    # Iterate through each configured library.
    @conf['libraries'].each do |lib|
      lib.dc!

      if @libs.include? lib
        # Don't load a library more than once!
        $log.error "Cannot load library twice (#{lib})! Please fix your configuration."
        next
      end

      begin
        load_library lib
        @libs.push lib
      rescue => e
        $log.error_bt "Failed to load core library '#{lib}': #{e}", e.backtrace
      end

    end

  end

  # Load a core library.
  def load_library lib
    # here is where magic occurs to load a library
    require "syndi/#{lib}"
    instance_variable_set "@#{lib}".to_sym, Object.const_get("LIBRARY_#{lib.uc}")
    define_singleton_method(lib.to_sym) { self.instance_variable_get("@#{__method__}".to_sym) }
  end

  # Load the Redis database.
  def load_database

    driver = @conf['database']['driver'] || 'redis'

    case driver
    when 'redis'
      load_db_redis
    when 'flatfile'
      load_db_flatfile
    end

  end

  # Initializes Redis.
  def load_db_redis

    config = Hash.new
    if host = @conf['database']['address']
      config[:host] = host
    end
    if port = @conf['database']['port']
      config[:port] = port
    end
    if path = @conf['database']['path']
      config[:path] = path
    end

    redis = Redis.new config

    if passwd = @conf['database']['password']
      redis.auth passwd
    end
    if id = @conf['database']['number']
      redis.select id
    end

    redis

  end

  # Initializes Flatfile.
  def load_db_flatfile
    FileKV.new 'syndi.db'
  end

end

#optsSlop (readonly)

Returns The options object.

Returns:

  • (Slop)

    The options object.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/syndi/bot.rb', line 42

class Bot

  attr_reader :opts, :log, :conf, :events, :clock, :db, :libs,
              :netloop, :sockets

  # Create a new instance of Syndi.
  #
  # @param [Hash{String => Object}] opts A hash of options.
  def initialize opts
    # Save options.
    @opts = opts
  end      

  # Initialize this instance.
  def init
  
    # Load configuration
    load_config

    # Initialize the central event system
    @events = Syndi::API::Events.new

    # Start the timer system.
    @clock = Syndi::API::Timers.new

    # Prepare for sockets.
    @sockets = []

    # Initialize the database
    @db = load_database

    # Load core libraries.
    load_libraries

    true
  end

  # Start the bot.
  def start
    
    # Call the start event.
    @events.call :start

    # Throw the program into the main loop.
    @events.threads.each { |thr| thr.join } # block until we're ready to go
    $log.verbose("Producing a thread and entering the main loop...", VUSEFUL) do
      @netloop = Thread.new { main_loop }
      @netloop.join
    end

  end

  # Daemonize the bot.
  def daemonize
    $log.info "Forking into the background. . . ."

    # Direct all incoming data on STDIN and outgoing data on STDOUT/STDERR to /dev/null.
    $stdin  =           File.open '/dev/null'
    $stdout = $stderr = File.open '/dev/null', 'w'
    
    # Fork and retrieve the PID.
    pid = fork

    # Save it to syndi.pid.
    unless pid.nil?
      File.open('syndi.pid', 'w') { |io| io.puts pid }
      exit 0
    end
  end

  # Main loop.
  def main_loop
    loop do
      # Build a list of sockets.
      sockets       = []
      assoc_objects = {}
      @sockets.each do |o|
        unless o.socket.nil? or o.socket.closed?
          sockets << o.socket
          assoc_objects[o.socket] = o
        end
      end
      next if sockets.empty?

      # Call #select.
      ready_read, _, _ = IO.select(sockets, [], [], nil)

      # Iterate through sockets ready for reading.
      ready_read.each do |socket|
        @events.call :net_receive, assoc_objects[socket]
      end
    end
  end

  # Terminate the bot.
  #
  # @param [String] reason The reason for termination.
  def terminate reason = 'Terminating'
    info "Syndi is terminating owing to thus: #{reason}"

    # Call :die
    @events.call :die, reason
  
    # Close the database.
    @db.disconnect

    # When dying, allow about three seconds for hooks to execute before
    # fully terminating.
    sleep 3

    # Delete syndi.pid
    File.delete 'syndi.pid' unless @opts.foreground?

    exit 0
  end

  #######
  private
  #######

  # Load the configuration.
  def load_config

    # Try to find the file
    # if we're a gem, we'll try ~/.syndi/syndi.yml
    # else we'll try ./conf/syndi.yml
    confpath = nil
    if Syndi.gem?
      confpath = File.join(SYNDI_DIR, 'syndi.yml')
    else
      confpath = File.join('conf', 'syndi.yml')
    end
    confpath = @opts[:conf] if @opts.conf? # --conf=FILE has supreme precedence

    $log.info "Reading the configuration file #{confpath}..."
    @conf = Syndi::Config.new File.expand_path(confpath)

  end

  # Load Syndi libraries.
  def load_libraries
    
    $log.info 'Loading core libraries...'
    @libs = []

    # Iterate through each configured library.
    @conf['libraries'].each do |lib|
      lib.dc!

      if @libs.include? lib
        # Don't load a library more than once!
        $log.error "Cannot load library twice (#{lib})! Please fix your configuration."
        next
      end

      begin
        load_library lib
        @libs.push lib
      rescue => e
        $log.error_bt "Failed to load core library '#{lib}': #{e}", e.backtrace
      end

    end

  end

  # Load a core library.
  def load_library lib
    # here is where magic occurs to load a library
    require "syndi/#{lib}"
    instance_variable_set "@#{lib}".to_sym, Object.const_get("LIBRARY_#{lib.uc}")
    define_singleton_method(lib.to_sym) { self.instance_variable_get("@#{__method__}".to_sym) }
  end

  # Load the Redis database.
  def load_database

    driver = @conf['database']['driver'] || 'redis'

    case driver
    when 'redis'
      load_db_redis
    when 'flatfile'
      load_db_flatfile
    end

  end

  # Initializes Redis.
  def load_db_redis

    config = Hash.new
    if host = @conf['database']['address']
      config[:host] = host
    end
    if port = @conf['database']['port']
      config[:port] = port
    end
    if path = @conf['database']['path']
      config[:path] = path
    end

    redis = Redis.new config

    if passwd = @conf['database']['password']
      redis.auth passwd
    end
    if id = @conf['database']['number']
      redis.select id
    end

    redis

  end

  # Initializes Flatfile.
  def load_db_flatfile
    FileKV.new 'syndi.db'
  end

end

#socketsArray<Object> (readonly)

Returns A list of socket objects.

Returns:

  • (Array<Object>)

    A list of socket objects.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/syndi/bot.rb', line 42

class Bot

  attr_reader :opts, :log, :conf, :events, :clock, :db, :libs,
              :netloop, :sockets

  # Create a new instance of Syndi.
  #
  # @param [Hash{String => Object}] opts A hash of options.
  def initialize opts
    # Save options.
    @opts = opts
  end      

  # Initialize this instance.
  def init
  
    # Load configuration
    load_config

    # Initialize the central event system
    @events = Syndi::API::Events.new

    # Start the timer system.
    @clock = Syndi::API::Timers.new

    # Prepare for sockets.
    @sockets = []

    # Initialize the database
    @db = load_database

    # Load core libraries.
    load_libraries

    true
  end

  # Start the bot.
  def start
    
    # Call the start event.
    @events.call :start

    # Throw the program into the main loop.
    @events.threads.each { |thr| thr.join } # block until we're ready to go
    $log.verbose("Producing a thread and entering the main loop...", VUSEFUL) do
      @netloop = Thread.new { main_loop }
      @netloop.join
    end

  end

  # Daemonize the bot.
  def daemonize
    $log.info "Forking into the background. . . ."

    # Direct all incoming data on STDIN and outgoing data on STDOUT/STDERR to /dev/null.
    $stdin  =           File.open '/dev/null'
    $stdout = $stderr = File.open '/dev/null', 'w'
    
    # Fork and retrieve the PID.
    pid = fork

    # Save it to syndi.pid.
    unless pid.nil?
      File.open('syndi.pid', 'w') { |io| io.puts pid }
      exit 0
    end
  end

  # Main loop.
  def main_loop
    loop do
      # Build a list of sockets.
      sockets       = []
      assoc_objects = {}
      @sockets.each do |o|
        unless o.socket.nil? or o.socket.closed?
          sockets << o.socket
          assoc_objects[o.socket] = o
        end
      end
      next if sockets.empty?

      # Call #select.
      ready_read, _, _ = IO.select(sockets, [], [], nil)

      # Iterate through sockets ready for reading.
      ready_read.each do |socket|
        @events.call :net_receive, assoc_objects[socket]
      end
    end
  end

  # Terminate the bot.
  #
  # @param [String] reason The reason for termination.
  def terminate reason = 'Terminating'
    info "Syndi is terminating owing to thus: #{reason}"

    # Call :die
    @events.call :die, reason
  
    # Close the database.
    @db.disconnect

    # When dying, allow about three seconds for hooks to execute before
    # fully terminating.
    sleep 3

    # Delete syndi.pid
    File.delete 'syndi.pid' unless @opts.foreground?

    exit 0
  end

  #######
  private
  #######

  # Load the configuration.
  def load_config

    # Try to find the file
    # if we're a gem, we'll try ~/.syndi/syndi.yml
    # else we'll try ./conf/syndi.yml
    confpath = nil
    if Syndi.gem?
      confpath = File.join(SYNDI_DIR, 'syndi.yml')
    else
      confpath = File.join('conf', 'syndi.yml')
    end
    confpath = @opts[:conf] if @opts.conf? # --conf=FILE has supreme precedence

    $log.info "Reading the configuration file #{confpath}..."
    @conf = Syndi::Config.new File.expand_path(confpath)

  end

  # Load Syndi libraries.
  def load_libraries
    
    $log.info 'Loading core libraries...'
    @libs = []

    # Iterate through each configured library.
    @conf['libraries'].each do |lib|
      lib.dc!

      if @libs.include? lib
        # Don't load a library more than once!
        $log.error "Cannot load library twice (#{lib})! Please fix your configuration."
        next
      end

      begin
        load_library lib
        @libs.push lib
      rescue => e
        $log.error_bt "Failed to load core library '#{lib}': #{e}", e.backtrace
      end

    end

  end

  # Load a core library.
  def load_library lib
    # here is where magic occurs to load a library
    require "syndi/#{lib}"
    instance_variable_set "@#{lib}".to_sym, Object.const_get("LIBRARY_#{lib.uc}")
    define_singleton_method(lib.to_sym) { self.instance_variable_get("@#{__method__}".to_sym) }
  end

  # Load the Redis database.
  def load_database

    driver = @conf['database']['driver'] || 'redis'

    case driver
    when 'redis'
      load_db_redis
    when 'flatfile'
      load_db_flatfile
    end

  end

  # Initializes Redis.
  def load_db_redis

    config = Hash.new
    if host = @conf['database']['address']
      config[:host] = host
    end
    if port = @conf['database']['port']
      config[:port] = port
    end
    if path = @conf['database']['path']
      config[:path] = path
    end

    redis = Redis.new config

    if passwd = @conf['database']['password']
      redis.auth passwd
    end
    if id = @conf['database']['number']
      redis.select id
    end

    redis

  end

  # Initializes Flatfile.
  def load_db_flatfile
    FileKV.new 'syndi.db'
  end

end

Instance Method Details

#daemonizeObject

Daemonize the bot.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/syndi/bot.rb', line 95

def daemonize
  $log.info "Forking into the background. . . ."
  
  # Direct all incoming data on STDIN and outgoing data on STDOUT/STDERR to /dev/null.
  $stdin  =           File.open '/dev/null'
  $stdout = $stderr = File.open '/dev/null', 'w'
  
  # Fork and retrieve the PID.
  pid = fork

  # Save it to syndi.pid.
  unless pid.nil?
    File.open('syndi.pid', 'w') { |io| io.puts pid }
    exit 0
  end
end

#initObject

Initialize this instance.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/syndi/bot.rb', line 56

def init

  # Load configuration
  load_config

  # Initialize the central event system
  @events = Syndi::API::Events.new

  # Start the timer system.
  @clock = Syndi::API::Timers.new

  # Prepare for sockets.
  @sockets = []

  # Initialize the database
  @db = load_database

  # Load core libraries.
  load_libraries

  true
end

#load_configObject (private)

Load the configuration.



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/syndi/bot.rb', line 163

def load_config

  # Try to find the file
  # if we're a gem, we'll try ~/.syndi/syndi.yml
  # else we'll try ./conf/syndi.yml
  confpath = nil
  if Syndi.gem?
    confpath = File.join(SYNDI_DIR, 'syndi.yml')
  else
    confpath = File.join('conf', 'syndi.yml')
  end
  confpath = @opts[:conf] if @opts.conf? # --conf=FILE has supreme precedence

  $log.info "Reading the configuration file #{confpath}..."
  @conf = Syndi::Config.new File.expand_path(confpath)

end

#load_databaseObject (private)

Load the Redis database.



217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/syndi/bot.rb', line 217

def load_database

  driver = @conf['database']['driver'] || 'redis'

  case driver
  when 'redis'
    load_db_redis
  when 'flatfile'
    load_db_flatfile
  end

end

#load_db_flatfileObject (private)

Initializes Flatfile.



258
259
260
# File 'lib/syndi/bot.rb', line 258

def load_db_flatfile
  FileKV.new 'syndi.db'
end

#load_db_redisObject (private)

Initializes Redis.



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/syndi/bot.rb', line 231

def load_db_redis

  config = Hash.new
  if host = @conf['database']['address']
    config[:host] = host
  end
  if port = @conf['database']['port']
    config[:port] = port
  end
  if path = @conf['database']['path']
    config[:path] = path
  end

  redis = Redis.new config

  if passwd = @conf['database']['password']
    redis.auth passwd
  end
  if id = @conf['database']['number']
    redis.select id
  end

  redis

end

#load_librariesObject (private)

Load Syndi libraries.



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/syndi/bot.rb', line 182

def load_libraries
  
  $log.info 'Loading core libraries...'
  @libs = []

  # Iterate through each configured library.
  @conf['libraries'].each do |lib|
    lib.dc!

    if @libs.include? lib
      # Don't load a library more than once!
      $log.error "Cannot load library twice (#{lib})! Please fix your configuration."
      next
    end

    begin
      load_library lib
      @libs.push lib
    rescue => e
      $log.error_bt "Failed to load core library '#{lib}': #{e}", e.backtrace
    end

  end

end

#load_library(lib) ⇒ Object (private)

Load a core library.



209
210
211
212
213
214
# File 'lib/syndi/bot.rb', line 209

def load_library lib
  # here is where magic occurs to load a library
  require "syndi/#{lib}"
  instance_variable_set "@#{lib}".to_sym, Object.const_get("LIBRARY_#{lib.uc}")
  define_singleton_method(lib.to_sym) { self.instance_variable_get("@#{__method__}".to_sym) }
end

#main_loopObject

Main loop.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/syndi/bot.rb', line 113

def main_loop
  loop do
    # Build a list of sockets.
    sockets       = []
    assoc_objects = {}
    @sockets.each do |o|
      unless o.socket.nil? or o.socket.closed?
        sockets << o.socket
        assoc_objects[o.socket] = o
      end
    end
    next if sockets.empty?

    # Call #select.
    ready_read, _, _ = IO.select(sockets, [], [], nil)

    # Iterate through sockets ready for reading.
    ready_read.each do |socket|
      @events.call :net_receive, assoc_objects[socket]
    end
  end
end

#startObject

Start the bot.



80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/syndi/bot.rb', line 80

def start
  
  # Call the start event.
  @events.call :start

  # Throw the program into the main loop.
  @events.threads.each { |thr| thr.join } # block until we're ready to go
  $log.verbose("Producing a thread and entering the main loop...", VUSEFUL) do
    @netloop = Thread.new { main_loop }
    @netloop.join
  end

end

#terminate(reason = 'Terminating') ⇒ Object

Terminate the bot.

Parameters:

  • reason (String) (defaults to: 'Terminating')

    The reason for termination.



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/syndi/bot.rb', line 139

def terminate reason = 'Terminating'
  info "Syndi is terminating owing to thus: #{reason}"

  # Call :die
  @events.call :die, reason

  # Close the database.
  @db.disconnect

  # When dying, allow about three seconds for hooks to execute before
  # fully terminating.
  sleep 3

  # Delete syndi.pid
  File.delete 'syndi.pid' unless @opts.foreground?

  exit 0
end