about summary refs log tree commit diff stats
path: root/lisp/+elfeed.el
blob: c0b74f022e7999bc51f7efcf55a3f379a8109451 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
;;; +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 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)

(defun +elfeed-update-command ()
  (interactive)
  (unless (or +elfeed--update-running
              (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 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
               (while (> (elfeed-queue-count-total) 0)
                 (sleep-for 5)
                 (message "%s" (elfeed-queue-count-total))
                 (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 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
    (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