Initial git import
[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, 2004 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    <youngs@xemacs.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          (search-dir package-get-remote)
939          (base-filename (package-get-info-prop this-package 'filename))
940          (package-status t)
941          filenames full-package-filename)
942     (if (and (equal (package-get-info package 'category) "mule")
943              (not (featurep 'mule)))
944         (error 'invalid-state 
945                "Mule packages can't be installed with a non-Mule SXEmacs"))
946     (if (null this-package)
947         (if package-get-remote
948             (error 'search-failed
949                    (format "Couldn't find package %s with version %s"
950                            package version))
951           (error 'syntax-error
952                  "No download site or local package location specified.")))
953     (if (null base-filename)
954         (error 'syntax-error
955                (format "No filename associated with package %s, version %s"
956                        package version)))
957     (setq install-dir (package-admin-get-install-dir package install-dir))
958
959     ;; If they asked for the latest using version=nil, don't get an older
960     ;; version than we already have.
961     (if installed
962         (if (> (if (stringp installed)
963                    (string-to-number installed)
964                  installed)
965                (if (stringp latest)
966                    (string-to-number latest)
967                  latest))
968             (if (not (null version))
969                 (warn "Installing %s package version %s, you had a newer version %s"
970                   package latest installed)
971               (warn "Skipping %s package, you have a newer version %s"
972                 package installed)
973               (throw 'skip-update t))))
974
975     ;; Contrive a list of possible package filenames.
976     ;; Ugly.  Is there a better way to do this?
977     (setq filenames (cons base-filename nil))
978     (if (string-match #r"^\(..*\)\.tar\.gz$" base-filename)
979         (setq filenames (append filenames
980                                 (list (concat (match-string 1 base-filename)
981                                               ".tgz")))))
982
983     (setq version latest)
984     (unless (and (eq conflict 'never)
985                  (package-get-installedp package version))
986       ;; Find the package from the search list in package-get-remote
987       ;; and copy it into the staging directory.  Then validate
988       ;; the checksum.  Finally, install the package.
989       (catch 'done
990         (let (search-filenames host dir current-filename dest-filename)
991           ;; In each search directory ...
992           (when search-dir
993             (setq host (car search-dir)
994                   dir (car (cdr search-dir))
995                   search-filenames filenames)
996
997             ;; Look for one of the possible package filenames ...
998             (while search-filenames
999               (setq current-filename (car search-filenames)
1000                     dest-filename (package-get-staging-dir current-filename))
1001               (cond
1002                ;; No host means look on the current system.
1003                ((null host)
1004                 (setq full-package-filename
1005                       (substitute-in-file-name
1006                        (expand-file-name current-filename
1007                                          (file-name-as-directory dir)))))
1008
1009                ;; If it's already on the disk locally, and the size is
1010                ;; correct
1011                ((and (file-exists-p dest-filename)
1012                      (eq (nth 7 (file-attributes dest-filename))
1013                          (package-get-info package 'size)))
1014                  (setq full-package-filename dest-filename))
1015
1016                ;; If the file exists on the remote system ...
1017                ((file-exists-p (package-get-remote-filename
1018                                 search-dir current-filename))
1019                 ;; Get it
1020                 (setq full-package-filename dest-filename)
1021                 (message "Retrieving package `%s' ..."
1022                          current-filename)
1023                 (sit-for 0)
1024                 (copy-file (package-get-remote-filename search-dir
1025                                                         current-filename)
1026                            full-package-filename t)))
1027
1028               ;; If we found it, we're done.
1029               (if (and full-package-filename
1030                        (file-exists-p full-package-filename))
1031                   (throw 'done nil))
1032               ;; Didn't find it.  Try the next possible filename.
1033               (setq search-filenames (cdr search-filenames))))))
1034
1035       (if (or (not full-package-filename)
1036               (not (file-exists-p full-package-filename)))
1037           (if package-get-remote
1038               (error 'search-failed
1039                      (format "Unable to find file %s" base-filename))
1040             (error 'syntax-error
1041                    "No download sites or local package locations specified.")))
1042       ;; Validate the md5 checksum
1043       ;; Doing it with SXEmacs removes the need for an external md5 program
1044       (message "Validating checksum for `%s'..." package) (sit-for 0)
1045       (with-temp-buffer
1046         (insert-file-contents-literally full-package-filename)
1047         (if (not (string= (md5 (current-buffer))
1048                           (package-get-info-prop this-package
1049                                                  'md5sum)))
1050             (progn
1051               (delete-file full-package-filename)
1052               (error 'process-error
1053                      (format "Package %s does not match md5 checksum %s has been deleted"
1054                              base-filename full-package-filename)))))
1055
1056       (package-admin-delete-binary-package package install-dir)
1057
1058       (message "Installing package `%s' ..." package) (sit-for 0)
1059       (let ((status
1060              (package-admin-add-binary-package full-package-filename
1061                                                install-dir)))
1062         (if (= status 0)
1063             (progn
1064               ;; clear messages so that only messages from
1065               ;; package-get-init-package are seen, below.
1066               (clear-message)
1067               (if (package-get-init-package (package-admin-get-lispdir
1068                                              install-dir package))
1069                   (progn
1070                     (run-hook-with-args 'package-install-hook package install-dir)
1071                     (message "Added package `%s'" package)
1072                     (sit-for 0))
1073                 (progn
1074                   ;; display message only if there isn't already one.
1075                   (if (not (current-message))
1076                       (progn
1077                         (message "Added package `%s' (errors occurred)"
1078                                  package)
1079                         (sit-for 0)))
1080                   (if package-status
1081                       (setq package-status 'errors)))))
1082           (message "Installation of package %s failed." base-filename)
1083           (sit-for 0)
1084           (switch-to-buffer package-admin-temp-buffer)
1085           (delete-file full-package-filename)
1086           (setq package-status nil)))
1087       (setq found t))
1088     (if (and found package-get-remove-copy)
1089         (delete-file full-package-filename))
1090     package-status)))
1091
1092 (defun package-get-info-find-package (which name)
1093   "Look in WHICH for the package called NAME and return all the info
1094 associated with it.  See `package-get-base' for info on the format
1095 returned.
1096
1097  To access fields returned from this, use
1098 `package-get-info-version' to return information about particular a
1099 version.  Use `package-get-info-find-prop' to find particular property
1100 from a version returned by `package-get-info-version'."
1101   (interactive "xPackage list: \nsPackage Name: ")
1102   (if which
1103       (if (eq (caar which) name)
1104           (cdar which)
1105         (if (cdr which)
1106             (package-get-info-find-package (cdr which) name)))))
1107
1108 (defun package-get-info-version (package version)
1109   "In PACKAGE, return the plist associated with a particular VERSION of the
1110   package.  PACKAGE is typically as returned by
1111   `package-get-info-find-package'.  If VERSION is nil, then return the
1112   first (aka most recent) version.  Use `package-get-info-find-prop'
1113   to retrieve a particular property from the value returned by this."
1114   (interactive (package-get-interactive-package-query t t))
1115   (while (and version package (not (string= (plist-get (car package) 'version) version)))
1116     (setq package (cdr package)))
1117   (if package (car package)))
1118
1119 (defun package-get-info-prop (package-version property)
1120   "In PACKAGE-VERSION, return the value associated with PROPERTY.
1121 PACKAGE-VERSION is typically returned by `package-get-info-version'
1122 and PROPERTY is typically (although not limited to) one of the
1123 following:
1124
1125 version         - version of this package
1126 provides                - list of symbols provided
1127 requires                - list of symbols that are required.
1128                   These in turn are provided by other packages.
1129 size            - size of the bundled package
1130 md5sum          - computed md5 checksum"
1131   (interactive "xPackage Version: \nSProperty")
1132   (plist-get package-version property))
1133
1134 (defun package-get-info-version-prop (package-list package version property)
1135   "In PACKAGE-LIST, search for PACKAGE with this VERSION and return
1136   PROPERTY value."
1137   (package-get-info-prop
1138    (package-get-info-version
1139     (package-get-info-find-package package-list package) version) property))
1140
1141 (defun package-get-staging-dir (filename)
1142   "Return a good place to stash FILENAME when it is retrieved.
1143 Use `package-get-dir' for directory to store stuff.
1144 Creates `package-get-dir'  if it doesn't exist."
1145   (interactive "FPackage filename: ")
1146   (if (not (file-exists-p package-get-dir))
1147       (make-directory package-get-dir))
1148   (expand-file-name
1149    (file-name-nondirectory (or (and-fboundp 'efs-ftp-path
1150                                  (nth 2 (efs-ftp-path filename)))
1151                                filename))
1152    (file-name-as-directory package-get-dir)))
1153
1154 (defun package-get-remote-filename (search filename)
1155   "Return FILENAME as a remote filename.
1156 It first checks if FILENAME already is a remote filename.  If it is
1157 not, then it uses the (car search) as the remote site-name and the (cadr
1158 search) as the remote-directory and concatenates filename.  In other
1159 words
1160         site-name:remote-directory/filename.
1161
1162 If (car search) is nil, (cadr search is interpreted as  a local directory).
1163 "
1164   (if (file-remote-p filename)
1165       filename
1166     (let ((dir (cadr search)))
1167       (concat (when (car search)
1168                 (concat
1169                  (if (string-match "@" (car search))
1170                      "/"
1171                    "/anonymous@")
1172                  (car search) ":"))
1173               (if (string-match "/$" dir)
1174                   dir
1175                 (concat dir "/"))
1176               filename))))
1177
1178 (defun package-get-installedp (package version)
1179   "Determine if PACKAGE with VERSION has already been installed.
1180 I'm not sure if I want to do this by searching directories or checking
1181 some built in variables.  For now, use packages-package-list."
1182   ;; Use packages-package-list which contains name and version
1183   (equal (plist-get
1184           (package-get-info-find-package packages-package-list
1185                                          package) ':version)
1186          (if (floatp version)
1187              version
1188            (string-to-number version))))
1189
1190 ;;;###autoload
1191 (defun package-get-package-provider (sym &optional force-current)
1192   "Search for a package that provides SYM and return the name and
1193   version.  Searches in `package-get-base' for SYM.   If SYM is a
1194   consp, then it must match a corresponding (provide (SYM VERSION)) from
1195   the package.
1196
1197 If FORCE-CURRENT is non-nil make sure the database is up to date. This might
1198 lead to Emacs accessing remote sites."
1199   (interactive "SSymbol: ")
1200   (package-get-require-base force-current)
1201   (let ((packages package-get-base)
1202         (done nil)
1203         (found nil))
1204     (while (and (not done) packages)
1205       (let* ((this-name (caar packages))
1206              (this-package (cdr (car packages)))) ;strip off package name
1207         (while (and (not done) this-package)
1208           (if (or (eq this-name sym)
1209                   (eq (cons this-name
1210                           (package-get-info-prop (car this-package) 'version))
1211                       sym)
1212                   (member sym
1213                         (package-get-info-prop (car this-package) 'provides)))
1214               (progn (setq done t)
1215                      (setq found
1216                        (list (caar packages)
1217                          (package-get-info-prop (car this-package) 'version))))
1218             (setq this-package (cdr this-package)))))
1219       (setq packages (cdr packages)))
1220     (when (interactive-p)
1221       (if found
1222           (message "%S" found)
1223         (message "No appropriate package found")))
1224     found))
1225
1226 (defun package-get-ever-installed-p (pkg &optional notused)
1227   (string-match "-package$" (symbol-name pkg))
1228   (custom-initialize-set
1229    pkg
1230    (if (package-get-info-find-package
1231         packages-package-list
1232         (intern (substring (symbol-name pkg) 0 (match-beginning 0))))
1233        t)))
1234
1235 ;;; FIXME: see comment at end of `pui-bootstrap'
1236
1237 ;;;###autoload
1238 (defun pui-bootstrap ()
1239   "Bootstrap the SXEmacs Package Tools.
1240
1241 The Package Tools, under normal circumstances, cannot work until a
1242 couple of packages are pre-installed by hand.  This function eliminates
1243 the need to do that.  It uses FFI and libcurl to download and install
1244 the lastest package index file, the EFS and xemacs-base packages.
1245
1246 Obviously you can't use this if you didn't enable FFI support in your
1247 SXEmacs or if you don't have libffi on your system.
1248
1249 This isn't designed to replace the existing Package Tools so after
1250 you have run `pui-bootstrap' once you should then use the normal PUI
1251 tools, `pui-list-packages' etc."
1252   (interactive)
1253   ;; A little sanity checking never hurt anybody
1254   (when (featurep '(and efs-autoloads xemacs-base-autoloads))
1255     (error 'invalid-operation "PUI doesn't need bootstrapping"))
1256   (when (and (fboundp 'ffi-defun)
1257              (not (featurep '(and ffi ffi-curl))))
1258     (require 'ffi-curl))
1259   (unless (featurep 'ffi)
1260     (error 'unimplemented "FFI"))
1261   ;; One last check... has `package-get-remote' been set?
1262   (if (not (cdr package-get-remote))
1263       (when (y-or-n-p "You haven't set a download site, do you need help ")
1264         (declare-fboundp (Info-goto-node "(sxemacs)Bootstrapping PUI")))
1265     ;; We should be good to go
1266     (let* ((site (car package-get-remote))
1267            (dir (cadr package-get-remote))
1268            (url (concat "ftp://" site "/" dir "/"))
1269            (dldir (temp-directory))
1270            (index (expand-file-name package-get-base-filename
1271                                     package-get-package-index-file-location))
1272            xemacs-base-pkg
1273            efs-pkg
1274                                         ;status)
1275            )
1276       ;; Grab the index
1277       (message "Retrieving index, please be patient")
1278       (declare-fboundp (curl:download (concat url package-get-base-filename) index))
1279       (message "Retrieving index, done!")
1280       ;; Update the db
1281       (set-buffer (find-file-noselect index))
1282       (package-get-update-base-from-buffer)
1283       (kill-buffer (current-buffer))
1284       ;; Get xemacs-base, EFS
1285       (setq xemacs-base-pkg (package-get-info 'xemacs-base 'filename))
1286       (setq efs-pkg (package-get-info 'efs 'filename))
1287       (message "Retrieving %s, please be patient" xemacs-base-pkg)
1288       (declare-fboundp (curl:download (concat url xemacs-base-pkg)
1289                                       (expand-file-name xemacs-base-pkg dldir)))
1290       (message "Retrieving %s, please be patient" efs-pkg)
1291       (declare-fboundp (curl:download (concat url efs-pkg)
1292                                       (expand-file-name efs-pkg dldir)))
1293       (message "Download complete.")
1294       ;; Install xemacs-base
1295       (if (equal (package-get-info 'xemacs-base 'md5sum)
1296                  (with-temp-buffer
1297                    (insert-file-contents-literally
1298                     (expand-file-name xemacs-base-pkg dldir))
1299                    (md5 (current-buffer))))
1300           (progn
1301             (package-admin-add-binary-package
1302              (expand-file-name xemacs-base-pkg dldir)
1303              (package-admin-get-install-dir 'xemacs-base))
1304             (push (file-name-as-directory
1305                    (expand-file-name "lisp/xemacs-base"
1306                                      (package-admin-get-install-dir 'xemacs-base)))
1307                   load-path)
1308             (load-file (expand-file-name "lisp/xemacs-base/_pkg.el"
1309                                          (package-admin-get-install-dir 'xemacs-base)))
1310             (load-file (expand-file-name "lisp/xemacs-base/auto-autoloads.el"
1311                                          (package-admin-get-install-dir 'xemacs-base)))
1312             (message "xemacs-base package installed"))
1313         (delete-file (expand-file-name xemacs-base-pkg dldir))
1314         (error "MD5 mismatch, %s deleted" (expand-file-name xemacs-base-pkg dldir)))
1315       ;; Install EFS
1316       (if (equal (package-get-info 'efs 'md5sum)
1317                  (with-temp-buffer
1318                    (insert-file-contents-literally
1319                     (expand-file-name efs-pkg dldir))
1320                    (md5 (current-buffer))))
1321           (progn
1322             (package-admin-add-binary-package
1323              (expand-file-name efs-pkg dldir)
1324              (package-admin-get-install-dir 'efs))
1325             (push (file-name-as-directory
1326                    (expand-file-name "lisp/efs"
1327                                      (package-admin-get-install-dir 'efs)))
1328                   load-path)
1329             (load-file (expand-file-name "lisp/efs/_pkg.el"
1330                                          (package-admin-get-install-dir 'efs)))
1331             (load-file (expand-file-name "lisp/efs/auto-autoloads.el"
1332                                          (package-admin-get-install-dir 'efs)))
1333             (message "efs package installed"))
1334         (delete-file (expand-file-name efs-pkg dldir))
1335         (error "MD5 mismatch, %s deleted" (expand-file-name efs-pkg dldir)))
1336       (when (y-or-n-p "Install more packages? ")
1337         ;; Remove this ugly hack as soon as a SXEmacs-friendly EFS is in
1338         ;; stable XE packages.  It is already in EFS upstream.
1339         (let ((emacs-version "21.4 (patch 17) \"Jumbo Shrimp\" XEmacs Lucid"))
1340           (pui-list-packages))))))
1341
1342 (provide 'package-get)
1343
1344 ;; On-load forms
1345 (unless (and (featurep 'package-ui)
1346              (fboundp 'loop))
1347   (require 'package-ui)
1348   (load "cl-macs"))
1349
1350 ;;; package-get.el ends here