changelog shortlog graph tags branches changeset files revisions annotate raw help

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
2 
3 ;; Provides the DOWNLOAD and FETCH functions for easily download remote files.
4 
5 ;;; Commentary:
6 
7 ;;; Code:
8 (in-package :net/fetch)
9 
10 (define-condition invalid-path-error (error)
11  ((text :initarg :text :reader text)))
12 
13 (defvar *default-fetch-output-file* #P"index.html")
14 
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*)
17  cookies)
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)
22  :cookie-jar cookies)
23  (when (= status 200)
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)
31  (loop
32  (let ((end (read-sequence buff stream :end 4096)))
33  (when progress (update-progress *progress-bar* end))
34  (write-sequence buff out :end end)
35  (unless (= end 4096)
36  (return))))))))
37  (values stream status uri header))))
38 
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))))
43 
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)))))
47 
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)))
51 
52 (defun is-file (path)
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)))))
56 
57 (defun %fetch (url-or-path &key (cache t)
58  dir
59  (flush nil))
60  (cond
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
68  file-pathstring)))
69  (if flush
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"))))
75 
76 (defun fetch (url
77  &key
78  (dir)
79  (external-format :utf-8)
80  (cache t)
81  (stream nil)
82  (flush nil))
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.
86 
87 Note that it is important to ensure that dir and subdir if used end in a /
88 
89 -return: path to file or stream if :stream parameter is passed
90 -arguments:
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.
96 "
97  (let ((fetched-path (%fetch url :dir dir :cache cache :flush flush)))
98  (if (not fetched-path)
99  nil
100  (if stream
101  (open fetched-path :direction :input :external-format external-format)
102  fetched-path))))