aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArtur Malabarba2015-04-01 11:03:43 +0100
committerArtur Malabarba2015-04-01 11:08:03 +0100
commitba7a1a7a4eb64dd391d2e866c82cadfcc00d364d (patch)
treea126160984860527537548d7950e90832c86a655
parent5ba4fbd9e3cc2fc31e5ec3ae22f1695800b86d21 (diff)
downloademacs-ba7a1a7a4eb64dd391d2e866c82cadfcc00d364d.tar.gz
emacs-ba7a1a7a4eb64dd391d2e866c82cadfcc00d364d.zip
* emacs-lisp/package.el: Implement asynchronous refreshing.
(package--with-work-buffer-async) (package--check-signature-content) (package--update-downloads-in-progress): New functions. (package--check-signature, package--download-one-archive) (package--download-and-read-archives, package-refresh-contents): Optional arguments for async usage. (package--post-download-archives-hook): New variable. Hook run after every refresh.
-rw-r--r--lisp/ChangeLog12
-rw-r--r--lisp/emacs-lisp/package.el183
2 files changed, 144 insertions, 51 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 621121e0f06..da3cd513ca2 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,15 @@
12015-04-01 Artur Malabarba <bruce.connor.am@gmail.com>
2
3 * emacs-lisp/package.el: Implement asynchronous refreshing.
4 (package--with-work-buffer-async)
5 (package--check-signature-content)
6 (package--update-downloads-in-progress): New functions.
7 (package--check-signature, package--download-one-archive)
8 (package--download-and-read-archives, package-refresh-contents):
9 Optional arguments for async usage.
10 (package--post-download-archives-hook): New variable. Hook run
11 after every refresh.
12
12015-03-31 Simen Heggestøyl <simenheg@gmail.com> 132015-03-31 Simen Heggestøyl <simenheg@gmail.com>
2 14
3 * textmodes/css-mode.el (css-mode): Derive from `prog-mode'. 15 * textmodes/css-mode.el (css-mode): Derive from `prog-mode'.
diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el
index 526c0b41a77..89d92464119 100644
--- a/lisp/emacs-lisp/package.el
+++ b/lisp/emacs-lisp/package.el
@@ -1082,20 +1082,43 @@ buffer is killed afterwards. Return the last value in BODY."
1082 (insert-file-contents (expand-file-name ,file ,location))) 1082 (insert-file-contents (expand-file-name ,file ,location)))
1083 ,@body)) 1083 ,@body))
1084 1084
1085(defun package--check-signature (location file) 1085(defmacro package--with-work-buffer-async (location file async &rest body)
1086 "Check signature of the current buffer. 1086 "Run BODY in a buffer containing the contents of FILE at LOCATION.
1087GnuPG keyring is located under \"gnupg\" in `package-user-dir'." 1087If ASYNC is non-nil, and if it is possible, the operation is run
1088asynchronously. If an error is encountered and ASYNC is a
1089function, it is called with no arguments (instead of executing
1090body), otherwise the error is propagated. For description on the
1091other arguments see `package--with-work-buffer'."
1092 (declare (indent 3) (debug t))
1093 `(if (or (not ,async)
1094 (not (string-match-p "\\`https?:" ,location)))
1095 (package--with-work-buffer ,location ,file ,@body)
1096 (url-retrieve (concat ,location ,file)
1097 (lambda (status)
1098 (if (eq (car status) :error)
1099 (if (functionp ,async)
1100 (funcall ,async)
1101 (signal (cdar status) (cddr status)))
1102 (goto-char (point-min))
1103 (unless (search-forward "\n\n" nil 'noerror)
1104 (error "Invalid url response"))
1105 (delete-region (point-min) (point))
1106 ,@body)
1107 (kill-buffer (current-buffer)))
1108 nil
1109 'silent)))
1110
1111(defun package--check-signature-content (content string &optional sig-file)
1112 "Check signature CONTENT against STRING.
1113SIG-FILE is the name of the signature file, used when signaling
1114errors."
1088 (let* ((context (epg-make-context 'OpenPGP)) 1115 (let* ((context (epg-make-context 'OpenPGP))
1089 (homedir (expand-file-name "gnupg" package-user-dir)) 1116 (homedir (expand-file-name "gnupg" package-user-dir)))
1090 (sig-file (concat file ".sig"))
1091 (sig-content (package--with-work-buffer location sig-file
1092 (buffer-string))))
1093 (setf (epg-context-home-directory context) homedir) 1117 (setf (epg-context-home-directory context) homedir)
1094 (condition-case error 1118 (condition-case error
1095 (epg-verify-string context sig-content (buffer-string)) 1119 (epg-verify-string context content string)
1096 (error 1120 (error (package--display-verify-error context sig-file)
1097 (package--display-verify-error context sig-file) 1121 (signal (car error) (cdr error))))
1098 (signal (car error) (cdr error))))
1099 (let (good-signatures had-fatal-error) 1122 (let (good-signatures had-fatal-error)
1100 ;; The .sig file may contain multiple signatures. Success if one 1123 ;; The .sig file may contain multiple signatures. Success if one
1101 ;; of the signatures is good. 1124 ;; of the signatures is good.
@@ -1114,6 +1137,30 @@ GnuPG keyring is located under \"gnupg\" in `package-user-dir'."
1114 (error "Failed to verify signature %s" sig-file)) 1137 (error "Failed to verify signature %s" sig-file))
1115 good-signatures))) 1138 good-signatures)))
1116 1139
1140(defun package--check-signature (location file &optional string async callback)
1141 "Check signature of the current buffer.
1142Signature file is downloaded from LOCATION by appending \".sig\"
1143to FILE.
1144GnuPG keyring is located under \"gnupg\" in `package-user-dir'.
1145STRING is the string to verify, it defaults to `buffer-string'.
1146If ASYNC is non-nil, the download of the signature file is
1147done asynchronously.
1148
1149If the signature is verified and CALLBACK was provided, CALLBACK
1150is `funcall'ed with the list of good signatures as argument (the
1151list can be empty). If the signatures file is not found,
1152CALLBACK is called with no arguments."
1153 (let ((sig-file (concat file ".sig"))
1154 (string (or string (buffer-string))))
1155 (condition-case nil
1156 (package--with-work-buffer-async
1157 location sig-file (when async (or callback t))
1158 (let ((sig (package--check-signature-content
1159 (buffer-string) string sig-file)))
1160 (when callback (funcall callback sig))
1161 sig))
1162 (file-error (funcall callback)))))
1163
1117 1164
1118;;; Packages on Archives 1165;;; Packages on Archives
1119;; The following variables store information about packages available 1166;; The following variables store information about packages available
@@ -1281,36 +1328,8 @@ If optional arg NO-ACTIVATE is non-nil, don't activate packages."
1281;;;; Populating `package-archive-contents' from archives 1328;;;; Populating `package-archive-contents' from archives
1282;; This subsection populates the variables listed above from the 1329;; This subsection populates the variables listed above from the
1283;; actual archives, instead of from a local cache. 1330;; actual archives, instead of from a local cache.
1284(defun package--download-one-archive (archive file) 1331(defvar package--downloads-in-progress nil
1285 "Retrieve an archive file FILE from ARCHIVE, and cache it. 1332 "List of in-progress asynchronous downloads.")
1286ARCHIVE should be a cons cell of the form (NAME . LOCATION),
1287similar to an entry in `package-alist'. Save the cached copy to
1288\"archives/NAME/archive-contents\" in `package-user-dir'."
1289 (let ((dir (expand-file-name (format "archives/%s" (car archive))
1290 package-user-dir))
1291 (sig-file (concat file ".sig"))
1292 good-signatures)
1293 (package--with-work-buffer (cdr archive) file
1294 ;; Check signature of archive-contents, if desired.
1295 (if (and package-check-signature
1296 (not (member archive package-unsigned-archives)))
1297 (if (package--archive-file-exists-p (cdr archive) sig-file)
1298 (setq good-signatures (package--check-signature (cdr archive)
1299 file))
1300 (unless (eq package-check-signature 'allow-unsigned)
1301 (error "Unsigned archive `%s'"
1302 (car archive)))))
1303 ;; Read the retrieved buffer to make sure it is valid (e.g. it
1304 ;; may fetch a URL redirect page).
1305 (when (listp (read (current-buffer)))
1306 (make-directory dir t)
1307 (write-region nil nil (expand-file-name file dir) nil 'silent)))
1308 (when good-signatures
1309 ;; Write out good signatures into archive-contents.signed file.
1310 (write-region (mapconcat #'epg-signature-to-string good-signatures "\n")
1311 nil
1312 (expand-file-name (concat file ".signed") dir)
1313 nil 'silent))))
1314 1333
1315(declare-function epg-check-configuration "epg-config" 1334(declare-function epg-check-configuration "epg-config"
1316 (config &optional minimum-version)) 1335 (config &optional minimum-version))
@@ -1331,12 +1350,81 @@ similar to an entry in `package-alist'. Save the cached copy to
1331 (epg-import-keys-from-file context file) 1350 (epg-import-keys-from-file context file)
1332 (message "Importing %s...done" (file-name-nondirectory file)))) 1351 (message "Importing %s...done" (file-name-nondirectory file))))
1333 1352
1353(defvar package--post-download-archives-hook nil
1354 "Hook run after the archive contents are downloaded.
1355Don't run this hook directly. It is meant to be run as part of
1356`package--update-downloads-in-progress'.")
1357(put 'package--post-download-archives-hook 'risky-local-variable t)
1358
1359(defun package--update-downloads-in-progress (entry)
1360 "Remove ENTRY from `package--downloads-in-progress'.
1361Once it's empty, run `package--post-download-archives-hook'."
1362 ;; Keep track of the downloading progress.
1363 (setq package--downloads-in-progress
1364 (remove entry package--downloads-in-progress))
1365 ;; If this was the last download, run the hook.
1366 (unless package--downloads-in-progress
1367 (package--build-compatibility-table)
1368 (package-read-all-archive-contents)
1369 ;; We message before running the hook, so the hook can give
1370 ;; messages as well.
1371 (message "Package refresh done")
1372 (run-hooks 'package--post-download-archives-hook)))
1373
1374(defun package--download-one-archive (archive file &optional async)
1375 "Retrieve an archive file FILE from ARCHIVE, and cache it.
1376ARCHIVE should be a cons cell of the form (NAME . LOCATION),
1377similar to an entry in `package-alist'. Save the cached copy to
1378\"archives/NAME/FILE\" in `package-user-dir'."
1379 (package--with-work-buffer-async (cdr archive) file async
1380 (let* ((location (cdr archive))
1381 (name (car archive))
1382 (content (buffer-string))
1383 (dir (expand-file-name (format "archives/%s" name) package-user-dir))
1384 (local-file (expand-file-name file dir)))
1385 (when (listp (read-from-string content))
1386 (make-directory dir t)
1387 (if (or (not package-check-signature)
1388 (member archive package-unsigned-archives))
1389 ;; If we don't care about the signature, save the file and
1390 ;; we're done.
1391 (progn (write-region content nil local-file nil 'silent)
1392 (package--update-downloads-in-progress archive))
1393 ;; If we care, check it (perhaps async) and *then* write the file.
1394 (package--check-signature
1395 location file content async
1396 (lambda (&optional good-sigs)
1397 (unless (or good-sigs (eq package-check-signature 'allow-unsigned))
1398 (error "Unsigned archive `%s'" name))
1399 ;; Write out the archives file.
1400 (write-region content nil local-file nil 'silent)
1401 ;; Write out good signatures into archive-contents.signed file.
1402 (when good-sigs
1403 (write-region (mapconcat #'epg-signature-to-string good-sigs "\n")
1404 nil (concat local-file ".signed") nil 'silent))
1405 (package--update-downloads-in-progress archive))))))))
1406
1407(defun package--download-and-read-archives (&optional async)
1408 "Download descriptions of all `package-archives' and read them.
1409This populates `package-archive-contents'. If ASYNC is non-nil,
1410the downloads are performed asynchronously."
1411 ;; The dowloaded archive contents will be read as part of
1412 ;; `package--update-downloads-in-progress'.
1413 (setq package--downloads-in-progress package-archives)
1414 (dolist (archive package-archives)
1415 (condition-case-unless-debug nil
1416 (package--download-one-archive archive "archive-contents" async)
1417 (error (message "Failed to download `%s' archive."
1418 (car archive))))))
1419
1334;;;###autoload 1420;;;###autoload
1335(defun package-refresh-contents () 1421(defun package-refresh-contents (&optional async)
1336 "Download descriptions of all configured ELPA packages. 1422 "Download descriptions of all configured ELPA packages.
1337For each archive configured in the variable `package-archives', 1423For each archive configured in the variable `package-archives',
1338inform Emacs about the latest versions of all packages it offers, 1424inform Emacs about the latest versions of all packages it offers,
1339and make them available for download." 1425and make them available for download.
1426Optional argument, ASYNC, specifies whether the downloads should
1427be performed in the background."
1340 (interactive) 1428 (interactive)
1341 ;; FIXME: Do it asynchronously. 1429 ;; FIXME: Do it asynchronously.
1342 (unless (file-exists-p package-user-dir) 1430 (unless (file-exists-p package-user-dir)
@@ -1349,14 +1437,7 @@ and make them available for download."
1349 (epg-check-configuration (epg-configuration)) 1437 (epg-check-configuration (epg-configuration))
1350 (package-import-keyring default-keyring)) 1438 (package-import-keyring default-keyring))
1351 (error (message "Cannot import default keyring: %S" (cdr error)))))) 1439 (error (message "Cannot import default keyring: %S" (cdr error))))))
1352 (dolist (archive package-archives) 1440 (package--download-and-read-archives async))
1353 (condition-case-unless-debug nil
1354 (package--download-one-archive archive "archive-contents")
1355 (error (message "Failed to download `%s' archive."
1356 (car archive)))))
1357 (package-read-all-archive-contents)
1358 (package--build-compatibility-table)
1359 (message "Package refresh done"))
1360 1441
1361 1442
1362;;; Dependency Management 1443;;; Dependency Management