Add the GIMP source xcf for SXEmacs logo/banner
[sxemacs] / lisp / package-get.el
1 ;;; package-get.el --- Retrieve SXEmacs package
2
3 ;; Copyright (C) 1998 by Pete Ware
4 ;; Copyright (C) 2002 Ben Wing.
5 ;; Copyright (C) 2003 - 2012 Steve Youngs
6
7 ;; Author: Pete Ware <ware@cis.ohio-state.edu>
8 ;; Heavy-Modifications: Greg Klanderman <greg@alphatech.com>
9 ;;                      Jan Vroonhof    <vroonhof@math.ethz.ch>
10 ;;                      Steve Youngs    <steve@sxemacs.org>
11 ;; Keywords: internal
12
13 ;; This file is part of SXEmacs.
14
15 ;; SXEmacs is free software: you can redistribute it and/or modify
16 ;; it under the terms of the GNU General Public License as published by
17 ;; the Free Software Foundation, either version 3 of the License, or
18 ;; (at your option) any later version.
19
20 ;; SXEmacs is distributed in the hope that it will be useful,
21 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 ;; GNU General Public License for more details.
24
25 ;; You should have received a copy of the GNU General Public License
26 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
27
28 ;;; Synched up with: Not in FSF
29
30 ;;; Commentary:
31
32 ;; package-get -
33 ;;      Retrieve a package and any other required packages from an archive
34 ;;
35 ;;
36 ;; Note (JV): Most of this no longer applies!
37 ;; Note (SY): Definitely no longer applies, but I'm leaving these
38 ;;            comments here because there are some nifty ideas here.
39 ;;
40 ;; The idea:
41 ;;      A new XEmacs lisp-only release is generated with the following steps:
42 ;;      1. The maintainer runs some yet to be written program that
43 ;;         generates all the dependency information.  This should
44 ;;         determine all the require and provide statements and associate
45 ;;         them with a package.
46 ;;      2. All the packages are then bundled into their own tar balls
47 ;;         (or whatever format)
48 ;;      3. Maintainer automatically generates a new `package-get-base'
49 ;;         data structure which contains information such as the
50 ;;         package name, the file to be retrieved, an md5 checksum,
51 ;;         etc (see `package-get-base').
52 ;;      4. The maintainer posts an announcement with the new version
53 ;;         of `package-get-base'.
54 ;;      5. A user/system manager saves this posting and runs
55 ;;         `package-get-update' which uses the previously saved list
56 ;;         of packages, `package-get-here' that the user/site
57 ;;         wants to determine what new versions to download and
58 ;;         install.
59 ;;
60 ;;      A user/site manager can generate a new `package-get-here' structure
61 ;;      by using `package-get-setup' which generates a customize like
62 ;;      interface to the list of packages.  The buffer looks something
63 ;;      like:
64 ;;
65 ;;      gnus    - a mail and news reader
66 ;;      []      Always install
67 ;;      []      Needs updating
68 ;;      []      Required by other [packages]
69 ;;      version: 2.0
70 ;;
71 ;;      vm      - a mail reader
72 ;;      []      Always install
73 ;;      []      Needs updating
74 ;;      []      Required by other [packages]
75 ;;
76 ;;      Where `[]' indicates a toggle box
77 ;;
78 ;;      - Clicking on "Always install" puts this into
79 ;;        `package-get-here' list.  "Needs updating" indicates a new
80 ;;        version is available.  Anything already in
81 ;;        `package-get-here' has this enabled.
82 ;;      - "Required by other" means some other packages are going to force
83 ;;        this to be installed.  Clicking on  [packages] gives a list
84 ;;        of packages that require this.
85 ;;
86 ;;      The `package-get-base' should be installed in a file in
87 ;;      `data-directory'.  The `package-get-here' should be installed in
88 ;;      site-lisp.  Both are then read at run time.
89 ;;
90 ;; TODO:
91 ;;      - Implement `package-get-setup'
92 ;;      - Actually put `package-get-base' and `package-get-here' into
93 ;;        files that are read.
94 ;;      - SOMEONE needs to write the programs that generate the
95 ;;        provides/requires database and makes it into a lisp data
96 ;;        structure suitable for `package-get-base'
97 ;;      - Handle errors such as no package providing a required symbol.
98 ;;      - Tie this into the `require' function to download packages
99 ;;        transparently.
100
101 ;;; Change Log
102
103 ;;; Code:
104
105 (require 'package-admin)
106 ;; (require 'package-get-base)
107
108 (defgroup package-tools nil
109   "Tools to manipulate packages."
110   :group 'emacs)
111
112 (defgroup package-get nil
113   "Automatic Package Fetcher and Installer."
114   :prefix "package-get"
115   :group 'package-tools)
116
117 ;;;###autoload
118 (defvar package-get-base nil
119   "List of packages that are installed at this site.
120 For each element in the alist,  car is the package name and the cdr is
121 a plist containing information about the package.   Typical fields
122 kept in the plist are:
123
124 version         - version of this package
125 provides        - list of symbols provided
126 requires        - list of symbols that are required.
127                   These in turn are provided by other packages.
128 filename        - name of the file.
129 size            - size of the file (aka the bundled package)
130 md5sum          - computed md5 checksum
131 description     - What this package is for.
132 type            - Whether this is a 'binary (default) or 'single file package
133
134 More fields may be added as needed.  An example:
135
136 '(
137  (name
138   (version \"<version 2>\"
139    file \"filename\"
140    description \"what this package is about.\"
141    provides (<list>)
142    requires (<list>)
143    size <integer-bytes>
144    md5sum \"<checksum\"
145    type single
146    )
147   (version \"<version 1>\"
148    file \"filename\"
149    description \"what this package is about.\"
150    provides (<list>)
151    requires (<list>)
152    size <integer-bytes>
153    md5sum \"<checksum\"
154    type single
155    )
156    ...
157    ))
158
159 For version information, it is assumed things are listed in most
160 recent to least recent -- in other words, the version names don't have to
161 be lexically ordered.  It is debatable if it makes sense to have more than
162 one version of a package available.")
163
164 (defcustom package-get-dir (temp-directory)
165   "*Where to store temporary files for staging."
166   :tag "Temporary directory"
167   :type 'directory
168   :group 'package-get)
169
170 ;;;###autoload
171 (defcustom package-get-package-index-file-location
172   (car (split-path (or (getenv "EMACSPACKAGEPATH") user-init-directory)))
173   "*The directory where the package-index file can be found."
174   :type 'directory
175   :group 'package-get)
176
177 ;;;###autoload
178 (defcustom package-get-install-to-user-init-directory nil
179   "*If non-nil install packages under `user-init-directory'."
180   :type 'boolean
181   :group 'package-get)
182
183 (define-widget 'host-name 'string
184   "A Host name."
185   :tag "Host")
186
187 (defcustom package-get-remote nil
188   "*The remote site to contact for downloading packages.
189 Format is '(site-name directory-on-site).  As a special case, `site-name'
190 can be `nil', in which case `directory-on-site' is treated as a local
191 directory."
192   :tag "Package repository"
193   :type '(set (choice (const :tag "None" nil)
194                       (list :tag "Local" (const :tag "Local" nil) directory)
195                       (list :tag "Remote" host-name directory)))
196   :group 'package-get)
197
198 ;;;###autoload
199 (defcustom package-get-download-sites
200   '(
201     ;; Main XEmacs Site (ftp.xemacs.org)
202     ("US (Main XEmacs Site)"
203      "ftp.xemacs.org" "pub/xemacs/packages")
204     ;; In alphabetical order of Country, our mirrors...
205     ("Argentina (xmundo.net)" "xemacs.xmundo.net" "pub/mirrors/xemacs/packages")
206     ("Australia (aarnet.edu.au)" "mirror.aarnet.edu.au" "pub/xemacs/packages")
207     ("Australia (au.xemacs.org)" "ftp.au.xemacs.org" "pub/xemacs/packages")
208     ("Austria (at.xemacs.org)" "ftp.at.xemacs.org" "editors/xemacs/packages")
209     ("Belgium (be.xemacs.org)" "ftp.be.xemacs.org" "xemacs/packages")
210     ("Brazil (br.xemacs.org)" "ftp.br.xemacs.org" "pub/xemacs/packages")
211     ("Canada (ca.xemacs.org)" "ftp.ca.xemacs.org" "pub/Mirror/xemacs/packages")
212     ("Canada (nrc.ca)" "ftp.nrc.ca" "pub/packages/editors/xemacs/packages")
213     ;; no anonymous ftp available, uncomment when updating website
214     ;; with
215     ;; xemacs-builds/adrian/website/package-get-2-download-sites.el
216 ;     ("Chile (cl.xemacs.org)" "ftp.cl.xemacs.org" "packages")
217     ("China (ftp.cn.xemacs.org)" "ftp.cn.xemacs.org" "pub/xemacs/packages")
218     ("Czech Republic (cz.xemacs.org)" "ftp.cz.xemacs.org" "MIRRORS/ftp.xemacs.org/pub/xemacs/packages")
219     ("Denmark (dk.xemacs.org)" "ftp.dk.xemacs.org" "xemacs/packages")
220     ("Finland (fi.xemacs.org)" "ftp.fi.xemacs.org" "pub/mirrors/ftp.xemacs.org/pub/tux/xemacs/packages")
221     ("France (fr.xemacs.org)" "ftp.fr.xemacs.org" "pub/xemacs/packages")
222     ("France (mirror.cict.fr)" "mirror.cict.fr" "xemacs/packages")
223     ("France (pasteur.fr)" "ftp.pasteur.fr" "pub/computing/xemacs/packages")
224     ("Germany (de.xemacs.org)" "ftp.de.xemacs.org" "pub/ftp.xemacs.org/tux/xemacs/packages")
225     ("Greece (gr.xemacs.org)" "ftp.gr.xemacs.org" "mirrors/XEmacs/ftp/packages")
226     ("Hong Kong (hk.xemacs.org)" "ftp.hk.xemacs.org" "pub/xemacsftp/packages")
227     ("Ireland (ie.xemacs.org)" "ftp.ie.xemacs.org" "mirrors/ftp.xemacs.org/pub/xemacs/packages")
228     ("Ireland (heanet.ie)" "ftp.heanet.ie" "mirrors/ftp.xemacs.org/packages")
229     ("Italy (it.xemacs.org)" "ftp.it.xemacs.org" "unix/packages/XEMACS/packages")
230     ("Japan (dti.ad.jp)" "ftp.dti.ad.jp" "pub/unix/editor/xemacs/packages")
231 ;   ("Japan (jaist.ac.jp)" "ftp.jaist.ac.jp" "pub/GNU/xemacs/packages")
232     ("Japan (jp.xemacs.org)" "ftp.jp.xemacs.org" "pub/text/xemacs/packages")
233 ;   ("Japan (nucba.ac.jp)" "mirror.nucba.ac.jp" "mirror/xemacs/packages")
234     ("Korea (kr.xemacs.org)" "ftp.kr.xemacs.org" "pub/tools/emacs/xemacs/packages")
235     ("Netherlands (nl.xemacs.org)" "ftp.nl.xemacs.org" "pub/xemacs/ftp/packages")
236     ;; no anonymous ftp available, uncomment when updating website
237     ;; with
238     ;; xemacs-builds/adrian/website/package-get-2-download-sites.el
239 ;     ("Netherlands (xemacsftp.digimirror.nl)" "xemacsftp.digimirror.nl" "packages")
240     ("Norway (no.xemacs.org)" "ftp.no.xemacs.org" "pub/xemacs/packages")
241     ("Portugal (pt.xemacs.org)" "ftp.pt.xemacs.org" "pub/MIRRORS/ftp.xemacs.org/packages")
242     ("Russia (ru.xemacs.org)" "ftp.ru.xemacs.org" "pub/emacs/xemacs/packages")
243     ("Saudi Arabia (sa.xemacs.org)" "ftp.sa.xemacs.org" "pub/xemacs.org/packages")
244     ("Sweden (se.xemacs.org)" "ftp.se.xemacs.org" "pub/gnu/xemacs/packages")
245     ("Switzerland (ch.xemacs.org)" "ftp.ch.xemacs.org" "mirror/xemacs/packages")
246     ("Taiwan (ftp.tw.xemacs.org)" "ftp.tw.xemacs.org" "Unix/Editors/XEmacs/packages")
247     ("UK (uk.xemacs.org)" "ftp.uk.xemacs.org" "sites/ftp.xemacs.org/pub/xemacs/packages")
248     ("US (ibiblio.org)" "mirrors.ibiblio.org" "pub/mirrors/xemacs/packages")
249     ("US (us.xemacs.org)" "ftp.us.xemacs.org" "pub/mirrors/xemacs/packages")
250     )
251   "*List of remote sites available for downloading packages.
252 List format is '(site-description site-name directory-on-site).
253 SITE-DESCRIPTION is a textual description of the site.  SITE-NAME
254 is the internet address of the download site.  DIRECTORY-ON-SITE
255 is the directory on the site in which packages may be found.
256 This variable is used to initialize `package-get-remote', the
257 variable actually used to specify package download sites."
258   :tag "Package download sites"
259   :type '(repeat (list (string :tag "Name") host-name directory))
260   :group 'package-get)
261
262 ;;;###autoload
263 (defcustom package-get-pre-release-download-sites
264   '(
265     ;; Main XEmacs Site (ftp.xemacs.org)
266     ("US Pre-Releases (Main XEmacs Site)" "ftp.xemacs.org"
267      "pub/xemacs/beta/experimental/packages")
268     ;; In alphabetical order of Country, our mirrors...
269     ("Argentina Pre-Releases (xmundo.net)" "xemacs.xmundo.net"
270      "pub/mirrors/xemacs/beta/experimental/packages")
271     ("Australia Pre-Releases (aarnet.edu.au)" "mirror.aarnet.edu.au"
272      "pub/xemacs/beta/experimental/packages")
273     ("Australia Pre-Releases (au.xemacs.org)" "ftp.au.xemacs.org"
274      "pub/xemacs/beta/experimental/packages")
275     ("Austria Pre-Releases (at.xemacs.org)" "ftp.at.xemacs.org"
276      "editors/xemacs/beta/experimental/packages")
277     ("Belgium Pre-Releases (be.xemacs.org)" "ftp.be.xemacs.org"
278      "xemacs/beta/experimental/packages")
279     ("Brazil Pre-Releases (br.xemacs.org)" "ftp.br.xemacs.org"
280      "pub/xemacs/xemacs-21.5/experimental/packages")
281     ("Canada Pre-Releases (ca.xemacs.org)" "ftp.ca.xemacs.org"
282      "pub/Mirror/xemacs/beta/experimental/packages")
283     ("Canada Pre-Releases (nrc.ca)" "ftp.nrc.ca"
284      "pub/packages/editors/xemacs/beta/experimental/packages")
285     ;; no anonymous ftp available, uncomment when updating website
286     ;; with
287     ;; xemacs-builds/adrian/website/package-get-2-download-sites.el
288 ;     ("Chile Pre-Releases (cl.xemacs.org)" "ftp.cl.xemacs.org"
289 ;      "beta/experimental/packages")
290     ("China Pre-Releases (ftp.cn.xemacs.org)" "ftp.cn.xemacs.org"
291      "pub/xemacs/beta/experimental/packages")
292     ("Czech Republic Pre-Releases (cz.xemacs.org)" "ftp.cz.xemacs.org"
293      "MIRRORS/ftp.xemacs.org/pub/xemacs/xemacs-21.5/experimental/packages")
294     ("Denmark Pre-Releases (dk.xemacs.org)" "ftp.dk.xemacs.org"
295      "xemacs/beta/experimental/packages")
296     ("Finland Pre-Releases (fi.xemacs.org)" "ftp.fi.xemacs.org"
297      "pub/mirrors/ftp.xemacs.org/pub/tux/xemacs/beta/experimental/packages")
298     ("France Pre-Releases (fr.xemacs.org)" "ftp.fr.xemacs.org"
299      "pub/xemacs/beta/experimental/packages")
300     ("France Pre-Releases (mirror.cict.fr)" "mirror.cict.fr"
301      "xemacs/beta/experimental/packages")
302     ("France Pre-Releases (pasteur.fr)" "ftp.pasteur.fr"
303      "pub/computing/xemacs/beta/experimental/packages")
304     ("Germany Pre-Releases (de.xemacs.org)" "ftp.de.xemacs.org"
305      "pub/ftp.xemacs.org/tux/xemacs/beta/experimental/packages")
306     ("Greece Pre-Releases (gr.xemacs.org)" "ftp.gr.xemacs.org"
307      "mirrors/XEmacs/ftp/beta/experimental/packages")
308     ("Hong Kong Pre-Releases (hk.xemacs.org)" "ftp.hk.xemacs.org"
309      "pub/xemacsftp/beta/experimental/packages")
310     ("Ireland Pre-Releases (ie.xemacs.org)" "ftp.ie.xemacs.org"
311      "mirrors/ftp.xemacs.org/pub/xemacs/beta/experimental/packages")
312     ("Ireland Pre-Releases (heanet.ie)" "ftp.heanet.ie"
313      "mirrors/ftp.xemacs.org/beta/experimental/packages")
314     ("Italy Pre-Releases (it.xemacs.org)" "ftp.it.xemacs.org"
315      "unix/packages/XEMACS/beta/experimental/packages")
316     ("Japan Pre-Releases (dti.ad.jp)" "ftp.dti.ad.jp"
317      "pub/unix/editor/xemacs/beta/experimental/packages")
318 ;   ("Japan Pre-Releases (jaist.ac.jp)" "ftp.jaist.ac.jp"
319 ;    "pub/GNU/xemacs/beta/experimental/packages")
320     ("Japan Pre-Releases (jp.xemacs.org)" "ftp.jp.xemacs.org"
321      "pub/text/xemacs/beta/experimental/packages")
322     ("Korea Pre-Releases (kr.xemacs.org)" "ftp.kr.xemacs.org"
323      "pub/tools/emacs/xemacs/beta/experimental/packages")
324     ("Netherlands Pre-Releases (nl.xemacs.org)" "ftp.nl.xemacs.org"
325      "pub/xemacs/ftp/beta/experimental/packages")
326     ;; no anonymous ftp available, uncomment when updating website
327     ;; with
328     ;; xemacs-builds/adrian/website/package-get-2-download-sites.el
329 ;     ("Netherlands Pre-Releases (xemacsftp.digimirror.nl)" "xemacsftp.digimirror.nl"
330 ;      "beta/experimental/packages")
331     ("Norway Pre-Releases (no.xemacs.org)" "ftp.no.xemacs.org"
332      "pub/xemacs/beta/experimental/packages")
333     ("Portugal Pre-Releases (pt.xemacs.org)" "ftp.pt.xemacs.org"
334      "pub/MIRRORS/ftp.xemacs.org/beta/experimental/packages")
335     ("Russia Pre-Releases (ru.xemacs.org)" "ftp.ru.xemacs.org"
336      "pub/emacs/xemacs/beta/experimental/packages")
337     ("Saudi Arabia Pre-Releases (sa.xemacs.org)" "ftp.sa.xemacs.org"
338      "pub/xemacs.org/beta/experimental/packages")
339     ("Sweden Pre-Releases (se.xemacs.org)" "ftp.se.xemacs.org"
340      "pub/gnu/xemacs/beta/experimental/packages")
341     ("Switzerland Pre-Releases (ch.xemacs.org)" "ftp.ch.xemacs.org"
342      "mirror/xemacs/beta/experimental/packages")
343     ("Taiwan Pre-Releases (ftp.tw.xemacs.org)" "ftp.tw.xemacs.org"
344      "Unix/Editors/XEmacs/beta/experimental/packages")
345     ("UK Pre-Releases (uk.xemacs.org)" "ftp.uk.xemacs.org"
346      "sites/ftp.xemacs.org/pub/xemacs/beta/experimental/packages")
347     ("US Pre-Releases (ibiblio.org)" "mirrors.ibiblio.org"
348      "pub/mirrors/xemacs/beta/experimental/packages")
349     ("US Pre-Releases (us.xemacs.org)" "ftp.us.xemacs.org"
350      "pub/mirrors/xemacs/beta/experimental/packages")
351     )
352   "*List of remote sites available for downloading \"Pre-Release\" packages.
353 List format is '(site-description site-name directory-on-site).
354 SITE-DESCRIPTION is a textual description of the site.  SITE-NAME
355 is the internet address of the download site.  DIRECTORY-ON-SITE
356 is the directory on the site in which packages may be found.
357 This variable is used to initialize `package-get-remote', the
358 variable actually used to specify package download sites."
359   :tag "Pre-Release Package download sites"
360   :type '(repeat (list (string :tag "Name") host-name directory))
361   :group 'package-get)
362
363 ;;;###autoload
364 (defcustom package-get-site-release-download-sites
365   nil
366   "*List of remote sites available for downloading \"Site Release\" packages.
367 List format is '(site-description site-name directory-on-site).
368 SITE-DESCRIPTION is a textual description of the site.  SITE-NAME
369 is the internet address of the download site.  DIRECTORY-ON-SITE
370 is the directory on the site in which packages may be found.
371 This variable is used to initialize `package-get-remote', the
372 variable actually used to specify package download sites."
373   :tag "Site Release Package download sites"
374   :type '(repeat (list (string :tag "Name") host-name directory))
375   :group 'package-get)
376
377 (defcustom package-get-remove-copy t
378   "*After copying and installing a package, if this is t, then remove the
379 copy.  Otherwise, keep it around."
380   :type 'boolean
381   :group 'package-get)
382
383 ;; #### it may make sense for this to be a list of names.
384 ;; #### also, should we rename "*base*" to "*index*" or "*db*"?
385 ;;      "base" is a pretty poor name.
386 (defcustom package-get-base-filename "package-index.LATEST.gpg"
387   "*Name of the default package-get database file.
388 This may either be a relative path, in which case it is interpreted
389 with respect to `package-get-remote', or an absolute path."
390   :type 'file
391   :group 'package-get)
392
393 (defcustom package-get-always-update nil
394   "*If Non-nil always make sure we are using the latest package index (base).
395 Otherwise respect the `force-current' argument of `package-get-require-base'."
396   :type 'boolean
397   :group 'package-get)
398
399 (defvar package-get-was-current nil
400   "Non-nil we did our best to fetch a current database.")
401
402 ;;;###autoload
403 (defun package-get-require-base (&optional force-current)
404   "Require that a package-get database has been loaded.
405 If the optional FORCE-CURRENT argument or the value of
406 `package-get-always-update' is Non-nil, try to update the database
407 from a location in `package-get-remote'. Otherwise a local copy is used
408 if available and remote access is never done.
409
410 Please use FORCE-CURRENT only when the user is explictly dealing with packages
411 and remote access is likely in the near future."
412   (setq force-current (or force-current package-get-always-update))
413   (unless (and (boundp 'package-get-base)
414                package-get-base
415                (or (not force-current) package-get-was-current))
416     (package-get-update-base nil force-current))
417   (if (or (not (boundp 'package-get-base))
418           (not package-get-base))
419       (error 'void-variable
420              "Package-get database not loaded")
421     (setq package-get-was-current force-current)))
422
423 ;;;###autoload
424 (defun package-get-update-base-entry (entry)
425   "Update an entry in `package-get-base'."
426   (let ((existing (assq (car entry) package-get-base)))
427     (if existing
428         (setcdr existing (cdr entry))
429       (setq package-get-base (cons entry package-get-base)))))
430
431 (defun package-get-locate-file (file &optional nil-if-not-found no-remote)
432   "Locate an existing FILE with respect to `package-get-remote'.
433 If FILE is an absolute path or is not found, simply return FILE.
434 If optional argument NIL-IF-NOT-FOUND is non-nil, return nil
435 if FILE can not be located.
436 If NO-REMOTE is non-nil never search remote locations."
437   (if (file-name-absolute-p file)
438       file
439     (let ((site package-get-remote)
440           (expanded nil))
441       (when site
442         (unless (and no-remote (caar (list site)))
443           (let ((expn (package-get-remote-filename (car (list site)) file)))
444             (if (and expn (file-exists-p expn))
445                 (setq site nil
446                       expanded expn)))))
447       (or expanded
448           (and (not nil-if-not-found)
449                file)))))
450
451 (defun package-get-locate-index-file (no-remote)
452   "Locate the package-get index file.
453
454 Do not return remote paths if NO-REMOTE is non-nil.  If the index
455 file doesn't exist in `package-get-package-index-file-location', ask
456 the user if one should be created using the index file in core as a
457 template."
458   (or (package-get-locate-file package-get-base-filename t no-remote)
459       (if (file-exists-p (expand-file-name package-get-base-filename
460                                            package-get-package-index-file-location))
461           (expand-file-name package-get-base-filename
462                             package-get-package-index-file-location)
463         (if (y-or-n-p (format "No index file, shall I create one in %s? "
464                               package-get-package-index-file-location))
465             (progn
466               (save-excursion
467                 (set-buffer
468                  (find-file-noselect (expand-file-name
469                                       package-get-base-filename
470                                       package-get-package-index-file-location)))
471                 (let ((coding-system-for-write 'binary))
472                   (erase-buffer)
473                   (insert-file-contents-literally
474                    (locate-data-file package-get-base-filename))
475                   (save-buffer (current-buffer))
476                   (kill-buffer (current-buffer))))
477               (expand-file-name package-get-base-filename
478                                 package-get-package-index-file-location))
479           (error 'search-failed
480                  "Can't locate a package index file.")))))
481
482 (defun package-get-maybe-save-index (filename)
483   "Offer to save the current buffer as the local package index file,
484 if different."
485   (let ((location (package-get-locate-index-file t)))
486     (unless (and filename (equal filename location))
487       (unless (and location
488                    (equal (md5 (current-buffer))
489                           (with-temp-buffer
490                             (insert-file-contents-literally location)
491                             (md5 (current-buffer)))))
492         (when (not (file-writable-p location))
493           (if (y-or-n-p (format "Sorry, %s is read-only, can I use %s? "
494                                 location user-init-directory))
495               (setq location (expand-file-name
496                               package-get-base-filename
497                               package-get-package-index-file-location))
498             (error 'file-error
499                    (format "%s is read-only" location))))
500         (when (y-or-n-p (concat "Update package index in " location "? "))
501           (let ((coding-system-for-write 'binary))
502             (write-file location)))))))
503
504 ;;;###autoload
505 (defun package-get-update-base (&optional db-file force-current)
506   "Update the package-get database file with entries from DB-FILE.
507 Unless FORCE-CURRENT is non-nil never try to update the database."
508   (interactive
509    (let ((dflt (package-get-locate-index-file nil)))
510      (list (read-file-name "Load package-get database: "
511                            (file-name-directory dflt)
512                            dflt
513                            t
514                            (file-name-nondirectory dflt)))))
515   (setq db-file (expand-file-name (or db-file
516                                       (package-get-locate-index-file
517                                          (not force-current)))))
518   (if (not (file-exists-p db-file))
519       (error 'file-error
520              (format "Package-get database file `%s' does not exist" db-file)))
521   (if (not (file-readable-p db-file))
522       (error 'file-error
523              (format "Package-get database file `%s' not readable" db-file)))
524   (let ((buf (get-buffer-create "*package database*")))
525     (unwind-protect
526         (save-excursion
527           (set-buffer buf)
528           (erase-buffer buf)
529           (insert-file-contents-literally db-file)
530           (package-get-update-base-from-buffer buf)
531           (if (file-remote-p db-file)
532               (package-get-maybe-save-index db-file)))
533       (kill-buffer buf))))
534
535 ;; This is here because the `process-error' datum doesn't exist in
536 ;; 21.4. --SY.
537 (define-error 'process-error "Process error")
538
539 ;;;###autoload
540 (defun package-get-update-base-from-buffer (&optional buf)
541   "Update the package-get database with entries from BUFFER.
542 BUFFER defaults to the current buffer.  This command can be
543 used interactively, for example from a mail or news buffer."
544   (interactive)
545   (setq buf (or buf (current-buffer)))
546   (let ((coding-system-for-read 'binary)
547         (coding-system-for-write 'binary)
548         content-beg content-end)
549     (save-excursion
550       (set-buffer buf)
551       (goto-char (point-min))
552       (setq content-beg (point))
553       (setq content-end (save-excursion (goto-char (point-max)) (point)))
554       (package-get-update-base-entries content-beg content-end)
555       (message "Updated package database"))))
556
557 (defun package-get-update-base-entries (start end)
558   "Update the package-get database with the entries found between
559 START and END in the current buffer."
560   (save-excursion
561     (goto-char start)
562     (if (not (re-search-forward "^(package-get-update-base-entry" nil t))
563         (error 'search-failed
564                "Buffer does not contain package-get database entries"))
565     (beginning-of-line)
566     (let ((count 0))
567       (while (and (< (point) end)
568                   (re-search-forward "^(package-get-update-base-entry" nil t))
569         (beginning-of-line)
570         (let ((entry (read (current-buffer))))
571           (if (or (not (consp entry))
572                   (not (eq (car entry) 'package-get-update-base-entry)))
573               (error 'syntax-error
574                      "Invalid package-get database entry found"))
575           (package-get-update-base-entry
576            (car (cdr (car (cdr entry)))))
577           (setq count (1+ count))))
578       (message "Got %d package-get database entries" count))))
579
580 ;;;###autoload
581 (defun package-get-save-base (file)
582   "Write the package-get database to FILE.
583
584 Note: This database will be unsigned of course."
585   (interactive "FSave package-get database to: ")
586   (package-get-require-base t)
587   (let ((buf (get-buffer-create "*package database*")))
588     (unwind-protect
589         (save-excursion
590           (set-buffer buf)
591           (erase-buffer buf)
592           (goto-char (point-min))
593           (let ((entries package-get-base) entry plist)
594             (insert ";; Package Index file -- Do not edit manually.\n")
595             (insert ";;;@@@\n")
596             (while entries
597               (setq entry (car entries))
598               (setq plist (car (cdr entry)))
599               (insert "(package-get-update-base-entry (quote\n")
600               (insert (format "(%s\n" (symbol-name (car entry))))
601               (while plist
602                 (insert (format "  %s%s %S\n"
603                                 (if (eq plist (car (cdr entry))) "(" " ")
604                                 (symbol-name (car plist))
605                                 (car (cdr plist))))
606                 (setq plist (cdr (cdr plist))))
607               (insert "))\n))\n;;;@@@\n")
608               (setq entries (cdr entries))))
609           (insert ";; Package Index file ends here\n")
610           (write-region (point-min) (point-max) file))
611       (kill-buffer buf))))
612
613 (defun package-get-interactive-package-query (get-version package-symbol)
614   "Perform interactive querying for package and optional version.
615 Query for a version if GET-VERSION is non-nil.  Return package name as
616 a symbol instead of a string if PACKAGE-SYMBOL is non-nil.
617 The return value is suitable for direct passing to `interactive'."
618   (package-get-require-base t)
619   (let ((table (mapcar #'(lambda (item)
620                            (let ((name (symbol-name (car item))))
621                              (cons name name)))
622                        package-get-base))
623         package package-symbol default-version version)
624     (save-window-excursion
625       (setq package (completing-read "Package: " table nil t))
626       (setq package-symbol (intern package))
627       (if get-version
628           (progn
629             (setq default-version
630                   (package-get-info-prop
631                    (package-get-info-version
632                     (package-get-info-find-package package-get-base
633                                                    package-symbol) nil)
634                    'version))
635             (while (string=
636                     (setq version (read-string "Version: " default-version))
637                     ""))
638             (if package-symbol
639                 (list package-symbol version)
640               (list package version)))
641         (if package-symbol
642             (list package-symbol)
643           (list package))))))
644
645 ;;;###autoload
646 (defun package-get-delete-package (package &optional pkg-topdir)
647   "Delete an installation of PACKAGE below directory PKG-TOPDIR.
648 PACKAGE is a symbol, not a string.
649 This is just an interactive wrapper for `package-admin-delete-binary-package'."
650   (interactive (package-get-interactive-package-query nil t))
651   (package-admin-delete-binary-package package pkg-topdir))
652
653 ;;;###autoload
654 (defun package-get-update-all ()
655   "Fetch and install the latest versions of all currently installed packages."
656   (interactive)
657   (package-get-require-base t)
658   ;; Load a fresh copy
659   (catch 'exit
660     (mapcar (lambda (pkg)
661               (if (not (package-get (car pkg) nil 'never))
662                   (throw 'exit nil)))           ;; Bail out if error detected
663             packages-package-list)))
664
665 ;;;###autoload
666 (defun package-get-all (package version &optional fetched-packages install-dir)
667   "Fetch PACKAGE with VERSION and all other required packages.
668 Uses `package-get-base' to determine just what is required and what
669 package provides that functionality.  If VERSION is nil, retrieves
670 latest version.  Optional argument FETCHED-PACKAGES is used to keep
671 track of packages already fetched.  Optional argument INSTALL-DIR,
672 if non-nil, specifies the package directory where fetched packages
673 should be installed.
674
675 Returns nil upon error."
676   (interactive (package-get-interactive-package-query t nil))
677   (let* ((the-package (package-get-info-find-package package-get-base
678                                                      package))
679          (this-package (package-get-info-version
680                         the-package version))
681          (this-requires (package-get-info-prop this-package 'requires)))
682     (catch 'exit
683       (setq version (package-get-info-prop this-package 'version))
684       (unless (package-get-installedp package version)
685         (if (not (package-get package version nil install-dir))
686             (progn
687               (setq fetched-packages nil)
688               (throw 'exit nil))))
689       (setq fetched-packages
690             (append (list package)
691                     (package-get-info-prop this-package 'provides)
692                     fetched-packages))
693       ;; grab everything that this package requires plus recursively
694       ;; grab everything that the requires require.  Keep track
695       ;; in `fetched-packages' the list of things provided -- this
696       ;; keeps us from going into a loop
697       (while this-requires
698         (if (not (member (car this-requires) fetched-packages))
699             (let* ((reqd-package (package-get-package-provider
700                                   (car this-requires) t))
701                    (reqd-version (cadr reqd-package))
702                    (reqd-name (car reqd-package)))
703               (if (null reqd-name)
704                   (error 'search-failed
705                          (format "Unable to find a provider for %s"
706                                  (car this-requires))))
707               (if (not (setq fetched-packages
708                              (package-get-all reqd-name reqd-version
709                                               fetched-packages
710                                               install-dir)))
711                   (throw 'exit nil))))
712         (setq this-requires (cdr this-requires))))
713     fetched-packages))
714
715 ;;;###autoload
716 (defun package-get-dependencies (packages)
717   "Compute dependencies for PACKAGES.
718 Uses `package-get-base' to determine just what is required and what
719 package provides that functionality.  Returns the list of packages
720 required by PACKAGES."
721   (package-get-require-base t)
722   (let ((orig-packages packages)
723         dependencies provided)
724     (while packages
725       (let* ((package (car packages))
726              (the-package (package-get-info-find-package
727                            package-get-base package))
728              (this-package (package-get-info-version
729                             the-package nil))
730              (this-requires (package-get-info-prop this-package 'requires))
731              (new-depends   (set-difference
732                              (mapcar
733                               #'(lambda (reqd)
734                                   (let* ((reqd-package (package-get-package-provider reqd))
735                                          (reqd-name    (car reqd-package)))
736                                     (if (null reqd-name)
737                                         (error 'search-failed
738                                                (format "Unable to find a provider for %s" reqd)))
739                                     reqd-name))
740                               this-requires)
741                              dependencies))
742              (this-provides (package-get-info-prop this-package 'provides)))
743         (setq dependencies
744               (union dependencies new-depends))
745         (setq provided
746               (union provided (union (list package) this-provides)))
747         (setq packages
748               (union new-depends (cdr packages)))))
749     (set-difference dependencies orig-packages)))
750
751 (defun package-get-load-package-file (lispdir file)
752   (let (pathname)
753     (setq pathname (expand-file-name file lispdir))
754     (condition-case err
755         (progn
756           (load pathname t)
757           t)
758       (t
759        (message "Error loading package file \"%s\" %s!" pathname err)
760        nil))
761     ))
762
763 (defun package-get-init-package (lispdir)
764   "Initialize the package.
765 This really assumes that the package has never been loaded.  Updating
766 a newer package can cause problems, due to old, obsolete functions in
767 the old package.
768
769 Return `t' upon complete success, `nil' if any errors occurred."
770   (progn
771     (if (and lispdir
772              (file-accessible-directory-p lispdir))
773         (progn
774           ;; Add lispdir to load-path if it doesn't already exist.
775           ;; NOTE: this does not take symlinks, etc., into account.
776           (if (let ((dirs load-path))
777                 (catch 'done
778                   (while dirs
779                     (if (string-equal (car dirs) lispdir)
780                         (throw 'done nil))
781                     (setq dirs (cdr dirs)))
782                   t))
783               (setq load-path (cons lispdir load-path)))
784           (if (not (package-get-load-package-file lispdir "auto-autoloads"))
785               (package-get-load-package-file lispdir "_pkg"))
786           t)
787       nil)))
788
789 ;;;###autoload
790 (defun package-get-info (package information &optional arg remote)
791   "Get information about a package.
792
793 Quite similar to `package-get-info-prop', but can retrieve a lot more
794 information.
795
796 Argument PACKAGE is the name of an XEmacs package (a symbol).  It must
797 be a valid package, ie, a member of `package-get-base'.
798
799 Argument INFORMATION is a symbol that can be any one of:
800
801    standards-version     Package system version (not used).
802    version               Version of the XEmacs package.
803    author-version        The upstream version of the package.
804    date                  The date the package was last modified.
805    build-date            The date the package was last built.
806    maintainer            The maintainer of the package.
807    distribution          Will always be \"xemacs\" (not used).
808    priority              \"low\", \"medium\", or \"high\" (not used).
809    category              Either \"standard\", \"mule\", or \"unsupported\"..
810    dump                  Is the package dumped (not used).
811    description           A description of the package.
812    filename              The filename of the binary tarball of the package.
813    md5sum                The md5sum of filename.
814    size                  The size in bytes of filename.
815    provides              A list of symbols that this package provides.
816    requires              A list of packages that this package requires.
817    type                  Can be either \"regular\" or \"single-file\".
818
819 If optional argument ARG is non-nil insert INFORMATION into current
820 buffer at point.  This is very useful for doing things like inserting
821 a maintainer's email address into a mail buffer.
822
823 If optional argument REMOTE is non-nil use a package list from a
824 remote site.  For this to work `package-get-remote' must be non-nil.
825
826 If this function is called interactively it will display INFORMATION
827 in the minibuffer."
828   (interactive "SPackage: \nSInfo: \nP")
829     (if remote
830         (package-get-require-base t)
831       (package-get-require-base nil))
832     (let ((all-pkgs package-get-base)
833           info)
834       (loop until (equal package (caar all-pkgs))
835         do (setq all-pkgs (cdr all-pkgs))
836         do (if (not all-pkgs)
837                (error 'invalid-argument
838                       (format "%s is not a valid package" package))))
839       (setq info (plist-get (cadar all-pkgs) information))
840       (if (interactive-p)
841           (if arg
842               (insert (format "%s" info))
843             (if (package-get-key package :version)
844                 (message "%s" info)
845               (message "%s (Package: %s is not installed)" info package)))
846         (if arg
847             (insert (format "%s" info))
848           info))))
849
850 ;;;###autoload
851 (defun package-get-list-packages-where (item field &optional arg)
852   "Return a list of packages that fulfill certain criteria.
853
854 Argument ITEM, a symbol, is what you want to check for.  ITEM must be a
855 symbol even when it doesn't make sense to be a symbol \(think, searching
856 maintainers, descriptions, etc\).  The function will convert the symbol
857 to a string if a string is what is needed.  The downside to this is that
858 ITEM can only ever be a single word.
859
860 Argument FIELD, a symbol, is the field to check in.  You can specify
861 any one of:
862
863       Field            Sane or Allowable Content
864     description          any single word
865     category             `standard' or `mule'
866     maintainer           any single word
867     build-date           yyyy-mm-dd
868     date                 yyyy-mm-dd
869     type                 `regular' or `single'
870     requires             any package name
871     provides             any symbol
872     priority             `low', `medium', or `high'
873
874 Optional Argument ARG, a prefix arg, insert output at point in the
875 current buffer."
876   (interactive "SList packages that have (item): \nSin their (field): \nP")
877   (package-get-require-base nil)
878   (let ((pkgs package-get-base)
879         (strings '(description category maintainer build-date date))
880         (symbols '(type requires provides priority))
881         results)
882     (cond ((memq field strings)
883            (setq item (symbol-name item))
884            (while pkgs
885              (when (string-match item (package-get-info (caar pkgs) field))
886                (setq results (push (caar pkgs) results)))
887              (setq pkgs (cdr pkgs))))
888           ((memq field symbols)
889            (if (or (eq field 'type)
890                    (eq field 'priority))
891                (while pkgs
892                  (when (eq item (package-get-info (caar pkgs) field))
893                    (setq results (push (caar pkgs) results)))
894                  (setq pkgs (cdr pkgs)))
895              (while pkgs
896                (when (memq item (package-get-info (caar pkgs) field))
897                  (setq results (push (caar pkgs) results)))
898                (setq pkgs (cdr pkgs)))))
899           (t
900            (error 'wrong-type-argument field)))
901     (if (interactive-p)
902         (if arg
903             (insert (format "%s" results))
904           (message "%s" results)))
905     results))
906
907 ;;;###autoload
908 (defun package-get (package &optional version conflict install-dir)
909   "Fetch PACKAGE from remote site.
910 Optional arguments VERSION indicates which version to retrieve, nil
911 means most recent version.  CONFLICT indicates what happens if the
912 package is already installed.  Valid values for CONFLICT are:
913 'always always retrieve the package even if it is already installed
914 'never  do not retrieve the package if it is installed.
915 INSTALL-DIR, if non-nil, specifies the package directory where
916 fetched packages should be installed.
917
918 The value of `package-get-base' is used to determine what files should
919 be retrieved.  The value of `package-get-remote' is used to determine
920 where a package should be retrieved from.
921
922 Once the package is retrieved, its md5 checksum is computed.  If that
923 sum does not match that stored in `package-get-base' for this version
924 of the package, an error is signalled.
925
926 Returns `t' upon success, the symbol `error' if the package was
927 successfully installed but errors occurred during initialization, or
928 `nil' upon error."
929   (interactive (package-get-interactive-package-query nil t))
930   (catch 'skip-update
931   (let* ((this-package
932           (package-get-info-version
933            (package-get-info-find-package package-get-base
934                                           package) version))
935          (latest (package-get-info-prop this-package 'version))
936          (installed (package-get-key package :version))
937          (found nil)
938          (host nil)
939          (search-dir package-get-remote)
940          (base-filename (package-get-info-prop this-package 'filename))
941          (package-status t)
942          filenames full-package-filename)
943     (if (and (equal (package-get-info package 'category) "mule")
944              (not (featurep 'mule)))
945         (error 'invalid-state
946                "Mule packages can't be installed with a non-Mule SXEmacs"))
947     (if (null this-package)
948         (if package-get-remote
949             (error 'search-failed
950                    (format "Couldn't find package %s with version %s"
951                            package version))
952           (error 'syntax-error
953                  "No download site or local package location specified.")))
954     (if (null base-filename)
955         (error 'syntax-error
956                (format "No filename associated with package %s, version %s"
957                        package version)))
958     (setq install-dir (package-admin-get-install-dir package install-dir))
959
960     ;; If they asked for the latest using version=nil, don't get an older
961     ;; version than we already have.
962     (if installed
963         (if (> (if (stringp installed)
964                    (string-to-number installed)
965                  installed)
966                (if (stringp latest)
967                    (string-to-number latest)
968                  latest))
969             (if (not (null version))
970                 (warn "Installing %s package version %s, you had a newer version %s"
971                   package latest installed)
972               (warn "Skipping %s package, you have a newer version %s"
973                 package installed)
974               (throw 'skip-update t))))
975
976     ;; Contrive a list of possible package filenames.
977     ;; Ugly.  Is there a better way to do this?
978     (setq filenames (cons base-filename nil))
979     (if (string-match #r"^\(..*\)\.tar\.gz$" base-filename)
980         (setq filenames (append filenames
981                                 (list (concat (match-string 1 base-filename)
982                                               ".tgz")))))
983
984     (setq version latest)
985     (unless (and (eq conflict 'never)
986                  (package-get-installedp package version))
987       ;; Find the package from the search list in package-get-remote
988       ;; and copy it into the staging directory.  Then validate
989       ;; the checksum.  Finally, install the package.
990       (catch 'done
991         (let (search-filenames dir current-filename dest-filename)
992           ;; In each search directory ...
993           (when search-dir
994             (setq host (car search-dir)
995                   dir (car (cdr search-dir))
996                   search-filenames filenames)
997
998             ;; Look for one of the possible package filenames ...
999             (while search-filenames
1000               (setq current-filename (car search-filenames)
1001                     dest-filename (package-get-staging-dir current-filename))
1002               (cond
1003                ;; No host means look on the current system.
1004                ((null host)
1005                 (setq full-package-filename
1006                       (substitute-in-file-name
1007                        (expand-file-name current-filename
1008                                          (file-name-as-directory dir)))))
1009
1010                ;; If it's already on the disk locally, and the size is
1011                ;; correct
1012                ((and (file-exists-p dest-filename)
1013                      (eq (nth 7 (file-attributes dest-filename))
1014                          (package-get-info package 'size)))
1015                  (setq full-package-filename dest-filename))
1016
1017                ;; If the file exists on the remote system ...
1018                ((file-exists-p (package-get-remote-filename
1019                                 search-dir current-filename))
1020                 ;; Get it
1021                 (setq full-package-filename dest-filename)
1022                 (message "Retrieving package `%s' ..."
1023                          current-filename)
1024                 (sit-for 0)
1025                 (copy-file (package-get-remote-filename search-dir
1026                                                         current-filename)
1027                            full-package-filename t)))
1028
1029               ;; If we found it, we're done.
1030               (if (and full-package-filename
1031                        (file-exists-p full-package-filename))
1032                   (throw 'done nil))
1033               ;; Didn't find it.  Try the next possible filename.
1034               (setq search-filenames (cdr search-filenames))))))
1035
1036       (if (or (not full-package-filename)
1037               (not (file-exists-p full-package-filename)))
1038           (if package-get-remote
1039               (error 'search-failed
1040                      (format "Unable to find file %s" base-filename))
1041             (error 'syntax-error
1042                    "No download sites or local package locations specified.")))
1043       ;; Validate the md5 checksum
1044       ;; Doing it with SXEmacs removes the need for an external md5 program
1045       (message "Validating checksum for `%s'..." package) (sit-for 0)
1046       (with-temp-buffer
1047         (insert-file-contents-literally full-package-filename)
1048         (if (not (string= (md5 (current-buffer))
1049                           (package-get-info-prop this-package
1050                                                  'md5sum)))
1051             (progn
1052               (unless (null host)
1053                 (delete-file full-package-filename))
1054               (error 'process-error
1055                      (format "Package %s does not match md5 checksum %s has been deleted"
1056                              base-filename full-package-filename)))))
1057
1058       (package-admin-delete-binary-package package install-dir)
1059
1060       (message "Installing package `%s' ..." package) (sit-for 0)
1061       (let ((status
1062              (package-admin-add-binary-package full-package-filename
1063                                                install-dir)))
1064         (if (= status 0)
1065             (progn
1066               ;; clear messages so that only messages from
1067               ;; package-get-init-package are seen, below.
1068               (clear-message)
1069               (if (package-get-init-package (package-admin-get-lispdir
1070                                              install-dir package))
1071                   (progn
1072                     (run-hook-with-args 'package-install-hook package install-dir)
1073                     (message "Added package `%s'" package)
1074                     (sit-for 0))
1075                 (progn
1076                   ;; display message only if there isn't already one.
1077                   (if (not (current-message))
1078                       (progn
1079                         (message "Added package `%s' (errors occurred)"
1080                                  package)
1081                         (sit-for 0)))
1082                   (if package-status
1083                       (setq package-status 'errors)))))
1084           (message "Installation of package %s failed." base-filename)
1085           (sit-for 0)
1086           (switch-to-buffer package-admin-temp-buffer)
1087           ;; null host means a local package mirror
1088           (unless (null host)
1089             (delete-file full-package-filename))
1090           (setq package-status nil)))
1091       (setq found t))
1092     (if (and found package-get-remove-copy (not (null host)))
1093         (delete-file full-package-filename))
1094     package-status)))
1095
1096 (defun package-get-info-find-package (which name)
1097   "Look in WHICH for the package called NAME and return all the info
1098 associated with it.  See `package-get-base' for info on the format
1099 returned.
1100
1101  To access fields returned from this, use
1102 `package-get-info-version' to return information about particular a
1103 version.  Use `package-get-info-find-prop' to find particular property
1104 from a version returned by `package-get-info-version'."
1105   (interactive "xPackage list: \nsPackage Name: ")
1106   (if which
1107       (if (eq (caar which) name)
1108           (cdar which)
1109         (if (cdr which)
1110             (package-get-info-find-package (cdr which) name)))))
1111
1112 (defun package-get-info-version (package version)
1113   "In PACKAGE, return the plist associated with a particular VERSION of the
1114   package.  PACKAGE is typically as returned by
1115   `package-get-info-find-package'.  If VERSION is nil, then return the
1116   first (aka most recent) version.  Use `package-get-info-find-prop'
1117   to retrieve a particular property from the value returned by this."
1118   (interactive (package-get-interactive-package-query t t))
1119   (while (and version package (not (string= (plist-get (car package) 'version) version)))
1120     (setq package (cdr package)))
1121   (if package (car package)))
1122
1123 (defun package-get-info-prop (package-version property)
1124   "In PACKAGE-VERSION, return the value associated with PROPERTY.
1125 PACKAGE-VERSION is typically returned by `package-get-info-version'
1126 and PROPERTY is typically (although not limited to) one of the
1127 following:
1128
1129 version         - version of this package
1130 provides                - list of symbols provided
1131 requires                - list of symbols that are required.
1132                   These in turn are provided by other packages.
1133 size            - size of the bundled package
1134 md5sum          - computed md5 checksum"
1135   (interactive "xPackage Version: \nSProperty")
1136   (plist-get package-version property))
1137
1138 (defun package-get-info-version-prop (package-list package version property)
1139   "In PACKAGE-LIST, search for PACKAGE with this VERSION and return
1140   PROPERTY value."
1141   (package-get-info-prop
1142    (package-get-info-version
1143     (package-get-info-find-package package-list package) version) property))
1144
1145 (defun package-get-staging-dir (filename)
1146   "Return a good place to stash FILENAME when it is retrieved.
1147 Use `package-get-dir' for directory to store stuff.
1148 Creates `package-get-dir'  if it doesn't exist."
1149   (interactive "FPackage filename: ")
1150   (if (not (file-exists-p package-get-dir))
1151       (make-directory package-get-dir))
1152   (expand-file-name
1153    (file-name-nondirectory (or (and-fboundp 'efs-ftp-path
1154                                  (nth 2 (efs-ftp-path filename)))
1155                                filename))
1156    (file-name-as-directory package-get-dir)))
1157
1158 (defun package-get-remote-filename (search filename)
1159   "Return FILENAME as a remote filename.
1160 It first checks if FILENAME already is a remote filename.  If it is
1161 not, then it uses the (car search) as the remote site-name and the (cadr
1162 search) as the remote-directory and concatenates filename.  In other
1163 words
1164         site-name:remote-directory/filename.
1165
1166 If (car search) is nil, (cadr search is interpreted as  a local directory).
1167 "
1168   (if (file-remote-p filename)
1169       filename
1170     (let ((dir (cadr search)))
1171       (concat (when (car search)
1172                 (concat
1173                  (if (string-match "@" (car search))
1174                      "/"
1175                    "/anonymous@")
1176                  (car search) ":"))
1177               (if (string-match "/$" dir)
1178                   dir
1179                 (concat dir "/"))
1180               filename))))
1181
1182 (defun package-get-installedp (package version)
1183   "Determine if PACKAGE with VERSION has already been installed.
1184 I'm not sure if I want to do this by searching directories or checking
1185 some built in variables.  For now, use packages-package-list."
1186   ;; Use packages-package-list which contains name and version
1187   (equal (plist-get
1188           (package-get-info-find-package packages-package-list
1189                                          package) ':version)
1190          (if (floatp version)
1191              version
1192            (string-to-number version))))
1193
1194 ;;;###autoload
1195 (defun package-get-package-provider (sym &optional force-current)
1196   "Search for a package that provides SYM and return the name and
1197   version.  Searches in `package-get-base' for SYM.   If SYM is a
1198   consp, then it must match a corresponding (provide (SYM VERSION)) from
1199   the package.
1200
1201 If FORCE-CURRENT is non-nil make sure the database is up to date. This might
1202 lead to Emacs accessing remote sites."
1203   (interactive "SSymbol: ")
1204   (package-get-require-base force-current)
1205   (let ((packages package-get-base)
1206         (done nil)
1207         (found nil))
1208     (while (and (not done) packages)
1209       (let* ((this-name (caar packages))
1210              (this-package (cdr (car packages)))) ;strip off package name
1211         (while (and (not done) this-package)
1212           (if (or (eq this-name sym)
1213                   (eq (cons this-name
1214                           (package-get-info-prop (car this-package) 'version))
1215                       sym)
1216                   (member sym
1217                         (package-get-info-prop (car this-package) 'provides)))
1218               (progn (setq done t)
1219                      (setq found
1220                        (list (caar packages)
1221                          (package-get-info-prop (car this-package) 'version))))
1222             (setq this-package (cdr this-package)))))
1223       (setq packages (cdr packages)))
1224     (when (interactive-p)
1225       (if found
1226           (message "%S" found)
1227         (message "No appropriate package found")))
1228     found))
1229
1230 (defun package-get-ever-installed-p (pkg &optional notused)
1231   (string-match "-package$" (symbol-name pkg))
1232   (custom-initialize-set
1233    pkg
1234    (if (package-get-info-find-package
1235         packages-package-list
1236         (intern (substring (symbol-name pkg) 0 (match-beginning 0))))
1237        t)))
1238
1239 ;;; FIXME: see comment at end of `pui-bootstrap'
1240
1241 ;;;###autoload
1242 (defun pui-bootstrap ()
1243   "Bootstrap the SXEmacs Package Tools.
1244
1245 The Package Tools, under normal circumstances, cannot work until a
1246 couple of packages are pre-installed by hand.  This function eliminates
1247 the need to do that.  It uses FFI and libcurl to download and install
1248 the lastest package index file, the EFS and xemacs-base packages.
1249
1250 Obviously you can't use this if you didn't enable FFI support in your
1251 SXEmacs or if you don't have libffi on your system.
1252
1253 This isn't designed to replace the existing Package Tools so after
1254 you have run `pui-bootstrap' once you should then use the normal PUI
1255 tools, `pui-list-packages' etc."
1256   (interactive)
1257   ;; A little sanity checking never hurt anybody
1258   (when (featurep '(and efs-autoloads xemacs-base-autoloads))
1259     (error 'invalid-operation "PUI doesn't need bootstrapping"))
1260   (when (and (fboundp 'ffi-defun)
1261              (not (featurep '(and ffi ffi-curl))))
1262     (require 'ffi-curl))
1263   (unless (featurep 'ffi)
1264     (error 'unimplemented "FFI"))
1265   ;; One last check... has `package-get-remote' been set?
1266   (if (not (cdr package-get-remote))
1267       (when (y-or-n-p "You haven't set a download site, do you need help ")
1268         (declare-fboundp (Info-goto-node "(sxemacs)Bootstrapping PUI")))
1269     ;; We should be good to go
1270     (let* ((site (car package-get-remote))
1271            (dir (cadr package-get-remote))
1272            (url (concat "ftp://" site "/" dir "/"))
1273            (dldir (temp-directory))
1274            (index (expand-file-name package-get-base-filename
1275                                     package-get-package-index-file-location))
1276            xemacs-base-pkg
1277            efs-pkg
1278                                         ;status)
1279            )
1280       ;; Grab the index
1281       (message "Retrieving index, please be patient")
1282       (declare-fboundp (curl:download (concat url package-get-base-filename) index))
1283       (message "Retrieving index, done!")
1284       ;; Update the db
1285       (set-buffer (find-file-noselect index))
1286       (package-get-update-base-from-buffer)
1287       (kill-buffer (current-buffer))
1288       ;; Get xemacs-base, EFS
1289       (setq xemacs-base-pkg (package-get-info 'xemacs-base 'filename))
1290       (setq efs-pkg (package-get-info 'efs 'filename))
1291       (message "Retrieving %s, please be patient" xemacs-base-pkg)
1292       (declare-fboundp (curl:download (concat url xemacs-base-pkg)
1293                                       (expand-file-name xemacs-base-pkg dldir)))
1294       (message "Retrieving %s, please be patient" efs-pkg)
1295       (declare-fboundp (curl:download (concat url efs-pkg)
1296                                       (expand-file-name efs-pkg dldir)))
1297       (message "Download complete.")
1298       ;; Install xemacs-base
1299       (if (equal (package-get-info 'xemacs-base 'md5sum)
1300                  (with-temp-buffer
1301                    (insert-file-contents-literally
1302                     (expand-file-name xemacs-base-pkg dldir))
1303                    (md5 (current-buffer))))
1304           (progn
1305             (package-admin-add-binary-package
1306              (expand-file-name xemacs-base-pkg dldir)
1307              (package-admin-get-install-dir 'xemacs-base))
1308             (push (file-name-as-directory
1309                    (expand-file-name "lisp/xemacs-base"
1310                                      (package-admin-get-install-dir 'xemacs-base)))
1311                   load-path)
1312             (load-file (expand-file-name "lisp/xemacs-base/_pkg.el"
1313                                          (package-admin-get-install-dir 'xemacs-base)))
1314             (load-file (expand-file-name "lisp/xemacs-base/auto-autoloads.el"
1315                                          (package-admin-get-install-dir 'xemacs-base)))
1316             (message "xemacs-base package installed"))
1317         (delete-file (expand-file-name xemacs-base-pkg dldir))
1318         (error "MD5 mismatch, %s deleted" (expand-file-name xemacs-base-pkg dldir)))
1319       ;; Install EFS
1320       (if (equal (package-get-info 'efs 'md5sum)
1321                  (with-temp-buffer
1322                    (insert-file-contents-literally
1323                     (expand-file-name efs-pkg dldir))
1324                    (md5 (current-buffer))))
1325           (progn
1326             (package-admin-add-binary-package
1327              (expand-file-name efs-pkg dldir)
1328              (package-admin-get-install-dir 'efs))
1329             (push (file-name-as-directory
1330                    (expand-file-name "lisp/efs"
1331                                      (package-admin-get-install-dir 'efs)))
1332                   load-path)
1333             (load-file (expand-file-name "lisp/efs/_pkg.el"
1334                                          (package-admin-get-install-dir 'efs)))
1335             (load-file (expand-file-name "lisp/efs/auto-autoloads.el"
1336                                          (package-admin-get-install-dir 'efs)))
1337             (message "efs package installed"))
1338         (delete-file (expand-file-name efs-pkg dldir))
1339         (error "MD5 mismatch, %s deleted" (expand-file-name efs-pkg dldir)))
1340       (when (y-or-n-p "Install more packages? ")
1341         (declare-fboundp (pui-list-packages))))))
1342
1343 (provide 'package-get)
1344
1345 ;; On-load forms
1346 (unless (and (featurep 'package-ui)
1347              (fboundp 'loop))
1348   (require 'package-ui)
1349   (load "cl-macs"))
1350
1351 ;;; package-get.el ends here