(defpackage :ywatds (:use :cl) (:export :program-entry)) (in-package :ywatds) ;; Databases (defparameter *ildb* nil) (defparameter *trackdb* nil) ;; Command-line arguments (defparameter *debugp* nil) (defparameter *gcp* nil) (defparameter *world-path* nil) ;; Note: do NOT change *server-port* and *server* at debug time (defparameter *server-port* nil) (defparameter *server* nil) ;; Note: please call (ywatds::update-savefile-paths) if you change the world path at runtime (defparameter *ildb-path* nil) (defparameter *nodedb-path* nil) #+sbcl(declaim (sb-ext:always-bound *ildb* *trackdb* *ildb-path* *nodedb-path* *debugp* *gcp* *world-path* *server-port* *server*)) (defmacro ensure-world-path (path) `(uiop:ensure-pathname ,path :defaults (uiop:getcwd) :ensure-directory t :want-existing t :ensure-absolute t)) (defmacro savefilepath (name) `(uiop:subpathname *world-path* ,(if (stringp name) (concatenate 'string "advtrains_" name) `(concatenate 'string "advtrains_" ,name)))) (defun update-savefile-paths () (psetf *ildb-path* (savefilepath "interlocking.ls") *nodedb-path* (savefilepath "ndb4.ls"))) (defun load-data () (let* ((ildb (atil:load-ildb *ildb-path*)) (tdb (tracks:load-trackdb *nodedb-path* ildb))) (psetf *ildb* ildb *trackdb* tdb) (when *gcp* #+sbcl(sb-ext:gc :full t)) (hunchentoot:acceptor-log-message *server* :info "Database updated"))) (defmacro mainloop () `(loop (load-data) (sleep 20))) (defun program-entry () (let ((argv (uiop:command-line-arguments))) (setf *world-path* (ensure-world-path (car argv))) (setf *server-port* (coerce (parse-integer (cadr argv)) '(integer 0 65535))) (setf *debugp* (member "--debug" (cddr argv) :test #'string=)) (setf *gcp* (member "--force-periodic-gc" (cddr argv) :test #'string=)) (setf *server* (make-instance 'easy-routes:routes-acceptor :port *server-port*)) (register-routes) (register-debugging-routes) (start-server))) (defun register-routes () (ywsw:safe-text-route dumpser ("/dumpser/:p" :method :get) (&path (p 'string)) ("Deserialize advtrains_p into a possibly nested associated list and return the result") (format nil "~s" (atsl:from-file (savefilepath p) :alist))) (ywsw:safe-text-route pretty-dump-interlocking ("/pretty-dump/interlocking" :method :get) () ("Return the interlocking database that is in use") (format nil "~s" *ildb*)) (ywsw:safe-text-route pretty-dump-registered-tracks ("/pretty-dump/registered-tracks" :method :get) () ("Return the list of tracks known to the server") (tracks:dump-track-definitions)) (ywsw:safe-json-route tcbinfo ("/tcbinfo" :method :get) () ("Return a list of TCBs and associated information") *ildb*) (ywsw:safe-json-route tcbinfo-pos ("/tcbinfo/:x/:y/:z" :method :get) (&get (ln :parameter-type 'string) (rc :parameter-type 'string) (side :parameter-type 'string) (next :parameter-type 'boolean) &path (x 'integer) (y 'integer) (z 'integer)) ("Return information on the TCB assigned to the track at (x,y,z). If side is provided, return only information related to side side of the TCB. If ln and/or rc is provided in addition to side, return the first route with ARS rule(s) matching the line ln or routing code rc. If next is specified in addition to side and any of ln and rc, return where the route ends.") (if (and x y z) (let ((tcb (atil:find-tcb-at *ildb* (aux:make-v3d :x x :y y :z z)))) (cond ((not tcb) nil) (side (let ((tcbs (cond ((string-equal side "a") (atil:tcb-side-a tcb)) ((string-equal side "b") (atil:tcb-side-b tcb)) (t nil)))) (cond ((not tcbs) nil) ((or ln rc) (let ((route (atil:match-route (atil:tcbdata-routes tcbs) (or ln "") (or rc "")))) (cond ((not route) nil) (next (atil:route-next route)) (t route)))) (t tcbs)))) (t tcb))))) (ywsw:safe-graphviz-route graph-ilroutes ("/graph/ilroutes" :method :get) () ("Return a simple graph of TCBs and available routes") (format nil "digraph{~%~{\"~a\"->\"~a\";~%~}}" (loop for pos being the hash-keys of (atil:ildb-tcbs *ildb*) using (hash-value tcb) append (loop with side = (atil:make-tcbside :pos pos :side 0) for i across (atil:tcbdata-routes (atil:tcb-side-a tcb)) append (list side (atil:route-next i))) append (loop with side = (atil:make-tcbside :pos pos :side 1) for i across (atil:tcbdata-routes (atil:tcb-side-b tcb)) append (list side (atil:route-next i)))))) (ywsw:safe-text-route graph-tracks ("/graph/paths" :method :get) () ("Return a simple graph of paths") (tracks:gvdump *trackdb*)) (ywsw:safe-text-route find-path ("/find-path/:from-pos/:from-side/:to-pos/:to-side" :method :get) (&path (from-pos 'string) (from-side 'integer) (to-pos 'string) (to-side 'integer)) ("Find path between two entries in the track database") (multiple-value-bind (path dist) (tracks:dijkstra *trackdb* (tracks:trackside (aux:string-to-v3d from-pos) from-side) (tracks:trackside (aux:string-to-v3d to-pos) to-side)) (if path (format nil "Distance: ~a~%~{~#[~;To~:;From~] ~a~@{~%~#[~;To~:;Via~] ~a~}~}" dist path) "No path"))) (ywsw:defsafe docroute-master ("/doc" :method :get) () (let ((entries (loop for i being the hash-keys of easy-routes::*routes* for s = (string-downcase (string i)) when (string/= (subseq s 0 3) "doc") collect s))) (ywsw:wrap-html "Documentation" `("h2" () "Documentation") `("ul" () ,@(loop for i in (stable-sort entries #'string<) collect `("li" () ("a" ("href" ,(format nil "/doc/http-~a" i)) ,i)))))))) (defun register-debugging-routes () (macrolet ((debug-routes (&body body) `(progn ,@(loop for i in body collect `(ywsw:safe-text-route ,(car i) ,(cadr i) ,(caddr i) ,(cons "[Only available in debugging mode] " (cadddr i)) (if *debugp* (progn ,@(cddddr i)) "Debugging mode is disabled. Please run the server with --debug or set ywatds::*debugp* to a non-nil value.")))))) (debug-routes (debug-room ("/debug/room" :method :get) () ("Dumps the output of (room t)") (with-output-to-string (*standard-output*) (room t))) (debug-routes ("/debug/routes" :method :get) () ("List defined routes") (with-output-to-string (s) (describe easy-routes:*routes-mapper* s)))))) (defun start-server () (tracks:init-tracks) (update-savefile-paths) (if *debugp* (break)) (load-data) (hunchentoot:start *server*) ;; loop until an error occurs (if *debugp* (mainloop) (handler-case (mainloop) (t (c) (format *error-output* "~&~a~%" c)))) (ignore-errors (hunchentoot:stop *server*) (uiop:quit)))