emacs/lisp/+elfeed.el

186 lines
7.6 KiB
EmacsLisp

;;; +elfeed.el -*- lexical-binding: t; -*-
;;; Code:
(require 'elfeed)
;; https://karthinks.com/software/lazy-elfeed/
(defun +elfeed-scroll-up-command (&optional arg)
"Scroll up or go to next feed item in Elfeed"
(interactive "^P")
(let ((scroll-error-top-bottom nil))
(condition-case-unless-debug nil
(scroll-up-command arg)
(error (elfeed-show-next)))))
(defun +elfeed-scroll-down-command (&optional arg)
"Scroll up or go to next feed item in Elfeed"
(interactive "^P")
(let ((scroll-error-top-bottom nil))
(condition-case-unless-debug nil
(scroll-down-command arg)
(error (elfeed-show-prev)))))
(defun +elfeed-search-browse-generic ()
"Browse a url with `browse-url-generic-browser'."
(interactive)
(elfeed-search-browse-url t))
(defun +elfeed-show-browse-generic ()
"Browse a url with `browse-url-generic-browser'."
(interactive)
(elfeed-show-visit t))
(defun +elfeed-show-mark-read-and-advance ()
"Mark an item as read and advance to the next item.
If multiple items are selected, don't advance."
(interactive)
(call-interactively #'elfeed-search-untag-all-unread)
(unless (region-active-p)
(call-interactively #'next-line)))
;;; Fetch feeds async
;; https://github.com/skeeto/elfeed/issues/367
(defun +elfeed--update-message ()
(message "[Elfeed] Update in progress")
'ignore)
(defvar +elfeed--update-running-p nil "Whether an update is currently running.")
(defvar +elfeed--update-count 0 "How many times `+elfeed-update-command' has run.")
(defcustom +elfeed-update-niceness 15
"How \"nice\" `+elfeed-update-command' should be."
:type 'integer
:group 'elfeed)
(defcustom +elfeed-update-lockfile
(expand-file-name "+elfeed-update-lock" (temporary-file-directory))
"The file to ")
(defun +elfeed-update-command ()
(interactive)
(unless (or +elfeed--update-running-p
(derived-mode-p 'elfeed-show-mode 'elfeed-search-mode))
(let ((script (expand-file-name "/tmp/elfeed-update.el"))
(update-message-format "[Elfeed] Background update: %s"))
(setq +elfeed--update-running-p t)
(elfeed-db-save)
(advice-add 'elfeed :override #'+elfeed--update-message)
(ignore-errors (kill-buffer "*elfeed-search*"))
(ignore-errors (kill-buffer "*elfeed-log*"))
(elfeed-db-unload)
(make-directory (file-name-directory script) :parents)
(with-temp-buffer
(insert
(let ((print-level nil)
(print-length nil))
(prin1-to-string ;; Print the following s-expression to a string
`(progn
;; Set up the environment
(setq lexical-binding t)
(load (locate-user-emacs-file "early-init"))
(dolist (pkg '(elfeed elfeed-org))
(straight-use-package pkg)
(require pkg))
;; Copy variables from current environment
(progn
,@(cl-loop for copy-var in '(rmh-elfeed-org-files
elfeed-db-directory
elfeed-curl-program-name
elfeed-use-curl
elfeed-curl-extra-arguments
elfeed-enclosure-default-dir)
collect `(progn (message "%S = %S" ',copy-var ',(symbol-value copy-var))
(setq ,copy-var ',(symbol-value copy-var)))))
;; Define new variables for this environment
(progn
,@(cl-loop for (new-var . new-val) in '((elfeed-curl-max-connections . 4))
collect `(progn (message "%S = %S" ',new-var ',new-val)
(setq ,new-var ',new-val))))
;; Redefine `elfeed-log' to log everything
(defun elfeed-log (level fmt &rest objects)
(princ (format "[%s] [%s]: %s\n"
(format-time-string "%F %T")
level
(apply #'format fmt objects))))
;; Run elfeed
(elfeed-org)
(elfeed)
(elfeed-db-load)
(elfeed-update)
;; Wait for `elfeed-update' to finish
(let ((q<5-count 0))
(while (and (> (elfeed-queue-count-total) 0)
(< q<5-count 5))
(sleep-for 5)
(message "Elfeed queue count total: %s" (elfeed-queue-count-total))
(when (< (elfeed-queue-count-total) 5)
(cl-incf q<5-count))
(accept-process-output)))
;; Garbage collect and save the database
(elfeed-db-gc)
(elfeed-db-save)
(princ (format ,update-message-format "done."))))))
(write-file script))
(chmod script #o777)
(message update-message-format "start")
(set-process-sentinel (start-process-shell-command
"Elfeed" "*+elfeed-update-background*"
(format "nice -n %d %s %s"
+elfeed-update-niceness
"emacs -Q --script"
script))
(lambda (proc stat)
(advice-remove 'elfeed #'+elfeed--update-message)
(setq +elfeed--update-running-p nil)
(unless (string= stat "killed")
(setq +elfeed--update-count (1+ +elfeed--update-count)))
(message update-message-format (string-trim stat)))))))
(defvar +elfeed--update-timer nil "Timer for `elfeed-update-command'.")
(defvar +elfeed--update-first-time 6 "How long to wait for the first time.")
(defvar +elfeed--update-repeat (* 60 15) "How long between updates.")
(defcustom +elfeed-update-proceed-hook nil
"Predicates to query before running `+elfeed-update-command'.
Each hook is passed no arguments."
:type 'hook)
(defun +elfeed-update-command-wrapper ()
"Run `+elfeed-update-command', but only sometimes.
If any of the predicates in `+elfeed-update-proceed-hook' return
nil, don't run `+elfeed-update-command'. If they all return
non-nil, proceed."
(when (run-hook-with-args-until-failure '+elfeed-update-proceed-hook)
(+elfeed-update-command)))
(defun +elfeed--cancel-update-timer ()
"Cancel `+elfeed--update-timer'."
(unless +elfeed--update-running-p
(ignore-errors (cancel-timer +elfeed--update-timer))
(setq +elfeed--update-timer nil)))
(defun +elfeed--reinstate-update-timer ()
"Reinstate `+elfeed--update-timer'."
;; First, unload the db
(setq +elfeed--update-timer
(run-at-time +elfeed--update-first-time
+elfeed--update-repeat
#'+elfeed-update-command-wrapper)))
(define-minor-mode +elfeed-update-async-mode
"Minor mode to update elfeed async-style."
:global t
(if +elfeed-update-async-mode
(progn ; enable
(+elfeed--reinstate-update-timer)
(advice-add 'elfeed :before '+elfeed--cancel-update-timer)
(advice-add 'elfeed-search-quit-window :after '+elfeed--reinstate-update-timer))
(progn ; disable
(advice-remove 'elfeed '+elfeed--cancel-update-timer)
(advice-remove 'elfeed-search-quit-window '+elfeed--reinstate-update-timer)
(+elfeed--cancel-update-timer))))
(provide '+elfeed)
;;; +elfeed.el ends here