Mercurial > core / lisp/lib/net/fetch.lisp
changeset 698: |
96958d3eb5b0 |
parent: |
7ce855f76e1d
|
author: |
Richard Westhaver <ellis@rwest.io> |
date: |
Fri, 04 Oct 2024 22:04:59 -0400 |
permissions: |
-rw-r--r-- |
description: |
fixes |
1 ;;; net/fetch.lisp --- Simple HTTP Downloads 3 ;; Provides the DOWNLOAD and FETCH functions for easily download remote files. 8 (in-package :net/fetch) 10 (define-condition invalid-path-error (error) 11 ((text :initarg :text :reader text))) 13 (defvar *default-fetch-output-file* #P"index.html") 15 (defun download (url &key (output (or (obj/uri:uri-path (obj/uri:uri url)) *default-fetch-output-file*)) 16 (if-exists :error) (progress nil) (connect-timeout net/req:*default-connect-timeout*) 18 "Download a file from URL to OUTPUT." 19 (let ((*progress-bar-enabled* progress)) 20 (multiple-value-bind (stream status header uri) 21 (req:get url :want-stream t :force-binary t :connect-timeout connect-timeout :verbose (log:trace-p) 24 (log:debug! "download connect OK:" url) 25 (log:debug! "headers:" (hash-table-alist header)) 26 (let ((len (gethash "content-length" header)) 27 (buff (make-array 4096 :element-type 'octet :adjustable t))) 28 (when len (setf len (parse-integer len))) 29 (with-progress-maybe progress (len "downloading ~a to ~a..." url output) 30 (with-open-file (out output :direction :output :element-type 'octet :if-exists if-exists) 32 (let ((end (read-sequence buff stream :end 4096))) 33 (when progress (update-progress *progress-bar* end)) 34 (write-sequence buff out :end end) 37 (values stream status uri header)))) 39 (defun split-file-path (path) 40 (let ((pos-last-slash (1+ (position #\/ path :from-end t)))) 41 (list (subseq path 0 pos-last-slash) 42 (subseq path pos-last-slash)))) 44 (defun split-uri-string (uri-string) 45 (let ((pu (parse-uri uri-string))) 46 (cons (uri-host pu) (split-file-path (uri-path pu))))) 48 (defun condition-path (path) 49 "Abuse parse-uri to strip possible get args from path" 50 (let ((p (parse-uri path))) (uri-path p))) 53 (handler-case (probe-file path) 54 (type-error (e) #+sbcl (declare (ignore e)) (error 'invalid-path-error 55 :text (format nil "Invalid path: ~A" path))))) 57 (defun %fetch (url-or-path &key (cache t) 61 ((is-file (condition-path url-or-path)) (condition-path url-or-path)) 62 ((is-file (condition-path (concatenate 'string dir (uri-path url-or-path)))) 63 (condition-path (concatenate 'string dir url-or-path))) 64 ((parse-uri url-or-path) 65 (let* ((tmp-pathname (split-uri-string url-or-path)) 66 (file-pathstring (format nil "~{~A~^~}" (if dir (cons dir tmp-pathname) tmp-pathname))) 67 (file-pathname (ensure-directories-exist 70 (when (is-file file-pathname) (delete-file file-pathname)) 71 (if (and cache (probe-file file-pathname)) 72 (values file-pathname 200 "OK") 73 (download url-or-path :output file-pathname))))) 74 (t (values nil 404 "Not file of url")))) 79 (external-format :utf-8) 83 "Fetch file from URL if not cached in DIR 84 stores the file in the location specified by dir if url or file is url the file 85 is stored in DIR/URI-HOST/URI-PATH. 87 Note that it is important to ensure that dir and subdir if used end in a / 89 -return: path to file or stream if :stream parameter is passed 91 - url: <string> pathname or url string identifying file to be fetched. 92 - stream: resuests that fetch returns a stream 93 - cache: <T|NIL> if T looks for file in -dir and uses that as source if NIL then the a fresh copy of the file is fetched 94 - dir: location to store fetched file. 95 - flush: if T fetch does not download the file it deletes the existing file. 97 (let ((fetched-path (%fetch url :dir dir :cache cache :flush flush))) 98 (if (not fetched-path) 101 (open fetched-path :direction :input :external-format external-format)