Permalink
Browse files

Add events, timers, and set up very basic network handling.

Add generic timers, specifically from my IRC client library.
Add supercool event-driven stuff, specifically from rhuidean again.
Add a basic I/O loop with select().
XMPPServers can accept connections now (mostly proof of concept).
Fix logging so it works on --nofork.
Turn on Ruby warnings on --debug.
Switch the main class' methods to class methods. Whoops.
  • Loading branch information...
1 parent 042f71e commit f45f1ba5f51f96c6cf1ad83f47cb788a1d698d3c @rakaur rakaur committed Aug 25, 2010
Showing with 318 additions and 18 deletions.
  1. +1 −1 bin/kintara
  2. +33 −12 lib/kintara.rb
  3. +116 −0 lib/kintara/event.rb
  4. +3 −3 lib/kintara/loggable.rb
  5. +67 −2 lib/kintara/server.rb
  6. +98 −0 lib/kintara/timer.rb
View
@@ -1,4 +1,4 @@
-#
+#!/usr/bin/env ruby
# kintara: malkier xmpp server
# bin/kintara: instantiates a new +Kintara+ object
#
View
@@ -10,7 +10,7 @@
%w(logger optparse yaml).each { |m| require m }
# Import required application modules
-%w(database server).each { |m| require 'kintara/' + m }
+%w(database timer server).each { |m| require 'kintara/' + m }
# XXX - Since IDN doesn't work in 1.9 yet, we're going to just make sure the
# qualifying strings are set to be encoded as UTF-8.
@@ -40,7 +40,10 @@ class Kintara
@@config = nil
# Application-wide Logger
- @logger = nil
+ @@logger = nil
+
+ # Application-wide time
+ @@time = Time.now.to_f
# A list of our connections
@@servers = []
@@ -99,6 +102,9 @@ def initialize
abort
end
+ # Interpreter warnings
+ $-w = true if debug
+
# Signal handlers
trap(:INT) { app_exit }
trap(:PIPE) { :SIG_IGN }
@@ -123,6 +129,8 @@ def initialize
puts "#{ME}: warning: all streams will be logged in the clear!"
end
+ # XXX - load db
+
# Check to see if we're already running
if File.exists?('var/kintara.pid')
curpid = nil
@@ -160,17 +168,22 @@ def initialize
$stdin.close
$stdout.close
$stderr.close
+
+ # Set up logging
+ @@logger = Logger.new('var/kintara.log', 'weekly') if logging or debug
else
puts "#{ME}: pid #{Process.pid}"
puts "#{ME}: running in foreground mode from #{Dir.getwd}"
+
+ # Set up logging
+ @@logger = Logger.new($stdout) if logging or debug
end
# Write the PID file
Dir.mkdir('var') unless Dir.exists?('var')
File.open('var/kintara.pid', 'w') { |f| f.puts(pid) }
- # Set up logging
- @logger = Logger.new('var/kintara.log', 'weekly') if logging or debug
+ # XXX - timers
# Start the listeners
@@config['listen']['c2s'].split.each do |c2s|
@@ -181,7 +194,7 @@ def initialize
s.port = port
s.type = :c2s
- s.logger = @logger if logging
+ s.logger = @@logger if logging
s.debug = debug
end
end
@@ -192,7 +205,7 @@ def initialize
Thread.list.each { |t| t.join unless t == Thread.main }
- @logger.debug(caller[0].split('/')[-1]) { @@servers }
+ @@logger.debug(caller[0].split('/')[-1]) { @@servers }
# Exiting...
app_exit
@@ -205,23 +218,31 @@ def initialize
public
######
- def config
+ def Kintara.config
@@config
end
- def servers
+ def Kintara.time
+ @@time
+ end
+
+ def Kintara.time=(value)
+ @@time = value
+ end
+
+ def Kintara.servers
@@servers
end
- def add_server(server)
+ def Kintara.add_server(server)
@@servers << server
end
- def clients
+ def Kintara.clients
@@clients
end
- def add_client(client)
+ def Kintara.add_client(client)
@@clients << client
end
@@ -230,7 +251,7 @@ def add_client(client)
#######
def app_exit
- @logger.close if @logger
+ @@logger.close if @@logger
File.delete('var/kintara.pid')
exit
end
View
@@ -0,0 +1,116 @@
+#
+# kintara: malkier xmpp server
+# lib/kintara/event.rb: classes for an event loop
+#
+# Copyright (c) 2003-2010 Eric Will <rakaur@malkier.net>
+#
+# encoding: utf-8
+
+# Contains information about a posted event
+class Event
+
+ ##
+ # instance attributes
+ attr_reader :event, :args
+
+ ##
+ # Creates a new Event.
+ # ---
+ # event:: event name as a Symbol
+ # args:: list of arguments to pass to handler
+ # returns:: +self+
+ #
+ def initialize(event, *args)
+ @event = event
+ @args = args
+ end
+end
+
+# A queue of events, with handlers (one per object)
+class EventQueue
+
+ ##
+ # instance attributes
+ attr_reader :queue, :handlers
+
+ ##
+ # Create a new EventQueue.
+ # ---
+ # returns:: +self+
+ #
+ def initialize
+ @queue = []
+ @handlers = {}
+ end
+
+ ######
+ public
+ ######
+
+ ##
+ # Post a new event to the queue to be handled.
+ # ---
+ # event:: event name as a Symbol
+ # args:: list of arguments to pass to handler
+ # returns:: +self+
+ #
+ def post(event, *args)
+ # Only one post per event per loop, otherwise we end up trying
+ # to read from a socket that has no data, or stuff like that.
+ return if m = @queue.find { |q| q.event == event }
+
+ @queue << Event.new(event, *args)
+
+ self
+ end
+
+ ##
+ # Register a handler for an event.
+ # ---
+ # event:: event name as a Symbol
+ # block:: block to call to handle event
+ # returns:: +self+
+ #
+ def handle(event, &block)
+ (@handlers[event] ||= []) << block
+
+ self
+ end
+
+ ##
+ # Does the event queue have anything in it?
+ # ---
+ # returns:: +true+ or +false+
+ #
+ def needs_ran?
+ @queue.empty? ? false : true
+ end
+
+ ##
+ # Goes through the event queue and runs the handlers.
+ # ---
+ # returns:: +self+
+ #
+ def run
+ needs_exit = false
+
+ while e = @queue.shift
+ next unless @handlers[e.event]
+
+ # If there's an :exit event in the queue wait until we're
+ # all the way done before we handle it.
+ if e.event == :exit
+ needs_exit = e
+ next
+ end
+
+ @handlers[e.event].each { |block| block.call(*e.args) }
+ end
+
+ # Now we can exit... any events that got added by handling routines
+ # just don't happen. This is arguably a bug.
+ @handlers[:exit].each { |b| b.call(*needs_exit.args) } if needs_exit
+
+ self
+ end
+end
View
@@ -7,7 +7,7 @@
# encoding: utf-8
module Loggable
- #
+ ##
# Logs a regular message.
# ---
# message:: the string to log
@@ -17,7 +17,7 @@ def log(message)
@logger.info(caller[0].split('/')[-1]) { message } if @logger
end
- #
+ ##
# Logs a debug message.
# ---
# message:: the string to log
@@ -29,7 +29,7 @@ def debug(message)
@logger.debug(caller[0].split('/')[-1]) { message } if @debug
end
- #
+ ##
# Sets the logging object to use.
# If it quacks like a Logger object, it should work.
# ---
View
@@ -10,7 +10,7 @@
%w(logger socket).each { |m| require m }
# Import required application modules
-%w(kintara/loggable).each { |m| require m }
+%w(event loggable).each { |m| require 'kintara/' + m }
class XMPPServer
# Add the logging methods.
@@ -21,6 +21,10 @@ class XMPPServer
attr_reader :socket
attr_writer :port, :bind_to, :type, :debug
+ # A simple Exception class for some errors
+ class Error < Exception
+ end
+
##
# Creates a new XMPPServer that listens for client connections.
# If given a block, it passes itself to the block for pretty
@@ -30,6 +34,9 @@ def initialize
# Is our socket dead?
@dead = false
+ # Our event queue
+ @eventq = EventQueue.new
+
# Our Logger object
self.logger = Logger.new($stderr)
@@ -54,14 +61,72 @@ def initialize
debug("#@type server successfully listening at #@bind_to:#@port")
end
+ # Set up event handlers
+ set_default_handlers
+
self
end
+ #######
+ private
+ #######
+
+ ##
+ # Sets up some default event handlers to track various states and such.
+ # ---
+ # returns:: +self+
+ #
+ def set_default_handlers
+ @eventq.handle(:new_connection) { new_connection }
+ end
+
+ def new_connection
+ newsock = @socket.accept_nonblock
+
+ # This is to get around some silly IPv6 stuff.
+ host = newsock.peeraddr[3].sub('::ffff:', '')
+
+ # XXX - client object, etc
+ debug("established new connection for #{host}")
+ end
+
######
public
######
+ def dead?
+ @dead
+ end
+
def io_loop
- sleep(10)
+ loop do
+ if dead? # XXX - restart listener, or handle as event...
+ log("listener is dead")
+ break
+ end
+
+ # Update the current time
+ Kintara.time = Time.now.to_f
+
+ # Run the event loop. These events will add IO, and possibly other
+ # events, so we keep running until it's empty.
+ @eventq.run while @eventq.needs_ran?
+
+ readfds = [@socket]
+ writefds = []
+ errorfds = []
+
+ # XXX - add clients to readfds for waiting, writefds for sendq
+
+ ret = IO.select(readfds, writefds, errorfds, 0)
+
+ next unless ret
+ next if ret[0].empty? # XXX
+
+ # XXX - socket events
+ ret[0].each do |s|
+ @eventq.post(:new_connection) if s == @socket
+ end
+ end
end
end
Oops, something went wrong.

0 comments on commit f45f1ba

Please sign in to comment.