Gemini download performance optimization.
[elpher.git] / elpher.el
index 06c669e..be78a0a 100644 (file)
--- a/elpher.el
+++ b/elpher.el
@@ -4,7 +4,7 @@
 
 ;; Author: Tim Vaughan <timv@ughan.xyz>
 ;; Created: 11 April 2019
-;; Version: 2.5.2
+;; Version: 2.6.1
 ;; Keywords: comm gopher
 ;; Homepage: http://thelambdalab.xyz/elpher
 ;; Package-Requires: ((emacs "26"))
@@ -37,7 +37,8 @@
 ;; - direct visualisation of image files,
 ;; - a simple bookmark management system,
 ;; - connections using TLS encryption,
-;; - the fledgling Gemini protocol.
+;; - the fledgling Gemini protocol,
+;; - the greybeard Finger protocol.
 
 ;; To launch Elpher, simply use 'M-x elpher'.  This will open a start
 ;; page containing information on key bindings and suggested starting
@@ -68,7 +69,7 @@
 ;;; Global constants
 ;;
 
-(defconst elpher-version "2.5.2"
+(defconst elpher-version "2.6.1"
   "Current version of elpher.")
 
 (defconst elpher-margin-width 6
@@ -89,6 +90,7 @@
     ((gopher ?s) elpher-get-gopher-page elpher-render-download "snd" elpher-binary)
     ((gopher ?h) elpher-get-gopher-page elpher-render-html "htm" elpher-html)
     (gemini elpher-get-gemini-page elpher-render-gemini "gem" elpher-gemini)
+    (finger elpher-get-finger-page elpher-render-text "txt" elpher-text)
     (telnet elpher-get-telnet-page nil "tel" elpher-telnet)
     (other-url elpher-get-other-url-page nil "url" elpher-other-url)
     ((special bookmarks) elpher-get-bookmarks-page nil "/" elpher-index)
@@ -270,6 +272,8 @@ address refers to, via the table `elpher-type-map'."
              'gemini)
             ((equal protocol "telnet")
              'telnet)
+            ((equal protocol "finger")
+             'finger)
             (t 'other-url)))))
 
 (defun elpher-address-protocol (address)
@@ -289,6 +293,10 @@ For gopher addresses this is a combination of the selector type and selector."
   "Retrieve host from ADDRESS object."
   (url-host address))
 
+(defun elpher-address-user (address)
+  "Retrieve user from ADDRESS object."
+  (url-user address))
+
 (defun elpher-address-port (address)
   "Retrieve port from ADDRESS object.
 If no address is defined, returns 0.  (This is for compatibility with the URL library.)"
@@ -619,9 +627,10 @@ If ADDRESS is not supplied or nil the record is rendered as an
     (if type-map-entry
         (let* ((margin-code (elt type-map-entry 2))
                (face (elt type-map-entry 3))
-               (page (elpher-make-page display-string address)))
+               (filtered-display-string (ansi-color-filter-apply display-string))
+               (page (elpher-make-page filtered-display-string address)))
           (elpher-insert-margin margin-code)
-          (insert-text-button display-string
+          (insert-text-button filtered-display-string
                               'face face
                               'elpher-page page
                               'action #'elpher-click-link
@@ -802,7 +811,7 @@ to ADDRESS."
         (let* ((kill-buffer-query-functions nil)
                (port (elpher-address-port address))
                (host (elpher-address-host address))
-               (response-string "")
+               (response-string-parts nil)
                (proc (open-network-stream "elpher-process"
                                           nil
                                           (if force-ipv4 (dns-query host) host)
@@ -823,8 +832,8 @@ to ADDRESS."
                                 (when timer
                                   (cancel-timer timer)
                                   (setq timer nil))
-                                (setq response-string
-                                      (concat response-string string))))
+                                (setq response-string-parts
+                                      (cons string response-string-parts))))
           (set-process-sentinel proc
                                 (lambda (proc event)
                                   (condition-case the-error
@@ -836,7 +845,7 @@ to ADDRESS."
                                            (concat (elpher-address-to-url address)
                                                    "\r\n"))))
                                        ((string-prefix-p "deleted" event)) ; do nothing
-                                       ((and (string-empty-p response-string)
+                                       ((and (not response-string-parts)
                                              (not force-ipv4))
                                         ; Try again with IPv4
                                         (message "Connection failed. Retrying with IPv4.")
@@ -844,7 +853,7 @@ to ADDRESS."
                                         (elpher-get-gemini-response address renderer t))
                                        (t
                                         (funcall #'elpher-process-gemini-response
-                                                 response-string
+                                                 (apply #'concat (reverse response-string-parts))
                                                  renderer)
                                         (elpher-restore-pos)))
                                     (error
@@ -1029,6 +1038,68 @@ For instance, the filename /a/b/../c/./d will reduce to /a/c/d"
     (elpher-page-address elpher-current-page)
     (buffer-string))))
 
+;; Finger page connection
+
+(defun elpher-get-finger-page (renderer &optional force-ipv4)
+  "Opens a finger connection to the current page address and renders it using RENDERER."
+  (let* ((address (elpher-page-address elpher-current-page))
+         (content (elpher-get-cached-content address)))
+    (if (and content (funcall renderer nil))
+        (elpher-with-clean-buffer
+         (insert content)
+         (elpher-restore-pos))
+      (elpher-with-clean-buffer
+       (insert "LOADING... (use 'u' to cancel)"))
+      (condition-case the-error
+          (let* ((kill-buffer-query-functions nil)
+                 (user (let ((filename (elpher-address-filename address)))
+                         (if (> (length filename) 1)
+                             (substring filename 1)
+                           (elpher-address-user address))))
+                 (port (let ((given-port (elpher-address-port address)))
+                         (if (> given-port 0) given-port 79)))
+                 (host (elpher-address-host address))
+                 (selector-string "")
+                 (proc (open-network-stream "elpher-process"
+                                            nil
+                                            (if force-ipv4 (dns-query host) host)
+                                            port
+                                            :type 'plain
+                                            :nowait t))
+                 (timer (run-at-time elpher-connection-timeout
+                                     nil
+                                     (lambda ()
+                                       (pcase (process-status proc)
+                                         ('connect
+                                          (elpher-process-cleanup)
+                                          (unless force-ipv4
+                                            (message "Connection timed out. Retrying with IPv4 address.")
+                                            (elpher-get-finger-page renderer t))))))))
+            (setq elpher-network-timer timer)
+            (set-process-coding-system proc 'binary)
+            (set-process-filter proc
+                                (lambda (_proc string)
+                                  (cancel-timer timer)
+                                  (setq selector-string
+                                        (concat selector-string string))))
+            (set-process-sentinel proc
+                                  (lambda (_proc event)
+                                    (condition-case the-error
+                                        (cond
+                                         ((string-prefix-p "deleted" event))
+                                         ((string-prefix-p "open" event)
+                                          (let ((inhibit-eol-conversion t))
+                                            (process-send-string
+                                             proc
+                                             (concat user "\r\n"))))
+                                         (t
+                                          (cancel-timer timer)
+                                          (funcall renderer selector-string)
+                                          (elpher-restore-pos)))))))
+        (error
+         (elpher-network-error address the-error))))))
+
+
 ;; Other URL page opening
 
 (defun elpher-get-other-url-page (renderer)
@@ -1362,9 +1433,7 @@ When run interactively HOST-OR-URL is read from the minibuffer."
           (let ((address-copy (elpher-address-from-url
                                (elpher-address-to-url address))))
             (setf (url-filename address-copy) "")
-            (elpher-visit-page
-             (elpher-make-page (elpher-address-to-url address-copy)
-                               address-copy))))
+            (elpher-go (elpher-address-to-url address-copy))))
       (error "Command invalid for %s" (elpher-page-display-string elpher-current-page)))))
 
 (defun elpher-bookmarks-current-p ()