Browse code
Refactoring to a more testable storage engine
fiddlerwoaroof authored on 07/08/2017 05:01:15
Showing 6 changed files
Showing 6 changed files
- alimenta-feed-archive.asd
- archive-root/style.css
- encoders.lisp
- feed-archive.lisp
- package.lisp
- stream-provider.lisp
... | ... |
@@ -3,19 +3,22 @@ |
3 | 3 |
:author "Fiddlerwoaroof <fiddlerwoaroof@howit.is>" |
4 | 4 |
:license "MIT" |
5 | 5 |
:depends-on (#:alexandria |
6 |
- #:alimenta |
|
7 |
- #:fwoar.lisputils |
|
8 |
- #:ironclad |
|
9 |
- #:local-time |
|
10 |
- #:serapeum |
|
11 |
- #:ubiquitous |
|
12 |
- #:uiop |
|
13 |
- #:yason) |
|
6 |
+ #:alimenta |
|
7 |
+ #:fwoar.lisputils |
|
8 |
+ #:ironclad |
|
9 |
+ #:local-time |
|
10 |
+ #:serapeum |
|
11 |
+ #:stream-provider |
|
12 |
+ #:trivia |
|
13 |
+ #:ubiquitous |
|
14 |
+ #:uiop |
|
15 |
+ #:vector-update-stream |
|
16 |
+ #:yason) |
|
14 | 17 |
:serial t |
15 | 18 |
:components ((:file "package") |
16 |
- (:file "tools") |
|
17 |
- (:file "yason-encoders") |
|
18 |
- (:file "encoders") |
|
19 |
- (:file "feed-index-utils") |
|
20 |
- (:file "feed-archive"))) |
|
19 |
+ (:file "tools") |
|
20 |
+ (:file "yason-encoders") |
|
21 |
+ (:file "encoders") |
|
22 |
+ (:file "feed-index-utils") |
|
23 |
+ (:file "feed-archive"))) |
|
21 | 24 |
|
... | ... |
@@ -285,9 +285,9 @@ pre { |
285 | 285 |
padding: 1em; |
286 | 286 |
} |
287 | 287 |
|
288 |
-.code .code, code code, pre pre, |
|
289 | 288 |
.code pre, code .code, pre code, |
290 |
-.code code, code pre, pre .code, |
|
289 |
+.code .code, code code, pre pre, |
|
290 |
+.code code, code pre, pre .code |
|
291 | 291 |
{ |
292 | 292 |
background: transparent; |
293 | 293 |
padding: 0; |
... | ... |
@@ -14,98 +14,163 @@ |
14 | 14 |
|
15 | 15 |
(defun wrap-condition (condition feed) |
16 | 16 |
(error 'feed-error |
17 |
- :feed feed |
|
18 |
- :condition condition)) |
|
17 |
+ :feed feed |
|
18 |
+ :condition condition)) |
|
19 | 19 |
|
20 | 20 |
(defmacro unwrap-feed-errors (() &body body) |
21 | 21 |
`(handler-bind ((feed-error (op (error (the-condition _))))) |
22 | 22 |
,@body)) |
23 | 23 |
|
24 |
+(defun %encode-item (root-dir item) |
|
25 |
+ (destructuring-bind (title path) item |
|
26 |
+ (format t "~&Encoding ~a~%" title) |
|
27 |
+ (restart-once (fix-pathname () (fix-path path)) |
|
28 |
+ (let ((pathname (uiop:enough-pathname path root-dir))) |
|
29 |
+ (yason:with-object () |
|
30 |
+ (yason:encode-object-element "title" title) |
|
31 |
+ (yason:encode-object-element "path" pathname)))))) |
|
32 |
+ |
|
24 | 33 |
(defun %encode-feed-as-json (feed item-storage-info root-dir &optional stream) |
25 | 34 |
(with-accessors ((description alimenta:description) |
26 |
- (feed-link alimenta:feed-link) |
|
27 |
- (items alimenta:items) |
|
28 |
- (link alimenta:link) |
|
29 |
- (source-type alimenta:source-type) |
|
30 |
- (title alimenta:title)) feed) |
|
35 |
+ (feed-link alimenta:feed-link) |
|
36 |
+ (items alimenta:items) |
|
37 |
+ (link alimenta:link) |
|
38 |
+ (source-type alimenta:source-type) |
|
39 |
+ (title alimenta:title)) feed) |
|
31 | 40 |
(yason:with-output (stream :indent t) |
32 | 41 |
(yason:with-object () |
33 | 42 |
(yason:encode-object-element "metadata" feed) |
34 | 43 |
(yason:with-object-element ("items") |
35 |
- (yason:with-array () |
|
36 |
- (dolist (item item-storage-info) |
|
37 |
- |
|
38 |
- (with-simple-restart (skip-item "Skip item ~s" (car item)) |
|
44 |
+ (yason:with-array () |
|
45 |
+ (dolist (item item-storage-info) |
|
46 |
+ (with-simple-restart (skip-item "Skip item ~s" (car item)) |
|
39 | 47 |
;; (format t "~&I Store Info: ~a~%~4t~a~%" (uiop:unix-namestring (cadr item)) root-dir) |
40 |
- (%encode-item root-dir item) |
|
41 |
- #+null |
|
48 |
+ (%encode-item root-dir item) |
|
49 |
+ #+null |
|
42 | 50 |
(yason:encode-array-element (uiop:unix-namestring (uiop:enough-pathname root-dir (cadr item)))) |
43 |
- ))))))) |
|
51 |
+ ))))))) |
|
44 | 52 |
|
45 |
-(defmethod store ((items sequence) (directory pathname)) |
|
46 |
- (map 'list (op (store _ directory)) |
|
53 |
+(defmethod store ((items sequence) storage) |
|
54 |
+ (when (next-method-p) |
|
55 |
+ (format t "calling next...~%") |
|
56 |
+ (call-next-method)) |
|
57 |
+ (map 'list (op (store _ storage)) |
|
47 | 58 |
(stable-sort (sort items #'string-lessp |
48 |
- :key #'alimenta:title) |
|
49 |
- #'local-time:timestamp> |
|
50 |
- :key #'alimenta:date))) |
|
59 |
+ :key #'alimenta:title) |
|
60 |
+ #'local-time:timestamp> |
|
61 |
+ :key #'alimenta:date))) |
|
51 | 62 |
|
52 | 63 |
(defmethod store ((feed alimenta:feed) (directory pathname)) |
53 | 64 |
(flet ((get-feed-store-name (feed directory) |
54 |
- (merge-pathnames (get-id feed) |
|
55 |
- directory))) |
|
65 |
+ (merge-pathnames (get-id feed) |
|
66 |
+ directory))) |
|
56 | 67 |
|
57 | 68 |
(with-accessors ((description alimenta:description) |
58 |
- (feed-link alimenta:feed-link) |
|
59 |
- (items alimenta:items) |
|
60 |
- (link alimenta:link) |
|
61 |
- (source-type alimenta:source-type) |
|
62 |
- (title alimenta:title)) feed |
|
63 |
- ; We wrap all errors with our own condition |
|
69 |
+ (feed-link alimenta:feed-link) |
|
70 |
+ (items alimenta:items) |
|
71 |
+ (link alimenta:link) |
|
72 |
+ (source-type alimenta:source-type) |
|
73 |
+ (title alimenta:title)) feed |
|
74 |
+ ; We wrap all errors with our own condition |
|
64 | 75 |
(handler-bind ((error (lambda (c) (error 'feed-error :feed feed :condition c)))) |
65 |
- (values (prog1-let ((feed-title title) |
|
66 |
- (feed-store (get-feed-store-name feed directory))) |
|
67 |
- (ensure-directories-exist feed-store) |
|
68 |
- (with-open-file (index (merge-pathnames "index.json" feed-store) :direction :output) |
|
69 |
- (%encode-feed-as-json feed |
|
70 |
- (store items feed-store) |
|
71 |
- feed-store |
|
72 |
- index))) |
|
73 |
- feed-link))))) |
|
76 |
+ (values (prog1-let ((feed-title title) |
|
77 |
+ (feed-store (get-feed-store-name feed directory))) |
|
78 |
+ (ensure-directories-exist feed-store) |
|
79 |
+ (with-open-file (index (merge-pathnames "index.json" feed-store) :direction :output) |
|
80 |
+ (%encode-feed-as-json feed |
|
81 |
+ (store (copy-seq items) feed-store) |
|
82 |
+ feed-store |
|
83 |
+ index))) |
|
84 |
+ feed-link))))) |
|
74 | 85 |
|
75 | 86 |
(defmethod store ((feed alimenta:feed) (stream stream)) |
76 | 87 |
(handler-bind ((error (lambda (c) |
77 |
- (typecase c |
|
78 |
- (feed-error c) |
|
79 |
- (t (wrap-condition c feed)))))) |
|
88 |
+ (typecase c |
|
89 |
+ (feed-error c) |
|
90 |
+ (t (wrap-condition c feed)))))) |
|
80 | 91 |
(yason:with-output (stream :indent t) |
81 | 92 |
(yason:with-object () |
82 |
- (yason:with-object-element ("metadata") |
|
83 |
- (yason:encode-object feed)) |
|
84 |
- (yason:with-object-element ("items") |
|
85 |
- (yason:with-array () |
|
86 |
- (for:for ((item over feed)) |
|
87 |
- (store item stream)))))))) |
|
88 |
- |
|
89 |
-(defun %encode-item (root-dir item) |
|
90 |
- (destructuring-bind (title path) item |
|
91 |
- (format t "~&Encoding ~a~%" title) |
|
92 |
- (restart-once (fix-pathname () (fix-path path)) |
|
93 |
- (let ((pathname (uiop:enough-pathname path root-dir))) |
|
94 |
- (yason:with-object () |
|
95 |
- (yason:encode-object-element "title" title) |
|
96 |
- (yason:encode-object-element "path" pathname)))))) |
|
93 |
+ (yason:with-object-element ("metadata") |
|
94 |
+ (yason:encode-object feed)) |
|
95 |
+ (yason:with-object-element ("items") |
|
96 |
+ (yason:with-array () |
|
97 |
+ (for:for ((item over feed)) |
|
98 |
+ (store item stream)))))) |
|
99 |
+ (list (alimenta:title feed) |
|
100 |
+ stream))) |
|
97 | 101 |
|
98 | 102 |
(defmethod store ((item alimenta:item) (directory pathname)) |
99 | 103 |
(flet ((get-item-store-name (item directory) |
100 |
- (let ((id (get-id item))) |
|
101 |
- (merge-pathnames (make-pathname :name id :version nil :type "json") directory)))) |
|
104 |
+ (let ((id (get-id item))) |
|
105 |
+ (merge-pathnames (make-pathname :name id :version nil :type "json") directory)))) |
|
102 | 106 |
|
103 | 107 |
(prog1-let ((item-title (alimenta:title item)) |
104 |
- (fn (get-item-store-name item directory))) |
|
108 |
+ (fn (get-item-store-name item directory))) |
|
105 | 109 |
(with-open-file (item-f fn :direction :output) |
106 |
- (yason:encode item item-f))))) |
|
110 |
+ (yason:encode item item-f))))) |
|
107 | 111 |
|
108 | 112 |
(defmethod store ((item alimenta:item) (stream stream)) |
109 | 113 |
(yason:with-output (stream :indent t) |
110 |
- (yason:encode-object item))) |
|
114 |
+ (yason:encode-object item)) |
|
115 |
+ (list (alimenta:title item) |
|
116 |
+ stream)) |
|
117 |
+ |
|
118 |
+;; The feed is always index.json |
|
119 |
+(defmethod stream-provider:stream-key (provider (feed alimenta:feed)) |
|
120 |
+ (pathname |
|
121 |
+ (string-join |
|
122 |
+ (list (get-id feed) |
|
123 |
+ "index.json") |
|
124 |
+ "/"))) |
|
125 |
+ |
|
126 |
+(defmethod stream-provider:stream-key :around ((provider stream-provider:file-provider) |
|
127 |
+ (feed alimenta:feed)) |
|
128 |
+ (prog1-bind (result (call-next-method)) |
|
129 |
+ (ensure-directories-exist (merge-pathnames result |
|
130 |
+ (stream-provider:root provider))))) |
|
131 |
+ |
|
132 |
+(defmethod stream-provider:stream-key (provider (item alimenta:item)) |
|
133 |
+ (let ((id (get-id item))) |
|
134 |
+ (make-pathname :name id :version nil :type "json"))) |
|
111 | 135 |
|
136 |
+(defclass feed-stream-provider (stream-provider:file-provider) |
|
137 |
+ ((%item-providers :accessor item-providers :initform (make-hash-table :test 'equal)))) |
|
138 |
+ |
|
139 |
+(defmethod stream-provider:get-nested-provider ((provider stream-provider:stream-provider) (streamable alimenta:feed)) |
|
140 |
+ (with (items-root (uiop:merge-pathnames* (uiop:pathname-directory-pathname (stream-provider:stream-key provider streamable)) |
|
141 |
+ (stream-provider:root provider))) |
|
142 |
+ (format t "~&items-root: ~a @#%@#$^#$&^&%$~%" items-root) (terpri) |
|
143 |
+ (ensure-gethash items-root |
|
144 |
+ (item-providers provider) |
|
145 |
+ (make-instance 'stream-provider:file-provider :root items-root)))) |
|
146 |
+ |
|
147 |
+(defmethod store :around ((item alimenta:feed-entity) (stream-provider stream-provider:stream-provider)) |
|
148 |
+ (call-next-method) |
|
149 |
+ (list (alimenta:title item) |
|
150 |
+ (stream-provider:stream-key stream-provider item))) |
|
151 |
+ |
|
152 |
+(defmethod store ((item alimenta:item) (stream-provider stream-provider:stream-provider)) |
|
153 |
+ (stream-provider:with-storage-stream (s item stream-provider) |
|
154 |
+ (store item s))) |
|
155 |
+ |
|
156 |
+(defmethod store ((feed alimenta:feed) (stream-provider stream-provider:stream-provider)) |
|
157 |
+ (stream-provider:with-storage-stream (s feed stream-provider) |
|
158 |
+ (with-accessors ((description alimenta:description) |
|
159 |
+ (feed-link alimenta:feed-link) |
|
160 |
+ (items alimenta:items) |
|
161 |
+ (link alimenta:link) |
|
162 |
+ (source-type alimenta:source-type) |
|
163 |
+ (title alimenta:title)) feed |
|
164 |
+ (let* ((item-provider (stream-provider:get-nested-provider stream-provider feed)) |
|
165 |
+ (item-storage-info (map 'list (op (store _ item-provider)) |
|
166 |
+ items))) |
|
167 |
+ (yason:with-output (s :indent t) |
|
168 |
+ (yason:with-object () |
|
169 |
+ (yason:encode-object-element "metadata" feed) |
|
170 |
+ (yason:with-object-element ("items") |
|
171 |
+ (yason:with-array () |
|
172 |
+ (dolist (item item-storage-info) |
|
173 |
+ (destructuring-bind (title path) item |
|
174 |
+ (yason:with-object () |
|
175 |
+ (yason:encode-object-elements "title" title |
|
176 |
+ "path" path)))))))))))) |
... | ... |
@@ -8,59 +8,59 @@ |
8 | 8 |
|
9 | 9 |
(defun get-store-directory-name (timestamp) |
10 | 10 |
(flet ((make-dirname (timestamp) |
11 |
- (-> (local-time:format-timestring nil (local-time:timestamp-minimize-part timestamp :sec) |
|
12 |
- :format +dirname-format+) |
|
13 |
- (merge-pathnames *feed-base*)))) |
|
11 |
+ (-> (local-time:format-timestring nil (local-time:timestamp-minimize-part timestamp :sec) |
|
12 |
+ :format +dirname-format+) |
|
13 |
+ (merge-pathnames *feed-base*)))) |
|
14 | 14 |
(-> (prog1-let ((result (make-dirname timestamp))) |
15 |
- (ensure-directories-exist result)) |
|
16 |
- (car)))) |
|
15 |
+ (ensure-directories-exist result)) |
|
16 |
+ (car)))) |
|
17 | 17 |
|
18 | 18 |
(defun test-feed-list () |
19 | 19 |
(values '("http://feeds.feedburner.com/GamasutraFeatureArticles/" |
20 |
- "https://www.codinghorror.com/blog/index.xml" |
|
21 |
- "https://sancrucensis.wordpress.com/feed/") |
|
22 |
- #p"/tmp/feed-archive/")) |
|
20 |
+ "https://www.codinghorror.com/blog/index.xml" |
|
21 |
+ "https://sancrucensis.wordpress.com/feed/") |
|
22 |
+ #p"/tmp/feed-archive/")) |
|
23 | 23 |
|
24 | 24 |
(defun init-feeds (&key feed-list archive-root) |
25 | 25 |
(ubiquitous:restore 'alimenta.feed-archiver) |
26 | 26 |
(let ((default-root (or archive-root |
27 |
- (merge-pathnames ".feed-archive/" |
|
28 |
- (truename "~/"))))) |
|
27 |
+ (merge-pathnames ".feed-archive/" |
|
28 |
+ (truename "~/"))))) |
|
29 | 29 |
(values (ubiquitous:defaulted-value feed-list :feeds) |
30 |
- (ubiquitous:defaulted-value default-root :archive :root)))) |
|
30 |
+ (ubiquitous:defaulted-value default-root :archive :root)))) |
|
31 | 31 |
|
32 | 32 |
(defun add-feed (feed) |
33 | 33 |
(init-feeds) |
34 | 34 |
(pushnew feed |
35 |
- (ubiquitous:value :feeds) |
|
36 |
- :test #'equalp)) |
|
35 |
+ (ubiquitous:value :feeds) |
|
36 |
+ :test #'equalp)) |
|
37 | 37 |
|
38 | 38 |
(defun safe-pull-feed (feed-url &aux (pop-times 0)) |
39 | 39 |
"Handles date parsing errors in the feed: chronicity won't parse |
40 | 40 |
certain date formats, this catches the error and modifies the |
41 | 41 |
format to something chronicity can handle." |
42 | 42 |
(flet ((pop-50-tokens (c) |
43 |
- (declare (ignore c)) |
|
44 |
- (when (find-restart 'alimenta:pop-token) |
|
45 |
- (if (< pop-times 50) |
|
46 |
- (progn (incf pop-times) |
|
47 |
- (format t "~&Processing error, trying to pop a token (popped ~d times)~%" |
|
48 |
- pop-times) |
|
49 |
- (alimenta:pop-token)) |
|
50 |
- (continue))))) |
|
43 |
+ (declare (ignore c)) |
|
44 |
+ (when (find-restart 'alimenta:pop-token) |
|
45 |
+ (if (< pop-times 50) |
|
46 |
+ (progn (incf pop-times) |
|
47 |
+ (format t "~&Processing error, trying to pop a token (popped ~d times)~%" |
|
48 |
+ pop-times) |
|
49 |
+ (alimenta:pop-token)) |
|
50 |
+ (continue))))) |
|
51 | 51 |
(handler-bind ((warning #'muffle-warning) |
52 |
- (error #'pop-50-tokens)) |
|
52 |
+ (error #'pop-50-tokens)) |
|
53 | 53 |
(prog1 (alimenta.pull-feed:pull-feed feed-url) |
54 |
- ;; Why am I decf-ing here? |
|
55 |
- (decf pop-times))))) |
|
54 |
+ ;; Why am I decf-ing here? |
|
55 |
+ (decf pop-times))))) |
|
56 | 56 |
|
57 | 57 |
(defmacro with-progress-message ((stream before after &optional (error-msg " ERROR~%~4t~a~%")) &body body) |
58 | 58 |
(once-only (before after stream) |
59 | 59 |
`(handler-bind ((error (op (format ,stream ,error-msg _)))) |
60 | 60 |
(format ,stream "~&~a . . ." ,before) |
61 | 61 |
(multiple-value-prog1 (progn |
62 |
- ,@body) |
|
63 |
- (format ,stream " ~a~%" ,after))))) |
|
62 |
+ ,@body) |
|
63 |
+ (format ,stream " ~a~%" ,after))))) |
|
64 | 64 |
|
65 | 65 |
(defun skip-feed () |
66 | 66 |
(when-let ((restart (find-restart 'skip-feed))) |
... | ... |
@@ -72,41 +72,57 @@ |
72 | 72 |
|
73 | 73 |
(defun pull-and-store-feeds (feeds pull-directory) |
74 | 74 |
(mapcar (op (pull-and-store-feed _ pull-directory)) |
75 |
- feeds)) |
|
75 |
+ feeds)) |
|
76 | 76 |
|
77 |
-(defun pull-and-store-feed (feed-url pull-directory) |
|
77 |
+(defun log-pull (feed-puller feed-url stream) |
|
78 |
+ (let ((before-message (concatenate 'string "Trying to pull: " feed-url))) |
|
79 |
+ (with-progress-message (stream before-message "Success") |
|
80 |
+ (funcall feed-puller feed-url)))) |
|
81 |
+ |
|
82 |
+(defun normalize-feed (feed-url feed) |
|
83 |
+ (alimenta:filter-feed (coerce-feed-link feed-url feed) |
|
84 |
+ (complement #'older-than-a-month) |
|
85 |
+ :key 'alimenta:date)) |
|
86 |
+ |
|
87 |
+(defun log-serialization (feed-url stream feed path) |
|
88 |
+ (with-progress-message (stream "Serializing XML" (format nil "done with ~a" feed-url)) |
|
89 |
+ (save-feed feed (merge-pathnames "feed.xml" path)))) |
|
90 |
+ |
|
91 |
+(defun feed-relative-pathname (path &optional (feed-base *feed-base*)) |
|
92 |
+ (uiop:enough-pathname path feed-base)) |
|
93 |
+ |
|
94 |
+(defun pull-and-store-feed (feed-url pull-directory &optional (feed-puller #'safe-pull-feed)) |
|
78 | 95 |
(flet ((log-pull (stream) |
79 |
- (let ((before-message (format nil "Trying to pull: ~a" feed-url))) |
|
80 |
- (with-progress-message (stream before-message "Success") |
|
81 |
- (prog1 (safe-pull-feed feed-url))))) |
|
82 |
- (log-serialization (stream feed path) |
|
83 |
- (with-progress-message (stream "Serializing XML" (format nil "done with ~a" feed-url)) |
|
84 |
- (save-feed feed (merge-pathnames "feed.xml" path))))) |
|
96 |
+ (declare (inline) (dynamic-extent stream)) |
|
97 |
+ (log-pull feed-puller feed-url stream)) |
|
98 |
+ (log-serialization (stream feed path) |
|
99 |
+ (declare (inline) (dynamic-extent stream)) |
|
100 |
+ (log-serialization feed-url stream feed path))) |
|
85 | 101 |
|
86 | 102 |
(with-simple-restart (skip-feed "Stop processing for ~a" feed-url) |
87 | 103 |
(let* ((feed (with-retry ("Pull feed again.") |
88 |
- (alimenta:filter-feed (coerce-feed-link feed-url |
|
89 |
- (log-pull t)) |
|
90 |
- (complement #'older-than-a-month) |
|
91 |
- :key 'alimenta:date)))) |
|
92 |
- (multiple-value-bind (result url) (store feed pull-directory) |
|
93 |
- (destructuring-bind (title path) result |
|
94 |
- (log-serialization t feed path) |
|
95 |
- (make-feed-reference url :title title |
|
96 |
- :path (uiop:enough-pathname path *feed-base*)))))))) |
|
104 |
+ (normalize-feed feed-url (log-pull t))))) |
|
105 |
+ (trivia:multiple-value-match (store feed pull-directory) |
|
106 |
+ (((list title path) url) |
|
107 |
+ (log-serialization t feed path) |
|
108 |
+ (make-feed-reference url :title title :path (feed-relative-pathname path)))))))) |
|
97 | 109 |
|
98 | 110 |
(defun feed-index (index-stream pull-time references) |
99 | 111 |
(yason:with-output (index-stream :indent t) |
100 | 112 |
(yason:encode-object |
101 | 113 |
(make-feed-index pull-time (remove-if 'null references))))) |
102 | 114 |
|
103 |
-(defun archive-feeds () |
|
115 |
+(defun archive-feeds (pull-time pull-directory index-stream) |
|
116 |
+ (let ((references (pull-and-store-feeds *feeds* pull-directory))) |
|
117 |
+ (feed-index index-stream pull-time references) |
|
118 |
+ references)) |
|
119 |
+ |
|
120 |
+(defun archive-feeds-nondeterm () |
|
104 | 121 |
(let* ((pull-time (local-time:now)) |
105 | 122 |
(pull-directory (get-store-directory-name pull-time)) |
106 |
- (references (pull-and-store-feeds *feeds* pull-directory)) |
|
107 | 123 |
(index-path (merge-pathnames "index.json" pull-directory))) |
108 | 124 |
(with-open-file (index index-path :direction :output) |
109 |
- (feed-index index pull-time references)) |
|
125 |
+ (archive-feeds pull-time pull-directory index)) |
|
110 | 126 |
(format t "~&!! pull-directory ~a~%" (uiop:enough-pathname pull-directory *feed-base*)))) |
111 | 127 |
|
112 | 128 |
;; This is an ungodly mess, we need to avoid funneling everything through fix-pathname-or-skip |
... | ... |
@@ -144,4 +160,5 @@ |
144 | 160 |
(fix-pathname-or-skip _1 :restart 'continue))))) |
145 | 161 |
(multiple-value-bind (*feeds* *feed-base*) (funcall feed-list-initializer) |
146 | 162 |
(alimenta.pull-feed:with-user-agent ("Feed Archiver v0.1b") |
147 |
- (archive-feeds))))))) |
|
163 |
+ (archive-feeds-nondeterm))))))) |
|
164 |
+ |
... | ... |
@@ -1,18 +1,32 @@ |
1 |
-(defpackage :alimenta.feed-archive.tools |
|
2 |
- (:use :cl :alexandria :serapeum :fw.lu) |
|
1 |
+(defpackage :afa-user |
|
2 |
+ (:use cl) |
|
3 |
+ (:export #:define-package)) |
|
4 |
+(in-package :afa-user) |
|
5 |
+ |
|
6 |
+(defmacro define-package (name &body arguments) |
|
7 |
+ (let ((uses (cons :use |
|
8 |
+ (union '(:cl :alexandria :serapeum) |
|
9 |
+ (cdr (assoc :use arguments)))))) |
|
10 |
+ `(progn |
|
11 |
+ (defpackage ,name |
|
12 |
+ ,uses |
|
13 |
+ ,@(remove :use arguments :key 'car))))) |
|
14 |
+ |
|
15 |
+(define-package :alimenta.feed-archive.tools |
|
16 |
+ (:use :fw.lu) |
|
3 | 17 |
(:shadow :->) |
4 | 18 |
(:export :fix-pathname :sha256-string :get-id :older-than-a-week :-> :get-feed-store-name |
5 | 19 |
:store :get-item-store-name :restart-once :coerce-feed-link :with-retry |
6 | 20 |
:older-than-a-month)) |
7 | 21 |
|
8 |
-(defpackage :alimenta.feed-archive.encoders |
|
9 |
- (:use :cl :alexandria :serapeum :fw.lu :alimenta.feed-archive.tools) |
|
22 |
+(define-package :alimenta.feed-archive.encoders |
|
23 |
+ (:use :fw.lu :alimenta.feed-archive.tools) |
|
10 | 24 |
(:shadowing-import-from :alimenta.feed-archive.tools :->) |
11 | 25 |
(:export :skip-item :the-condition :the-feed :feed-error |
12 |
- :unwrap-feed-errors)) |
|
26 |
+ :unwrap-feed-errors :feed-stream-provider)) |
|
13 | 27 |
|
14 |
-(defpackage :alimenta.feed-archive |
|
15 |
- (:use :cl :alexandria :serapeum :fw.lu :alimenta.feed-archive.tools) |
|
28 |
+(define-package :alimenta.feed-archive |
|
29 |
+ (:use :fw.lu :alimenta.feed-archive.tools) |
|
16 | 30 |
(:shadowing-import-from :alimenta.feed-archive.tools :->) |
17 | 31 |
(:export #:init-feeds #:archive-feeds #:command-line-main)) |
18 | 32 |
|
19 | 33 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,68 @@ |
1 |
+(afa-user:define-package :stream-provider |
|
2 |
+ (:use :fw.lu) |
|
3 |
+ (:export |
|
4 |
+ #:get-stream-for |
|
5 |
+ #:stream-provider |
|
6 |
+ #:string-provider |
|
7 |
+ #:file-provider |
|
8 |
+ #:stream-key |
|
9 |
+ #:root |
|
10 |
+ #:streams |
|
11 |
+ #:with-storage-stream)) |
|
12 |
+(cl:in-package :stream-provider) |
|
13 |
+ |
|
14 |
+(defclass stream-provider () |
|
15 |
+ ()) |
|
16 |
+ |
|
17 |
+(defvar *store-root* *default-pathname-defaults*) |
|
18 |
+ |
|
19 |
+(defgeneric stream-key (provider item) |
|
20 |
+ (:documentation "return a key for an item: must be able to be compared with EQUAL |
|
21 |
+ |
|
22 |
+If you override on provider, make sure to CALL-NEXT-METHOD")) |
|
23 |
+ |
|
24 |
+(defgeneric get-stream-for (provider streamable &rest extra-args) |
|
25 |
+ (:documentation "get a stream for a given streamable object")) |
|
26 |
+ |
|
27 |
+(defgeneric root (provider) |
|
28 |
+ (:documentation "get the base path for the streams")) |
|
29 |
+ |
|
30 |
+ |
|
31 |
+(defclass string-provider (stream-provider) |
|
32 |
+ ((%streams :reader streams :initform (make-hash-table :test 'equal)))) |
|
33 |
+ |
|
34 |
+(defmethod root ((provider string-provider)) |
|
35 |
+ #p"/") |
|
36 |
+ |
|
37 |
+(defmethod stream-key :around (provider item) |
|
38 |
+ (let ((key (call-next-method))) |
|
39 |
+ (check-type key (or string pathname)) |
|
40 |
+ (uiop:enough-pathname key (root provider)))) |
|
41 |
+ |
|
42 |
+(defmethod get-stream-for ((provider string-provider) streamable &rest extra-args) |
|
43 |
+ (declare (ignore extra-args)) |
|
44 |
+ (with-accessors* (streams) provider |
|
45 |
+ (vector-update-stream:make-update-stream |
|
46 |
+ (setf (gethash (stream-key provider streamable) streams) |
|
47 |
+ (make-array 10 :adjustable t :fill-pointer 0))))) |
|
48 |
+ |
|
49 |
+(defclass file-provider (stream-provider) |
|
50 |
+ ((%root :initarg :root :initform (error "need a root for a file-provider") :reader root) |
|
51 |
+ (%if-exists :initarg :if-exists :initform :supersede :reader if-exists))) |
|
52 |
+ |
|
53 |
+(defmethod get-stream-for ((provider file-provider) streamable &rest extra-args) |
|
54 |
+ (declare (ignore extra-args)) |
|
55 |
+ (with-accessors* (if-exists root) provider |
|
56 |
+ (let ((stream-key (merge-pathnames (stream-key provider streamable) |
|
57 |
+ root))) |
|
58 |
+ (when (eql if-exists :if-exists) |
|
59 |
+ (ensure-directories-exist stream-key)) |
|
60 |
+ (open stream-key :direction :output :if-exists if-exists |
|
61 |
+ :element-type 'octet)))) |
|
62 |
+ |
|
63 |
+(defmacro with-storage-stream ((stream-sym object provider &rest extra-args) &body body) |
|
64 |
+ (once-only (object) |
|
65 |
+ `(let ((,stream-sym (flexi-streams:make-flexi-stream (get-stream-for ,provider ,object ,@extra-args) |
|
66 |
+ :external-format :utf-8))) |
|
67 |
+ (unwind-protect (progn ,@body) |
|
68 |
+ (close ,stream-sym))))) |