X-Git-Url: http://git.pond.sub.org/?p=eow;a=blobdiff_plain;f=empire.lisp;h=edb8316846ed5ede51dadfbd2b9eac86c7a77118;hp=fa088df853f29dc7613639fa036a139ce82ddcab;hb=d0df339544e58ba26876518ca78eedc1ebb72583;hpb=f457ab38fddc9a587b37b3d97b8bcc19d5c412cb diff --git a/empire.lisp b/empire.lisp index fa088df..edb8316 100644 --- a/empire.lisp +++ b/empire.lisp @@ -33,16 +33,22 @@ :initarg :stream :accessor network-stream :documentation "Stream used to talk to the empire server.") - (logging-stream - :initarg :logging-stream - :accessor logging-stream - :initform t - :documentation "Messages coming back from the server is sent to -this stream.") (connection-handler :accessor connection-handler :initform nil - :documentation "Handler function for incoming messages"))) + :documentation "Handler function for incoming messages") + (mode + :accessor connection-mode + :initform nil + :documentation "TODO(gefla)") + (session + :accessor session + :initarg :session + :documentation "web session to which this connection belongs") + (send-queue + :accessor send-queue + :initform (locked-queue:create) + :documentation "lines outstanding to be sent at next prompt"))) (defun make-connection (&key (user nil) (password nil) @@ -50,7 +56,7 @@ this stream.") (server-port *default-empire-server-port*) (socket nil) (network-stream nil) - (logging-stream nil)) + session) (make-instance 'connection :user user :password password @@ -58,14 +64,14 @@ this stream.") :server-port server-port :socket socket :stream network-stream - :logging-stream logging-stream)) + :session session)) ;;; interface (defun connect (&key (user nil) (password nil) (server-name *default-empire-server*) (server-port *default-empire-server-port*) - (logging-stream *standard-output*)) + session) "Connect to server and return a connection object." (let* ((socket (usocket:socket-connect server-name server-port)) (stream (usocket:socket-stream socket)) @@ -73,19 +79,34 @@ this stream.") :password password :socket socket :network-stream stream - :logging-stream logging-stream :server-name server-name - :server-port server-port)) + :server-port server-port + :session session)) (handler (spawn-with-name (format nil "empire-handler-~a" user) #'handle-connection connection))) (setf (connection-handler connection) handler) + (empire-log:info "~a: connect" connection) connection)) - (defgeneric quit (connection)) (defgeneric handle-connection (connection)) (defgeneric read-message (connection)) -(defgeneric send-message (connection message)) +(defgeneric send-message (connection message &key next-mode)) +(defgeneric connected-p (connection)) +(defgeneric reconnect (conncetion)) + +(defmethod connected-p ((c connection)) + (sb-thread:thread-alive-p (connection-handler c))) + +(defmethod reconnect ((c connection)) + (with-slots (server-name server-port user password socket stream connection-handler) c + (let* ((sock (usocket:socket-connect server-name server-port)) + (s (usocket:socket-stream sock))) + (setf socket sock + stream s + connection-handler (spawn-with-name (format nil "empire-handler-~a" user) + #'handle-connection c)))) + (empire-log:info "~a: reconnect" c)) (defparameter +C_CMDOK+ "0") (defparameter +C_DATA+ "1") @@ -93,7 +114,7 @@ this stream.") (defparameter +C_EXIT+ "3") (defparameter +C_FLUSH+ "4") (defparameter +C_NOECHO+ "5") -(defparameter +C_PROMPT+ "6") +(defparameter +C_PROMPT+ "6") (defparameter +C_ABORT+ "7") (defparameter +C_REDIR+ "8") (defparameter +C_PIPE+ "9") @@ -103,13 +124,118 @@ this stream.") (defparameter +C_FLASH+ "d") (defparameter +C_INFORM+ "e") -(defgeneric init-mode (connection message)) -(defmethod init-mode ((c connection) message) +(defclass base-mode () + ((connection :initarg :connection :accessor connection))) +(defclass init-mode (base-mode) + ((phase :initform :initial + :documentation "Initialization phase"))) +(defclass play-mode (base-mode) nil) + +(defun make-mode (connection mode) + (make-instance mode :connection connection)) + +(defgeneric set-new-mode (base-mode new-mode)) +(defmethod set-new-mode ((m base-mode) new-mode) + (let* ((c (connection m)) + (mode (make-mode c new-mode))) + (empire-log:info "~a: set-new-mode ~a -> ~a" c (connection-mode c) new-mode) + (setf (connection-mode c) mode))) + +(defgeneric handle-cmdok (base-mode message)) +(defgeneric handle-data (base-mode message)) +(defgeneric handle-init (base-mode message)) +(defgeneric handle-exit (base-mode message)) +(defgeneric handle-prompt (base-mode message)) +(defgeneric handle-flush (base-mode message)) +(defgeneric handle-simple-message (base-mode message)) +(defgeneric handle-ignore (base-mode message)) + +(defmethod handle-init ((m init-mode) message) + (declare (ignorable message)) + (with-slots (phase connection) m + (ecase phase + (:initial (handle-cmdok m message)) + (:play-sent (set-new-mode m 'play-mode))))) + +(defmethod handle-cmdok ((m init-mode) message) + (declare (ignorable message)) + (with-slots (phase connection) m + (flet ((init-phase (mode send-args next-phase) + (with-slots (phase connection) mode + (send-message-one connection (apply #'format nil send-args)) + (setf phase next-phase)))) + (with-slots (user password) (connection m) + (ecase phase + (:initial (init-phase m `("client eow ~a" ,*version*) :client-sent)) + (:client-sent (init-phase m `("coun ~a" ,user) :coun-sent)) + (:coun-sent (init-phase m `("pass ~a" ,password) :pass-sent)) + (:pass-sent (init-phase m '("play") :play-sent))))))) + +(defmethod handle-exit ((m init-mode) message) + (declare (ignorable message)) + (with-slots (phase connection) m + (ecase phase + (:play-sent (send-message-one connection "kill") + (setf phase :kill-sent)) + (:kill-sent (send-message-one connection "play") + (setf phase :play-sent))))) + +(defmethod handle-data ((m init-mode) message) (declare (ignorable message)) - (play c)) + t) + +(defmethod handle-exit ((m play-mode) message) + (declare (ignorable message)) + (quit (connection m))) + +(defmethod handle-data ((m play-mode) message) + (let* ((c (connection m))) + (empire-web:data (session c) message))) + +(defmethod handle-simple-message ((m play-mode) message) + (let* ((c (connection m))) + (empire-web:data (session c) message))) -(defvar *line-type-dispatch* - (list `(,+C_INIT+ . init-mode))) +(defmethod handle-flush ((m play-mode) message) + (let* ((c (connection m))) + (empire-web:prompt (session c) message) + (send-next-line c))) + +(defmethod send-next-line ((c connection)) + (let ((next-event (locked-queue:dequeue (send-queue c)))) + (etypecase next-event + (string (send-message-one c next-event)) + (cons (let ((message (car next-event)) + (mode (cdr next-event))) + (send-message-one c message) + (set-new-mode (connection-mode c) mode)))))) + +(defun read-no-eval (stream) + (let ((*read-eval* nil)) + (read stream))) + +(defmethod handle-prompt ((m play-mode) message) + (let* ((c (connection m))) + (with-input-from-string (s message) + (let* ((minutes (read-no-eval s)) + (btus (read-no-eval s))) + (empire-web:prompt (session c) + (format nil "[~a,~a]: " minutes btus)))) + (send-next-line c))) + +(defmethod handle-ignore ((m init-mode) message) + (declare (ignorable message)) + t) + +(defparameter *line-type-dispatch* + (list `(,+C_DATA+ . handle-data) + `(,+C_INIT+ . handle-init) + `(,+C_EXIT+ . handle-exit) + `(,+C_FLUSH+ . handle-flush) + `(,+C_BADCMD+ . handle-simple-message) + `(,+C_FLASH+ . handle-simple-message) + `(,+C_CMDOK+ . handle-cmdok) + `(,+C_PROMPT+ . handle-prompt))) (defun parse-server-line (line) (let* ((first-space-index @@ -117,43 +243,120 @@ this stream.") if (eq (char line i) #\Space) do (return i))) (message-type (subseq line 0 first-space-index)) - (message (subseq line (+ first-space-index 1)))) + (message (if first-space-index + (subseq line (+ first-space-index 1)) + ""))) (values message message-type))) (defmethod handle-connection ((c connection)) + (setf (connection-mode c) (make-mode c 'init-mode)) (loop while (read-message c))) (defun lookup-handler (message-type) (cdr (assoc message-type *line-type-dispatch* :test 'string=))) +(define-condition no-handler (error) + ((mode :initarg :mode) + (type :initarg :type)) + (:report (lambda (condition stream) + (with-slots (mode type) condition + (format stream "No handler for ~a in mode ~a." + type mode))))) (defmethod read-message ((c connection)) - (let* ((s (network-stream c)) - (log (logging-stream c)) - (line (read-line s))) - (format log "< ~a~%" line) - (multiple-value-bind (message type) (parse-server-line line) - (let ((handler (lookup-handler type))) - (funcall handler c message))) - line)) - -(defmethod send-message ((c connection) message) - (let ((s (network-stream c)) - (log (logging-stream c))) - (format log "< ~a~%" message) + (handler-case + (let* ((s (network-stream c)) + (line (read-line s))) + (empire-log:info "~a: < ~a" c line) + (multiple-value-bind (message type) (parse-server-line line) + (let ((handler (lookup-handler type)) + (mode (connection-mode c))) + (if handler + (funcall handler mode message) + (error 'no-handler :mode mode :type type)))) + line) + (sb-int:closed-stream-error () nil) + (sb-int:simple-stream-error () nil) + (end-of-file () nil))) + +(defun raw-send-message (s message) (write-sequence message s) (terpri s) - (force-output s) - message)) + (force-output s)) -(defgeneric play (connection)) -(defmethod play ((c connection)) - (with-slots (user password) c - (send-message c (format nil "play FIXME ~a ~a" user password)))) +(defgeneric send-message-one (connection string)) +(defmethod send-message-one ((c connection) message) + (empire-log:info "~a: > ~a" c message) + (let ((s (network-stream c))) + (raw-send-message s message))) -(defmethod quit ((c connection)) - (usocket:socket-close (socket c))) +(defmethod send-message ((c connection) message &key next-mode) + (if (not (connected-p c)) + (reconnect c)) + (if next-mode + (locked-queue:enqueue (send-queue c) (cons message next-mode)) + (locked-queue:enqueue (send-queue c) message))) +(defparameter +special-command-char+ #\;) -;;; Tests -(defvar *l1* "2 Empire server ready") -;; (parse-server-line *l1*) +(defun special-command-p (line) + (and (> (length line) 0) + (char= +special-command-char+ (aref line 0)))) + +(defclass xdump-mode (play-mode) + ((parser :initform (xdump:make-parser)) + (phase :initform :meta-meta) + (dump-queue :initform nil) + (dump-index :initform 0))) + +(defmethod handle-data ((m xdump-mode) message) + (with-slots (connection parser phase dump-queue dump-index) m + (if (xdump:parse-line parser message) + ;;XXX consider something like a 'pop-mode function + (ccase phase + (:meta-meta + (setf phase :meta-table + parser (xdump:make-parser)) + (send-message connection "xdump meta table")) + (:meta-table + (setf phase :table-table + parser (xdump:make-parser)) + (send-message connection "xdump table *")) + (:table-table + (setf phase :tables-meta + dump-queue (xdump::table-entries (xdump::get-table "table")) + dump-index 0 + parser (xdump:make-parser)) + (send-message connection (format nil "xdump meta ~a" (xdump-data::table-name (aref dump-queue 0))))) + (:tables-meta + (setf phase :tables-content + parser (xdump:make-parser)) + (send-message connection (format nil "xdump ~a *" (xdump-data::table-name (aref dump-queue dump-index))))) + (:tables-content + (if (< dump-index (fill-pointer dump-queue)) + (progn (setf phase :tables-meta + parser (xdump:make-parser)) + (incf dump-index) + (loop while (and (< dump-index (fill-pointer dump-queue)) + (let ((dump-queue-entry (aref dump-queue dump-index))) + (or (null dump-queue-entry) + (string= "meta" (xdump-data::table-name dump-queue-entry))))) + do (incf dump-index)) + (if (and (<= dump-index (fill-pointer dump-queue)) + (not (null (aref dump-queue dump-index)))) + (send-message connection (format nil "xdump meta ~a" (xdump-data::table-name (aref dump-queue dump-index)))))) + (set-new-mode (connection-mode connection) 'play-mode))))))) + +(defmethod special-xup ((c connection)) + (send-message c "xdump meta meta" :next-mode 'xdump-mode)) + +(defmethod special-command ((c connection) line) + (cond ((string= line "xup") (special-xup c)) + (t (empire-web:data (session c) "Unknown special command"))) + line) + +(defmethod command ((c connection) line) + (cond ((special-command-p line) (special-command c (subseq line 1))) + (t (send-message c line)))) + +(defmethod quit ((c connection)) + (usocket:socket-close (socket c)))