2023-02-05 14:07:13 +01:00
|
|
|
(in-package :client-main-window)
|
|
|
|
|
|
|
|
(named-readtables:in-readtable nodgui.syntax:nodgui-syntax)
|
|
|
|
|
|
|
|
(defclass gemini-stream ()
|
|
|
|
((server-stream-handle
|
|
|
|
:initform nil
|
|
|
|
:initarg :server-stream-handle
|
|
|
|
:accessor server-stream-handle)
|
|
|
|
(status
|
|
|
|
:initform :streaming
|
|
|
|
:initarg :status
|
|
|
|
:accessor status)
|
|
|
|
(status-lock
|
|
|
|
:initform (bt:make-lock)
|
|
|
|
:reader status-lock)
|
|
|
|
(fetching-thread
|
|
|
|
:initform nil
|
|
|
|
:initarg :fetching-thread
|
|
|
|
:accessor fetching-thread)))
|
|
|
|
|
|
|
|
(defgeneric status (object))
|
|
|
|
|
|
|
|
(defmethod status ((object gemini-stream))
|
|
|
|
(misc:with-lock ((status-lock object))
|
|
|
|
(slot-value object 'status)))
|
|
|
|
|
|
|
|
(defmethod (setf status) ((object gemini-stream) val)
|
|
|
|
(misc:with-lock ((status-lock object))
|
|
|
|
(setf (slot-value object 'status) val)
|
|
|
|
val))
|
|
|
|
|
|
|
|
(defparameter *gemini-streams-db* ())
|
|
|
|
|
|
|
|
(defun push-db-stream (stream-object)
|
|
|
|
(pushnew stream-object
|
|
|
|
*gemini-streams-db*
|
|
|
|
:test (lambda (a b)
|
|
|
|
(string= (server-stream-handle a)
|
|
|
|
(server-stream-handle b))))
|
|
|
|
*gemini-streams-db*)
|
|
|
|
|
|
|
|
(defun remove-db-stream (stream-object)
|
|
|
|
(setf *gemini-streams-db*
|
|
|
|
(remove stream-object *gemini-streams-db*))
|
|
|
|
*gemini-streams-db*)
|
|
|
|
|
|
|
|
|
|
|
|
(defmethod abort-downloading ((object gemini-stream))
|
|
|
|
(setf (status object) :canceled))
|
|
|
|
|
|
|
|
(defun remove-all-db-stream ()
|
|
|
|
(map nil
|
|
|
|
(lambda (a) (abort-downloading a))
|
|
|
|
*gemini-streams-db*)
|
|
|
|
(setf *gemini-streams-db* ())
|
|
|
|
*gemini-streams-db*)
|
|
|
|
|
|
|
|
(defun find-db-stream-if (predicate)
|
|
|
|
(find-if predicate *gemini-streams-db*))
|
|
|
|
|
|
|
|
(defun find-db-stream-url (url)
|
|
|
|
(find-db-stream-if (lambda (a) (string= (server-stream-handle a) url))))
|
|
|
|
|
2023-02-10 12:57:08 +01:00
|
|
|
(defun notify-request-error (e)
|
2023-02-19 14:52:53 +01:00
|
|
|
(let ((message (format nil (_ "Comunication with backend failed: ~a") e)))
|
|
|
|
#+debug-mode (misc:dbg "request error ~a" message)
|
|
|
|
#-debug-mode (gui-goodies:error-dialog gui:*tk* message)))
|
2023-02-10 12:57:08 +01:00
|
|
|
|
|
|
|
(defmacro with-notify-errors (&body body)
|
|
|
|
`(handler-case
|
|
|
|
(progn ,@body)
|
|
|
|
(error (e)
|
|
|
|
(notify-request-error e))))
|
|
|
|
|
2023-02-09 16:28:53 +01:00
|
|
|
(defun slurp-gemini-stream (iri &key
|
|
|
|
(use-cache t)
|
|
|
|
(process-function #'identity)
|
|
|
|
(aborting-function (constantly nil)))
|
2023-02-19 14:52:53 +01:00
|
|
|
(cev:with-enqueue-request (:gemini-request 1 the-error iri use-cache)
|
2023-02-09 16:28:53 +01:00
|
|
|
(notify-request-error the-error))
|
|
|
|
(labels ((stream-exausted-p ()
|
2023-02-19 14:52:53 +01:00
|
|
|
(let ((status-completed (cev:with-enqueue-request
|
|
|
|
(:gemini-stream-completed-p 1 the-error iri)
|
2023-02-09 16:28:53 +01:00
|
|
|
(notify-request-error the-error))))
|
|
|
|
status-completed))
|
|
|
|
(loop-fetch (&optional (last-lines-fetched-count 0))
|
|
|
|
(when (not (or (funcall aborting-function)
|
|
|
|
(stream-exausted-p)))
|
|
|
|
(ev:with-enqueued-process-and-unblock ()
|
2023-02-09 17:04:29 +01:00
|
|
|
(with-notify-errors
|
|
|
|
(let* ((last-lines-fetched (comm:make-request :gemini-stream-parsed-line-slice
|
|
|
|
1
|
|
|
|
iri
|
|
|
|
last-lines-fetched-count
|
|
|
|
nil))
|
|
|
|
(next-start-fetching (length last-lines-fetched)))
|
|
|
|
(when last-lines-fetched
|
|
|
|
(funcall process-function last-lines-fetched))
|
|
|
|
(loop-fetch (+ last-lines-fetched-count
|
|
|
|
next-start-fetching))))))))
|
2023-02-09 16:28:53 +01:00
|
|
|
(loop-fetch)))
|
|
|
|
|
2023-02-05 14:07:13 +01:00
|
|
|
(defun start-streaming-thread (iri &key
|
|
|
|
(use-cache t)
|
|
|
|
(process-function #'identity)
|
|
|
|
(status :streaming))
|
|
|
|
(when (not (find-db-stream-url iri))
|
|
|
|
(let ((stream-wrapper (make-instance 'gemini-stream
|
|
|
|
:server-stream-handle iri
|
|
|
|
:status status)))
|
|
|
|
(flet ((aborting-function ()
|
|
|
|
(eq (status stream-wrapper) :canceled)))
|
|
|
|
(let ((stream-thread (bt:make-thread (lambda ()
|
2023-02-09 16:28:53 +01:00
|
|
|
(slurp-gemini-stream iri
|
|
|
|
:use-cache use-cache
|
|
|
|
:process-function
|
|
|
|
process-function
|
|
|
|
:aborting-function
|
|
|
|
#'aborting-function)))))
|
2023-02-05 14:07:13 +01:00
|
|
|
(setf (fetching-thread stream-wrapper) stream-thread)
|
|
|
|
(push-db-stream stream-wrapper))))))
|
|
|
|
|
|
|
|
(defun initialize-menu (parent)
|
|
|
|
(with-accessors ((main-window main-window)) parent
|
2023-02-10 12:57:08 +01:00
|
|
|
(let* ((bar (gui:make-menubar parent))
|
2023-02-08 13:02:26 +01:00
|
|
|
(file (gui:make-menu bar (_ "File") :underline 0))
|
|
|
|
(help (gui:make-menu bar (_ "Help") :underline 0)))
|
2023-02-05 14:07:13 +01:00
|
|
|
(gui:make-menubutton file (_ "Quit") #'menu:quit :underline 0)
|
|
|
|
(gui:make-menubutton help (_ "About") #'menu:help-about :underline 0))))
|
|
|
|
|
2023-02-08 13:02:26 +01:00
|
|
|
(defclass tool-bar (gui:frame)
|
|
|
|
((iri-entry
|
|
|
|
:initform nil
|
|
|
|
:initarg :iri-entry
|
|
|
|
:accessor iri-entry)
|
|
|
|
(back-button
|
|
|
|
:initform nil
|
|
|
|
:initarg :back-button
|
|
|
|
:accessor back-button)
|
|
|
|
(reload-button
|
|
|
|
:initform nil
|
|
|
|
:initarg :reload-button
|
|
|
|
:accessor reload-button)
|
2023-02-18 13:02:56 +01:00
|
|
|
(up-button
|
|
|
|
:initform nil
|
|
|
|
:initarg :up-button
|
|
|
|
:accessor up-button)
|
2023-02-08 13:02:26 +01:00
|
|
|
(go-button
|
|
|
|
:initform nil
|
|
|
|
:initarg :go-button
|
|
|
|
:accessor go-button)))
|
|
|
|
|
|
|
|
(defun autocomplete-iri-clsr (toolbar)
|
|
|
|
(declare (ignore toolbar))
|
|
|
|
(lambda (hint)
|
2023-02-09 17:04:29 +01:00
|
|
|
(if (> (length hint) 2)
|
|
|
|
(with-notify-errors
|
2023-02-19 14:52:53 +01:00
|
|
|
(let ((match-results (cev:enqueue-request-and-wait-results :complete-net-address
|
|
|
|
1
|
|
|
|
ev:+maximum-event-priority+
|
|
|
|
hint)))
|
2023-02-17 14:30:33 +01:00
|
|
|
(values (getf match-results :matches)
|
|
|
|
(getf match-results :indices))))
|
2023-02-09 17:04:29 +01:00
|
|
|
hint)))
|
2023-02-08 13:02:26 +01:00
|
|
|
|
2023-02-19 14:52:53 +01:00
|
|
|
(defun start-stream-iri-clsr (widget use-cache)
|
|
|
|
(lambda ()
|
|
|
|
(with-accessors ((iri-entry iri-entry)) widget
|
|
|
|
(let ((iri (gui:text iri-entry)))
|
|
|
|
(slurp-gemini-stream iri
|
|
|
|
:use-cache use-cache
|
|
|
|
:process-function (lambda (lines)
|
|
|
|
(misc:dbg "lines ~a" lines)))))))
|
|
|
|
|
2023-02-18 14:28:57 +01:00
|
|
|
(defun setup-main-window-events (main-window)
|
|
|
|
(with-accessors ((iri-entry iri-entry)
|
|
|
|
(back-button back-button)
|
|
|
|
(reload-button reload-button)
|
|
|
|
(up-button up-button)
|
|
|
|
(go-button go-button)) main-window
|
2023-02-18 14:57:26 +01:00
|
|
|
(let ((entry-autocomplete (gui-mw:autocomplete-entry-widget iri-entry)))
|
2023-02-18 14:28:57 +01:00
|
|
|
(gui:bind entry-autocomplete
|
|
|
|
#$<KeyPress-Return>$
|
|
|
|
(lambda (e)
|
|
|
|
(declare (ignore e))
|
2023-02-19 14:52:53 +01:00
|
|
|
(funcall (start-stream-iri-clsr main-window t)))
|
|
|
|
:append nil))
|
|
|
|
(setf (gui:command go-button) (start-stream-iri-clsr main-window t))))
|
2023-02-18 14:28:57 +01:00
|
|
|
|
2023-02-08 13:02:26 +01:00
|
|
|
(defmethod initialize-instance :after ((object tool-bar) &key &allow-other-keys)
|
|
|
|
(with-accessors ((iri-entry iri-entry)
|
|
|
|
(back-button back-button)
|
|
|
|
(reload-button reload-button)
|
2023-02-18 13:02:56 +01:00
|
|
|
(up-button up-button)
|
2023-02-08 13:02:26 +01:00
|
|
|
(go-button go-button)) object
|
|
|
|
(gui:configure object :relief :raised)
|
2023-02-18 13:02:56 +01:00
|
|
|
(setf iri-entry (make-instance 'gui-mw:autocomplete-entry
|
|
|
|
:master object
|
|
|
|
:autocomplete-function (autocomplete-iri-clsr object)))
|
|
|
|
(setf back-button (make-instance 'gui:button :master object :image icons:*back*))
|
|
|
|
(setf reload-button (make-instance 'gui:button :master object :image icons:*refresh*))
|
|
|
|
(setf go-button (make-instance 'gui:button :master object :image icons:*open-iri*))
|
|
|
|
(setf up-button (make-instance 'gui:button :master object :image icons:*up*))
|
2023-02-08 13:02:26 +01:00
|
|
|
(gui-goodies:attach-tooltips (back-button (_ "go back"))
|
2023-02-18 13:02:56 +01:00
|
|
|
(reload-button (_ "reload address"))
|
2023-02-08 13:02:26 +01:00
|
|
|
(go-button (_ "go to address"))
|
2023-02-18 13:02:56 +01:00
|
|
|
(up-button (_ "one level up")))
|
|
|
|
(gui:grid back-button 0 1 :sticky :nsw)
|
2023-02-08 13:02:26 +01:00
|
|
|
(gui:grid iri-entry 0 2 :sticky :we :padx +minimum-padding+)
|
2023-02-18 13:02:56 +01:00
|
|
|
(gui:grid go-button 0 3 :sticky :e)
|
2023-02-08 13:02:26 +01:00
|
|
|
(gui:grid reload-button 0 4 :sticky :e)
|
2023-02-18 13:02:56 +01:00
|
|
|
(gui:grid up-button 0 5 :sticky :e)
|
2023-02-08 13:02:26 +01:00
|
|
|
(gui:grid-columnconfigure object 2 :weight 2)
|
2023-02-18 14:28:57 +01:00
|
|
|
(setup-main-window-events object)
|
2023-02-08 13:02:26 +01:00
|
|
|
object))
|
|
|
|
|
|
|
|
(defclass toc-frame (gui:frame)
|
|
|
|
((toc-listbox
|
|
|
|
:initform nil
|
|
|
|
:initarg :toc-listbox
|
|
|
|
:accessor toc-listbox)
|
|
|
|
(toc-data
|
|
|
|
:initform nil
|
|
|
|
:initarg :toc-data
|
|
|
|
:accessor toc-data)))
|
|
|
|
|
|
|
|
(defmethod initialize-instance :after ((object toc-frame) &key &allow-other-keys)
|
|
|
|
(with-accessors ((toc-listbox toc-listbox)
|
|
|
|
(toc-data toc-data)) object
|
|
|
|
(setf toc-listbox (make-instance 'gui:scrolled-listbox
|
|
|
|
:master object
|
|
|
|
:name nil))
|
|
|
|
(gui:grid toc-listbox 0 0
|
|
|
|
:sticky :nswe
|
|
|
|
:ipadx +minimum-padding+
|
|
|
|
:ipady +minimum-padding+)
|
|
|
|
(gui-goodies:gui-resize-grid-all object)
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
(defclass main-frame (gui:frame)
|
2023-02-05 14:07:13 +01:00
|
|
|
((main-window
|
|
|
|
:initform nil
|
|
|
|
:initarg :main-window
|
|
|
|
:accessor main-window)
|
|
|
|
(tool-bar
|
|
|
|
:initform nil
|
|
|
|
:initarg :tool-bar
|
|
|
|
:accessor tool-bar)
|
2023-02-08 13:02:26 +01:00
|
|
|
(toc-frame
|
2023-02-05 14:07:13 +01:00
|
|
|
:initform nil
|
2023-02-08 13:02:26 +01:00
|
|
|
:initarg :toc-frame
|
|
|
|
:accessor toc-frame)
|
|
|
|
(info-frame
|
2023-02-05 14:07:13 +01:00
|
|
|
:initform nil
|
2023-02-08 13:02:26 +01:00
|
|
|
:initarg :info-frame
|
|
|
|
:accessor info-frame)))
|
|
|
|
|
|
|
|
(defmethod initialize-instance :after ((object main-frame) &key &allow-other-keys)
|
|
|
|
(with-accessors ((main-window main-window)
|
|
|
|
(tool-bar tool-bar)
|
|
|
|
(toc-frame toc-frame)
|
|
|
|
(info-frame info-frame)) object
|
|
|
|
(setf tool-bar (make-instance 'tool-bar :master object))
|
|
|
|
(setf toc-frame (make-instance 'toc-frame :master object))
|
|
|
|
(gui:grid tool-bar 0 0 :sticky :new)
|
|
|
|
(gui:grid toc-frame 1 0 :sticky :nsw)
|
|
|
|
(gui:grid-columnconfigure object :all :weight 1)
|
|
|
|
(gui:grid-rowconfigure object 1 :weight 1)
|
|
|
|
object))
|
2023-02-05 14:07:13 +01:00
|
|
|
|
|
|
|
(defun init-main-window ()
|
2023-02-18 14:28:57 +01:00
|
|
|
(gui:with-nodgui (:title +program-name+)
|
|
|
|
(icons:load-icons)
|
|
|
|
(initialize-menu gui:*tk*)
|
|
|
|
(let ((main-frame (make-instance 'main-frame)))
|
|
|
|
(gui:grid main-frame 0 0 :sticky :nswe)
|
|
|
|
(gui-goodies:gui-resize-grid-all gui:*tk*))))
|