git.fiddlerwoaroof.com
Browse code

Added update thread, various reorg/cleanup

fiddlerwoaroof authored on 12/10/2015 05:37:23
Showing 4 changed files
... ...
@@ -12,6 +12,8 @@
12 12
 (ql:quickload :ubiquitous)
13 13
 (ql:quickload :iterate)
14 14
 (ql:quickload :jonathan)
15
+(ql:quickload :cl-actors)
16
+(ql:quickload :simple-tasks)
15 17
 
16 18
 (declaim (optimize (speed 0) (safety 3) (debug 2)))
17 19
 
... ...
@@ -19,10 +21,11 @@
19 21
 (push (cons "application" "rss+xml") drakma:*text-content-types*)
20 22
 (push (cons "text" "rss+xml") drakma:*text-content-types*)
21 23
 
24
+;(load "utils.lisp")
22 25
 (load "rss.lisp")
23 26
 
24 27
 (defpackage :whitespace
25
-  (:use #:cl #:whitespace.utils #:whitespace.rss #:whitespace.tables))
28
+  (:use #:cl #:whitespace.utils #:whitespace.feeds.rss #:whitespace.tables))
26 29
 
27 30
 (in-package plump-dom)
28 31
 
... ...
@@ -58,6 +61,15 @@
58 61
     (defparameter *feeds* (map 'vector (lambda (x) (make-rss-feed x)) docs))))
59 62
 
60 63
 (defparameter *db-connection-info* (ubiquitous:value 'db 'connection 'info))
64
+(defmacro with-whitespace-db (&body body)
65
+  `(postmodern:with-connection *db-connection-info*
66
+     ,@body))
67
+(defmacro wc (&body body)
68
+  `(with-whitespace-db ,@body))
69
+
70
+(defmacro with-xml-tags (&body body)
71
+  `(let ((plump:*tag-dispatchers* plump:*xml-tags*))
72
+     ,@body))
61 73
 
62 74
 (defmacro def-markup (name (&rest args) &body body)
63 75
   `(defmacro ,name ,args
... ...
@@ -302,6 +314,7 @@
302 314
 
303 315
                    `((:or (:and .link-header :hover) (.link.closed (:and .link-header :hover)))
304 316
                      :background-color ,(colors:colorscheme-hover-highlight *colorscheme*)))))
317
+        (declare (ignorable main-right-margin)) ; TODO: use this!!!
305 318
         `(200 (:content-type "text/css") ,ss)))))
306 319
 
307 320
 (cl-oid-connect:def-route ("/theme/dark.css" (params) :app *app*)
... ...
@@ -348,4 +361,31 @@
348 361
     (stop))
349 362
   (start))
350 363
 
364
+(defun update-feed (url)
365
+  (wc
366
+    (postmodern:with-transaction ()
367
+      (upsert-feed (make-rss-feed (with-xml-tags (plump:parse (drakma:http-request url))))))))
368
+
369
+(defun update-all-feeds ()
370
+  (wc
371
+    (let ((urls (postmodern:query (:select 'fetch-url :from 'rss-feed-store))))
372
+      (mapcar (lambda (x) (apply #'update-feed x)) urls))))
373
+
374
+(defun minutes (minutes) (* minutes 60))
375
+
376
+(let (update-thread stop)
377
+  (defun start-update-thread ()
378
+    (setf update-thread
379
+          (bordeaux-threads:make-thread
380
+            (lambda ()
381
+              (loop
382
+                (update-all-feeds)
383
+                (sleep (ubiquitous:value 'update-frequency))
384
+                (when stop
385
+                  (return-from nil nil))))
386
+            :name "Whitespace Update Thread")))
387
+  (defun stop-update-thread ()
388
+    (setf stop t)
389
+    (setf update-thread nil)))
390
+
351 391
 ; vim: foldmethod=marker foldmarker=(,) foldminlines=3 :
... ...
@@ -1,27 +1,60 @@
1 1
 (in-package :cl-user)
2
+(use-package :lquery)
2 3
 (declaim (optimize (safety 3) (speed 0) (debug 3)))
3 4
 
5
+(define-lquery-list-function where-attr (els attr &key is)
6
+  (remove-if-not (lambda (y) (string= (plump:attribute y attr) is))
7
+                 els))
8
+
9
+(define-lquery-list-function tag-name (nodes &rest tags)
10
+  "Manipulate elements on the basis of their tag-name.  With no arguments,
11
+   return their names else return the corresponding tags."
12
+  (if (null tags)
13
+    (map 'vector #'plump:tag-name nodes)
14
+    (apply #'vector
15
+           (loop for node across nodes
16
+                 if (find (plump:tag-name node) tags :test #'string=)
17
+                 collect node))))
18
+
19
+
20
+
4 21
 (load "tables.lisp")
5 22
 
6
-(defpackage :whitespace.rss
23
+(defpackage :whitespace.feeds.autodiscovery
7 24
   (:use #:cl #:alexandria #:postmodern #:lquery #:cl-syntax #:cl-annot.syntax #:cl-annot.class
8 25
         #:whitespace.tables #:iterate #:whitespace.utils)
9 26
   (:import-from anaphora it))
27
+(in-package #:whitespace.feeds.autodiscovery)
28
+
29
+(defun discover-feeds (doc)
30
+  "Given a plump DOM element, discover any feeds in the document using the link tags.
31
+   Returns a vector "
32
+  (coerce
33
+    (lquery:$ (inline doc)
34
+            "link"
35
+            (where-attr "rel" :is "alternate")
36
+            (combine (attr "href")
37
+                     (attr "type")))
38
+    'list))
39
+(export 'discover-feeds)
40
+
41
+(defpackage :whitespace.feeds.opml
42
+  (:use #:cl #:alexandria #:postmodern #:lquery #:cl-syntax #:cl-annot.syntax #:cl-annot.class
43
+        #:whitespace.tables #:iterate #:whitespace.utils)
44
+  (:import-from anaphora it))
45
+(in-package #:whitespace.feeds.opml)
10 46
 
11 47
 
12
-(in-package :whitespace.rss)
13
-(cl-annot.syntax:enable-annot-syntax)
14 48
 
15
-(lquery:define-lquery-list-function tag-name (nodes &rest tags)
16
-  "Manipulate elements on the basis of their tag-name.
17
-   With no arguments, return their names else return
18
-   the corresponding tags."
19
-  (if (null tags)
20
-    (map 'vector #'plump:tag-name nodes)
21
-    (apply #'vector
22
-           (loop for node across nodes
23
-                 if (find (plump:tag-name node) tags :test #'string=)
24
-                 collect node))))
49
+(in-package :cl-user)
50
+(defpackage :whitespace.feeds.rss
51
+  (:use #:cl #:alexandria #:postmodern #:lquery #:cl-syntax #:cl-annot.syntax #:cl-annot.class
52
+        #:whitespace.tables #:iterate #:whitespace.utils #:whitespace.feeds.autodiscovery)
53
+  (:import-from anaphora it))
54
+
55
+
56
+(in-package :whitespace.feeds.rss)
57
+(cl-annot.syntax:enable-annot-syntax)
25 58
 
26 59
 @export
27 60
 (defmacro get-elements (feed &optional (filter nil))
... ...
@@ -39,12 +72,6 @@
39 72
 (defmacro extract-text (selector &optional (default ""))
40 73
   `(or (lquery:$ ,selector (text) (node)) ,default))
41 74
 
42
-(defmacro defserializer ((specializes) &body slots)
43
-  (with-gensyms (obj o-t p-t)
44
-    `(defmethod serialize ((,obj ,specializes) &optional (,o-t #'identity) (,p-t #'%default-pair-transform))
45
-      (transform-result (,o-t ,p-t)
46
-        (slots-to-pairs ,obj ,slots)))))
47
-
48 75
 @export
49 76
 (defmacro xml-text-bind (syms &body body)
50 77
   "Bind the symbols passed in the second arg to the text of the matching
... ...
@@ -84,7 +111,7 @@
84 111
    (source :accessor rss-item-source  :initarg :source)))
85 112
 
86 113
 @export
87
-(defun make-rss-item (item)
114
+(defun make-rss-item (item fallback-date)
88 115
   (lquery:initialize item)
89 116
   (flet ((dehtml (h) (plump:text (plump:parse h)))
90 117
          (get-category-names (it) ;;; TODO: simplify this---Ask Shinmera on IRC
... ...
@@ -100,16 +127,18 @@
100 127
 
101 128
            (description-raw (normalize-html
102 129
                               (default-when description-element (emptyp description-element)
103
-                                (extract-text "description"))))
104
-
105
-           (description-munged (dehtml description-raw))
106
-           (category (get-category-names (lquery:$ "category"))))
130
+                                (extract-text "description")))))
107 131
            ;(enclosure) --- TODO: implement comment / enclosure handling
108 132
 
109
-      (xml-text-bind (title link guid pub-date source comments)
133
+      (xml-text-bind (title link guid pubdate source comments)
134
+        (when (string= pubdate "")
135
+          (setf pubdate fallback-date))
110 136
         (make-instance-from-symbols 'rss-item
111
-                                    item title link description-raw (description description-munged)
112
-                                    category guid pub-date source comments)))))
137
+                                    item title link
138
+                                    description-raw
139
+                                    (description (dehtml description-raw))
140
+                                    (category (get-category-names (lquery:$ "category")))
141
+                                    guid (pub-date pubdate) source comments)))))
113 142
       ;(setf (rss-item-enclosure result) enclosure)      -- TODO: comment/enclosure . . .
114 143
 
115 144
 
... ...
@@ -120,20 +149,17 @@
120 149
          (fetch-url (lquery:$ "channel" (children) (tag-name "atom:link") (filter "[rel=self]") (attr :href) (node)))
121 150
          (link (lquery:$ "channel > link" (text) (node)))
122 151
          (link (if (string= link "") (lquery:$ "channel" (children) (tag-name "atom:link") (attr :href) (node)) link))
123
-         (items (lquery:$ "item")))
152
+         (items (lquery:$ "item"))
153
+         (last-build (or (lquery:$ "lastBuildDate" (text) (node)) ""))
154
+         (pub-date (default-when last-build (string= last-build "")
155
+                     (lquery:$ "pubDate" (text) (node))))
156
+         (fallback-date (if (string= pub-date "") "2015-01-01 0:0:0+00" pub-date)))
157
+    (format t "fallback-date: ~a~%" fallback-date)
124 158
     (xml-text-bind (title description)
125 159
       (make-instance-from-symbols 'rss-feed
126 160
                                   feed title link description channel fetch-url
127 161
                                   (items (iterate (for it in-sequence items)
128
-                                                  (collecting (make-rss-item it))))))))
129
-
130
-@export
131
-(defgeneric serialize (cls &optional output-transform pair-transform))
132
-
133
-
134
-(defmethod serialize ((obj sequence) &optional (o-t #'identity) (p-t #'%default-pair-transform))
135
-  (iterate (for item in-sequence obj)
136
-           (collect (serialize item o-t p-t))))
162
+                                                  (collecting (make-rss-item it fallback-date))))))))
137 163
 
138 164
 ; These are the interface I'm planning to remove as duplicate
139 165
 (defserializer (rss-feed)
... ...
@@ -144,13 +170,6 @@
144 170
 (defserializer (rss-item)
145 171
   title link (description description-raw :bind-from description-raw) guid pub-date source)
146 172
 
147
-; this is the interface to be used
148
-(defserializer (rss_feed_store)
149
-  title link description fetch-url)
150
-
151
-(defserializer (rss_item_store)
152
-  title link description fetch-url)
153
-
154 173
 (defmethod jonathan:%to-json ((obj rss-feed))
155 174
   (jonathan:%to-json (serialize obj #'alexandria:alist-hash-table #'%json-pair-transform)))
156 175
 
... ...
@@ -164,63 +183,22 @@
164 183
                  (caar it))))
165 184
        ,@body)))
166 185
 
167
-(defgeneric get-dao-for (obj &optional link)
168
-  ; NOTE: this won't make dao objects for the _items_ when called on the feed!
169
-  ; also NOTE: this _prefers_ the passed object
170
-  (:method ((obj rss-feed) &optional linked-objects)
171
-   (declare (ignore linked-objects))
172
-   (with-slots (title link description fetch-url) obj
173
-     (get-id-for-object (rss_feed_store link) link
174
-       (make-instance-from-symbols 'rss_feed_store id title link description fetch-url (fetch-defaults t))) ))
175
-
176
-  (:method ((obj rss-item) &optional feed)
177
-   (with-slots (title link description-raw guid pub-date source) obj
178
-     (get-id-for-object (rss_item_store guid) guid
179
-       (let ((result (make-instance-from-symbols 'rss_item_store title link (description description-raw)
180
-                                   guid pub-date source feed (fetch-defaults t))))
181
-         (unless (null id)
182
-           (setf (ris-id result) id))
183
-         result)))))
184
-
185
-(define-condition blarg () ((text :initarg text)))
186
-@export
187
-(defun get-feed-from-dao (rss-feed)
188
-  (let ((feed-dao (get-dao-for rss-feed)))
189
-    (list feed-dao
190
-          ;(error 'blarg :text (format t "~a~%" rss-feed))
191
-          (with-slots (items) rss-feed
192
-            (iterate (for item in items)
193
-                     (collect (get-dao-for item (slot-value feed-dao 'id))))))))
194
-
195
-
196
-@export
197
-(defun upsert-feed (rss-feed)
198
-  (postmodern:ensure-transaction
199
-    (destructuring-bind (feed items) (get-feed-from-dao rss-feed)
200
-      (postmodern:upsert-dao feed)
201
-      (mapcar #'postmodern:upsert-dao items))))
202
-
203
-@export
204
-(defun store-item-dao (serialized-rss-item link)
205
-  (apply #'postmodern:make-dao
206
-         (list* 'rss_item_store :feed link
207
-                (iterate (for (k . v) in-sequence serialized-rss-item)
208
-                         (appending (list k v))))))
209
-
210
-@export
211
-(defun store-feed-dao (serialized-rss-feed &optional link)
212
-  (declare (ignore link))
213
-  (let* ((items nil)
214
-         (rss_feed (apply #'postmodern:make-dao
215
-                          (cons 'rss_feed_store
216
-                                (iterate (for (k . v) in-sequence serialized-rss-feed)
217
-                                         (if (eql k :items)
218
-                                           (setf items v)
219
-                                           (appending (list k v))))))))
220
-    (iterate (for item in items)
221
-             (store-item-dao (serialize item)
222
-                             (slot-value rss_feed 'id)))
223
-    rss_feed))
186
+; NOTE: this won't make dao objects for the _items_ when called on the feed!
187
+; also NOTE: this _prefers_ the passed object
188
+(defmethod get-dao-for ((obj rss-feed) &optional linked-objects)
189
+ (declare (ignore linked-objects))
190
+ (with-slots (title link description fetch-url) obj
191
+   (get-id-for-object (rss_feed_store link) link
192
+     (make-instance-from-symbols 'rss_feed_store id title link description fetch-url (fetch-defaults t))) ))
193
+
194
+(defmethod get-dao-for ((obj rss-item) &optional feed)
195
+ (with-slots (title link description-raw guid pub-date source) obj
196
+   (get-id-for-object (rss_item_store guid) guid
197
+     (let ((result (make-instance-from-symbols 'rss_item_store title link (description description-raw)
198
+                                 guid pub-date source feed (fetch-defaults t))))
199
+       (unless (null id)
200
+         (setf (ris-id result) id))
201
+       result))))
224 202
 
225 203
 (defun get-and-possibly-store-feed (rss-feed)
226 204
   "Given an rss-feed, return the db's feed-id, persisting it if it doesn't already exist."
... ...
@@ -259,7 +237,10 @@
259 237
 @export
260 238
 (defun deserialize-items (feed-id)
261 239
   (let ((items (postmodern:query-dao 'rss_item_store
262
-                                     (:select :* :from 'rss_item_store :where (:= :feed feed-id)))))
240
+                                     (:order-by
241
+                                       (:select :* :from 'rss_item_store
242
+                                        :where (:= :feed feed-id))
243
+                                       (:desc 'pub-date)))))
263 244
     (loop for item in items collect (deserialize-item item))))
264 245
 
265 246
 @export
... ...
@@ -281,15 +262,21 @@
281 262
                                    :where (:= 'reader_user.foreign_id (user-foreign-id user-info))))))
282 263
       (apply #'vector (loop for feed in feeds collect (deserialize-feed feed))))))
283 264
 
265
+(export
266
+  (defun get-feed-from-dao (rss-feed)
267
+    (let ((feed-dao (get-dao-for rss-feed)))
268
+      (list feed-dao
269
+            (with-slots (items) rss-feed
270
+              (iterate (for item in items)
271
+                       (collect (get-dao-for item (slot-value feed-dao 'id)))))))))
272
+
284 273
 
285 274
 @export
286
-(defun normalize-html (html)
287
-  (let ((plump-parser:*tag-dispatchers* plump:*html-tags*))
288
-    (with-output-to-string (ss)
289
-      (map 'vector
290
-           (lambda (x) (plump:serialize (plump:parse (plump:text x)) ss))
291
-           html)
292
-      ss)))
275
+(defun upsert-feed (rss-feed)
276
+  (postmodern:ensure-transaction
277
+    (destructuring-bind (feed items) (get-feed-from-dao rss-feed)
278
+      (postmodern:upsert-dao feed)
279
+      (mapcar #'postmodern:upsert-dao items))))
293 280
 
294 281
 
295 282
 ; \o/
... ...
@@ -1,6 +1,6 @@
1 1
 (in-package :cl-user)
2 2
 (defpackage :whitespace.tables
3
-  (:use #:cl #:alexandria #:postmodern #:annot.class))
3
+  (:use #:cl #:alexandria #:postmodern #:annot.class #:iterate #:whitespace.utils))
4 4
 (in-package :whitespace.tables)
5 5
 (cl-annot.syntax:enable-annot-syntax)
6 6
 
... ...
@@ -74,6 +74,59 @@
74 74
   (!foreign "reader_user" "uid" "id" :on-delete :cascade :on-update :cascade)
75 75
   (!unique '(uid feedid)))
76 76
 
77
+
78
+@export
79
+(defgeneric get-dao-for (obj &optional link)
80
+  (:documentation "Take an object an return the equivalent dao object. Use link to specify a single
81
+                   foreign key relationship---this probably should be generalized further"))
82
+
83
+@export
84
+(defgeneric serialize (cls &optional output-transform pair-transform))
85
+
86
+@export
87
+(defmacro defserializer ((specializes) &body slots)
88
+  (with-gensyms (obj o-t p-t)
89
+    `(defmethod serialize ((,obj ,specializes) &optional (,o-t #'identity) (,p-t #'%default-pair-transform))
90
+      (transform-result (,o-t ,p-t)
91
+        (slots-to-pairs ,obj ,slots)))))
92
+
93
+(defmethod serialize ((obj sequence) &optional (o-t #'identity) (p-t #'%default-pair-transform))
94
+  (iterate (for item in-sequence obj)
95
+           (collect (serialize item o-t p-t))))
96
+
97
+; this is the interface to be used
98
+(defserializer (rss_feed_store)
99
+  title link description fetch-url)
100
+
101
+(defserializer (rss_item_store)
102
+  title link description fetch-url)
103
+
104
+@export
105
+(defun store-item-dao (serialized-rss-item link)
106
+  (pomo:ensure-transaction
107
+    (apply #'postmodern:make-dao
108
+           (list* 'rss_item_store :feed link
109
+                  (iterate (for (k . v) in-sequence serialized-rss-item)
110
+                           (appending (list k v)))))))
111
+
112
+@export
113
+(defun store-feed-dao (serialized-rss-feed &optional link)
114
+  (declare (ignore link))
115
+  (pomo:ensure-transaction
116
+    (let* ((items nil)
117
+           (rss_feed (apply #'postmodern:make-dao
118
+                            (cons 'rss_feed_store
119
+                                  (iterate (for (k . v) in-sequence serialized-rss-feed)
120
+                                           (if (eql k :items)
121
+                                             (setf items v)
122
+                                             (appending (list k v))))))))
123
+      (iterate (for item in items)
124
+               (handler-case (pomo:with-savepoint store-item
125
+                               (store-item-dao (serialize item) (slot-value rss_feed 'id)))
126
+                 (cl-postgres-error:unique-violation ())))
127
+      rss_feed)))
128
+
129
+
77 130
 #|
78 131
 
79 132
 (with-connection whitespace::*db-connection-info* 
... ...
@@ -66,4 +66,12 @@
66 66
          ,(make-pairs slots)))))
67 67
 (export 'slots-to-pairs)
68 68
 
69
+(defun normalize-html (html)
70
+  (let ((plump-parser:*tag-dispatchers* plump:*html-tags*))
71
+    (with-output-to-string (ss)
72
+      (prog1 ss
73
+        (map 'vector
74
+           (lambda (x) (plump:serialize (plump:parse (plump:text x)) ss))
75
+           html)))))
76
+(export 'normalize-html)
69 77