dotfiles/sbcl/.quicklisp/quicklisp/client-info.lisp
2020-01-20 14:13:08 -05:00

262 lines
8.5 KiB
Common Lisp

;;;; client-info.lisp
(in-package #:quicklisp-client)
(defparameter *client-base-url* "http://beta.quicklisp.org/")
(defgeneric info-equal (info1 info2)
(:documentation "Return TRUE if INFO1 and INFO2 are 'equal' in some
important sense."))
;;; Information for checking the validity of files fetched for
;;; installing/updating the client code.
(defclass client-file-info ()
((plist-key
:initarg :plist-key
:reader plist-key)
(file-url
:initarg :url
:reader file-url)
(name
:reader name
:initarg :name)
(size
:initarg :size
:reader size)
(md5
:reader md5
:initarg :md5)
(sha256
:reader sha256
:initarg :sha256)
(plist
:reader plist
:initarg :plist)))
(defmethod print-object ((info client-file-info) stream)
(print-unreadable-object (info stream :type t)
(format stream "~S ~D ~S"
(name info)
(size info)
(md5 info))))
(defmethod info-equal ((info1 client-file-info) (info2 client-file-info))
(and (eql (size info1) (size info2))
(equal (name info1) (name info2))
(equal (md5 info1) (md5 info2))))
(defclass asdf-file-info (client-file-info)
()
(:default-initargs
:plist-key :asdf
:name "asdf.lisp"))
(defclass setup-file-info (client-file-info)
()
(:default-initargs
:plist-key :setup
:name "setup.lisp"))
(defclass client-tar-file-info (client-file-info)
()
(:default-initargs
:plist-key :client-tar
:name "quicklisp.tar"))
(define-condition invalid-client-file (error)
((file
:initarg :file
:reader invalid-client-file-file)))
(define-condition badly-sized-client-file (invalid-client-file)
((expected-size
:initarg :expected-size
:reader badly-sized-client-file-expected-size)
(actual-size
:initarg :actual-size
:reader badly-sized-client-file-actual-size))
(:report (lambda (condition stream)
(format stream "Unexpected file size for ~A ~
- expected ~A but got ~A"
(invalid-client-file-file condition)
(badly-sized-client-file-expected-size condition)
(badly-sized-client-file-actual-size condition)))))
(defun check-client-file-size (file expected-size)
(let ((actual-size (file-size file)))
(unless (eql expected-size actual-size)
(error 'badly-sized-client-file
:file file
:expected-size expected-size
:actual-size actual-size))))
;;; TODO: check cryptographic digests too.
(defgeneric check-client-file (file client-file-info)
(:documentation
"Signal an INVALID-CLIENT-FILE error if FILE does not match the
metadata in CLIENT-FILE-INFO.")
(:method (file client-file-info)
(check-client-file-size file (size client-file-info))
client-file-info))
;;; Structuring and loading information about the Quicklisp client
;;; code
(defclass client-info ()
((setup-info
:reader setup-info
:initarg :setup-info)
(asdf-info
:reader asdf-info
:initarg :asdf-info)
(client-tar-info
:reader client-tar-info
:initarg :client-tar-info)
(canonical-client-info-url
:reader canonical-client-info-url
:initarg :canonical-client-info-url)
(version
:reader version
:initarg :version)
(subscription-url
:reader subscription-url
:initarg :subscription-url)
(plist
:reader plist
:initarg :plist)
(source-file
:reader source-file
:initarg :source-file)))
(defmethod print-object ((client-info client-info) stream)
(print-unreadable-object (client-info stream :type t)
(prin1 (version client-info) stream)))
(defmethod available-versions-url ((info client-info))
(make-versions-url (subscription-url info)))
(defgeneric extract-client-file-info (file-info-class plist)
(:method (file-info-class plist)
(let* ((instance (make-instance file-info-class))
(key (plist-key instance))
(file-info-plist (getf plist key)))
(unless file-info-plist
(error "Missing client-info data for ~S" key))
(destructuring-bind (&key url size md5 sha256 &allow-other-keys)
file-info-plist
(unless (and url size md5 sha256)
(error "Missing client-info data for ~S" key))
(reinitialize-instance instance
:plist file-info-plist
:url url
:size size
:md5 md5
:sha256 sha256)))))
(defun format-client-url (path &rest format-arguments)
(if format-arguments
(format nil "~A~{~}" *client-base-url* path format-arguments)
(format nil "~A~A" *client-base-url* path)))
(defun client-info-url-from-version (version)
(format-client-url "client/~A/client-info.sexp" version))
(define-condition invalid-client-info (error)
((plist
:initarg plist
:reader invalid-client-info-plist)))
(defun load-client-info (file)
(let ((plist (safely-read-file file)))
(destructuring-bind (&key subscription-url
version
canonical-client-info-url
&allow-other-keys)
plist
(make-instance 'client-info
:setup-info (extract-client-file-info 'setup-file-info
plist)
:asdf-info (extract-client-file-info 'asdf-file-info
plist)
:client-tar-info
(extract-client-file-info 'client-tar-file-info
plist)
:canonical-client-info-url canonical-client-info-url
:version version
:subscription-url subscription-url
:plist plist
:source-file (probe-file file)))))
(defun mock-client-info ()
(flet ((mock-client-file-info (class)
(make-instance class
:size 0
:url ""
:md5 ""
:sha256 ""
:plist nil)))
(make-instance 'client-info
:version ql-info:*version*
:subscription-url
(format-client-url "client/quicklisp.sexp")
:setup-info (mock-client-file-info 'setup-file-info)
:asdf-info (mock-client-file-info 'asdf-file-info)
:client-tar-info (mock-client-file-info
'client-tar-file-info))))
(defun fetch-client-info (url)
(let ((info-file (qmerge "tmp/client-info.sexp")))
(delete-file-if-exists info-file)
(fetch url info-file :quietly t)
(handler-case
(load-client-info info-file)
;; FIXME: So many other things could go wrong here; I think it
;; would be nice to catch and report them clearly as bogus URLs
(invalid-client-info ()
(error "Invalid client info URL -- ~A" url)))))
(defun local-client-info ()
(let ((info-file (qmerge "client-info.sexp")))
(if (probe-file info-file)
(load-client-info info-file)
(progn
(warn "Missing client-info.sexp, using mock info")
(mock-client-info)))))
(defun newest-client-info (&optional (info (local-client-info)))
(let ((latest (subscription-url info)))
(when latest
(fetch-client-info latest))))
(defun client-version-lessp (client-info-1 client-info-2)
(string-lessp (version client-info-1)
(version client-info-2)))
(defun client-version ()
"Return the version for the current local client installation. May
or may not be suitable for passing as the :VERSION argument to
INSTALL-CLIENT, depending on if it's a standard Quicklisp-provided
client."
(version (local-client-info)))
(defun client-url ()
"Return an URL suitable for passing as the :URL argument to
INSTALL-CLIENT for the current local client installation."
(canonical-client-info-url (local-client-info)))
(defun available-client-versions ()
(let ((url (available-versions-url (local-client-info)))
(temp-file (qmerge "tmp/client-versions.sexp")))
(when url
(handler-case
(progn
(maybe-fetch-gzipped url temp-file)
(prog1
(with-open-file (stream temp-file)
(safely-read stream))
(delete-file-if-exists temp-file)))
(unexpected-http-status (condition)
(unless (url-not-suitable-error-p condition)
(error condition)))))))