mirror of
https://github.com/SqrtMinusOne/elfeed-sync.git
synced 2025-12-10 20:13:02 +03:00
feat: sync seems to work
This commit is contained in:
parent
65ca6e1564
commit
671c690d60
2 changed files with 253 additions and 263 deletions
376
elfeed-sync.el
376
elfeed-sync.el
|
|
@ -66,32 +66,25 @@
|
||||||
:group 'elfeed-sync
|
:group 'elfeed-sync
|
||||||
:type 'symbol)
|
:type 'symbol)
|
||||||
|
|
||||||
(defcustom elfeed-sync-missing-attempts 5
|
|
||||||
"How many attempts to sync missing entries."
|
|
||||||
:group 'elfeed-sync
|
|
||||||
:type 'number)
|
|
||||||
|
|
||||||
(defcustom elfeed-sync-missing-targets-keep (* 60 60 24 7)
|
|
||||||
"How long to keep missing targets."
|
|
||||||
:group 'elfeed-sync
|
|
||||||
:type 'number)
|
|
||||||
|
|
||||||
(defvar elfeed-sync--tt-rss-sid nil
|
(defvar elfeed-sync--tt-rss-sid nil
|
||||||
"Session ID.")
|
"Session ID.")
|
||||||
|
|
||||||
(defvar elfeed-sync--state nil
|
(defvar elfeed-sync--state nil
|
||||||
"State of the tt-rss sync.")
|
"State of the tt-rss sync.")
|
||||||
|
|
||||||
|
(defvar elfeed-sync--start-time nil
|
||||||
|
"Start time of the tt-rss sync.")
|
||||||
|
|
||||||
|
(defvar elfeed-sync--elfeed-missed nil
|
||||||
|
"List of elfeed entries missed in tt-rss.")
|
||||||
|
|
||||||
(cl-defstruct (elfeed-sync-datum (:constructor elfeed-sync-datum--create))
|
(cl-defstruct (elfeed-sync-datum (:constructor elfeed-sync-datum--create))
|
||||||
id tags)
|
id tags)
|
||||||
|
|
||||||
(defun elfeed-sync--state-empty ()
|
(defun elfeed-sync--state-empty ()
|
||||||
"Create an empty elfeed-sync state."
|
"Create an empty elfeed-sync state."
|
||||||
`((:last-sync . nil)
|
`((:last-sync-time . nil)
|
||||||
(:feeds . ,(make-hash-table :test #'equal))
|
(:ids-missing-tt-rss . ,(make-hash-table :test #'equal))))
|
||||||
(:missing . ,(make-hash-table :test #'equal))
|
|
||||||
(:discarded . ,(make-hash-table :test #'equal))
|
|
||||||
(:missing-target . nil)))
|
|
||||||
|
|
||||||
(defun elfeed-sync--state-file ()
|
(defun elfeed-sync--state-file ()
|
||||||
(concat elfeed-db-directory "/sync-state"))
|
(concat elfeed-db-directory "/sync-state"))
|
||||||
|
|
@ -265,103 +258,20 @@ called only after a succesful login query."
|
||||||
(elfeed-db-return)))
|
(elfeed-db-return)))
|
||||||
bad-feeds-hash))
|
bad-feeds-hash))
|
||||||
|
|
||||||
|
(defun elfeed-sync--entry-unread-p (entry)
|
||||||
|
"Return non-nil if ENTRY is unread."
|
||||||
|
(not (null (member elfeed-sync-unread-tag (elfeed-entry-tags entry)))))
|
||||||
|
|
||||||
(defun elfeed-sync--datum-chaged (datum entry)
|
(defun elfeed-sync--entry-marked-p (entry)
|
||||||
(let* ((old-tags (elfeed-sync-datum-tags datum))
|
"Return non-nil if ENTRY is marked."
|
||||||
(new-tags (elfeed-entry-tags entry))
|
(not (null (member elfeed-sync-marked-tag (elfeed-entry-tags entry)))))
|
||||||
(common-tags (seq-intersection old-tags new-tags)))
|
|
||||||
(or (not (= (length old-tags) (length common-tags)))
|
|
||||||
(not (= (length new-tags) (length common-tags))))))
|
|
||||||
|
|
||||||
(defun elfeed-sync--entry-good-p (id)
|
|
||||||
(null (gethash id (alist-get :discarded elfeed-sync--state))))
|
|
||||||
|
|
||||||
(defun elfeed-sync--get-changed ()
|
|
||||||
(let ((feed-hash (alist-get :feeds elfeed-sync--state))
|
|
||||||
changed)
|
|
||||||
(with-elfeed-db-visit (entry feed)
|
|
||||||
(when-let ((id (elfeed-ref-id (elfeed-entry-content entry))))
|
|
||||||
(when (elfeed-sync--entry-good-p id)
|
|
||||||
(let ((entry-hash (gethash (elfeed-feed-id feed) feed-hash)))
|
|
||||||
(unless entry-hash
|
|
||||||
(setq entry-hash (make-hash-table :test #'equal))
|
|
||||||
(puthash (elfeed-feed-id feed) entry-hash feed-hash))
|
|
||||||
(if-let ((datum (gethash id entry-hash)))
|
|
||||||
(when (elfeed-sync--datum-chaged datum entry)
|
|
||||||
(push (list entry feed id) changed))
|
|
||||||
(push (list entry feed id) changed)))))
|
|
||||||
(when (> (- (time-convert nil 'integer)
|
|
||||||
elfeed-sync-look-back)
|
|
||||||
(elfeed-entry-date entry))
|
|
||||||
(elfeed-db-return)))
|
|
||||||
changed))
|
|
||||||
|
|
||||||
(defun elfeed-sync--prepare-request (bad-feeds)
|
|
||||||
(let ((changed (elfeed-sync--get-changed)))
|
|
||||||
`((bad_feeds . ,(cl-loop for key being the hash-keys of bad-feeds
|
|
||||||
collect key))
|
|
||||||
(changed .,(mapcar
|
|
||||||
(lambda (datum)
|
|
||||||
(let ((entry (nth 0 datum))
|
|
||||||
(feed (nth 1 datum))
|
|
||||||
(id (nth 2 datum)))
|
|
||||||
(if (gethash (or (elfeed-feed-url feed)
|
|
||||||
(elfeed-feed-id feed))
|
|
||||||
bad-feeds)
|
|
||||||
`((id . ,id)
|
|
||||||
(title . ,(elfeed-entry-title entry))
|
|
||||||
(url . ,(elfeed-entry-link entry))
|
|
||||||
(feed_url . ,(or (elfeed-feed-url feed)
|
|
||||||
(elfeed-feed-id feed)))
|
|
||||||
(updated . ,(format-time-string
|
|
||||||
"%Y-%m-%d %H:%M:%S"
|
|
||||||
(seconds-to-time (elfeed-entry-date entry))
|
|
||||||
"UTC0"))
|
|
||||||
(tags . ,(elfeed-entry-tags entry)))
|
|
||||||
`((id . ,id)
|
|
||||||
(feed_url . ,(or (elfeed-feed-url feed)
|
|
||||||
(elfeed-feed-id feed)))
|
|
||||||
(url . ,(elfeed-entry-link entry))
|
|
||||||
(tags . ,(elfeed-entry-tags entry))))))
|
|
||||||
changed))
|
|
||||||
(last_sync . ,(alist-get :last-sync elfeed-sync--state))
|
|
||||||
(unread_tag . ,elfeed-sync-unread-tag)
|
|
||||||
(marked_tag . ,elfeed-sync-marked-tag)
|
|
||||||
(look_back . ,elfeed-sync-look-back))))
|
|
||||||
|
|
||||||
(defun elfeed-sync--sort-response-entries (entries bad-feeds entries-by-title-date entries-by-url is-new)
|
|
||||||
(dolist (entry entries)
|
|
||||||
(setf (alist-get 'is-new entry) is-new)
|
|
||||||
(if (gethash (alist-get 'feed_url entry) bad-feeds)
|
|
||||||
(let ((title-date (format "%s---%s"
|
|
||||||
(alist-get 'title entry)
|
|
||||||
(alist-get 'updated entry))))
|
|
||||||
(puthash title-date entry entries-by-title-date))
|
|
||||||
(puthash (alist-get 'link entry) entry entries-by-url))))
|
|
||||||
|
|
||||||
(defun elfeed-sync--get-response-entry (entry feed entries-by-title-date entries-by-url)
|
|
||||||
(if (gethash (or (elfeed-feed-url feed)
|
|
||||||
(elfeed-feed-id feed))
|
|
||||||
bad-feeds)
|
|
||||||
(let ((title-date (format "%s---%s"
|
|
||||||
(elfeed-entry-title entry)
|
|
||||||
(format-time-string
|
|
||||||
"%Y-%m-%d %H:%M:%S"
|
|
||||||
(seconds-to-time (elfeed-entry-date entry))
|
|
||||||
"UTC0"))))
|
|
||||||
(prog1
|
|
||||||
(gethash title-date entries-by-title-date)
|
|
||||||
(remhash title-date entries-by-title-date)))
|
|
||||||
(prog1
|
|
||||||
(gethash (elfeed-entry-link entry) entries-by-url)
|
|
||||||
(remhash (elfeed-entry-link entry) entries-by-url))))
|
|
||||||
|
|
||||||
(defun elfeed-sync--set-entry-unread (entry status)
|
(defun elfeed-sync--set-entry-unread (entry status)
|
||||||
"Set the unread status of ENTRY to STATUS.
|
"Set the unread status of ENTRY to STATUS.
|
||||||
|
|
||||||
STATUS is a boolean. If nil, the entry is marked as read. ENTRY is an instance of `elfeed-entry'."
|
STATUS is a boolean. If nil, the entry is marked as read. ENTRY
|
||||||
(let ((is-unread (member elfeed-sync-unread-tag
|
is an instance of `elfeed-entry'."
|
||||||
(elfeed-entry-tags entry))))
|
(let ((is-unread (elfeed-sync--entry-unread-p entry)))
|
||||||
(if (and is-unread status)
|
(if (and is-unread status)
|
||||||
(elfeed-untag entry elfeed-sync-unread-tag)
|
(elfeed-untag entry elfeed-sync-unread-tag)
|
||||||
(when (not is-unread)
|
(when (not is-unread)
|
||||||
|
|
@ -372,85 +282,219 @@ STATUS is a boolean. If nil, the entry is marked as read. ENTRY is an instance
|
||||||
|
|
||||||
STATUS is a boolean. If nil, the entry is marked as
|
STATUS is a boolean. If nil, the entry is marked as
|
||||||
unmarked. ENTRY is an instance of `elfeed-entry'."
|
unmarked. ENTRY is an instance of `elfeed-entry'."
|
||||||
(let ((is-marked (member elfeed-sync-marked-tag
|
(let ((is-marked (elfeed-sync--entry-marked-p entry)))
|
||||||
(elfeed-entry-tags entry))))
|
|
||||||
(if (and is-marked status)
|
(if (and is-marked status)
|
||||||
(elfeed-untag entry elfeed-sync-marked-tag)
|
(elfeed-untag entry elfeed-sync-marked-tag)
|
||||||
(when (not is-marked)
|
(when (not is-marked)
|
||||||
(elfeed-tag entry elfeed-sync-marked-tag)))))
|
(elfeed-tag entry elfeed-sync-marked-tag)))))
|
||||||
|
|
||||||
(defun elfeed-sync--process-response (response bad-feeds)
|
(defun elfeed-sync--ttrss-key (bad-feeds ttrss-entry)
|
||||||
(cl-loop for entry in (alist-get 'missing-entries response)
|
(let ((feed-url (alist-get 'feed_url ttrss-entry)))
|
||||||
do (let* ((id (alist-get 'id entry))
|
(if (gethash feed-url bad-feeds)
|
||||||
(attempts (or
|
(format "%s---%s" (alist-get 'title ttrss-entry)
|
||||||
(gethash id (alist-get :missing elfeed-sync--state))
|
(alist-get 'updated ttrss-entry))
|
||||||
0)))
|
(alist-get 'link ttrss-entry))))
|
||||||
(if (>= attempts elfeed-sync-max-retries)
|
|
||||||
(puthash id (1+ attempts) (alist-get :missing elfeed-sync--state))
|
(defun elfeed-sync--elfeed-key (bad-feeds elfeed-entry)
|
||||||
(puthash id t (alist-get :discarded elfeed-sync--state))
|
(let ((feed-url (elfeed-entry-feed elfeed-entry)))
|
||||||
(remhash id (alist-get :missing elfeed-sync--state)))))
|
(if (gethash feed-url bad-feeds)
|
||||||
(setf (alist-get :missing-target elfeed-sync--state)
|
(format "%s---%s" (elfeed-entry-title elfeed-entry)
|
||||||
(seq-filter (lambda (datum)
|
(floor (elfeed-entry-date elfeed-entry)))
|
||||||
(< (- (time-convert nil 'integer) (car datum))
|
(elfeed-entry-link elfeed-entry))))
|
||||||
elfeed-sync-missing-targets-keep))
|
|
||||||
(alist-get :missing-target elfeed-sync--state)))
|
(defun elfeed-sync--ttrss-get-updated-time (ttrss-entry)
|
||||||
(let ((entries-by-title-date (make-hash-table :test #'equal))
|
(if (and (alist-get 'last_read ttrss-entry)
|
||||||
(entries-by-url (make-hash-table :test #'equal)))
|
(alist-get 'last_marked ttrss-entry))
|
||||||
(elfeed-sync--sort-response-entries
|
(max (alist-get 'last_read ttrss-entry)
|
||||||
(alist-get 'updated response) bad-feeds entries-by-title-date entries-by-url t)
|
(alist-get 'last_marked ttrss-entry))
|
||||||
(elfeed-sync--sort-response-entries
|
(or (alist-get 'last_read ttrss-entry)
|
||||||
(mapcar #'cdr (alist-get :missing-target elfeed-sync--state))
|
(alist-get 'last_marked ttrss-entry))))
|
||||||
bad-feeds entries-by-title-date entries-by-url nil)
|
|
||||||
|
(defun elfeed-sync--ttrss-get-last-sync-time (ttrss-id ttrss-time)
|
||||||
|
(if ttrss-time
|
||||||
|
(if-let* ((val (gethash
|
||||||
|
ttrss-id
|
||||||
|
(alist-get :ids-missing-tt-rss
|
||||||
|
elfeed-sync--state)))
|
||||||
|
(time-equal (= (car var) ttrss-time)))
|
||||||
|
(cdr var)
|
||||||
|
(alist-get :last-sync-time elfeed-sync--state))
|
||||||
|
(alist-get :last-sync-time elfeed-sync--state)))
|
||||||
|
|
||||||
|
(defun elfeed-sync--update-ttrss-missing (ttrss-entries ttrss-entries-processed)
|
||||||
|
(maphash (lambda (ttrss-id ttrss-entry)
|
||||||
|
(when-let ((ttrss-time (elfeed-sync--ttrss-get-updated-time
|
||||||
|
ttrss-entry)))
|
||||||
|
(if (gethash ttrss-id ttrss-entries-processed)
|
||||||
|
(remhash ttrss-id (alist-get :ids-missing-tt-rss
|
||||||
|
elfeed-sync--state))
|
||||||
|
(if-let ((old-val (gethash ttrss-id
|
||||||
|
(alist-get :ids-missing-tt-rss
|
||||||
|
elfeed-sync--state)))
|
||||||
|
(is-equal (= (car old-val) ttrss-time)))
|
||||||
|
t ;; do nothing
|
||||||
|
(puthash ttrss-id (cons ttrss-time
|
||||||
|
elfeed-sync--start-time)
|
||||||
|
(alist-get :ids-missing-tt-rss
|
||||||
|
elfeed-sync--state))))))
|
||||||
|
ttrss-entries))
|
||||||
|
|
||||||
|
(defun elfeed-sync--do-sync (entries bad-feeds)
|
||||||
|
(let ((ttrss-entries (make-hash-table :test #'equal))
|
||||||
|
(ttrss-entries-processed (make-hash-table :test #'equal))
|
||||||
|
(ttrss-toggle-marked nil)
|
||||||
|
(ttrss-toggle-unread nil)
|
||||||
|
(elfeed-toggle-unread-count 0)
|
||||||
|
(elfeed-toggle-marked-count 0)
|
||||||
|
(elfeed-total-entries 0)
|
||||||
|
(missing-elfeed))
|
||||||
|
(cl-loop for ttrss-entry being the elements of entries
|
||||||
|
do (puthash (elfeed-sync--ttrss-key bad-feeds ttrss-entry)
|
||||||
|
ttrss-entry ttrss-entries))
|
||||||
(with-elfeed-db-visit (entry feed)
|
(with-elfeed-db-visit (entry feed)
|
||||||
(when-let ((id (elfeed-ref-id (elfeed-entry-content entry))))
|
(cl-incf elfeed-total-entries)
|
||||||
(if-let ((entry (elfeed-sync--get-response-entry entry feed entries-by-title-date entries-by-url)))
|
(if-let ((ttrss-entry (gethash (elfeed-sync--elfeed-key bad-feeds entry)
|
||||||
(progn
|
ttrss-entries)))
|
||||||
(elfeed-sync--set-entry-unread entry (alist-get 'unread entry))
|
(let* ((is-unread (elfeed-sync--entry-unread-p entry))
|
||||||
(elfeed-sync--set-entry-marked entry (alist-get 'marked entry))
|
(is-marked (elfeed-sync--entry-marked-p entry))
|
||||||
(puthash id (elfeed-sync-datum--create
|
(ttrss-id (alist-get 'id ttrss-entry))
|
||||||
:id id
|
(ttrss-time (elfeed-sync--ttrss-get-updated-time ttrss-entry))
|
||||||
:tags (elfeed-entry-tags entry))))
|
(last-sync-time
|
||||||
(unless (or (gethash id (alist-get :missing elfeed-sync--state))
|
(elfeed-sync--ttrss-get-last-sync-time ttrss-id ttrss-time))
|
||||||
(gethash id (alist-get :discarded elfeed-sync--state)))
|
(ttrss-priority (if (and last-sync-time ttrss-time)
|
||||||
(puthash id (elfeed-sync-datum--create
|
(> ttrss-time last-sync-time)
|
||||||
:id id
|
(not (null ttrss-time))))
|
||||||
:tags (elfeed-entry-tags entry))
|
(ttrss-is-unread (eq (alist-get 'unread ttrss-entry) t))
|
||||||
(gethash
|
(ttrss-is-marked (eq (alist-get 'marked ttrss-entry) t)))
|
||||||
(or (elfeed-feed-url feed)
|
(when (not (eq ttrss-is-unread is-unread))
|
||||||
(elfeed-feed-id feed))
|
(if ttrss-priority
|
||||||
(alist-get :feeds elfeed-sync--state))))))
|
(progn
|
||||||
|
(elfeed-sync--set-entry-unread entry ttrss-is-unread)
|
||||||
|
(cl-incf elfeed-toggle-unread-count))
|
||||||
|
(push ttrss-id ttrss-toggle-unread)))
|
||||||
|
(when (not (eq ttrss-is-marked is-marked))
|
||||||
|
(if ttrss-priority
|
||||||
|
(progn
|
||||||
|
(elfeed-sync--set-entry-marked entry ttrss-is-marked)
|
||||||
|
(cl-incf elfeed-toggle-marked-count))
|
||||||
|
(push ttrss-id ttrss-toggle-marked)))
|
||||||
|
(puthash ttrss-id t ttrss-entries-processed))
|
||||||
|
(push entry missing-elfeed))
|
||||||
(when (> (- (time-convert nil 'integer)
|
(when (> (- (time-convert nil 'integer)
|
||||||
elfeed-sync-look-back)
|
elfeed-sync-look-back)
|
||||||
(elfeed-entry-date entry))
|
(elfeed-entry-date entry))
|
||||||
(elfeed-db-return)))
|
(elfeed-db-return)))
|
||||||
(maphash (lambda (_ datum)
|
(elfeed-sync--update-ttrss-missing ttrss-entries ttrss-entries-processed)
|
||||||
(when (alist-get 'is-new datum)
|
(setf (alist-get :last-sync-time elfeed-sync--state)
|
||||||
(push datum (alist-get :missing-target elfeed-sync--state))))
|
elfeed-sync--start-time)
|
||||||
entries-by-title-date)
|
`((:ttrss-toggle-unread . ,ttrss-toggle-unread)
|
||||||
(maphash (lambda (_ datum)
|
(:ttrss-toggle-marked . ,ttrss-toggle-marked)
|
||||||
(when (alist-get 'is-new datum)
|
(:missing-elfeed . ,missing-elfeed)
|
||||||
(push datum (alist-get :missing-target elfeed-sync--state))))
|
(:ttrss-entries . ,ttrss-entries)
|
||||||
entries-by-url))
|
(:elfeed-total-entries . ,elfeed-total-entries)
|
||||||
(setf (alist-get :last-sync elfeed-sync--state)
|
(:ttrss-total-entries . ,(hash-table-count ttrss-entries))
|
||||||
(time-convert nil 'integer)))
|
(:elfeed-toggle-unread-count . ,elfeed-toggle-unread-count)
|
||||||
|
(:elfeed-toggle-marked-count . ,elfeed-toggle-marked-count)
|
||||||
|
(:ttrss-missing-count . ,(- (hash-table-count ttrss-entries)
|
||||||
|
(hash-table-count ttrss-entries-processed))))))
|
||||||
|
|
||||||
|
(defun elfeed-sync--process-sync-data (sync-data)
|
||||||
|
(setq my/test4 sync-data)
|
||||||
|
(elfeed-log 'info "Total entries in %s: %s"
|
||||||
|
(propertize "elfeed" 'face 'elfeed-log-info-level-face)
|
||||||
|
(alist-get :elfeed-total-entries sync-data))
|
||||||
|
(elfeed-log 'info "Total entries in %s: %s"
|
||||||
|
(propertize "tt-rss" 'face 'elfeed-log-warn-level-face)
|
||||||
|
(alist-get :ttrss-total-entries sync-data))
|
||||||
|
(elfeed-log 'info "Toggled unread in %s: %s"
|
||||||
|
(propertize "elfeed" 'face 'elfeed-log-info-level-face)
|
||||||
|
(alist-get :elfeed-toggle-unread-count sync-data))
|
||||||
|
(elfeed-log 'info "Toggled marked in %s: %s"
|
||||||
|
(propertize "elfeed" 'face 'elfeed-log-info-level-face)
|
||||||
|
(alist-get :elfeed-toggle-unread-count sync-data))
|
||||||
|
(elfeed-log 'info "Toggled unread in %s: %s"
|
||||||
|
(propertize "tt-rss" 'face 'elfeed-log-warn-level-face)
|
||||||
|
(length (alist-get :ttrss-toggle-unread sync-data)))
|
||||||
|
(elfeed-log 'info "Toggled marked in %s: %s"
|
||||||
|
(propertize "tt-rss" 'face 'elfeed-log-warn-level-face)
|
||||||
|
(length (alist-get :ttrss-toggle-marked sync-data)))
|
||||||
|
(elfeed-log 'info "Missing entries in %s: %s"
|
||||||
|
(propertize "elfeed" 'face 'elfeed-log-info-level-face)
|
||||||
|
(length (alist-get :missing-elfeed sync-data)))
|
||||||
|
(elfeed-log 'info "Missing entries in %s: %s"
|
||||||
|
(propertize "tt-rss" 'face 'elfeed-log-warn-level-face)
|
||||||
|
(alist-get :ttrss-missing-count sync-data))
|
||||||
|
(setq elfeed-sync--elfeed-missed (alist-get :missing-elfeed sync-data))
|
||||||
|
(message "Sync complete!"))
|
||||||
|
|
||||||
|
(defun elfeed-sync--apply-to-ttrss (sync-data)
|
||||||
|
(message "Propagating changes to tt-rss...")
|
||||||
|
(elfeed-sync--session
|
||||||
|
(request (concat elfeed-sync-tt-rss-instance "/api/")
|
||||||
|
:type "POST"
|
||||||
|
:data (setq my/test3
|
||||||
|
(json-encode
|
||||||
|
`(("op" . "toggleEntries")
|
||||||
|
("sid" . ,elfeed-sync--tt-rss-sid)
|
||||||
|
("data" .
|
||||||
|
(("toggle_unread" . ,(alist-get :ttrss-toggle-unread sync-data))
|
||||||
|
("toggle_marked" . ,(alist-get :ttrss-toggle-marked sync-data)))))))
|
||||||
|
:headers '(("Content-Type" . "application/json"))
|
||||||
|
:parser 'elfeed-sync--json-read-safe
|
||||||
|
:success (elfeed-sync--handler
|
||||||
|
(elfeed-sync--process-sync-data sync-data))
|
||||||
|
:error
|
||||||
|
(cl-function (lambda (&key error-thrown &allow-other-keys)
|
||||||
|
(message "Error: %S" error-thrown))))))
|
||||||
|
|
||||||
(defun elfeed-sync ()
|
(defun elfeed-sync ()
|
||||||
(interactive)
|
(interactive)
|
||||||
(elfeed-sync--session
|
(elfeed-sync--session
|
||||||
(let* ((bad-feeds (elfeed-sync--get-bad-feeds))
|
(setq elfeed-sync--start-time (time-convert nil 'integer))
|
||||||
(request (elfeed-sync--prepare-request bad-feeds)))
|
(elfeed-log 'info "Sync start: %s" (format-time-string "%Y-%m-%d %H:%M:%S"))
|
||||||
|
(let ((bad-feeds (elfeed-sync--get-bad-feeds)))
|
||||||
(request (concat elfeed-sync-tt-rss-instance "/api/")
|
(request (concat elfeed-sync-tt-rss-instance "/api/")
|
||||||
:type "POST"
|
:type "POST"
|
||||||
:data (json-encode
|
:data (json-encode
|
||||||
`(("op" . "syncElfeed")
|
`(("op" . "getSyncEntries")
|
||||||
("sid" . ,elfeed-sync--tt-rss-sid)
|
("sid" . ,elfeed-sync--tt-rss-sid)
|
||||||
("data" . ,request)))
|
("data" .
|
||||||
:parser 'elfeed-sync--json-read-safe
|
(("bad_feeds" . ,(cl-loop for feed being the hash-keys of bad-feeds
|
||||||
|
collect feed))
|
||||||
|
("look_back" . ,elfeed-sync-look-back)))))
|
||||||
:headers '(("Content-Type" . "application/json"))
|
:headers '(("Content-Type" . "application/json"))
|
||||||
|
:parser 'elfeed-sync--json-read-safe
|
||||||
:success (elfeed-sync--handler
|
:success (elfeed-sync--handler
|
||||||
(elfeed-sync--process-response
|
(elfeed-sync--apply-to-ttrss
|
||||||
(alist-get 'content data)
|
(elfeed-sync--do-sync
|
||||||
bad-feeds))))))
|
(alist-get 'entries
|
||||||
|
(alist-get 'content data))
|
||||||
|
bad-feeds)))
|
||||||
|
:error
|
||||||
|
(cl-function (lambda (&key error-thrown &allow-other-keys)
|
||||||
|
(message "Error: %S" error-thrown)))))))
|
||||||
|
|
||||||
|
(defun elfeed-sync-search-missing ()
|
||||||
|
(interactive)
|
||||||
|
(switch-to-buffer (elfeed-search-buffer))
|
||||||
|
(unless (eq major-mode 'elfeed-search-mode)
|
||||||
|
(elfeed-search-mode))
|
||||||
|
(with-current-buffer (elfeed-search-buffer)
|
||||||
|
(elfeed-save-excursion
|
||||||
|
(let ((inhibit-read-only t)
|
||||||
|
(standard-output (current-buffer)))
|
||||||
|
(erase-buffer)
|
||||||
|
(setf elfeed-search-entries elfeed-sync--elfeed-missed)
|
||||||
|
(unless (eq elfeed-sort-order 'ascending)
|
||||||
|
(setf elfeed-search-entries (nreverse elfeed-search-entries)))
|
||||||
|
(dolist (entry elfeed-search-entries)
|
||||||
|
(funcall elfeed-search-print-entry-function entry)
|
||||||
|
(insert "\n"))
|
||||||
|
(setf elfeed-search-last-update (float-time))))
|
||||||
|
(when (zerop (buffer-size))
|
||||||
|
;; If nothing changed, force a header line update
|
||||||
|
(force-mode-line-update))
|
||||||
|
(run-hooks 'elfeed-search-update-hook)))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(define-minor-mode elfeed-sync-mode
|
(define-minor-mode elfeed-sync-mode
|
||||||
|
|
|
||||||
140
init.php
140
init.php
|
|
@ -14,7 +14,8 @@ class Elfeed_Sync extends Plugin {
|
||||||
$this->host = $host;
|
$this->host = $host;
|
||||||
|
|
||||||
$this->host->add_api_method("setFeedsTree", $this);
|
$this->host->add_api_method("setFeedsTree", $this);
|
||||||
$this->host->add_api_method("syncElfeed", $this);
|
$this->host->add_api_method("getSyncEntries", $this);
|
||||||
|
$this->host->add_api_method("toggleEntries", $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCategoriesTree($tree, $feed_categories = null, $current_cat = null) {
|
function createCategoriesTree($tree, $feed_categories = null, $current_cat = null) {
|
||||||
|
|
@ -123,123 +124,68 @@ class Elfeed_Sync extends Plugin {
|
||||||
return array(API::STATUS_OK, array('subscribed' => $subscribed));
|
return array(API::STATUS_OK, array('subscribed' => $subscribed));
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateEntries($data) {
|
function getSyncEntries() {
|
||||||
|
$data = $_REQUEST["data"];
|
||||||
$entries_query = ORM::for_table('ttrss_entries')
|
$entries_query = ORM::for_table('ttrss_entries')
|
||||||
->table_alias('e')
|
->table_alias('e')
|
||||||
->select_many('e.id', 'e.link', 'e.title', 'e.updated',
|
->select_many('e.id', 'e.link', 'e.title', 'e.updated',
|
||||||
'ue.marked', 'ue.unread', 'ue.feed_id')
|
'ue.marked', 'ue.unread', 'f.feed_url', 'ue.last_read', 'ue.last_marked')
|
||||||
->join('ttrss_user_entries', ['ue.ref_id', '=', 'e.id'], 'ue')
|
->join('ttrss_user_entries', array('e.id', '=', 'ue.ref_id'), 'ue')
|
||||||
->join('ttrss_feeds', ['f.id', '=', 'ue.feed_id'], 'f')
|
->join('ttrss_feeds', array('ue.feed_id', '=', 'f.id'), 'f')
|
||||||
->where('ue.owner_uid', $_SESSION['uid']);
|
->where('ue.owner_uid', $_SESSION['uid']);
|
||||||
if (!is_null($data['look_back'])) {
|
if (!is_null($data['look_back'])) {
|
||||||
$entries_query = $entries_query->where_gte('updated', date('Y-m-d H:i:s', time() - $data['look_back']));
|
$entries_query = $entries_query->where_gte('updated', date('Y-m-d H:i:s', time() - $data['look_back']));
|
||||||
}
|
}
|
||||||
$entries = $entries_query->find_array();
|
$entries = $entries_query->find_array();
|
||||||
|
|
||||||
$feeds = ORM::for_table('ttrss_feeds')
|
$bad_feed_links = array();
|
||||||
->select_many('id', 'feed_url')
|
|
||||||
->where('owner_uid', $_SESSION['uid'])
|
|
||||||
->find_array();
|
|
||||||
|
|
||||||
$feed_by_link = array();
|
|
||||||
foreach ($feeds as $feed) {
|
|
||||||
$feed_by_link[$feed['feed_url']] = $feed['id'];
|
|
||||||
}
|
|
||||||
$bad_feed_ids = array();
|
|
||||||
foreach($data['bad_feeds'] as $bad_feed_link) {
|
foreach($data['bad_feeds'] as $bad_feed_link) {
|
||||||
$feed_id = $feed_by_link[$bad_feed_link];
|
$bad_feed_links[$bad_feed_link] = true;
|
||||||
if ($feed_id) {
|
|
||||||
$bad_feed_ids[$feed_id] = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$entries_by_link = array();
|
$result = array();
|
||||||
$entries_by_title_date = array();
|
|
||||||
foreach ($entries as $entry) {
|
foreach ($entries as $entry) {
|
||||||
if ($bad_feed_ids[$entry['feed_id']]) {
|
if (array_key_exists($entry['feed_url'], $bad_feed_links)) {
|
||||||
$title_date = $entry['title'] . '---' . $entry['updated'];
|
$result[] = array(
|
||||||
$entries_by_title_date[$title_date] = $entry;
|
'id' => $entry['id'],
|
||||||
|
'link' => $entry['link'],
|
||||||
|
'title' => $entry['title'],
|
||||||
|
'updated' => $entry['updated'] ? strtotime($entry['updated']) : null,
|
||||||
|
'marked' => $entry['marked'],
|
||||||
|
'unread' => $entry['unread'],
|
||||||
|
'feed_url' => $entry['feed_url'],
|
||||||
|
'last_read' => $entry['last_read'] ? strtotime($entry['last_read']) : null,
|
||||||
|
'last_marked' => $entry['last_marked'] ? strtotime($entry['last_marked']) : null
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
$entries_by_link[$entry['link']] = $entry;
|
$result[] = array(
|
||||||
|
'id' => $entry['id'],
|
||||||
|
'link' => $entry['link'],
|
||||||
|
'marked' => $entry['marked'],
|
||||||
|
'unread' => $entry['unread'],
|
||||||
|
'feed_url' => $entry['feed_url'],
|
||||||
|
'last_read' => $entry['last_read'] ? strtotime($entry['last_read']) : null,
|
||||||
|
'last_marked' => $entry['last_marked'] ? strtotime($entry['last_marked']) : null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$missing_feeds = array();
|
return array(API::STATUS_OK, array('entries' => $result));
|
||||||
$missing_entries = array();
|
}
|
||||||
$toggle_unread = array();
|
|
||||||
$toggle_marked = array();
|
|
||||||
foreach($data['changed'] as $changed_entry) {
|
|
||||||
$feed_id = $feed_by_link[$changed_entry['feed_url']];
|
|
||||||
if (!$feed_id) {
|
|
||||||
$missing_feeds[$changed_entry['feed_url']] = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$bad_feed = $bad_feed_ids[$feed_id];
|
|
||||||
$entry = null;
|
|
||||||
if ($bad_feed) {
|
|
||||||
$title_date = $changed_entry['title'] . '---' . $changed_entry['date'];
|
|
||||||
$entry = $entries_by_title_date[$title_date];
|
|
||||||
} else {
|
|
||||||
$entry = $entries_by_link[$changed_entry['url']];
|
|
||||||
}
|
|
||||||
if (!$entry) {
|
|
||||||
array_push($missing_entries, $changed_entry);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_array($changed_entry['tags'])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$unread = in_array($data['unread_tag'], $changed_entry['tags']);
|
|
||||||
$marked = in_array($data['starred_tag'], $changed_entry['tags']);
|
|
||||||
if ($entry['unread'] != $unread) {
|
|
||||||
array_push($toggle_unread, $entry['id']);
|
|
||||||
}
|
|
||||||
if ($entry['marked'] != $marked) {
|
|
||||||
array_push($toggle_marked, $entry['id']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
function toggleEntries() {
|
||||||
|
$data = $_REQUEST["data"];
|
||||||
|
$toggle_unread = $data["toggle_unread"];
|
||||||
|
$toggle_marked = $data["toggle_marked"];
|
||||||
ORM::get_db()->beginTransaction();
|
ORM::get_db()->beginTransaction();
|
||||||
if (count($toggle_unread) > 0) {
|
if (!is_null($toggle_unread) && count($toggle_unread) > 0) {
|
||||||
ORM::raw_execute('UPDATE ttrss_user_entries t SET unread = not t.unread WHERE ref_id IN ('.implode(',', $toggle_unread).')');
|
ORM::raw_execute('UPDATE ttrss_user_entries t SET unread = not t.unread WHERE ref_id IN ('.implode(',', $toggle_unread).')');
|
||||||
}
|
}
|
||||||
if (count($toggle_marked) > 0) {
|
if (!is_null($toggle_marked) && count($toggle_marked) > 0) {
|
||||||
ORM::raw_execute('UPDATE ttrss_user_entries t SET marked = not t.marked WHERE ref_id IN ('.implode(',', $toggle_marked).')');
|
ORM::raw_execute('UPDATE ttrss_user_entries t SET marked = not t.marked WHERE ref_id IN ('.implode(',', $toggle_marked).')');
|
||||||
}
|
}
|
||||||
ORM::get_db()->commit();
|
ORM::get_db()->commit();
|
||||||
|
|
||||||
return array($missing_feeds, $missing_entries, sizeof($toggle_unread), sizeof($toggle_marked));
|
return array(API::STATUS_OK, array());
|
||||||
}
|
|
||||||
|
|
||||||
function getUpdatedEntries($data) {
|
|
||||||
if (is_null($data['last_sync'])) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
$last_sync = date('Y-m-d H:i:s', $data['last_sync']);
|
|
||||||
$entries = ORM::for_table('ttrss_entries')
|
|
||||||
->select_many('link', 'ttrss_entries.title', 'updated', 'feed_url', 'marked', 'unread')
|
|
||||||
->join('ttrss_user_entries', ['ttrss_entries.id', '=', 'ttrss_user_entries.ref_id'])
|
|
||||||
->join('ttrss_feeds', ['ttrss_feeds.id', '=', 'ttrss_user_entries.feed_id'])
|
|
||||||
->where_raw('ttrss_user_entries.last_read >= ? OR ttrss_user_entries.last_marked >= ?', [$last_sync, $last_sync])
|
|
||||||
->where('ttrss_user_entries.owner_uid', $_SESSION['uid'])
|
|
||||||
->find_array();
|
|
||||||
return $entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncElfeed() {
|
|
||||||
$data = $_REQUEST["data"];
|
|
||||||
|
|
||||||
list($missing_feeds, $missing_entries, $toggled_unread, $toggled_marked) = $this->updateEntries($data);
|
|
||||||
$changed_entries = $this->getUpdatedEntries($data);
|
|
||||||
return array(API::STATUS_OK,
|
|
||||||
array(
|
|
||||||
'missing_feeds' => $missing_feeds,
|
|
||||||
'missing_entries' => $missing_entries,
|
|
||||||
'toggled_unread' => $toggled_unread,
|
|
||||||
'toggled_marked' => $toggled_marked,
|
|
||||||
'updated' => $changed_entries
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue