Author: nornagon
Date: 2005-07-05 03:48:19 -0400 (Tue, 05 Jul 2005)
New Revision: 830
Added:
trunk/clients/ravish/eventmanager.rb
trunk/clients/ravish/haver.rb
trunk/clients/ravish/haver/
trunk/clients/ravish/haver/net.rb
trunk/clients/ravish/ravish2.rb
Modified:
trunk/clients/ravish/ravish.rb
Log:
Ravish broken; redone as Ravish2. Ruby haver lib much better and cleaner.
Ravish2 has no UI yet.
Added: trunk/clients/ravish/eventmanager.rb
===================================================================
--- trunk/clients/ravish/eventmanager.rb 2005-06-26 04:37:26 UTC (rev
829)
+++ trunk/clients/ravish/eventmanager.rb 2005-07-05 07:48:19 UTC (rev
830)
@@ -0,0 +1,214 @@
+# EventManager - ruby event loop
+# Copyright (c) 2005 Bryan J. Donlan and Jeremy C. Apthorp
+# This program is FREE SOFTWARE.
+# You may edit and redistribute it under the same terms as ruby.
+
+require 'singleton'
+
+class EventManager
+ include Singleton
+
+ def initialize
+ # fd watch hashes
+ # @fd{mode}[fd] = { handle -> code }
+ @fdread = {}
+ @fdwrite = {}
+ @fderror = {}
+ # revtok[handle] = fd
+ @revtok = {}
+ # timers = [[fd, code, handle], ...]
+ @timers = []
+ # timertok[handle] = ref to [fd, code, handle] in timers
+ @timertok = {}
+ # token is the next handle returned by the watch functions
+ @token = 0
+ @pipe = IO.pipe
+ end
+
+ def jolt
+ @pipe[1].write 'a'
+ end
+
+ # Check and block for events, once
+ def runonce
+ nexttimer = self.checktimers
+
+ readhand = @fdread.keys + [EMAIL PROTECTED]
+ writehand = @fdwrite.keys
+ errorhand = @fderror.keys
+
+ ret = select(readhand, writehand, errorhand, nexttimer)
+ if !ret then ret = [[],[],[]] end
+
+ ret[0].delete @pipe[0]
+
+ ret[0].each { |fd| @fdread[fd].values.each { |c| c.call fd } }
+ ret[1].each { |fd| @fdwrite[fd].values.each { |c| c.call fd } }
+ ret[2].each { |fd| @fderror[fd].values.each { |c| c.call fd } }
+
+ self.checktimers
+ end
+
+ # Check for any timers which should have gone off by now.
+ # Dispatch the ones that have, then return the time remaining
+ # until the next timer, or nil if no timers remain
+ def checktimers
+ now = Time.now.to_f
+ while (@timers.length > 0)
+ if @timers[0][0] < now
+ unless @timers[0][1].nil?
+ # pass it the time...
+ @timers[0][1].call @timers[0][0]
+ end
+ @timertok.delete @timers[0][2]
+ @timers.shift
+ else
+ break
+ end
+ end
+ if (@timers.length > 0)
+ return @timers[0][0].to_f - now
+ else
+ return nil
+ end
+ end
+
+ # Schedule lambda f to be called in t seconds. Return an opaque value
+ # which may be passed to canceltimer.
+ def in t, f
+ self.at(Time.now.to_f + t.to_f, f)
+ end
+
+ # Schedule lambda f to be called at the absolute time t. Return
+ # an opaque value which may be passed to canceltimer.
+ def at t, f
+ mytok = @token
+ @token = @token + 1
+ pair = [t, f, mytok]
+ if @timers.empty?
+ @timers.push pair
+ else
+ # Hopefully, this will usually be faster than push/sort
+ idx = nil
+ @timers.each_index { |i|
+ t2 = @timers[i][0]
+ if idx.nil? && t2 > t
+ idx = i
+ end
+ }
+ if idx.nil?
+ @timers.push pair
+ else
+ @timers.insert idx, pair
+ end
+ end
+ @timertok[mytok] = pair
+ jolt
+ return mytok
+ end
+
+ # Cancel a timer indicated by tok, which should be a value returned by
+ # at or in
+ def canceltimer tok
+ pair = @timertok[tok]
+ unless pair.nil?
+ pair[1] = nil
+ @timertok.delete tok
+ jolt
+ end
+ end
+
+ def watchgeneral arr, fd, code
+ if (!arr[fd])
+ # We can't generate empty values here
+ # as that'd create spurious fds for the select
+ # in runonce
+ arr[fd] = Hash.new
+ end
+ mytok = @token
+ @token = @token + 1
+ arr[fd][mytok] = code
+ @revtok[mytok] = [arr, fd]
+ jolt
+ return mytok
+ end
+
+ # Watch a file descriptor for a readable condition. When it is
+ # readable call the lambda code. Returns an opaque value suitable
+ # for cancelwatch.
+ def watchread fd, code
+ watchgeneral @fdread, fd, code
+ end
+
+ # Watch a file descriptor for a writable condition. When it is
+ # writable call the lambda code. Returns an opaque value suitable
+ # for cancelwatch.
+ def watchwrite fd, code
+ watchgeneral @fdwrite, fd, code
+ end
+
+ # Watch a file descriptor for an error condition. When an error
+ # condition exists call the lambda code. Returns an opaque value
+ # suitable for cancelwatch.
+ def watcherror fd, code
+ watchgeneral @fderror, fd, code
+ end
+
+ def unwatchgeneral arr, fd
+ unless arr[fd].nil?
+ arr[fd].keys.each { |h| @revtok.delete h }
+ end
+ arr.delete fd
+ end
+
+ # Cancel all readable condition watches on fd
+ def unwatchread fd
+ unwatchgeneral @fdread, fd
+ end
+ # Cancel all writable condition watches on fd
+ def unwatchwrite fd
+ unwatchgeneral @fdwrite, fd
+ end
+ # Cancel all error condition watches on fd
+ def unwatcherror fd
+ unwatchgeneral @fderror, fd
+ end
+
+ # Cancel all watches on fd
+ def unwatch fd
+ unwatchread fd
+ unwatchwrite fd
+ unwatcherror fd
+ end
+
+ # Given handle, a value returned from one of the watch* functions,
+ # cancel only that watch, leaving other watches on that fd
+ # unaffected
+ def cancelwatch handle
+ pair = @revtok.delete handle
+ unless pair.nil?
+ arr = pair[0]
+ fd = pair[1]
+ arr[fd].delete handle
+ if (arr[fd].empty?)
+ arr.delete fd
+ end
+ end
+ end
+
+ # Run the event loop until stop is called
+ def run
+ @stopme = false
+ while ([EMAIL PROTECTED])
+ self.runonce
+ end
+ end
+
+ # Exits the loop started with run.
+ def stop
+ @stopme = true
+ end
+end
+
+# vim: set noet ts=4 sw=4 :
+
Added: trunk/clients/ravish/haver/net.rb
===================================================================
--- trunk/clients/ravish/haver/net.rb 2005-06-26 04:37:26 UTC (rev 829)
+++ trunk/clients/ravish/haver/net.rb 2005-07-05 07:48:19 UTC (rev 830)
@@ -0,0 +1,121 @@
+# ROOM::Net
+# Copyright (c) 2005 Jeremy Campbell Apthorp
+# This program is FREE SOFTWARE.
+# You may edit and redistribute it under the same terms as ruby.
+
+require 'socket'
+require 'io/nonblock'
+require 'observer'
+
+module Haver
+ class Net
+ include Observable
+ BufSize = 64
+
+ attr_reader :socket
+
+ def initialize socket, observers=[]
+ if !socket.kind_of?(TCPSocket)
+ raise ArgumentError,
+ "#{self.class}.new takes a TCPSocket as
an argument."
+ end
+ if observers.kind_of?(Array)
+ observers.each { |observer|
+ if observer.kind_of? Array
+ self.add_observer observer[0],
observer[1]
+ else
+ self.add_observer observer
+ end
+ }
+ end
+ @socket = socket
+ socket.nonblock = true
+
+ @buffer = ''
+ @outbox = Queue.new
+ @closed = false
+
+ @rthread = Thread.new { rthread }
+ @wthread = Thread.new { wthread }
+ end
+
+ def put string
+ if @closed
+ return
+ end
+ @outbox.push string
+ self
+ end
+ alias_method :<<, :put
+
+ def rthread
+ loop {
+ select([EMAIL PROTECTED])
+ doread
+ }
+ end
+
+ def wthread
+ loop {
+ m = @outbox.pop
+ select(nil, [EMAIL PROTECTED])
+ dowrite m
+ }
+ end
+
+ def doread
+ if @socket.closed? || @closed
+ raise "Tried to read from closed socket."
+ end
+ if b = @socket.gets
+ b.each("\r\n") { |l|
+ if l =~ /\r\n$/
+ if @buffer.empty?
+ event :data, l.chomp
+ else
+ event :data, @buffer +
l.chomp
+ @buffer = ''
+ end
+ else
+ @buffer += l
+ end
+ }
+ end
+ end
+
+ def dowrite m
+ if @socket.closed? or @closed
+ return
+ end
+ b = 0
+ begin
+ b += @socket.write(m)
+ end while b < m.length
+ end
+
+ def event type, *args
+ changed
+ notify_observers type, *args
+ end
+ private :event
+
+ def close
+ # TODO: defer until flush maybe?
+ unless write_ready?
+ EventManager.instance.unwatch self
+ @socket.close
+ end
+ @closed = true
+ end
+ def closed?; @socket.closed?; end
+ def eof?; @socket.eof?; end
+
+ def write_ready?
+ [EMAIL PROTECTED]
+ end
+
+ def to_io
+ @socket
+ end
+ end
+end
Added: trunk/clients/ravish/haver.rb
===================================================================
--- trunk/clients/ravish/haver.rb 2005-06-26 04:37:26 UTC (rev 829)
+++ trunk/clients/ravish/haver.rb 2005-07-05 07:48:19 UTC (rev 830)
@@ -0,0 +1,61 @@
+require 'observer'
+require 'eventmanager'
+
+DEBUG = 2
+
+module Haver
+ class Client
+ include Observable
+
+ def initialize server, port=7070, hash={}
+ @server = server.to_s
+ @port = port.to_i
+ @conf = hash.to_hash
+ end
+
+ def start
+ event :connecting
+ Thread.new {
+ sock = TCPSocket.new(@server, @port)
+ connect sock
+ }
+ end
+
+ def connect sock
+ begin
+ @sock = Haver::Net.new sock, self
+ rescue StandardError, SocketError => e
+ event :link_failed, e
+ end
+ @sock.add_observer self
+ event :connected
+ msg 'HAVER', 'ravish/0.1.0'
+ end
+
+ def msg *args
+ m = args.join("\t") + "\r\n"
+ puts "C: #{m}" if DEBUG >= 2
+ @sock.put m
+ end
+
+ def update type, *args
+ if type == :data
+ handle_line args[0]
+ end
+ end
+
+ def handle_line line
+ puts "S: #{line}" if DEBUG >= 2
+ message = line.split(/\t/)
+ cmd = message.shift
+ event ("ev_" + cmd).to_sym, message
+ end
+
+ def event type, *args
+ changed
+ notify_observers type, *args
+ end
+ end
+end
+
+require 'haver/net'
Modified: trunk/clients/ravish/ravish.rb
===================================================================
--- trunk/clients/ravish/ravish.rb 2005-06-26 04:37:26 UTC (rev 829)
+++ trunk/clients/ravish/ravish.rb 2005-07-05 07:48:19 UTC (rev 830)
@@ -84,14 +84,6 @@
$vt.bind('A-O', switch_window)
module Haver
- module Event
- CONNECTING = 0
- CONNECTED = 1
- MESG_RECV = 2
- DISCONNECTING = 3
- DISCONNECTED = 4
- end
-
class Connector
include Observable
@@ -119,11 +111,11 @@
return false
end
- notify Event::CONNECTING
+ notify :CONNECTING
begin
@socket = TCPSocket.new(@server, @port)
rescue StandardError, SocketError
- notify Event::LINK_FAILED, $!
+ notify :LINK_FAILED, $!
return false
end
@socket.nonblock = true
@@ -136,12 +128,12 @@
self.writetosocket
end
- notify Event::CONNECTED
+ notify :CONNECTED
return true
end
def close
- notify Event::DISCONNECTING
+ notify :DISCONNECTING
if [EMAIL PROTECTED] and @recvthread.alive?
@recvthread.exit
@@ -153,7 +145,7 @@
@socket.close unless @socket.closed?
- notify Event::DISCONNECTED
+ notify :DISCONNECTED
end
def alive?
@@ -176,14 +168,14 @@
end
def read
- @recvqueue.pop
+ r = @recvqueue.pop
end
protected
- def notify event
+ def notify event, *args
changed
- notify_observers event, Time.now
+ notify_observers event, Time.now, args
end
def readfromsocket
@@ -201,7 +193,7 @@
Thread.pass
end
@recvqueue.push(buf.slice!(/^(.+?)\r\n/).chomp)
- notify Event::MESG_RECV
+ notify :MESG_RECV
end
rescue StandardError, SocketError, IOError
break
@@ -223,6 +215,7 @@
if res then sent +=
@socket.write msg end
Thread.pass
end until sent == msg.length
+ p msg
rescue StandardError, SocketError, IOError
break
end
@@ -246,36 +239,42 @@
@conn.nick
end
- def update event, time
+ def update event, time, args
case event
- when Event::CONNECTING
+ when :CONNECTING
if self.respond_to?(:on_connecting)
self.on_connecting
end
- when Event::CONNECTED
+ when :CONNECTED
if self.respond_to?(:on_connected)
self.on_connected
end
- when Event::DISCONNECTING
+ when :DISCONNECTING
if self.respond_to?(:on_disconnecting)
self.on_disconnecting
end
- when Event::DISCONNECTED
+ when :DISCONNECTED
if self.respond_to?(:on_disconnected)
self.on_disconnected
end
- when Event::MESG_RECV
+ when :LINK_FAILED
+ if self.respond_to?(:on_link_failed)
+ self.on_link_failed args[0]
+ end
+ when :MESG_RECV
mesg = @conn.read
- args = mesg.split("\t")
- cmd = args.shift
- fun = ("on_cmd_" + cmd.downcase).to_sym
+ margs = mesg.split("\t")
+ mcmd = args.shift
+ p margs
+ fun = ("on_cmd_" + mcmd.downcase).to_sym
if self.respond_to?(fun)
- self.send fun, *args
+ p "fun #{fun}"
+ self.send fun, *margs
elsif self.respond_to?(:on_unhandled_cmd)
- self.send :on_unhandled_cmd, cmd, args
+ self.send :on_unhandled_cmd, mcmd, margs
end
if self.respond_to?(:on_mesg_recv)
- self.send :on_mesg_recv, cmd, args
+ self.send :on_mesg_recv, mcmd, margs
end
end
end
@@ -284,11 +283,9 @@
@conn.write ['HAVER', 'ravish/0.0.1']
end
- def on_cmd_want cmd
- case cmd
- when 'IDENT'
- @conn.write ['IDENT', @conn.nick]
- end
+ def on_cmd_haver *args
+ p args
+ @conn.write ['IDENT', @conn.nick]
end
def on_cmd_fail cmd, reason, *args
@@ -338,6 +335,10 @@
super
end
+ def on_link_failed e
+ $vt.message "Link failed: #{e}"
+ end
+
def on_mesg_recv cmd, args
$vt.message "S: #{cmd} #{args.join(' ')}"
end
@@ -458,7 +459,7 @@
# event loop!
-$haver = HaverConnection.new('hardison.net')
+$haver = HaverConnection.new('localhost')
loop do
cwin = $windows.find { |w| w[1] == $vt.current_window }
Added: trunk/clients/ravish/ravish2.rb
===================================================================
--- trunk/clients/ravish/ravish2.rb 2005-06-26 04:37:26 UTC (rev 829)
+++ trunk/clients/ravish/ravish2.rb 2005-07-05 07:48:19 UTC (rev 830)
@@ -0,0 +1,32 @@
+# ravish -- Haver client written in ruby. Second attempt.
+
+$:.unshift "/home/nornagon/dev/termvisual/lib"
+
+require 'term/visual'
+require 'haver'
+
+Thread.abort_on_exception = true
+
+class Ravish
+ def initialize server, port=7070
+ @term = Term::Visual.new
+ @haver = Haver::Client.new server
+ @haver.add_observer self
+ @haver.start
+ end
+
+ def update type, *args
+ case type
+ when :connecting
+ puts 'connecting'
+ when :connected
+ puts 'connected'
+ when :ev_HAVER
+ @haver.msg 'IDENT', 'nornagon'
+ end
+ end
+end
+
+ravish = Ravish.new 'hardison.net'
+
+EventManager.instance.run