get-gruiks.scm (5547B)
1 ; Copyright (c) 2026, Natacha Porté 2 ; 3 ; Permission to use, copy, modify, and distribute this software for any 4 ; purpose with or without fee is hereby granted, provided that the above 5 ; copyright notice and this permission notice appear in all copies. 6 ; 7 ; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 ; WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 ; MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 ; ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 ; WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 ; ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 ; OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15 (import 16 (chicken io) 17 (chicken process-context) 18 (chicken string) 19 (chicken time posix) 20 openssl ; must be above http-client 21 http-client 22 intarweb 23 rss 24 sql-de-lite 25 uri-common) 26 27 ;;;;;;;;;;;;;;;;;;;;;;;;;;;; 28 ;; Command-Line Processing 29 30 (define db-name 31 (let ((arg-list (command-line-arguments))) 32 (if (>= (length arg-list) 1) 33 (car arg-list) 34 "iens.sqlite"))) 35 36 ;;;;;;;;;;;;;;;;;;;;;;; 37 ;; Persistent Storage 38 39 (define db 40 (open-database db-name)) 41 (exec (sql/transient db "PRAGMA foreign_keys = ON; 42 PRAGMA busy_timeout = 5000;")) 43 ; PRAGMA journal_mode = WAL; 44 ; PRAGMA synchronous = NORMAL; 45 46 (include "common.scm") 47 48 (assert (= 8 (db-version))) 49 50 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 51 ;; Gruik build from sources 52 53 (define (process-gruik source url title comm) 54 (if (= 0 (query fetch-value 55 (sql db "SELECT count(id) FROM gruik 56 WHERE section=? AND url=? AND title=?;") 57 source url title) 58 (query fetch-value 59 (sql db "SELECT count(id) FROM entry 60 WHERE source=? AND url=? AND title=?;") 61 source url title)) 62 (exec 63 (sql db "INSERT INTO gruik(position, notes, ptime, 64 section, url, title, comment_url, 65 mark, ctime, mtime) 66 VALUES (-1, '', datetime(?1,'unixepoch'), 67 ?2, ?3, ?4, ?5, 68 ?6, ?1, ?1);") 69 (query fetch-value 70 (sql db "SELECT MAX(CAST(strftime('%s', 'now') as INT), 71 (SELECT max(mtime) FROM gruik) + 1);")) 72 source url title (if comm comm '()) 73 (if (= 0 (query fetch-value 74 (sql db "SELECT count(id) FROM gruik WHERE url=?;") 75 url) 76 (query fetch-value 77 (sql db "SELECT count(id) FROM entry WHERE url=?;") 78 url)) 79 0 -1)) 80 (exec (sql db "UPDATE gruik 81 SET mtime=MAX(CAST(strftime('%s', 'now') as INT), 82 (SELECT max(mtime) FROM gruik) + 1) 83 WHERE section=? AND url=? AND title=? AND mark<0;") 84 source url title))) 85 86 (define (process-rss source items) 87 (unless (null? items) 88 (let* ((item (car items)) 89 (attr (rss:item-attributes item)) 90 (link (rss:item-link item)) 91 (title (rss:item-title item)) 92 (comm (alist-ref 'comments attr))) 93 (process-gruik source link (if title title link) comm) 94 (process-rss source (cdr items))))) 95 96 (define (get-source parse url last-modified etag) 97 (let* ((hlm (if (null? last-modified) '() 98 `((if-modified-since 99 #(,(seconds->local-time last-modified) ()))))) 100 (het (cond ((null? etag) '()) 101 ((string=? etag "") '()) 102 ((eqv? (string-ref etag 0) #\S) 103 `((if-none-match (strong . ,(substring etag 1))))) 104 ((eqv? (string-ref etag 0) #\W) 105 `((if-none-match (weak . ,(substring etag 1))))) 106 (else '()))) 107 (req (make-request 108 uri: (uri-reference url) 109 headers: (headers `(,@hlm ,@het))))) 110 (let-values (((result _ resp) (with-input-from-request req #f parse))) 111 (let* ((hdr (response-headers resp)) 112 (lm (header-value 'last-modified hdr)) 113 (et (header-value 'etag hdr))) 114 (when (or (not (null? last-modified)) (not (null? etag)) lm et) 115 (exec (sql db "UPDATE source_rss SET last_modified=?, etag=? 116 WHERE url=?;") 117 (if lm (local-time->seconds lm) '()) 118 (if et (string-append 119 (cond ((eq? (car et) 'weak) "W") 120 ((eq? (car et) 'strong) "S") 121 (else "*")) 122 (cdr et)) 123 '()) 124 url))) 125 result))) 126 127 (define (process-source name url last-modified etag) 128 (let* ((rss (get-source rss:read url last-modified etag)) 129 (source (if (string=? name url) 130 (begin 131 (exec (sql db "UPDATE source_rss SET name=? 132 WHERE name=? AND url=?;") 133 (rss:item-title (rss:feed-channel rss)) 134 name url) 135 (rss:item-title (rss:feed-channel rss))) 136 name))) 137 (assert source) 138 (process-rss source (rss:feed-items rss)))) 139 140 ;;;;;;;;;;;;;;; 141 ;; Actual Run 142 143 (query 144 (for-each-row* process-source) 145 (sql db "SELECT name,url,last_modified,etag FROM source_rss;"))