tmud-3.0.0/benchmark/
tmud-3.0.0/cmd/
tmud-3.0.0/cmd/objects/
tmud-3.0.0/cmd/tiny/
tmud-3.0.0/doc/SQLite/
tmud-3.0.0/doc/SQLite3/
tmud-3.0.0/doc/TernaryTrie/
tmud-3.0.0/farts/
tmud-3.0.0/lib/
tmud-3.0.0/lib/engine/
tmud-3.0.0/lib/farts/
tmud-3.0.0/logs/
#
# file::    connection.rb
# author::  Jon A. Lambert
# version:: 2.8.0
# date::    01/19/2006
#
# This source code copyright (C) 2005, 2006 by Jon A. Lambert
# All rights reserved.
#
# Released under the terms of the TeensyMUD Public License
# See LICENSE file for additional information.
#
$:.unshift "lib" if !$:.include? "lib"
$:.unshift "vendor" if !$:.include? "vendor"

require 'network/session'
require 'network/sockio'
require 'network/lineio'
require 'network/packetio'
require 'network/protocol/protocolstack'

# The connection class maintains a socket connection with a
# reactor and handles all events dispatched by the reactor.
#
class Connection < Session
  logger 'DEBUG'
  attr_reader :server, :initdone, :pstack, :sockio, :addr, :host
  attr_accessor :inbuffer, :outbuffer # filters need to this in charmode

  # Create a new connection object
  # [+server+]  The reactor this connection is associated with.
  # [+sock+]    The socket for this connection.
  # [+returns+] A connection object.
  def initialize(server, sock)
    super(server, sock)
    case @server.service_io
    when :lineio
      @sockio = LineIO.new(@sock)
    when :packetio
      @sockio = PacketIO.new(@sock)
    else
      @sockio = SockIO.new(@sock)
    end
    @inbuffer = ""              # buffer lines waiting to be processed
    @outbuffer = ""             # buffer lines waiting to be output
    if @server.service_filters.include? :telnetfilter
      @initdone = false           # keeps silent until we're done with negotiations
    else
      @initdone = true
    end
    @pstack = ProtocolStack.new(self)
  end

  # init is called before using the connection.
  # [+returns+] true is connection is properly initialized
  def init
    @host, @addr = @sock.peeraddr.slice(2..3)
    @connected = true
    @server.register(self)
    log.info "(#{self.object_id}) New Connection on '#{@host}(#{@addr})'"
    @pstack.filter_call(:init,nil)
    true
  rescue Exception
    log.error "(#{self.object_id}) Connection#init"
    log.error $!
    false
  end

  # handle_input is called to order a connection to process any input
  # waiting on its socket.  Input is parsed into lines based on the
  # occurance of the CRLF terminator and pushed into a buffer
  # which is a list of lines.  The buffer expands dynamically as input
  # is processed.  Input that has yet to see a CRLF terminator
  # is left in the connection's inbuffer.
  def handle_input
    buf = @sockio.read
    raise(EOFError) if !buf || buf.empty?
    buf = @pstack.filter_call(:filter_in,buf)
    if @server.service_io == :packetio ||
       @server.service_type == :client
      publish(buf)
    else
      @inbuffer << buf
      if @initdone  # Just let buffer fill until we indicate we're done
                    # negotiating.  Set by calling initdone from TelnetFilter
        while p = @inbuffer.index("\n")
          ln = @inbuffer.slice!(0..p).chop
          publish(ln)
        end
      end
    end
  rescue EOFError, Errno::ECONNRESET, Errno::ECONNABORTED
    @closing = true
    publish(:disconnected)
    unsubscribe_all
    log.info "(#{self.object_id}) Connection '#{@host}(#{@addr})' disconnecting"
    log.error $!
  rescue Exception
    @closing = true
    publish(:disconnected)
    unsubscribe_all
    log.error "(#{self.object_id}) Connection#handle_input disconnecting"
    log.error $!
  end

  # handle_output is called to order a connection to process any output
  # waiting on its socket.
  def handle_output
    @outbuffer = @pstack.filter_call(:filter_out,@outbuffer)
    done = @sockio.write(@outbuffer)
    @outbuffer = ""
    if done
      @write_blocked = false
    else
      @write_blocked = true
    end
  rescue EOFError, Errno::ECONNABORTED, Errno::ECONNRESET
    @closing = true
    publish(:disconnected)
    unsubscribe_all
    log.info "(#{self.object_id}) Connection '#{@host}(#{@addr})' disconnecting"
  rescue Exception
    @closing = true
    publish(:disconnected)
    unsubscribe_all
    log.error "(#{self.object_id}) Connection#handle_output disconnecting"
    log.error $!
  end

  # handle_close is called to when an close event occurs for this session.
  def handle_close
    @connected = false
    publish(:disconnected)
    unsubscribe_all
    log.info "(#{self.object_id}) Connection '#{@host}(#{@addr})' closing"
    @server.unregister(self)
#    @sock.shutdown   # odd errors thrown with this
    @sock.close
  rescue Exception
    log.error "(#{self.object_id}) Connection#handle_close closing"
    log.error $!
  end

  # handle_oob is called when an out of band data event occurs for this
  # session.
  def handle_oob
    buf = @sockio.read_urgent
    log.debug "(#{self.object_id}) Connection urgent data received - '#{buf[0]}'"
    @pstack.set(:urgent, true)
    buf = @pstack.filter_call(:filter_in,buf)
  rescue Exception
    log.error "(#{self.object_id}) Connection#handle_oob"
    log.error $!
  end

  # This is called from TelnetFilter when we are done with negotiations.
  # The event :initdone wakens observer to begin user activity
  def set_initdone
    @initdone = true
    publish(:initdone)
  end


  # Update will be called when the object the connection is observing
  # wants to notify us of a change in state or new message.
  # When a new connection is accepted in acceptor that connection
  # is passed to the observer of the acceptor which allows the client
  # to attach an observer to the connection and make the connection
  # an observer of that object.  We need to keep both sides interest
  # in each other limited to a narrow but flexible interface to
  # prevent tight coupling.
  #
  # This supports the following:
  # [:quit] - This symbol message from the client is a request to
  #               close the Connection.  It is handled here.
  # [String] - A String is assumed to be output and placed in our
  #            @outbuffer.
  def update(msg)
    case msg
    when :quit
      handle_output
      @closing = true
    when :reconnecting
      unsubscribe_all
      log.info "(#{self.object_id}) Connection '#{@host}(#{@addr})' closing for reconnection"
      @server.unregister(self)
  #    @sock.shutdown   # odd errors thrown with this
      @sock.close
    when String
      sendmsg(msg)
    else
      log.error "(#{self.object_id}) Connection#update - unknown message '#{@msg.inspect}'"
    end
  rescue
    # We squash and print out all exceptions here.  There is no reason to
    # throw these back at out subscribers.
    log.error $!
  end

  # [+attrib+] - A Symbol not handled here is assumed to be a query and
  #           its handling is delegated to the ProtocolStack, the result
  #           of which is a pair immediately sent back to as a message
  #           to the client.
  #
  #         <pre>
  #         client -> us
  #             :echo
  #         us     -> ProtocolStack
  #             query(:echo)
  #         ProtocolStack -> us
  #             [:echo, true]
  #         us -> client
  #             [:echo, true]
  #         </pre>
  #
  def query(attrib)
    @pstack.query(attrib)
  end

  # [+attrib,val+] - An Array not handled here is assumed to be a set command and
  #           its handling is delegated to the ProtocolStack.
  #
  #         <pre>
  #         client -> us
  #             [:color, true]
  #         us     -> ProtocolStack
  #             set(:color, true)
  #         </pre>
  #
  def set(attrib, val)
    @pstack.set(attrib, val)
  end


  # sendmsg places a message on the Connection's output buffer.
  # [+msg+]  The message, a reference to a buffer
  def sendmsg(msg)
    @outbuffer << msg
    @write_blocked = true  # change status to write_blocked
  end

end