;;; +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