diff options
| author | Artur Malabarba | 2015-04-01 11:03:43 +0100 |
|---|---|---|
| committer | Artur Malabarba | 2015-04-01 11:08:03 +0100 |
| commit | ba7a1a7a4eb64dd391d2e866c82cadfcc00d364d (patch) | |
| tree | a126160984860527537548d7950e90832c86a655 | |
| parent | 5ba4fbd9e3cc2fc31e5ec3ae22f1695800b86d21 (diff) | |
| download | emacs-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/ChangeLog | 12 | ||||
| -rw-r--r-- | lisp/emacs-lisp/package.el | 183 |
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 @@ | |||
| 1 | 2015-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 | |||
| 1 | 2015-03-31 Simen Heggestøyl <simenheg@gmail.com> | 13 | 2015-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. |
| 1087 | GnuPG keyring is located under \"gnupg\" in `package-user-dir'." | 1087 | If ASYNC is non-nil, and if it is possible, the operation is run |
| 1088 | asynchronously. If an error is encountered and ASYNC is a | ||
| 1089 | function, it is called with no arguments (instead of executing | ||
| 1090 | body), otherwise the error is propagated. For description on the | ||
| 1091 | other 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. | ||
| 1113 | SIG-FILE is the name of the signature file, used when signaling | ||
| 1114 | errors." | ||
| 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. | ||
| 1142 | Signature file is downloaded from LOCATION by appending \".sig\" | ||
| 1143 | to FILE. | ||
| 1144 | GnuPG keyring is located under \"gnupg\" in `package-user-dir'. | ||
| 1145 | STRING is the string to verify, it defaults to `buffer-string'. | ||
| 1146 | If ASYNC is non-nil, the download of the signature file is | ||
| 1147 | done asynchronously. | ||
| 1148 | |||
| 1149 | If the signature is verified and CALLBACK was provided, CALLBACK | ||
| 1150 | is `funcall'ed with the list of good signatures as argument (the | ||
| 1151 | list can be empty). If the signatures file is not found, | ||
| 1152 | CALLBACK 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.") |
| 1286 | ARCHIVE should be a cons cell of the form (NAME . LOCATION), | ||
| 1287 | similar 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. | ||
| 1355 | Don'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'. | ||
| 1361 | Once 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. | ||
| 1376 | ARCHIVE should be a cons cell of the form (NAME . LOCATION), | ||
| 1377 | similar 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. | ||
| 1409 | This populates `package-archive-contents'. If ASYNC is non-nil, | ||
| 1410 | the 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. |
| 1337 | For each archive configured in the variable `package-archives', | 1423 | For each archive configured in the variable `package-archives', |
| 1338 | inform Emacs about the latest versions of all packages it offers, | 1424 | inform Emacs about the latest versions of all packages it offers, |
| 1339 | and make them available for download." | 1425 | and make them available for download. |
| 1426 | Optional argument, ASYNC, specifies whether the downloads should | ||
| 1427 | be 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 |