changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / lisp/lib/net/fetch.lisp

changeset 584: 35bb0d5ec95e
parent: 32bd859533b3
child: 7ce855f76e1d
author: Richard Westhaver <ellis@rwest.io>
date: Sat, 10 Aug 2024 00:30:45 -0400
permissions: -rw-r--r--
description: bug fixes, added freedesktop.org.xml rule. more work on prolog/dql - considering lib/lang+forrth..
1 (in-package :net/fetch)
2 
3 (define-condition invalid-path-error (error)
4  ((text :initarg :text :reader text)))
5 
6 (defun download (url &key (output (obj/uri:uri-path (obj/uri:uri url)))
7  (if-exists :error) (progress nil) (connect-timeout net/req:*default-connect-timeout*)
8  cookies)
9  (let ((*progress-bar-enabled* progress))
10  (multiple-value-bind (stream status header uri)
11  (req:get url :want-stream t :force-binary t :connect-timeout connect-timeout :verbose (log:trace-p)
12  :cookie-jar cookies)
13  (when (= status 200)
14  (log:debug! "download connect OK:" url)
15  (log:debug! "headers:" (hash-table-alist header))
16  (let ((len (gethash "content-length" header)))
17  (when len (setf len (parse-integer len)))
18  (with-progress-bar (len "downloading ~a to ~a..." url output)
19  (with-open-file (out output :direction :output :element-type '(unsigned-byte 8) :if-exists if-exists)
20  (loop for c = (read-byte stream nil nil)
21  while c
22  do (progn
23  (update-progress *progress-bar* 1)
24  (write-byte c out)))))
25  (values stream status uri header))))))
26 
27 (defun split-file-path (path)
28  (let ((pos-last-slash (1+ (position #\/ path :from-end t))))
29  (list (subseq path 0 pos-last-slash)
30  (subseq path pos-last-slash))))
31 
32 (defun split-uri-string (uri-string)
33  (let ((pu (parse-uri uri-string)))
34  (cons (uri-host pu) (split-file-path (uri-path pu)))))
35 
36 (defun condition-path (path)
37  "Abuse parse-uri to strip possible get args from path"
38  (let ((p (parse-uri path))) (uri-path p)))
39 
40 (defun is-file (path)
41  (handler-case (probe-file path)
42  (type-error (e) #+sbcl (declare (ignore e)) (error 'invalid-path-error
43  :text (format nil "Invalid path: ~A" path)))))
44 
45 (defun %fetch (url-or-path &key (cache t)
46  dir
47  (flush nil))
48  (cond
49  ((is-file (condition-path url-or-path)) (condition-path url-or-path))
50  ((is-file (condition-path (concatenate 'string dir (uri-path url-or-path))))
51  (condition-path (concatenate 'string dir url-or-path)))
52  ((parse-uri url-or-path)
53  (let* ((tmp-pathname (split-uri-string url-or-path))
54  (file-pathstring (format nil "~{~A~^~}" (if dir (cons dir tmp-pathname) tmp-pathname)))
55  (file-pathname (ensure-directories-exist
56  file-pathstring)))
57  (if flush
58  (when (is-file file-pathname) (delete-file file-pathname))
59  (if (and cache (probe-file file-pathname))
60  (values file-pathname 200 "OK")
61  (download url-or-path :output file-pathname)))))
62  (t (values nil 404 "Not file of url"))))
63 
64 (defun fetch (url-or-path
65  &key
66  (dir)
67  (external-format :utf-8)
68  (cache t)
69  (stream nil)
70  (flush nil))
71  "Fetch file from ~url-or-location~ if not cached in ~dir~
72 stores the file in the location specified by dir if url or file is url the file
73 is stored in ~dir~/~uri-host~/~uri-path~.
74 
75 Note that it is important to ensure that dir and subdir if used end in a /
76 
77 -return: path to file or stream if :stream parameter is passed
78 -arguments:
79  - url-or-path: <string> pathname or url string identifying file to be fetched.
80  - stream: resuests that fetch returns a stream
81  - 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
82  - dir: location to store fetched file.
83  - flush: if T fetch does not download the file it deletes the existing file.
84 "
85  (let ((fetched-path (%fetch url-or-path :dir dir :cache cache :flush flush)))
86  (if (not fetched-path)
87  nil
88  (if stream
89  (open fetched-path :direction :input :external-format external-format)
90  fetched-path))))