Type I is now supported - clic - Clic is an command line interactive client for gopher written in Common LISP HTML git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/ DIR Log DIR Files DIR Refs DIR Tags DIR README DIR LICENSE --- DIR commit f7e9f10ff74b617b12c066771b8637a9abfdc2be DIR parent a14e99ad9030bfb5141e762542098fe8ea902363 HTML Author: Solene Rapenne <solene@perso.pw> Date: Thu, 28 Dec 2017 18:54:24 +0100 Type I is now supported Diffstat: M clic.lisp | 109 +++++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 27 deletions(-) --- DIR diff --git a/clic.lisp b/clic.lisp @@ -1,4 +1,4 @@ -;;;; let's hide the loading +;;; let's hide the loading (let ((*standard-output* (make-broadcast-stream))) (require 'asdf) #+sbcl @@ -19,7 +19,6 @@ "return terminal height" (sb-alien:with-alien ((res unsigned-int (getTerminalHeight)))))) - #+ecl (progn (ffi:clines " @@ -106,6 +105,16 @@ t nil)) +(defun copy-array(from) + (let ((dest (make-array 200 + :fill-pointer 0 + :initial-element nil + :adjustable t))) + (loop for element across from + do + (vector-push element dest)) + dest)) + (defun print-with-color(text &optional (color 'reset) (line-number nil)) "Used to display a line with a color" (format t "~3A| ~a~a~a~%" (if line-number line-number "") (get-color color) text (get-color 'reset))) @@ -194,7 +203,7 @@ ;; 9 Binary (check "9" - (print-with-color "selector 9 not implemented" 'red)) + (print-with-color text 'red line-number)) ;; + redundant server (check "+" @@ -210,7 +219,9 @@ ;; I image (check "I" - (print-with-color "selector I not implemented" 'red)) + (setf (gethash line-number *links*) + (make-location :host host :port port :uri uri :type line-type )) + (print-with-color text 'red line-number)) ;; h http link (check "h" @@ -222,7 +233,7 @@ "invalid type ~a : ~a" line-type text) 'red)))))) -(defun getpage(host port uri) +(defun getpage(host port uri &optional (binary nil)) "connect and display" ;; we reset the buffer @@ -240,27 +251,41 @@ (sb-bsd-sockets:socket-connect socket host port) ;; we open a stream for input/output - (let ((stream (sb-bsd-sockets:socket-make-stream socket :input t :output t))) + (let ((stream (sb-bsd-sockets:socket-make-stream socket + :input t + :output t + :element-type :default))) - ;; sending the request here - ;; if the selector is 1 we omit it + ;; sending the request to the server (format stream "~a~%" uri) (force-output stream) - ;; save current buffer to display it back if needed - (setf *previous-buffer* *buffer*) - - ;; for each line we receive we display it - (loop for line = (read-line stream nil nil) - while line - do - (vector-push line *buffer*))))) + (if binary + ;; binary + ;; save into a file in $PWD + (with-open-file (output (subseq uri (1+ (position #\/ uri :from-end t))) + :element-type '(unsigned-byte 8) + :direction :output :if-exists :supersede) + (let ((buf (make-array 4096 :element-type '(unsigned-byte 8)))) + (loop for pos = (read-sequence buf stream) + while (plusp pos) + do + (write-sequence buf output :end pos)))) + + ;; not binary + ;; for each line we receive we store it in *buffer* + (loop for line = (read-line stream nil nil) + while line + do + (vector-push line *buffer*)))))) (defun g(key) "browse to the N-th link" (let ((destination (gethash key *links*))) (when destination + (cond + ;; visit a gopher link (type 0 or 1) ((location-p destination) (visit destination)) @@ -430,7 +455,6 @@ ;;;; output is a text file ? ;;;; call the $PAGER ! ((string= "0" type) - (pop *history*) ;; it's not a menu, we need to remove it from history ;;; generate a string from *buffer* array (let ((text (string-right-trim ; remove last newline (string #\Newline) @@ -439,16 +463,30 @@ collect line))))) ;; create input stream used as stdin for $PAGER (let ((input (make-string-input-stream text))) - (uiop:run-program (list #+ecl - (si:getenv "PAGER") - #+sbcl - (sb-unix::posix-getenv "PAGER")) + (uiop:run-program (list (uiop:getenv "PAGER")) :input input :output :interactive)) ;; display last menu - (setf *buffer* *previous-buffer*) + (pop *history*) + (setf *buffer* (copy-array *previous-buffer*)) + (setf *links* (make-hash-table)) (display-buffer "1"))) + ;; image + ((string= "I" type) + (let ((location (car *history*))) + (uiop:run-program (list "xdg-open" + (subseq ;; get the text after last / + (location-uri location) + (1+ (position #\/ + (location-uri location) + :from-end t)))))) + (pop *history*) + (setf *buffer* (copy-array *previous-buffer*)) + (setf *links* (make-hash-table)) + (display-buffer "1")) + + ;;;; output is a menu ? ;;;; display the menu and split it in pages if needed ((string= "1" type) @@ -494,17 +532,34 @@ (defun visit(destination) "visit a location" - (getpage (location-host destination) - (location-port destination) - (location-uri destination)) + (cond + + ;; we retrieve text / lines + ;; when type is 1 or 0 + ((or + (string= "1" (location-type destination)) + (string= "0" (location-type destination))) + + (getpage (location-host destination) + (location-port destination) + (location-uri destination))) + + (t + (getpage (location-host destination) + (location-port destination) + (location-uri destination) + t))) + ;; we reset the links table ONLY if we have a new folder + ;; we also keep the last menu buffer (when (string= "1" (location-type destination)) + (setf *previous-buffer* (copy-array *buffer*)) (setf *links* (make-hash-table))) - + ;; goes to the history ! (push destination *history*) - + (when *offline* (let ((path (concatenate 'string "history/" (location-host destination)