├── README.md ├── plan.org └── package-store.el /README.md: -------------------------------------------------------------------------------- 1 | emacs-package-store 2 | =================== 3 | 4 | An add on to package.el that caches packages in a local cache and 5 | let's you use them when the network is down. 6 | 7 | If you install this package (it's in http://marmalade-repo.org) your 8 | package downloads will be cached to: 9 | 10 | ``` 11 | package-store-cache-dir 12 | ``` 13 | 14 | which by default is: 15 | 16 | ``` 17 | ~/.emacs.d/package-cache 18 | ``` 19 | 20 | This allows you to then download the packages from the cache instead 21 | of the network. This is most useful when you are on a plane or 22 | somewhere else that does not have a network connection. Particularly 23 | so you can continue to test code with packages. 24 | 25 | 26 | Use 27 | 28 | ``` 29 | M-x toggle-package-store-connected 30 | ``` 31 | 32 | To manually turn on retrieval from the cache. 33 | 34 | -------------------------------------------------------------------------------- /plan.org: -------------------------------------------------------------------------------- 1 | 2 | * what? 3 | ** a personal archive of packages 4 | ** a directory structure 5 | *** in your ~/.emacs.d? 6 | ** stores all the packages you download 7 | ** in raw form 8 | 9 | * why? 10 | ** so you can keep a package store around of everything you use 11 | ** back it up, for example 12 | 13 | * how 14 | ** package--with-work-buffer does the download for the package 15 | ** that's a macro 16 | ** but we have 17 | *** package-download-tar 18 | *** package-download-single 19 | ** which both use it 20 | ** url-retrieve-synchronously is used to retrieve 21 | ** so advise those 2 functions with something that sets 22 | *** url-cache-directory 23 | ** to the package cache dir 24 | 25 | * retrieval? 26 | ** how to know when we are disconnected? 27 | ** url-fetch-from-cache can fetch from cache 28 | *** needs to access the args 29 | **** that package-download-tar passes to package--with-work-buffer 30 | ** could we flet url-retrieve-synchronously in the advice? 31 | *** that gets the url 32 | 33 | * issues 34 | ** doesn't work because 35 | *** the package archive is not cached 36 | **** could just try and copy the archive from the elpa near the package-store 37 | **** or could try and cache that as well 38 | **** function package--download-one-archive 39 | ***** does the download and needs to be cached 40 | *** url doesn't detect network down 41 | **** we could detect this: 42 | ***** url-http (url-http.el) calls 43 | ****** url-http-find-free-connection makes the connection 44 | ***** and on nil return errors 45 | ****** "Could not create connection to host:port" 46 | **** we could advise url-http to catch the error 47 | ***** and then try again with the disconnect mode turned on 48 | 49 | * other ideas 50 | ** this idea from the HN thread 51 | *** http://news.ycombinator.com/item?id=4278704 52 | *** keep the installed packages as a list in customize 53 | *** then you can install the packages you want just by having access to the customize value 54 | 55 | -------------------------------------------------------------------------------- /package-store.el: -------------------------------------------------------------------------------- 1 | ;;; package-store.el --- a package cache 2 | 3 | ;; Copyright (C) 2012 Nic Ferrier 4 | 5 | ;; Author: Nic Ferrier 6 | ;; Maintainer: Nic Ferrier 7 | ;; Created: 17th July 2012 8 | ;; Version: 0.3 9 | ;; Keywords: lisp, http, hypermedia 10 | 11 | ;; This file is NOT part of GNU Emacs. 12 | 13 | ;; This program is free software; you can redistribute it and/or modify 14 | ;; it under the terms of the GNU General Public License as published by 15 | ;; the Free Software Foundation, either version 3 of the License, or 16 | ;; (at your option) any later version. 17 | 18 | ;; This program is distributed in the hope that it will be useful, 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | ;; GNU General Public License for more details. 22 | 23 | ;; You should have received a copy of the GNU General Public License 24 | ;; along with this program. If not, see . 25 | 26 | ;;; Commentary: 27 | ;; 28 | ;; A simple adaption for Emacs' package system, this stores downloaded 29 | ;; packages directly in a cache. This promotes easier transfer to 30 | ;; other machines or disconnected (from the Internet) testing. 31 | 32 | ;;; Source code 33 | ;; 34 | ;; package-store's code can be found here: 35 | ;; http://github.com/nicferrier/package-store 36 | 37 | ;;; Style note 38 | ;; 39 | ;; This codes uses the Emacs style of: 40 | ;; 41 | ;; package-store--private-function 42 | ;; 43 | ;; for private functions. 44 | 45 | 46 | ;;; Code: 47 | 48 | (require 'cl) 49 | (require 'ert) 50 | 51 | ;;;###autoload 52 | (defcustom package-store-cache-dir 53 | (concat user-emacs-directory "package-cache") 54 | "The directory to store downloaded packages." 55 | :type 'directory 56 | :group 'package) 57 | 58 | (defvar package-store-cache-package-name 59 | nil 60 | "Special variable declaration for the package name. 61 | 62 | This is used to communicate the package name to the name 63 | production function for the url cache: 64 | `package-store-url-cache-create-filename-package'") 65 | 66 | (defvar package-store-cache-package-version 67 | nil 68 | "Special variable declaration for the package version. 69 | 70 | This is used to communicate the package version to the name 71 | production function for the url cache: 72 | `package-store-url-cache-create-filename-package'") 73 | 74 | ;;;###autoload 75 | (defvar package-store-disconnected 76 | nil 77 | "Is the network disconnected?") 78 | 79 | ;;;###autoload 80 | (defun toggle-package-store-connected (&optional arg) 81 | "Toggle package network downloads on or off." 82 | (interactive "P") 83 | (setq package-store-disconnected 84 | (if (null arg) 85 | (not package-store-disconnected) 86 | (> (prefix-numeric-value arg) 0)))) 87 | 88 | ;;;###autoload 89 | (defun package-store-url-cache-create-filename-package (url) 90 | "A url cache file namer. 91 | 92 | This depends on the special variables 93 | `package-store-cache-package-name' and 94 | `package-store-cache-package-version'." 95 | (if url 96 | (let* ((package-name "test") 97 | (package-version "0.9.9") 98 | (urlobj (url-generic-parse-url url)) 99 | (url-file-name (url-filename urlobj)) 100 | (protocol (url-type urlobj)) 101 | (hostname (url-host urlobj)) 102 | (host-components 103 | (cons (or protocol "file") 104 | (nreverse 105 | (delq nil 106 | (split-string (or hostname "localhost") 107 | "\\."))))) 108 | (dname (file-name-directory url-file-name)) 109 | (ext (file-name-extension url-file-name))) 110 | (message "package-store url: %s" url) 111 | (and dname 112 | (expand-file-name 113 | (concat 114 | (mapconcat 'identity host-components "/") 115 | (file-name-as-directory dname) 116 | (file-name-as-directory 117 | (if (symbolp package-store-cache-package-name) 118 | (symbol-name package-store-cache-package-name) 119 | package-store-cache-package-name)) 120 | package-store-cache-package-version 121 | "." ext) 122 | url-cache-directory))))) 123 | 124 | (ert-deftest package-store-url-cache-create-filename-package () 125 | "Test the cache naming function." 126 | (let* ((package-store-cache-dir "/home/packagestore/.emacs.d/packagecache") 127 | (package-store-cache-package-name "eldoc") 128 | (package-store-cache-package-version "0.9.9") 129 | (url-cache-directory package-store-cache-dir)) 130 | (should 131 | (equal 132 | (package-store-url-cache-create-filename-package 133 | "http://marmalade-repo.org/package/eldoc-0.9.9.tar") 134 | (concat "/home/packagestore/.emacs.d" 135 | "/packagecache/http/org/marmalade-repo" 136 | "/package/eldoc/0.9.9.tar"))))) 137 | 138 | ;;;###autoload 139 | (defadvice package-download-tar (around 140 | package-store-do-cache-tar 141 | activate) 142 | "Turn on caching around tar downloads. 143 | 144 | Downloads are cached to `package-store-cache-dir'." 145 | ;; the normal api is (package-download-tar NAME VERSION) 146 | (if (and 147 | package-store-cache-dir 148 | (file-exists-p package-store-cache-dir) 149 | (file-directory-p package-store-cache-dir)) 150 | (let* ((url-automatic-caching t) 151 | (url-cache-directory package-store-cache-dir) 152 | (url-cache-creation-function 153 | 'package-store-url-cache-create-filename-package) 154 | (package-store-cache-package-name name) 155 | (package-store-cache-package-version version)) 156 | (if package-store-disconnected 157 | (flet ((url-retrieve-synchronously (url) 158 | ;; STUPID STUPID URL - this is just to fix the 159 | ;; bad cache handling 160 | (with-current-buffer (url-fetch-from-cache url) 161 | (goto-char (point-min)) 162 | (re-search-forward "\n\n" nil t) 163 | (setq url-http-end-of-headers (point)) 164 | (current-buffer)))) 165 | ad-do-it) 166 | ;; Else 167 | ad-do-it)) 168 | ad-do-it)) 169 | 170 | ;;;###autoload 171 | (defadvice package-download-single (around 172 | package-store-do-cache-file 173 | activate) 174 | "Turn on caching around tar downloads. 175 | 176 | Downloads are cached to `package-store-cache-dir'." 177 | ;; the normal API is (package-download-single NAME VERSION DESC REQUIRES) 178 | (if (and 179 | package-store-cache-dir 180 | (file-exists-p package-store-cache-dir) 181 | (file-directory-p package-store-cache-dir)) 182 | (let* ((url-automatic-caching t) 183 | (url-cache-directory package-store-cache-dir) 184 | (url-cache-creation-function 185 | 'package-store-url-cache-create-filename-package) 186 | (package-store-cache-package-name name) 187 | (package-store-cache-package-version version)) 188 | (if package-store-disconnected 189 | (flet ((url-retrieve-synchronously (url) 190 | ;; STUPID STUPID URL - this is just to fix the 191 | ;; bad cache handling 192 | (with-current-buffer (url-fetch-from-cache url) 193 | (goto-char (point-min)) 194 | (re-search-forward "\n\n" nil t) 195 | (setq url-http-end-of-headers (point)) 196 | (current-buffer)))) 197 | ad-do-it) 198 | ;; Else 199 | ad-do-it)) 200 | ad-do-it)) 201 | 202 | (defun map-regex (buffer regex fn) 203 | "Map the REGEX over the BUFFER executing FN. 204 | 205 | FN is called with the match-data of the regex. 206 | 207 | Returns the results of the FN as a list." 208 | (with-current-buffer buffer 209 | (save-excursion 210 | (goto-char (point-min)) 211 | (let (res) 212 | (save-match-data 213 | (while (re-search-forward regex nil t) 214 | (let ((f (match-data))) 215 | (setq res 216 | (append res 217 | (list 218 | (save-match-data 219 | (funcall fn f)))))))) 220 | res)))) 221 | 222 | ;;;###autoload 223 | (defun package-load-all (filename) 224 | "Load all the files in a package." 225 | (interactive "Gfile list:") 226 | (map-regex 227 | (find-file-noselect 228 | (cond 229 | ((and filename 230 | (file-exists-p filename) 231 | (file-directory-p filename)) 232 | (concat (file-name-as-directory filename) "build-parts.txt")) 233 | ((file-exists-p filename) 234 | filename))) 235 | "^\\(.*.el\\(\\.gz\\)*\\)$" 236 | (lambda (md) 237 | (let ((filename (match-string 0))) 238 | (when (file-exists-p filename) 239 | (load-file filename)))))) 240 | 241 | (provide 'package-store) 242 | 243 | ;;; package-store.el ends here 244 | --------------------------------------------------------------------------------