-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathspu.el
291 lines (261 loc) · 11.4 KB
/
spu.el
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
;;; spu.el --- Silently upgrade package in the background -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2016 Mola-T
;; Author: Mola-T <Mola@molamola.xyz>
;; URL: https://github.com/mola-T/spu
;; Version: 1.0.1
;; Package-Requires: ((emacs "24.4") (signal "1.0") (timp "1.2.0"))
;; Keywords: convenience, package
;;
;;; License:
;; This file is NOT part of GNU Emacs.
;;
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;
;;; Commentary:
;; spu stands for Silent Package Upgrader.
;; It can upgrade installed packages completely in the backgroud.
;; You will never being blocked by "Contacting host: elpa.gnu.org:80...."
;; when upgrading package.
;;
;; You will never worry about packages ugrade
;; with just one line : (spu-package-upgrade-daily) added to your init file,
;;
;; See https://github.com/mola-T/spu for more information.
;;
;;; code:
(require 'signal)
(require 'timp)
(require 'subr-x)
(require 'package)
(defgroup spu nil
"Group for Silenct Package Upgrader."
:group 'convenience
:group 'package)
(defcustom spu-log-path (file-name-as-directory
(concat
(file-name-as-directory (expand-file-name user-emacs-directory))
"spu_log"))
"Path to save SPU packages upgrade log."
:group 'spu)
(defcustom spu-require-confirm-upgrade-package nil
"Non-nil value will prompt for confirmation before upgrading packages."
:group 'spu)
(defvar spu-thread nil
"Store thread for upgrading packages.")
(defvar spu-package-upgrade-buffer "*SPU Package Upgrades*"
"Buffer name for package upgrade prompt.")
(defvar spu-upgrade-in-process nil
"Ensure one `spu-package-upgrade' is doing at a time.")
(defvar spu-upgrade-timer nil
"Store the timer for next scheduled package upgrade.")
(defsignal spu-package-upgrade-finished-signal
"Signal emitted when upgrade finished.")
(defsignal spu-package-upgraded-list-signal
"Signal emitted with upgraded list when upgrade finished.")
(defsignal spu-package-upgraded-error-list-signal
"Signal emitted with error list when upgrade finished.")
(define-derived-mode spu-package-menu-mode tabulated-list-mode "Package Upgrade Menu"
"Major mode for browsing a list of upgradable packages."
(setq tabulated-list-format
`[("Package" 18 package-menu--name-predicate)
("Version" 13 nil)
("Status" 10 package-menu--status-predicate)
,@(if (cdr package-archives)
'(("Archive" 10 package-menu--archive-predicate)))
("Description" 0 nil)])
(setq tabulated-list-padding 10)
(setq tabulated-list-sort-key (cons "Status" nil))
(tabulated-list-init-header))
;;;###autoload
(defun spu-package-upgrade (&optional prefix)
"Upgrade package."
(interactive "p")
;; Ensure only run once a day
(catch 'ran-already
(when (and (not prefix) (file-exists-p (concat spu-log-path (format-time-string "%Y%m%d") ".log")))
(throw 'ran-already nil))
(when spu-upgrade-in-process
(message "[SPU] package upgrade is already running.")
(throw 'ran-already nil))
(setq spu-upgrade-in-process t)
(when prefix
(message "[SPU] Checking package information."))
(unless (timp-validate spu-thread)
(setq spu-thread (timp-get :persist t)))
(timp-require-package spu-thread 'spu-dark)
(timp-send-variable spu-thread spu-log-path)
(timp-send-exec spu-thread 'spu-dark-set-package-acrhives package-archives)
(timp-send-exec spu-thread 'spu-dark-init)
(if spu-require-confirm-upgrade-package
(timp-send-exec spu-thread 'spu-dark-get-package-upgrade-list
:reply-func #'spu-confirm-upgrade-package
:error-handler #'spu-print-error-message)
(timp-send-exec spu-thread 'spu-dark-upgrade-packages
:reply-func #'spu-upgrade-finished
:error-handler #'spu-print-error-message))))
;;;###autoload
(defun spu-package-upgrade-daily ()
"Upgrade package daily. Don't call it by any interactive way."
(spu-package-upgrade)
(when spu-upgrade-timer
(cancel-timer spu-upgrade-timer)
(setq spu-upgrade-timer nil))
(setq spu-upgrade-timer (run-at-time (time-add (current-time) (seconds-to-time 86401)) nil #'spu-package-upgrade-daily)))
(defun spu-upgrade-finished (result)
;; result is in form of (installed-list . error-list)
"Echo the package upgrade RESULT."
(let ((installed-list (car result))
(error-list (cdr result)))
(if (and (= (length installed-list) 0) (= (length error-list) 0))
(message "[SPU] All packages are up to date.")
(signal-emit 'spu-package-upgrade-finished-signal)
(when installed-list
(signal-emit 'spu-package-upgraded-list-signal :arg (list installed-list)))
(when error-list
(signal-emit 'spu-package-upgraded-error-list-signal :arg (list error-list)))
(message "[SPU] %d package%s upgraded. %s\n M-x %s for details."
(length installed-list)
(if (> (length installed-list) 1) "s" "")
(if (> (length error-list) 0)
(propertize (format "%d error%s occurs."
(length error-list)
(if (> (length error-list) 1) "s" ""))
'face
'error)
"")
(propertize "spu-view-upgrade-log" 'face 'font-lock-builtin-face)))
(timp-quit spu-thread)
(setq spu-upgrade-in-process nil)))
(defun spu-confirm-upgrade-package (packages)
"Generate a buffer prompt for upgrading PACKAGES."
(if packages
(let ((buf (get-buffer-create spu-package-upgrade-buffer)))
(with-current-buffer buf
(spu-package-menu-mode)
(spu-package-menu--generate packages)
(let ((inhibit-read-only t))
(save-excursion
(goto-char (point-max))
(unless (eq (point) (line-beginning-position))
(newline))
(insert "\n** Press 'c' to toggle cancelling upgrade.\n"
"** Press 'x' to execute upgrade.\n"
"** Press 'q' or 'C-g' to abort upgrade.")))
(switch-to-buffer buf)))
(spu-upgrade-finished nil)))
(defun spu-package-menu--generate (packages)
"Populate the Package Menu.
If REMEMBER-POS is non-nil, keep point on the same entry.
PACKAGES should be t, which means to display all known packages,
or a list of package names (symbols) to display.
With KEYWORDS given, only packages with those keywords are
shown."
(spu-package-menu--refresh packages)
(setf (car (aref tabulated-list-format 0)) "Package")
(tabulated-list-init-header)
(tabulated-list-print))
(defun spu-package-menu--refresh (packages)
"Re-populate the `tabulated-list-entries'.
PACKAGES should be nil or t, which means to display all known packages.
KEYWORDS should be nil or a list of keywords."
;; Construct list of (PKG-DESC . STATUS).
(let (info-list)
(dolist (package packages)
(push (cons (cdr package) "Upgradable") info-list))
;; Print the result.
(setq tabulated-list-entries
(mapcar #'spu-package-menu--print-info info-list))))
(defun spu-package-menu--print-info (pkg)
"Return a package entry suitable for `tabulated-list-entries'.
PKG has the form (PKG-DESC . STATUS).
Return (PKG-DESC [NAME VERSION STATUS DOC])."
(let* ((pkg-desc (car pkg))
(status (cdr pkg))
(face 'bold)) ; obsolete.
(list pkg-desc
`[,(list (symbol-name (package-desc-name pkg-desc))
'face 'link
'follow-link t
'package-desc pkg-desc
'action 'package-menu-describe-package)
,(propertize (package-version-join
(package-desc-version pkg-desc))
'font-lock-face face)
,(propertize status 'font-lock-face face)
,@(if (cdr package-archives)
(list (propertize (or (package-desc-archive pkg-desc) "")
'font-lock-face face)))
,(propertize (package-desc-summary pkg-desc)
'font-lock-face face)])))
(defun spu-package-menu-toggle-cancel-upgrades ()
"Cancel the selected package from updating."
(interactive)
(unless (derived-mode-p 'spu-package-menu-mode)
(error "The current buffer is not a SDPU Package Menu"))
(unless (or (eobp) (string-match "\\` *\\*\\*" (thing-at-point 'line t)))
(if (equal (save-excursion (beginning-of-line) (thing-at-point 'word t)) "Cancel")
(tabulated-list-put-tag "" t)
(tabulated-list-put-tag "Cancel" t))))
(defun spu-package-menu-abort-upgrades ()
"Abort package upgrades."
(interactive)
(unless (derived-mode-p 'spu-package-menu-mode)
(error "The current buffer is not a SDPU Package Menu"))
(when (timp-validate spu-thread)
(timp-quit spu-thread))
(kill-buffer spu-package-upgrade-buffer))
(defun spu-package-menu-execute-upgrades ()
"Execute package upgrades."
(interactive)
(unless (derived-mode-p 'spu-package-menu-mode)
(error "The current buffer is not a SDPU Package Menu"))
(let (upgrade-list)
(save-excursion
(goto-char (point-min))
(while (and (not (eobp))
(not (string-blank-p (string-trim (thing-at-point 'line t))))
(not (string-match "\\` *\\*\\*" (thing-at-point 'line t))))
(unless (string= (thing-at-point 'word t) "Cancel")
(push (tabulated-list-get-id) upgrade-list))
(forward-line)))
(timp-send-exec spu-thread 'spu-dark-package-menu-mark-upgrades upgrade-list
:reply-func #'spu-upgrade-finished
:error-handler #'spu-print-error-message)
(message "Packages will be upgraded.")
(kill-buffer spu-package-upgrade-buffer)))
;;;###autoload
(defun spu-view-upgrade-log (prefix)
"Open the last package upgrade log.
With PREFIX, open the directory containing the upgrade logs."
(interactive "p")
(if (= prefix 1)
(let ((files (directory-files spu-log-path t ".*\\.log")))
(if files
(view-file (car (last files)))
(message "[SPU] No package upgrade log availiable.")))
(dired spu-log-path)))
(defun spu-print-error-message (err)
"Print ERR to message log."
(message (concat
(propertize "[SPU]" 'face 'error) (pp-to-string err)))
(timp-quit spu-thread))
(define-key spu-package-menu-mode-map (kbd "c") #'spu-package-menu-toggle-cancel-upgrades)
(define-key spu-package-menu-mode-map (kbd "x") #'spu-package-menu-execute-upgrades)
(define-key spu-package-menu-mode-map (kbd "q") #'spu-package-menu-abort-upgrades)
(define-key spu-package-menu-mode-map (kbd "C-g") #'spu-package-menu-abort-upgrades)
(provide 'spu)
;;; spu.el ends here