comparison lisp/dired/ange-ftp.el @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children ac2d302a0011
comparison
equal deleted inserted replaced
-1:000000000000 0:376386a54a3c
1 ;;; ange-ftp.el --- transparent FTP support for GNU Emacs
2 ;; Keywords: comm
3
4 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
5 ;;
6 ;; File: ange-ftp.el
7 ;; RCS: Header: ange-ftp.el,v 4.20 92/08/14 17:04:34 ange Exp
8 ;; Description: transparent FTP support for GNU Emacs
9 ;; Author: Andy Norman, ange@hplb.hpl.hp.com
10 ;; Created: Thu Oct 12 14:00:05 1989
11 ;; Modified: Wed May 3 00:50:40 1995 (Andy Norman) ange@hplb.hpl.hp.com
12 ;; Modified for XEmacs by jwz
13 ;;
14 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
15
16 ;;; Copyright (C) 1989, 1990, 1991, 1992 Andy Norman.
17 ;;;
18 ;;; Author: Andy Norman (ange@hplb.hpl.hp.com)
19 ;;;
20 ;;; This program is free software; you can redistribute it and/or modify
21 ;;; it under the terms of the GNU General Public License as published by
22 ;;; the Free Software Foundation; either version 1, or (at your option)
23 ;;; any later version.
24 ;;;
25 ;;; This program is distributed in the hope that it will be useful,
26 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
27 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 ;;; GNU General Public License for more details.
29 ;;;
30 ;;; A copy of the GNU General Public License can be obtained from this
31 ;;; program's author (send electronic mail to ange@hplb.hpl.hp.com) or from
32 ;;; the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
33 ;;; 02139, USA.
34
35 ;;; Synched up with: Not synched with FSF.
36
37 ;;; Description:
38 ;;;
39 ;;; This package attempts to make accessing files and directories using FTP
40 ;;; from within GNU Emacs as simple and transparent as possible. A subset of
41 ;;; the common file-handling routines are extended to interact with FTP.
42
43 ;;; Installation:
44 ;;;
45 ;;; Byte-compile ange-ftp.el to ange-ftp.elc and put them both in a directory
46 ;;; on your load-path. Load the package from your .emacs file with:
47 ;;;
48 ;;; (require 'ange-ftp).
49 ;;;
50 ;;; ange-ftp can't sensibly be auto-loaded; you are either using it, or you
51 ;;; ain't.
52
53 ;;; Usage:
54 ;;;
55 ;;; Some of the common GNU Emacs file-handling operations have been made
56 ;;; FTP-smart. If one of these routines is given a filename that matches
57 ;;; '/user@host:path' then it will spawn an FTP process connecting to machine
58 ;;; 'host' as account 'user' and perform its operation on the file 'path'.
59 ;;;
60 ;;; For example: if find-file is given a filename of:
61 ;;;
62 ;;; /ange@anorman:/tmp/notes
63 ;;;
64 ;;; then ange-ftp will spawn an FTP process, connect to the host 'anorman' as
65 ;;; user 'ange', get the file '/tmp/notes' and pop up a buffer containing the
66 ;;; contents of that file as if it were on the local filesystem. If ange-ftp
67 ;;; needed a password to connect then it would prompt the user in the
68 ;;; minibuffer.
69
70 ;;; Extended filename syntax:
71 ;;;
72 ;;; The default extended filename syntax is '/user@host:path', where the
73 ;;; 'user@' part may be omitted. This syntax can be customised to a certain
74 ;;; extent by changing ange-ftp-path-format. There are limitations.
75 ;;;
76 ;;; If the user part is omitted then ange-ftp will generate a default user
77 ;;; instead whose value depends on the variable ange-ftp-default-user.
78
79 ;;; Passwords:
80 ;;;
81 ;;; A password is required for each host / user pair. This will be prompted
82 ;;; for when needed, unless already set by calling ange-ftp-set-passwd, or
83 ;;; specified in a *valid* ~/.netrc file.
84
85 ;;; Passwords for user "anonymous":
86 ;;;
87 ;;; Passwords for the user "anonymous" (or "ftp") are handled specially. The
88 ;;; variable ange-ftp-generate-anonymous-password controls what happens: if
89 ;;; the value of this variable is a string, then this is used as the password;
90 ;;; if non-nil, then a password is created from the name of the user and the
91 ;;; hostname of the machine on which GNU Emacs is running; if nil (the
92 ;;; default) then the user is prompted for a password as normal.
93
94 ;;; "Dumb" UNIX hosts:
95 ;;;
96 ;;; The FTP servers on some UNIX machines have problems if the 'ls' command is
97 ;;; used.
98 ;;;
99 ;;; The routine ange-ftp-add-dumb-unix-host can be called to tell ange-ftp to
100 ;;; limit itself to the DIR command and not 'ls' for a given UNIX host. Note
101 ;;; that this change will take effect for the current GNU Emacs session only.
102 ;;; See below for a discussion of non-UNIX hosts. If a large number of
103 ;;; machines with similar hostnames have this problem then it is easier to set
104 ;;; the value of ange-ftp-dumb-unix-host-regexp in your .emacs file. ange-ftp
105 ;;; is unable to automatically recognize dumb unix hosts.
106
107 ;;; File name completion:
108 ;;;
109 ;;; Full file-name completion is supported on UNIX, VMS, CMS, and MTS hosts.
110 ;;; To do filename completion, ange-ftp needs a listing from the remote host.
111 ;;; Therefore, for very slow connections, it might not save any time.
112
113 ;;; FTP processes:
114 ;;;
115 ;;; When ange-ftp starts up an FTP process, it leaves it running for speed
116 ;;; purposes. Some FTP servers will close the connection after a period of
117 ;;; time, but ange-ftp should be able to quietly reconnect the next time that
118 ;;; the process is needed.
119 ;;;
120 ;;; The FTP process will be killed should the associated "*ftp user@host*"
121 ;;; buffer be deleted. This should not cause ange-ftp any grief.
122
123 ;;; Binary file transfers:
124 ;;;
125 ;;; By default ange-ftp will transfer files in ASCII mode. If a file being
126 ;;; transferred matches the value of ange-ftp-binary-file-name-regexp then the
127 ;;; FTP process will be toggled into BINARY mode before the transfer and back
128 ;;; to ASCII mode after the transfer.
129
130 ;;; Account passwords:
131 ;;;
132 ;;; Some FTP servers require an additional password which is sent by the
133 ;;; ACCOUNT command. ange-ftp partially supports this by allowing the user to
134 ;;; specify an account password by either calling ange-ftp-set-account, or by
135 ;;; specifying an account token in the .netrc file. If the account password
136 ;;; is set by either of these methods then ange-ftp will issue an ACCOUNT
137 ;;; command upon starting the FTP process.
138
139 ;;; Preloading:
140 ;;;
141 ;;; ange-ftp can be preloaded, but must be put in the site-init.el file and
142 ;;; not the site-load.el file in order for the documentation strings for the
143 ;;; functions being overloaded to be available.
144
145 ;;; Status reports:
146 ;;;
147 ;;; Most ange-ftp commands that talk to the FTP process output a status
148 ;;; message on what they are doing. In addition, ange-ftp can take advantage
149 ;;; of the FTP client's HASH command to display the status of transferring
150 ;;; files and listing directories. See the documentation for the variables
151 ;;; ange-ftp-{ascii,binary}-hash-mark-size, ange-ftp-send-hash and
152 ;;; ange-ftp-process-verbose for more details.
153
154 ;;; Gateways:
155 ;;;
156 ;;; Sometimes it is neccessary for the FTP process to be run on a different
157 ;;; machine than the machine running GNU Emacs. This can happen when the
158 ;;; local machine has restrictions on what hosts it can access.
159 ;;;
160 ;;; ange-ftp has support for running the ftp process on a different (gateway)
161 ;;; machine. The way it works is as follows:
162 ;;;
163 ;;; 1) Set the variable 'ange-ftp-gateway-host' to the name of a machine
164 ;;; that doesn't have the access restrictions.
165 ;;;
166 ;;; 2) Set the variable 'ange-ftp-local-host-regexp' to a regular expression
167 ;;; that matches hosts that can be contacted from running a local ftp
168 ;;; process, but fails to match hosts that can't be accessed locally. For
169 ;;; example:
170 ;;;
171 ;;; "\\.hp\\.com$\\|^[^.]*$"
172 ;;;
173 ;;; will match all hosts that are in the .hp.com domain, or don't have an
174 ;;; explicit domain in their name, but will fail to match hosts with
175 ;;; explicit domains or that are specified by their ip address.
176 ;;;
177 ;;; 3) Using NFS and symlinks, make sure that there is a shared directory with
178 ;;; the *same* name between the local machine and the gateway machine.
179 ;;; This directory is neccessary for temporary files created by ange-ftp.
180 ;;;
181 ;;; 4) Set the variable 'ange-ftp-gateway-tmp-name-template' to the name of
182 ;;; this directory plus an identifying filename prefix. For example:
183 ;;;
184 ;;; "/nfs/hplose/ange/ange-ftp"
185 ;;;
186 ;;; where /nfs/hplose/ange is a directory that is shared between the
187 ;;; gateway machine and the local machine.
188 ;;;
189 ;;; The simplest way of getting a ftp process running on the gateway machine
190 ;;; is if you can spawn a remote shell using either 'rsh' or 'remsh'. If you
191 ;;; can't do this for some reason such as security then points 7 onwards will
192 ;;; discuss an alternative approach.
193 ;;;
194 ;;; 5) Set the variable ange-ftp-gateway-program to the name of the remote
195 ;;; shell process such as 'remsh' or 'rsh' if the default isn't correct.
196 ;;;
197 ;;; 6) Set the variable ange-ftp-gateway-program-interactive to nil if it
198 ;;; isn't already. This tells ange-ftp that you are using a remote shell
199 ;;; rather than logging in using telnet or rlogin.
200 ;;;
201 ;;; That should be all you need to allow ange-ftp to spawn a ftp process on
202 ;;; the gateway machine. If you have to use telnet or rlogin to get to the
203 ;;; gateway machine then follow the instructions below.
204 ;;;
205 ;;; 7) Set the variable ange-ftp-gateway-program to the name of the program
206 ;;; that lets you log onto the gateway machine. This may be something like
207 ;;; telnet or rlogin.
208 ;;;
209 ;;; 8) Set the variable ange-ftp-gateway-prompt-pattern to a regular
210 ;;; expression that matches the prompt you get when you login to the
211 ;;; gateway machine. Be very specific here; this regexp must not match
212 ;;; *anything* in your login banner except this prompt.
213 ;;; shell-prompt-pattern is far too general as it appears to match some
214 ;;; login banners from Sun machines. For example:
215 ;;;
216 ;;; "^$*$ *"
217 ;;;
218 ;;; 9) Set the variable ange-ftp-gateway-program-interactive to 't' to let
219 ;;; ange-ftp know that it has to "hand-hold" the login to the gateway
220 ;;; machine.
221 ;;;
222 ;;; 10) Set the variable ange-ftp-gateway-setup-term-command to a UNIX command
223 ;;; that will put the pty connected to the gateway machine into a
224 ;;; no-echoing mode, and will strip off carriage-returns from output from
225 ;;; the gateway machine. For example:
226 ;;;
227 ;;; "stty -onlcr -echo"
228 ;;;
229 ;;; will work on HP-UX machines, whereas:
230 ;;;
231 ;;; "stty -echo nl"
232 ;;;
233 ;;; appears to work for some Sun machines.
234 ;;;
235 ;;; That's all there is to it.
236
237 ;;; Smart gateways:
238 ;;;
239 ;;; If you have a "smart" ftp program that allows you to issue commands like
240 ;;; "USER foo@bar" which do nice proxy things, then look at the variables
241 ;;; ange-ftp-smart-gateway and ange-ftp-smart-gateway-port.
242
243 ;;; Tips for using ange-ftp:
244 ;;;
245 ;;; 1. For dired to work on a host which marks symlinks with a trailing @ in
246 ;;; an ls -alF listing, you need to (setq dired-ls-F-marks-symlinks t).
247 ;;; Most UNIX systems do not do this, but ULTRIX does. If you think that
248 ;;; there is a chance you might connect to an ULTRIX machine (such as
249 ;;; prep.ai.mit.edu), then set this variable accordingly. This will have
250 ;;; the side effect that dired will have problems with symlinks whose names
251 ;;; end in an @. If you get youself into this situation then editing
252 ;;; dired's ls-switches to remove "F", will temporarily fix things.
253 ;;;
254 ;;; 2. If you know that you are connecting to a certain non-UNIX machine
255 ;;; frequently, and ange-ftp seems to be unable to guess its host-type,
256 ;;; then setting the appropriate host-type regexp
257 ;;; (ange-ftp-vms-host-regexp, ange-ftp-mts-host-regexp, or
258 ;;; ange-ftp-cms-host-regexp) accordingly should help. Also, please report
259 ;;; ange-ftp's inability to recognize the host-type as a bug.
260 ;;;
261 ;;; 3. For slow connections, you might get "listing unreadable" error
262 ;;; messages, or get an empty buffer for a file that you know has something
263 ;;; in it. The solution is to increase the value of ange-ftp-retry-time.
264 ;;; Its default value is 5 which is plenty for reasonable connections.
265 ;;; However, for some transatlantic connections I set this to 20.
266 ;;;
267 ;;; 4. Beware of compressing files on non-UNIX hosts. Ange-ftp will do it by
268 ;;; copying the file to the local machine, compressing it there, and then
269 ;;; sending it back. Binary file transfers between machines of different
270 ;;; architectures can be a risky business. Test things out first on some
271 ;;; test files. See "Bugs" below. Also, note that ange-ftp copies files by
272 ;;; moving them through the local machine. Again, be careful when doing
273 ;;; this with binary files on non-Unix machines.
274 ;;;
275 ;;; 5. Beware that dired over ftp will use your setting of dired-no-confirm
276 ;;; (list of dired commands for which confirmation is not asked). You
277 ;;; might want to reconsider your setting of this variable, because you
278 ;;; might want confirmation for more commands on remote direds than on
279 ;;; local direds. For example, I strongly recommend that you not include
280 ;;; compress and uncompress in this list. If there is enough demand it
281 ;;; might be a good idea to have an alist ange-ftp-dired-no-confirm of
282 ;;; pairs ( TYPE . LIST ), where TYPE is an operating system type and LIST
283 ;;; is a list of commands for which confirmation would be suppressed. Then
284 ;;; remote dired listings would take their (buffer-local) value of
285 ;;; dired-no-confirm from this alist. Who votes for this?
286
287 ;;; ---------------------------------------------------------------------
288 ;;; Non-UNIX support:
289 ;;; ---------------------------------------------------------------------
290
291 ;;; VMS support:
292 ;;;
293 ;;; Ange-ftp has full support for VMS hosts, including tree dired support. It
294 ;;; should be able to automatically recognize any VMS machine. However, if it
295 ;;; fails to do this, you can use the command ange-ftp-add-vms-host. As well,
296 ;;; you can set the variable ange-ftp-vms-host-regexp in your .emacs file. We
297 ;;; would be grateful if you would report any failures to automatically
298 ;;; recognize a VMS host as a bug.
299 ;;;
300 ;;; Filename Syntax:
301 ;;;
302 ;;; For ease of *implementation*, the user enters the VMS filename syntax in a
303 ;;; UNIX-y way. For example:
304 ;;; PUB$:[ANONYMOUS.SDSCPUB.NEXT]README.TXT;1
305 ;;; would be entered as:
306 ;;; /PUB$$:/ANONYMOUS/SDSCPUB/NEXT/README.TXT;1
307 ;;; i.e. to log in as anonymous on ymir.claremont.edu and grab the file:
308 ;;; [.CSV.POLICY]RULES.MEM
309 ;;; you would type:
310 ;;; C-x C-f /anonymous@ymir.claremont.edu:CSV/POLICY/RULES.MEM
311 ;;;
312 ;;; A legal VMS filename is of the form: FILE.TYPE;##
313 ;;; where FILE can be up to 39 characters
314 ;;; TYPE can be up to 39 characters
315 ;;; ## is a version number (an integer between 1 and 32,767)
316 ;;; Valid characters in FILE and TYPE are A-Z 0-9 _ - $
317 ;;; $ cannot begin a filename, and - cannot be used as the first or last
318 ;;; character.
319 ;;;
320 ;;; Tips:
321 ;;; 1. Although VMS is not case sensitive, EMACS running under UNIX is.
322 ;;; Therefore, to access a VMS file, you must enter the filename with upper
323 ;;; case letters.
324 ;;; 2. To access the latest version of file under VMS, you use the filename
325 ;;; without the ";" and version number. You should always edit the latest
326 ;;; version of a file. If you want to edit an earlier version, copy it to a
327 ;;; new file first. This has nothing to do with ange-ftp, but is simply
328 ;;; good VMS operating practice. Therefore, to edit FILE.TXT;3 (say 3 is
329 ;;; latest version), do C-x C-f /ymir.claremont.edu:FILE.TXT. If you
330 ;;; inadvertently do C-x C-f /ymir.claremont.edu:FILE.TXT;3, you will find
331 ;;; that VMS will not allow you to save the file because it will refuse to
332 ;;; overwrite FILE.TXT;3, but instead will want to create FILE.TXT;4, and
333 ;;; attach the buffer to this file. To get out of this situation, M-x
334 ;;; write-file /ymir.claremont.edu:FILE.TXT will attach the buffer to
335 ;;; latest version of the file. For this reason, in tree dired "f"
336 ;;; (dired-find-file), always loads the file sans version, whereas "v",
337 ;;; (dired-view-file), always loads the explicit version number. The
338 ;;; reasoning being that it reasonable to view old versions of a file, but
339 ;;; not to edit them.
340 ;;; 3. EMACS has a feature in which it does environment variable substitution
341 ;;; in filenames. Therefore, to enter a $ in a filename, you must quote it
342 ;;; by typing $$. There is a bug in EMACS, in that it neglects to quote the
343 ;;; $'s in the default directory when it writes it in the minibuffer. You
344 ;;; must edit the minibuffer to quote the $'s manually. Hopefully, this bug
345 ;;; will be fixed in EMACS 19. If you use Sebastian Kremer's gmhist (V 4.26
346 ;;; or newer), you will not have this problem.
347
348 ;;; MTS support:
349 ;;;
350 ;;; Ange-ftp has full support, including tree dired support, for hosts running
351 ;;; the Michigan terminal system. It should be able to automatically
352 ;;; recognize any MTS machine. However, if it fails to do this, you can use
353 ;;; the command ange-ftp-add-mts-host. As well, you can set the variable
354 ;;; ange-ftp-mts-host-regexp in your .emacs file. We would be grateful if you
355 ;;; would report any failures to automatically recognize a MTS host as a bug.
356 ;;;
357 ;;; Filename syntax:
358 ;;;
359 ;;; MTS filenames are entered in a UNIX-y way. For example, if your account
360 ;;; was YYYY, the file FILE in the account XXXX: on mtsg.ubc.ca would be
361 ;;; entered as
362 ;;; /YYYY@mtsg.ubc.ca:/XXXX:/FILE
363 ;;; In other words, MTS accounts are treated as UNIX directories. Of course,
364 ;;; to access a file in another account, you must have access permission for
365 ;;; it. If FILE were in your own account, then you could enter it in a
366 ;;; relative path fashion as
367 ;;; /YYYY@mtsg.ubc.ca:FILE
368 ;;; MTS filenames can be up to 12 characters. Like UNIX, the structure of the
369 ;;; filename does not contain a TYPE (i.e. it can have as many "."'s as you
370 ;;; like.) MTS filenames are always in upper case, and hence be sure to enter
371 ;;; them as such! MTS is not case sensitive, but an EMACS running under UNIX
372 ;;; is.
373
374 ;;; CMS support:
375 ;;;
376 ;;; Ange-ftp has full support, including tree dired support, for hosts running
377 ;;; CMS. It should be able to automatically recognize any CMS machine.
378 ;;; However, if it fails to do this, you can use the command
379 ;;; ange-ftp-add-cms-host. As well, you can set the variable
380 ;;; ange-ftp-cms-host-regexp in your .emacs file. We would be grateful if you
381 ;;; would report any failures to automatically recognize a CMS host as a bug.
382 ;;;
383 ;;; Filename syntax:
384 ;;;
385 ;;; CMS filenames are entered in a UNIX-y way. In otherwords, minidisks are
386 ;;; treated as UNIX directories. For example to access the file READ.ME in
387 ;;; minidisk *.311 on cuvmb.cc.columbia.edu, you would enter
388 ;;; /anonymous@cuvmb.cc.columbia.edu:/*.311/READ.ME
389 ;;; If *.301 is the default minidisk for this account, you could access
390 ;;; FOO.BAR on this minidisk as
391 ;;; /anonymous@cuvmb.cc.columbia.edu:FOO.BAR
392 ;;; CMS filenames are of the form FILE.TYPE, where both FILE and TYPE can be
393 ;;; up to 8 characters. Again, beware that CMS filenames are always upper
394 ;;; case, and hence must be entered as such.
395 ;;;
396 ;;; Tips:
397 ;;; 1. CMS machines, with the exception of anonymous accounts, nearly always
398 ;;; need an account password. To have ange-ftp send an account password,
399 ;;; you can either include it in your .netrc file, or use
400 ;;; ange-ftp-set-account.
401 ;;; 2. Ange-ftp cannot send "write passwords" for a minidisk. Hopefully, we
402 ;;; can fix this.
403 ;;;
404 ;;; ------------------------------------------------------------------
405 ;;; Bugs:
406 ;;; ------------------------------------------------------------------
407 ;;;
408 ;;; 1. Umask problems:
409 ;;; Be warned that files created by using ange-ftp will take account of the
410 ;;; umask of the ftp daemon process rather than the umask of the creating
411 ;;; user. This is particulary important when logging in as the root user.
412 ;;; The way that I tighten up the ftp daemon's umask under HP-UX is to make
413 ;;; sure that the umask is changed to 027 before I spawn /etc/inetd. I
414 ;;; suspect that there is something similar on other systems.
415 ;;;
416 ;;; 2. Some combinations of FTP clients and servers break and get out of sync
417 ;;; when asked to list a non-existent directory. Some of the ai.mit.edu
418 ;;; machines cause this problem for some FTP clients. Using
419 ;;; ange-ftp-kill-process can be used to restart the ftp process, which
420 ;;; should get things back in synch.
421 ;;;
422 ;;; 3. Ange-ftp does not check to make sure that when creating a new file,
423 ;;; you provide a valid filename for the remote operating system.
424 ;;; If you do not, then the remote FTP server will most likely
425 ;;; translate your filename in some way. This may cause ange-ftp to
426 ;;; get confused about what exactly is the name of the file. The
427 ;;; most common causes of this are using lower case filenames on systems
428 ;;; which support only upper case, and using filenames which are too
429 ;;; long.
430 ;;;
431 ;;; 4. Null (blank) passwords confuse both ange-ftp and some FTP daemons.
432 ;;;
433 ;;; 5. Ange-ftp likes to use pty's to talk to its FTP processes. If GNU Emacs
434 ;;; for some reason creates a FTP process that only talks via pipes then
435 ;;; ange-ftp won't be getting the information it requires at the time that
436 ;;; it wants it since pipes flush at different times to pty's. One
437 ;;; disgusting way around this problem is to talk to the FTP process via
438 ;;; rlogin which does the 'right' things with pty's.
439 ;;;
440 ;;; 6. For CMS support, we send too many cd's. Since cd's are cheap, I haven't
441 ;;; worried about this too much. Eventually, we should have some caching
442 ;;; of the current minidisk.
443 ;;;
444 ;;; 7. Some CMS machines do not assign a default minidisk when you ftp them as
445 ;;; anonymous. It is then necessary to guess a valid minidisk name, and cd
446 ;;; to it. This is (understandably) beyond ange-ftp.
447 ;;;
448 ;;; 8. Remote to remote copying of files on non-Unix machines can be risky.
449 ;;; Depending on the variable ange-ftp-binary-file-name-regexp, ange-ftp
450 ;;; will use binary mode for the copy. Between systems of different
451 ;;; architecture, this still may not be enough to guarantee the integrity
452 ;;; of binary files. Binary file transfers from VMS machines are
453 ;;; particularly problematical. Should ange-ftp-binary-file-name-regexp be
454 ;;; an alist of OS type, regexp pairs?
455 ;;;
456 ;;; 9. The code to do compression of files over ftp is not as careful as it
457 ;;; should be. It deletes the old remote version of the file, before
458 ;;; actually checking if the local to remote transfer of the compressed
459 ;;; file succeeds. Of course to delete the original version of the file
460 ;;; after transferring the compressed version back is also dangerous,
461 ;;; because some OS's have severe restrictions on the length of filenames,
462 ;;; and when the compressed version is copied back the "-Z" or ".Z" may be
463 ;;; truncated. Then, ange-ftp would delete the only remaining version of
464 ;;; the file. Maybe ange-ftp should make backups when it compresses files
465 ;;; (of course, the backup "~" could also be truncated off, sigh...).
466 ;;; Suggestions?
467 ;;;
468
469 ;;; 10. If a dir listing is attempted for an empty directory on (at least
470 ;;; some) VMS hosts, an ftp error is given. This is really an ftp bug, and
471 ;;; I don't know how to get ange-ftp work to around it.
472 ;;;
473 ;;; 11. Bombs on filenames that start with a space. Deals well with filenames
474 ;;; containing spaces, but beware that the remote ftpd may not like them
475 ;;; much.
476 ;;;
477 ;;; 12. No classic dired support for non-UNIX systems. Tree dired was enough.
478 ;;;
479 ;;; 13. The famous @ bug. As mentioned above in TIPS, ULTRIX marks symlinks
480 ;;; with a trailing @ in a ls -alF listing. In order to account for this
481 ;;; ange-ftp looks to chop trailing @'s off of symlink names when it is
482 ;;; parsing a listing with the F switch. This will cause ange-ftp to
483 ;;; incorrectly get the name of a symlink on a non-ULTRIX host if its name
484 ;;; ends in an @. ange-ftp will correct itself if you take F out of the
485 ;;; dired ls switches (C-u s will allow you to edit the switches). The
486 ;;; dired buffer will be automatically reverted, which will allow ange-ftp
487 ;;; to fix its files hashtable. A cookie to anyone who can think of a
488 ;;; fast, sure-fire way to recognize ULTRIX over ftp.
489
490 ;;; If you find any bugs or problems with this package, PLEASE either e-mail
491 ;;; the above author, or send a message to the ange-ftp-lovers mailing list
492 ;;; below. Ideas and constructive comments are especially welcome.
493
494 ;;; ange-ftp-lovers:
495 ;;;
496 ;;; ange-ftp has its own mailing list modestly called ange-ftp-lovers. All
497 ;;; users of ange-ftp are welcome to subscribe (see below) and to discuss
498 ;;; aspects of ange-ftp. New versions of ange-ftp are posted periodically to
499 ;;; the mailing list.
500 ;;;
501 ;;; To [un]subscribe to ange-ftp-lovers, or to report mailer problems with the
502 ;;; list, please mail one of the following addresses:
503 ;;;
504 ;;; ange-ftp-lovers-request@anorman.hpl.hp.com
505 ;;; or
506 ;;; ange-ftp-lovers-request%anorman.hpl.hp.com@hplb.hpl.hp.com
507 ;;;
508 ;;; Please don't forget the -request part.
509 ;;;
510 ;;; For mail to be posted directly to ange-ftp-lovers, send to one of the
511 ;;; following addresses:
512 ;;;
513 ;;; ange-ftp-lovers@anorman.hpl.hp.com
514 ;;; or
515 ;;; ange-ftp-lovers%anorman.hpl.hp.com@hplb.hpl.hp.com
516 ;;;
517 ;;; Alternatively, there is a mailing list that only gets announcements of new
518 ;;; ange-ftp releases. This is called ange-ftp-lovers-announce, and can be
519 ;;; subscribed to by e-mailing to the -request address as above. Please make
520 ;;; it clear in the request which mailing list you wish to join.
521
522 ;;; The latest version of ange-ftp can usually be obtained via anonymous ftp
523 ;;; from:
524 ;;; alpha.gnu.ai.mit.edu:ange-ftp/ange-ftp.tar.Z
525 ;;; or:
526 ;;; ugle.unit.no:/pub/gnu/emacs-lisp/ange-ftp.tar.Z
527 ;;; or:
528 ;;; archive.cis.ohio-state.edu:pub/gnu/emacs/elisp-archive/packages/ange-ftp.tar.Z
529
530 ;;; The archives for ange-ftp-lovers can be found via anonymous ftp under:
531 ;;;
532 ;;; ftp.reed.edu:pub/mailing-lists/ange-ftp/
533
534 ;;; -----------------------------------------------------------
535 ;;; Technical information on this package:
536 ;;; -----------------------------------------------------------
537
538 ;;; The following GNU Emacs functions are replaced by this package:
539 ;;;
540 ;;; write-region
541 ;;; insert-file-contents
542 ;;; dired-readin
543 ;;; dired-revert
544 ;;; dired-call-process
545 ;;; diff
546 ;;; delete-file
547 ;;; read-file-name-internal
548 ;;; verify-visited-file-modtime
549 ;;; directory-files
550 ;;; backup-buffer
551 ;;; file-directory-p
552 ;;; file-writable-p
553 ;;; file-exists-p
554 ;;; file-readable-p
555 ;;; file-symlink-p
556 ;;; file-attributes
557 ;;; copy-file
558 ;;; rename-file
559 ;;; file-name-as-directory
560 ;;; file-name-directory
561 ;;; file-name-nondirectory
562 ;;; file-name-completion
563 ;;; directory-file-name
564 ;;; expand-file-name
565 ;;; file-name-all-completions
566
567 ;;; LISPDIR ENTRY for the Elisp Archive
568 ;;;
569 ;;; LCD Archive Entry:
570 ;;; ange-ftp|Andy Norman|ange@hplb.hpl.hp.com
571 ;;; |transparent FTP Support for GNU Emacs
572 ;;; |Date: 92/08/14 17:04:34 |Revision: 4.20 |
573
574 ;;; Checklist for adding non-UNIX support for TYPE
575 ;;;
576 ;;; The following functions may need TYPE versions:
577 ;;; (not all functions will be needed for every OS)
578 ;;;
579 ;;; ange-ftp-fix-path-for-TYPE
580 ;;; ange-ftp-fix-dir-path-for-TYPE
581 ;;; ange-ftp-TYPE-host
582 ;;; ange-ftp-TYPE-add-host
583 ;;; ange-ftp-parse-TYPE-listing
584 ;;; ange-ftp-TYPE-delete-file-entry
585 ;;; ange-ftp-TYPE-add-file-entry
586 ;;; ange-ftp-TYPE-file-name-as-directory
587 ;;;
588 ;;; Variables:
589 ;;;
590 ;;; ange-ftp-TYPE-host-regexp
591 ;;; May need to add TYPE to ange-ftp-dumb-host-types
592 ;;;
593 ;;; Check the following functions for OS dependent coding:
594 ;;;
595 ;;; ange-ftp-host-type
596 ;;; ange-ftp-guess-host-type
597 ;;; ange-ftp-allow-child-lookup
598 ;;;
599 ;;; For Tree Dired support:
600 ;;;
601 ;;; ange-ftp-dired-TYPE-insert-headerline
602 ;;; ange-ftp-dired-TYPE-move-to-filename
603 ;;; ange-ftp-dired-TYPE-move-to-end-of-filename
604 ;;; ange-ftp-dired-TYPE-get-filename
605 ;;; ange-ftp-dired-TYPE-between-files
606 ;;; ange-ftp-TYPE-make-compressed-filename
607 ;;; ange-ftp-dired-TYPE-ls-trim
608 ;;; ange-ftp-TYPE-bob-version
609 ;;; ange-ftp-dired-TYPE-clean-directory
610 ;;; ange-ftp-dired-TYPE-flag-backup-files
611 ;;; ange-ftp-dired-TYPE-backup-diff
612 ;;;
613 ;;; Variables for dired:
614 ;;;
615 ;;; ange-ftp-dired-TYPE-re-exe
616 ;;; ange-ftp-dired-TYPE-re-dir
617
618 ;;; Host type conventions:
619 ;;;
620 ;;; The function ange-ftp-host-type and the variable ange-ftp-dired-host-type
621 ;;; (mostly) follow the following conventions for remote host types. At
622 ;;; least, I think that future code should try to follow these conventions,
623 ;;; and the current code should eventually be made compliant.
624 ;;;
625 ;;; nil = local host type, whatever that is (probably unix).
626 ;;; Think nil as in "not a remote host". This value is used by
627 ;;; ange-ftp-dired-host-type for local buffers.
628 ;;;
629 ;;; t = a remote host of unknown type. Think t is in true, it's remote.
630 ;;; Currently, 'unix is used as the default remote host type.
631 ;;; Maybe we should use t.
632 ;;;
633 ;;; 'type = a remote host of TYPE type.
634 ;;;
635 ;;; 'type:list = a remote host of TYPE type, using a specialized ftp listing
636 ;;; program called list. This is currently only used for Unix
637 ;;; dl (descriptive listings), when ange-ftp-dired-host-type
638 ;;; is set to 'unix:dl.
639
640 ;;; Bug report codes:
641 ;;;
642 ;;; Because of their naive faith in this code, there are certain situations
643 ;;; which the writers of this program believe could never happen. However,
644 ;;; being realists they have put calls to 'error in the program at these
645 ;;; points. These errors provide a code, which is an integer, greater than 1.
646 ;;; To aid debugging. the error codes, and the functions in which they reside
647 ;;; are listed below.
648 ;;;
649 ;;; 1: See ange-ftp-ls
650 ;;;
651
652 ;;; XEmacs changes from 4.20
653 ;;;
654 ;;; - added gzip support
655 ;;; - added "lazy" messages
656 ;;; - fixed completion list in the root dir (nil vs (nil))
657 ;;; - use (message nil) to repaint minibuf instead of that awful kludge
658 ;;; - call compute-buffer-file-truename to set truenames properly for
659 ;;; when find-file-compare-truenames is set
660 ;;; - make-directory takes a second optional argument
661 ;;; - made ange-ftp-overwrite-fn use the 19.8 interface to byte-code objects
662 ;;; - made ange-ftp-shell-mode work better with the latest comint
663 ;;; - insert-file-contents takes 2-5 args in v19
664 ;;; - moved invocation of shell-mode to get along with the latest shell-font.el
665 ;;; - implemented ange-ftp-read-passwd in terms of read-passwd (from passwd.el)
666 ;;; - initialize all buffer-local variables to nil
667 ;;; - Apollo stuff from Bob Weiner
668
669
670 ;;; -----------------------------------------------------------
671 ;;; Hall of fame:
672 ;;; -----------------------------------------------------------
673 ;;;
674 ;;; Thanks to Roland McGrath for improving the filename syntax handling,
675 ;;; for suggesting many enhancements and for numerous cleanups to the code.
676 ;;;
677 ;;; Thanks to Jamie Zawinski for bugfixes and for ideas such as gateways.
678 ;;;
679 ;;; Thanks to Ken Laprade for improved .netrc parsing, password reading, and
680 ;;; dired / shell auto-loading.
681 ;;;
682 ;;; Thanks to Sebastian Kremer for tree dired support and for many ideas and
683 ;;; bugfixes.
684 ;;;
685 ;;; Thanks to Joe Wells for bugfixes, the original non-UNIX system support,
686 ;;; VOS support, and hostname completion.
687 ;;;
688 ;;; Thanks to Nakagawa Takayuki for many good ideas, filename-completion, help
689 ;;; with file-name expansion, efficiency worries, stylistic concerns and many
690 ;;; bugfixes.
691 ;;;
692 ;;; Thanks to Sandy Rutherford who re-wrote most of ange-ftp to support VMS,
693 ;;; MTS, CMS and UNIX-dls. Sandy also added dired-support for non-UNIX OS and
694 ;;; auto-recognition of the host type.
695 ;;;
696 ;;; Thanks to Dave Smith who wrote the info file for ange-ftp.
697 ;;;
698 ;;; Finally, thanks to Keith Waclena, Mark D. Baushke, Terence Kelleher, Ping
699 ;;; Zhou, Edward Vielmetti, Jack Repenning, Mike Balenger, Todd Kaufmann,
700 ;;; Kjetil Svarstad, Tom Wurgler, Linus Tolke, Niko Makila, Carl Edman, Bill
701 ;;; Trost, Dave Brennan, Dan Jacobson, Andy Scott, Steve Anderson, Sanjay
702 ;;; Mathur, the folks on the ange-ftp-lovers mailing list and many others
703 ;;; whose names I've forgotten who have helped to debug and fix problems with
704 ;;; ange-ftp.el.
705
706 ;;;; ------------------------------------------------------------
707 ;;;; User customization variables.
708 ;;;; ------------------------------------------------------------
709
710 ;;;###autoload
711 (defvar ange-ftp-path-format
712 '("^/\\(\\([^@/:]*\\)@\\)?\\([^@/:]*\\):\\(.*\\)" . (3 2 4))
713 "*Format of a fully expanded remote pathname. This is a cons
714 \(REGEXP . \(HOST USER PATH\)\), where REGEXP is a regular expression matching
715 the full remote pathname, and HOST, USER, and PATH are the numbers of
716 parenthesized expressions in REGEXP for the components (in that order).")
717
718 ;; ange-ftp-multi-skip-msgs should only match ###-, where ### is one of
719 ;; the number codes corresponding to ange-ftp-good-msgs or ange-ftp-fatal-msgs.
720 ;; Otherwise, ange-ftp will go into multi-skip mode, and never come out.
721
722 ;; XEmacs patch from Bob Weiner
723 (defvar ange-ftp-multi-msgs
724 "^331-\\|^220-\\|^230-\\|^226\\|^25.-\\|^221-\\|^200-\\|^530-\\|^4[25]1-"
725 "*Regular expression matching messages from the ftp process that start
726 a multiline reply.")
727
728 (defvar ange-ftp-good-msgs
729 "^220 \\|^230 \\|^226 \\|^25. \\|^221 \\|^200 \\|^[Hh]ash mark"
730 "*Regular expression matching messages from the ftp process that indicate
731 that the action that was initiated has completed successfully.")
732
733 ;; CMS and the odd VMS machine say 200 Port rather than 200 PORT.
734 ;; Also CMS machines use a multiline 550- reply to say that you
735 ;; don't have write permission. ange-ftp gets into multi-line skip
736 ;; mode and hangs. Have it ignore 550- instead. It will then barf
737 ;; when it gets the 550 line, as it should.
738
739 (defvar ange-ftp-skip-msgs
740 (concat "^200 \\(PORT\\|Port\\) \\|^331 \\|^150 \\|^350 \\|^[0-9]+ bytes \\|"
741 "^Connected \\|^$\\|^Remote system\\|^Using\\|^ \\|Password:\\|"
742 "^local:\\|^Trying\\|^125 \\|^550-\\|^221 .*oodbye")
743 "*Regular expression matching messages from the ftp process that can be
744 ignored.")
745
746 (defvar ange-ftp-fatal-msgs
747 (concat "^ftp: \\|^Not connected\\|^530 \\|^4[25]1 \\|rcmd: \\|"
748 "^No control connection\\|unknown host\\|^lost connection")
749 "*Regular expression matching messages from the FTP process that indicate
750 something has gone drastically wrong attempting the action that was
751 initiated and that the FTP process should (or already has) been killed.")
752
753 (defvar ange-ftp-gateway-fatal-msgs
754 "No route to host\\|Connection closed\\|No such host\\|Login incorrect"
755 "*Regular expression matching messages from the rlogin / telnet process that
756 indicates that logging in to the gateway machine has gone wrong.")
757
758 (defvar ange-ftp-xfer-size-msgs
759 "^150 .* connection for .* (\\([0-9]+\\) bytes)"
760 "*Regular expression used to determine the number of bytes in a FTP transfer.")
761
762 (defvar ange-ftp-tmp-name-template "/tmp/ange-ftp"
763 "*Template used to create temporary files.")
764
765 (defvar ange-ftp-gateway-tmp-name-template "/tmp/ange-ftp"
766 "*Template used to create temporary files when ftp-ing through a gateway.
767 Files starting with this prefix need to be accessible from BOTH the local
768 machine and the gateway machine, and need to have the SAME name on both
769 machines, that is, /tmp is probably NOT what you want, since that is rarely
770 cross-mounted.")
771
772 (defvar ange-ftp-netrc-filename "~/.netrc"
773 "*File in .netrc format to search for passwords.")
774
775 (defvar ange-ftp-disable-netrc-security-check nil
776 "*If non-nil avoid checking permissions on the .netrc file.")
777
778 (defvar ange-ftp-default-user "anonymous"
779 "*User name to use when none is specied in a pathname.
780 If nil, then the name under which the user is logged in is used.
781 If non-nil but not a string, the user is prompted for the name.")
782
783 (defvar ange-ftp-default-password nil
784 "*Password to use when the user is the same as ange-ftp-default-user.")
785
786 (defvar ange-ftp-default-account nil
787 "*Account password to use when the user is the same as ange-ftp-default-user.")
788
789 (defvar ange-ftp-generate-anonymous-password t ;; changed wing@666.com
790 "*If t, use a password of user@host when logging in as the anonymous user.
791 If a string then use that as the password.
792 If nil then prompt the user for a password.")
793
794 (defvar ange-ftp-dumb-unix-host-regexp nil
795 "*If non-nil, if the host being ftp'd to matches this regexp then the FTP
796 process uses the \'dir\' command to get directory information.")
797
798 (defvar ange-ftp-binary-file-name-regexp
799 (concat "\\.g?z$\\|\\.Z$\\|\\.lzh$\\|\\.arc$\\|\\.zip$\\|\\.zoo$\\|\\.tar$\\|"
800 "\\.dvi$\\|\\.ps$\\|\\.elc$\\|TAGS$\\|\\.gif$\\|"
801 "\\.EXE\\(;[0-9]+\\)?$\\|\\.g?z-part-..$\\|\\.Z-part-..$")
802 "*If a file matches this regexp then it is transferred in binary mode.")
803
804 (defvar ange-ftp-gateway-host nil
805 "*Name of host to use as gateway machine when local FTP isn't possible.")
806
807 (defvar ange-ftp-local-host-regexp ".*"
808 "*If a host being FTP'd to matches this regexp then the ftp process is started
809 locally, otherwise the FTP process is started on \`ange-ftp-gateway-host\'
810 instead.")
811
812 (defvar ange-ftp-gateway-program-interactive nil
813 "*If non-nil then the gateway program is expected to connect to the gateway
814 machine and eventually give a shell prompt. Both telnet and rlogin do something
815 like this.")
816
817 (defvar ange-ftp-gateway-program (if (eq system-type 'hpux) "remsh" "rsh")
818 "*Name of program to spawn a shell on the gateway machine. Valid candidates
819 are rsh (remsh on hp-ux), telnet and rlogin. See also the gateway variable
820 above.")
821
822 (defvar ange-ftp-gateway-prompt-pattern "^[^#$%>;]*[#$%>;] *"
823 "*Regexp used to detect that the logging-in sequence is completed on the
824 gateway machine and that the shell is now awaiting input. Make this regexp as
825 strict as possible; it shouldn't match *anything* at all except the user's
826 initial prompt. The above string will fail under most SUN-3's since it
827 matches the login banner.")
828
829 (defvar ange-ftp-gateway-setup-term-command
830 (if (eq system-type 'hpux)
831 "stty -onlcr -echo\n"
832 "stty -echo nl\n")
833 "*Command to use after logging in to the gateway machine to stop the terminal
834 echoing each command and to strip out trailing ^M characters.")
835
836 (defvar ange-ftp-smart-gateway nil
837 "*If the gateway FTP is smart enough to use proxy server, then don't bother
838 telnetting etc, just issue a user@host command instead.")
839
840 (defvar ange-ftp-smart-gateway-port "21"
841 "*Port on gateway machine to use when smart gateway is in operation.")
842
843 (defvar ange-ftp-send-hash t
844 "*If non-nil, send the HASH command to the FTP client.")
845
846 (defvar ange-ftp-binary-hash-mark-size nil
847 "*Default size, in bytes, between hash-marks when transferring a binary file.
848 If NIL, this variable will be locally overridden if the FTP client outputs a
849 suitable response to the HASH command. If non-NIL then this value takes
850 precedence over the local value.")
851
852 (defvar ange-ftp-ascii-hash-mark-size 1024
853 "*Default size, in bytes, between hash-marks when transferring an ASCII file.
854 This variable is buffer-local and will be locally overridden if the FTP client
855 outputs a suitable response to the HASH command.")
856
857 (defvar ange-ftp-process-verbose t
858 "*If non-NIL then be chatty about interaction with the FTP process.")
859
860 (defvar ange-ftp-ftp-program-name "ftp"
861 "*Name of FTP program to run.")
862
863 (defvar ange-ftp-gateway-ftp-program-name "ftp"
864 "*Name of FTP program to run on gateway machine.
865 Some AT&T folks claim to use something called `pftp' here.")
866
867 (defvar ange-ftp-ftp-program-args '("-i" "-n" "-g" "-v")
868 "*A list of arguments passed to the FTP program when started.")
869
870 (defvar ange-ftp-nslookup-program nil
871 "*If non-NIL then a string naming nslookup program." )
872
873 (defvar ange-ftp-make-backup-files ()
874 "*A list of operating systems for which ange-ftp will make Emacs backup
875 files files on the remote host. For example, '\(unix\) makes sense, but
876 '\(unix vms\) or '\(vms\) would be silly, since vms makes its own backups.")
877
878 (defvar ange-ftp-retry-time 5
879 "*Number of seconds to wait before retrying if a file or listing
880 doesn't arrive. This might need to be increased for very slow connections.")
881
882 (defvar ange-ftp-auto-save 0
883 "If 1, allows ange-ftp files to be auto-saved.
884 If 0, suppresses auto-saving of ange-ftp files.
885 Don't use any other value.")
886
887 ;;;; ------------------------------------------------------------
888 ;;;; Hash table support.
889 ;;;; ------------------------------------------------------------
890
891 (require 'backquote)
892
893 (defun ange-ftp-make-hashtable (&optional size)
894 "Make an obarray suitable for use as a hashtable.
895 SIZE, if supplied, should be a prime number."
896 (make-vector (or size 31) 0))
897
898 (defun ange-ftp-map-hashtable (fun tbl)
899 "Call FUNCTION on each key and value in HASHTABLE."
900 (mapatoms
901 (function
902 (lambda (sym)
903 (funcall fun (get sym 'key) (get sym 'val))))
904 tbl))
905
906 (defmacro ange-ftp-make-hash-key (key)
907 "Convert KEY into a suitable key for a hashtable."
908 (` (if (stringp (, key))
909 (, key)
910 (prin1-to-string (, key)))))
911
912 (defun ange-ftp-get-hash-entry (key tbl)
913 "Return the value associated with KEY in HASHTABLE."
914 (let ((sym (intern-soft (ange-ftp-make-hash-key key) tbl)))
915 (and sym (get sym 'val))))
916
917 (defun ange-ftp-put-hash-entry (key val tbl)
918 "Record an association between KEY and VALUE in HASHTABLE."
919 (let ((sym (intern (ange-ftp-make-hash-key key) tbl)))
920 (put sym 'val val)
921 (put sym 'key key)))
922
923 (defun ange-ftp-del-hash-entry (key tbl)
924 "Copy all symbols except KEY in HASHTABLE and return modified hashtable."
925 (let* ((len (length tbl))
926 (new-tbl (ange-ftp-make-hashtable len))
927 (i (1- len)))
928 (ange-ftp-map-hashtable
929 (function
930 (lambda (k v)
931 (or (equal k key)
932 (ange-ftp-put-hash-entry k v new-tbl))))
933 tbl)
934 (while (>= i 0)
935 (aset tbl i (aref new-tbl i))
936 (setq i (1- i)))
937 tbl))
938
939 (defun ange-ftp-hash-entry-exists-p (key tbl)
940 "Return whether there is an association for KEY in TABLE."
941 (intern-soft (ange-ftp-make-hash-key key) tbl))
942
943 (defun ange-ftp-hash-table-keys (tbl)
944 "Return a sorted list of all the active keys in the hashtable, as strings."
945 (sort (all-completions "" tbl)
946 (function string-lessp)))
947
948 ;;;; ------------------------------------------------------------
949 ;;;; Internal variables.
950 ;;;; ------------------------------------------------------------
951
952 (defconst ange-ftp-version "Revision: 4.20.XEmacs")
953
954 (defvar ange-ftp-data-buffer-name " *ftp data*"
955 "Buffer name to hold directory listing data received from ftp process.")
956
957 (defvar ange-ftp-netrc-modtime nil
958 "Last modified time of the netrc file from file-attributes.")
959
960 (defvar ange-ftp-user-hashtable (ange-ftp-make-hashtable)
961 "Hash table holding associations between HOST, USER pairs.")
962
963 (defvar ange-ftp-passwd-hashtable (ange-ftp-make-hashtable)
964 "Mapping between a HOST, USER pair and a PASSWORD for them.")
965
966 (defvar ange-ftp-account-hashtable (ange-ftp-make-hashtable)
967 "Mapping between a HOST, USER pair and a ACCOUNT password for them.")
968
969 (defvar ange-ftp-files-hashtable (ange-ftp-make-hashtable 97)
970 "Hash table for storing directories and their respective files.")
971
972 (defvar ange-ftp-ls-cache-lsargs nil
973 "Last set of args used by ange-ftp-ls.")
974
975 (defvar ange-ftp-ls-cache-file nil
976 "Last file passed to ange-ftp-ls.")
977
978 (defvar ange-ftp-ls-cache-res nil
979 "Last result returned from ange-ftp-ls.")
980
981 ;; New error symbols.
982 ;; XEmacs change
983 (define-error 'ftp-error "FTP error" 'file-error)
984
985 ;;; ------------------------------------------------------------
986 ;;; Match-data support (stolen from Kyle I think)
987 ;;; ------------------------------------------------------------
988
989 (defmacro ange-ftp-save-match-data (&rest body)
990 "Execute the BODY forms, restoring the global value of the match data.
991 Before executing BODY, case-fold-search is locally bound to nil."
992 (let ((original (make-symbol "match-data"))
993 case-fold-search)
994 (list
995 'let (list (list original '(match-data)))
996 (list 'unwind-protect
997 (cons 'progn body)
998 (list 'store-match-data original)))))
999
1000 (put 'ange-ftp-save-match-data 'lisp-indent-hook 0)
1001 (put 'ange-ftp-save-match-data 'edebug-form-hook '(&rest form))
1002
1003 ;;; ------------------------------------------------------------
1004 ;;; Enhanced message support.
1005 ;;; ------------------------------------------------------------
1006
1007 (defun ange-ftp-message (fmt &rest args)
1008 "Output the given message, but truncate to the size of the minibuffer
1009 window."
1010 (let ((msg (apply (function format) fmt args))
1011 (max (window-width (minibuffer-window))))
1012 (if (>= (length msg) max)
1013 (setq msg (concat "> " (substring msg (- 3 max)))))
1014 (message "%s" msg)))
1015
1016 (defvar ange-ftp-lazy-message-time 0)
1017 (defun ange-ftp-lazy-message (fmt &rest args)
1018 "Output the given message, but truncate to the size of the minibuffer
1019 window, and don't print the message if we've printed another message
1020 less than one second ago."
1021 (if (= ange-ftp-lazy-message-time
1022 (setq ange-ftp-lazy-message-time (nth 1 (current-time))))
1023 nil
1024 (apply 'ange-ftp-message fmt args)))
1025
1026 (or (fboundp 'current-time) (fset 'ange-ftp-lazy-message 'ange-ftp-message))
1027
1028
1029 (defun ange-ftp-abbreviate-filename (file &optional new)
1030 "Abbreviate the given filename relative to the default-directory. If the
1031 optional parameter NEW is given and the non-directory parts match, only return
1032 the directory part of the file."
1033 (ange-ftp-save-match-data
1034 (if (and default-directory
1035 (string-match (concat "^"
1036 (regexp-quote default-directory)
1037 ".") file))
1038 (setq file (substring file (1- (match-end 0)))))
1039 (if (and new
1040 (string-equal (file-name-nondirectory file)
1041 (file-name-nondirectory new)))
1042 (setq file (file-name-directory file)))
1043 (or file "./")))
1044
1045 ;;;; ------------------------------------------------------------
1046 ;;;; User / Host mapping support.
1047 ;;;; ------------------------------------------------------------
1048
1049 (defun ange-ftp-set-user (host user)
1050 "For a given HOST, set or change the default USER."
1051 (interactive "sHost: \nsUser: ")
1052 (ange-ftp-put-hash-entry host user ange-ftp-user-hashtable))
1053
1054 (defun ange-ftp-get-user (host)
1055 "Given a HOST, return the default USER."
1056 (ange-ftp-parse-netrc)
1057 (let ((user (ange-ftp-get-hash-entry host ange-ftp-user-hashtable)))
1058 (or user
1059 (prog1
1060 (setq user
1061 (cond ((stringp ange-ftp-default-user)
1062 ;; We have a default name. Use it.
1063 ange-ftp-default-user)
1064 (ange-ftp-default-user
1065 ;; Ask the user.
1066 (let ((enable-recursive-minibuffers t))
1067 (read-string (format "User for %s: " host)
1068 (user-login-name))))
1069 ;; Default to the user's login name.
1070 (t
1071 (user-login-name))))
1072 (ange-ftp-set-user host user)))))
1073
1074 ;;;; ------------------------------------------------------------
1075 ;;;; Password support.
1076 ;;;; ------------------------------------------------------------
1077
1078 (defun ange-ftp-read-passwd (prompt &optional default)
1079 "Read a password from the user.
1080 See documentation of `read-passwd' for more info."
1081 (read-passwd prompt nil default))
1082
1083 ;(defun ange-ftp-read-passwd (prompt &optional default)
1084 ; "Read a password from the user. Echos a . for each character typed.
1085 ;End with RET, LFD, or ESC. DEL or C-h rubs out. ^U kills line.
1086 ;Optional DEFAULT is password to start with."
1087 ; (let ((pass (if default default ""))
1088 ; (c 0)
1089 ; (echo-keystrokes 0)
1090 ; (cursor-in-echo-area t))
1091 ; (while (and (/= c ?\r) (/= c ?\n) (/= c ?\e))
1092 ; (message "%s%s"
1093 ; prompt
1094 ; (make-string (length pass) ?.))
1095 ; (setq c (read-char))
1096 ; (if (= c ?\C-u)
1097 ; (setq pass "")
1098 ; (if (and (/= c ?\b) (/= c ?\177))
1099 ; (setq pass (concat pass (char-to-string c)))
1100 ; (if (> (length pass) 0)
1101 ; (setq pass (substring pass 0 -1))))))
1102 ; (ange-ftp-repaint-minibuffer)
1103 ; (substring pass 0 -1)))
1104
1105 (defmacro ange-ftp-generate-passwd-key (host user)
1106 (` (concat (, host) "/" (, user))))
1107
1108 (defmacro ange-ftp-lookup-passwd (host user)
1109 (` (ange-ftp-get-hash-entry (ange-ftp-generate-passwd-key (, host) (, user))
1110 ange-ftp-passwd-hashtable)))
1111
1112 (defun ange-ftp-set-passwd (host user passwd)
1113 "For a given HOST and USER, set or change the associated PASSWORD."
1114 (interactive (list (read-string "Host: ")
1115 (read-string "User: ")
1116 (ange-ftp-read-passwd "Password: ")))
1117 (ange-ftp-put-hash-entry (ange-ftp-generate-passwd-key host user)
1118 passwd
1119 ange-ftp-passwd-hashtable))
1120
1121 (defun ange-ftp-get-host-with-passwd (user)
1122 "Given a USER, return a host we know the password for."
1123 (ange-ftp-parse-netrc)
1124 (catch 'found-one
1125 (ange-ftp-map-hashtable
1126 (function (lambda (host val)
1127 (if (ange-ftp-lookup-passwd host user)
1128 (throw 'found-one host))))
1129 ange-ftp-user-hashtable)
1130 (ange-ftp-save-match-data
1131 (ange-ftp-map-hashtable
1132 (function
1133 (lambda (key value)
1134 (if (string-match "^[^/]*\\(/\\).*$" key)
1135 (let ((host (substring key 0 (match-beginning 1))))
1136 (if (and (string-equal user (substring key (match-end 1)))
1137 value)
1138 (throw 'found-one host))))))
1139 ange-ftp-passwd-hashtable))
1140 nil))
1141
1142 (defun ange-ftp-get-passwd (host user)
1143 "Given a HOST and USER, return the FTP password, prompting if it was not
1144 previously set."
1145 (ange-ftp-parse-netrc)
1146
1147 ;; look up password in the hash table first; user might have overriden the
1148 ;; defaults.
1149 (cond ((ange-ftp-lookup-passwd host user))
1150
1151 ;; see if default user and password set from the .netrc file.
1152 ((and (stringp ange-ftp-default-user)
1153 ange-ftp-default-password
1154 (string-equal user ange-ftp-default-user))
1155 ange-ftp-default-password)
1156
1157 ;; anonymous ftp password is handled specially since there is an
1158 ;; unwritten rule about how that is used on the Internet.
1159 ((and (or (string-equal user "anonymous")
1160 (string-equal user "ftp"))
1161 ange-ftp-generate-anonymous-password)
1162 (if (stringp ange-ftp-generate-anonymous-password)
1163 ange-ftp-generate-anonymous-password
1164 (concat (user-login-name) "@" (system-name))))
1165
1166 ;; see if same user has logged in to other hosts; if so then prompt
1167 ;; with the password that was used there.
1168 (t
1169 (let* ((other (ange-ftp-get-host-with-passwd user))
1170 (passwd (if other
1171
1172 ;; found another machine with the same user.
1173 ;; Try that account.
1174 (ange-ftp-read-passwd
1175 (format "passwd for %s@%s (same as %s@%s): "
1176 user host user other)
1177 (ange-ftp-lookup-passwd other user))
1178
1179 ;; I give up. Ask the user for the password.
1180 (ange-ftp-read-passwd
1181 (format "Password for %s@%s: " user host)))))
1182 (ange-ftp-set-passwd host user passwd)
1183 passwd))))
1184
1185 ;;;; ------------------------------------------------------------
1186 ;;;; Account support
1187 ;;;; ------------------------------------------------------------
1188
1189 ;; Account passwords must be either specified in the .netrc file, or set
1190 ;; manually by calling ange-ftp-set-account. For the moment, ange-ftp doesn't
1191 ;; check to see whether the FTP process is actually prompting for an account
1192 ;; password.
1193
1194 (defun ange-ftp-set-account (host user account)
1195 "For a given HOST and USER, set or change the associated ACCOUNT password."
1196 (interactive (list (read-string "Host: ")
1197 (read-string "User: ")
1198 (ange-ftp-read-passwd "Account password: ")))
1199 (ange-ftp-put-hash-entry (ange-ftp-generate-passwd-key host user)
1200 account
1201 ange-ftp-account-hashtable))
1202
1203 (defun ange-ftp-get-account (host user)
1204 "Given a HOST and USER, return the FTP account."
1205 (ange-ftp-parse-netrc)
1206 (or (ange-ftp-get-hash-entry (ange-ftp-generate-passwd-key host user)
1207 ange-ftp-account-hashtable)
1208 (and (stringp ange-ftp-default-user)
1209 (string-equal user ange-ftp-default-user)
1210 ange-ftp-default-account)))
1211
1212 ;;;; ------------------------------------------------------------
1213 ;;;; ~/.netrc support
1214 ;;;; ------------------------------------------------------------
1215
1216 (defun ange-ftp-chase-symlinks (file)
1217 "Return the filename that FILENAME references, following all symbolic links."
1218 (let (temp)
1219 (while (setq temp (ange-ftp-real-file-symlink-p file))
1220 (setq file
1221 (if (file-name-absolute-p temp)
1222 temp
1223 (concat (file-name-directory file) temp)))))
1224 file)
1225
1226 (defun ange-ftp-parse-netrc-token (token limit)
1227 "Move along current line looking for the value of the TOKEN. Valid
1228 separators between TOKEN and its value are commas and whitespace.
1229 Second arg LIMIT is a limit for the search."
1230 (if (search-forward token limit t)
1231 (let (beg)
1232 (skip-chars-forward ", \t\r\n" limit)
1233 (if (eq (following-char) ?\") ;quoted token value
1234 (progn (forward-char 1)
1235 (setq beg (point))
1236 (skip-chars-forward "^\"" limit)
1237 (forward-char 1)
1238 (buffer-substring beg (1- (point))))
1239 (setq beg (point))
1240 (skip-chars-forward "^, \t\r\n" limit)
1241 (buffer-substring beg (point))))))
1242
1243 (defun ange-ftp-parse-netrc-group ()
1244 "Extract the values for the tokens \`machine\', \`login\', \`password\'
1245 and \`account\' in the current buffer. If successful, record the information
1246 found."
1247 (beginning-of-line)
1248 (let ((start (point))
1249 (end (progn (re-search-forward "machine\\|default"
1250 (point-max) 'end 2) (point)))
1251 machine login password account)
1252 (goto-char start)
1253 (setq machine (ange-ftp-parse-netrc-token "machine" end)
1254 login (ange-ftp-parse-netrc-token "login" end)
1255 password (ange-ftp-parse-netrc-token "password" end)
1256 account (ange-ftp-parse-netrc-token "account" end))
1257 (if (and machine login)
1258 ;; found a `machine` token.
1259 (progn
1260 (ange-ftp-set-user machine login)
1261 (ange-ftp-set-passwd machine login password)
1262 (and account
1263 (ange-ftp-set-account machine login account)))
1264 (goto-char start)
1265 (if (search-forward "default" end t)
1266 ;; found a `default' token
1267 (progn
1268 (setq login (ange-ftp-parse-netrc-token "login" end)
1269 password (ange-ftp-parse-netrc-token "password" end)
1270 account (ange-ftp-parse-netrc-token "account" end))
1271 (and login
1272 (setq ange-ftp-default-user login))
1273 (and password
1274 (setq ange-ftp-default-password password))
1275 (and account
1276 (setq ange-ftp-default-account account)))))
1277 (goto-char end)))
1278
1279 (defun ange-ftp-parse-netrc ()
1280 "If ~/.netrc file exists and has the correct permissions then extract the
1281 \`machine\', \`login\', \`password\' and \`account\' information from within."
1282
1283 ;; We set this before actually doing it to avoid the possibility
1284 ;; of an infinite loop if ange-ftp-netrc-filename is an FTP file.
1285 (interactive)
1286 (let* ((file (ange-ftp-chase-symlinks
1287 (ange-ftp-real-expand-file-name ange-ftp-netrc-filename)))
1288 (attr (ange-ftp-real-file-attributes file)))
1289 (if (and attr ; file exists.
1290 (not (equal (nth 5 attr) ange-ftp-netrc-modtime))) ; file changed
1291 (ange-ftp-save-match-data
1292 (if (or ange-ftp-disable-netrc-security-check
1293 (and (eq (nth 2 attr) (user-uid)) ; Same uids.
1294 (string-match ".r..------" (nth 8 attr))))
1295 (save-excursion
1296 ;; we are cheating a bit here. I'm trying to do the equivalent
1297 ;; of find-file on the .netrc file, but then nuke it afterwards.
1298 ;; with the bit of logic below we should be able to have
1299 ;; encrypted .netrc files.
1300 (set-buffer (generate-new-buffer "*ftp-.netrc*"))
1301 (ange-ftp-real-insert-file-contents file)
1302 (setq buffer-file-name file)
1303 (setq default-directory (file-name-directory file))
1304 (normal-mode t)
1305 (mapcar 'funcall find-file-hooks)
1306 (setq buffer-file-name nil)
1307 (goto-char (point-min))
1308 (while (not (eobp))
1309 (ange-ftp-parse-netrc-group))
1310 (kill-buffer (current-buffer)))
1311 (ange-ftp-message "%s either not owned by you or badly protected."
1312 ange-ftp-netrc-filename)
1313 (sit-for 1))
1314 (setq ange-ftp-netrc-modtime (nth 5 attr))))))
1315
1316 (defun ange-ftp-generate-root-prefixes ()
1317 "Return a list of prefixes of the form 'user@host:' to be used when
1318 completion is done in the root directory."
1319 (ange-ftp-parse-netrc)
1320 (ange-ftp-save-match-data
1321 (let (res)
1322 (ange-ftp-map-hashtable
1323 (function
1324 (lambda (key value)
1325 (if (string-match "^[^/]*\\(/\\).*$" key)
1326 (let ((host (substring key 0 (match-beginning 1)))
1327 (user (substring key (match-end 1))))
1328 (setq res (cons (list (concat user "@" host ":"))
1329 res))))))
1330 ange-ftp-passwd-hashtable)
1331 (ange-ftp-map-hashtable
1332 (function (lambda (host user)
1333 (setq res (cons (list (concat host ":"))
1334 res))))
1335 ange-ftp-user-hashtable)
1336 ;; (or res (list nil))
1337 res
1338 )))
1339
1340 ;;;; ------------------------------------------------------------
1341 ;;;; Remote pathname syntax support.
1342 ;;;; ------------------------------------------------------------
1343
1344 (defmacro ange-ftp-ftp-path-component (n ns path)
1345 "Extract the Nth ftp path component from NS."
1346 (` (let ((elt (nth (, n) (, ns))))
1347 (if (match-beginning elt)
1348 (substring (, path) (match-beginning elt) (match-end elt))))))
1349
1350 (defvar ange-ftp-ftp-path-arg "")
1351 (defvar ange-ftp-ftp-path-res nil)
1352
1353 (defun ange-ftp-ftp-path (path)
1354 "Parse PATH according to ange-ftp-path-format (which see).
1355 Returns a list (HOST USER PATH), or nil if PATH does not match the format."
1356 (if (string-equal path ange-ftp-ftp-path-arg)
1357 ange-ftp-ftp-path-res
1358 (setq ange-ftp-ftp-path-arg path
1359 ange-ftp-ftp-path-res
1360 (ange-ftp-save-match-data
1361 (if (string-match (car ange-ftp-path-format) path)
1362 (let* ((ns (cdr ange-ftp-path-format))
1363 (host (ange-ftp-ftp-path-component 0 ns path))
1364 (user (ange-ftp-ftp-path-component 1 ns path))
1365 (path (ange-ftp-ftp-path-component 2 ns path)))
1366 (if (zerop (length user))
1367 (setq user (ange-ftp-get-user host)))
1368 (list host user path))
1369 nil)))))
1370
1371 (defun ange-ftp-replace-path-component (fullpath path)
1372 "Take a FULLPATH that matches according to ange-ftp-path-format and
1373 replace the path component with PATH."
1374 (ange-ftp-save-match-data
1375 (if (string-match (car ange-ftp-path-format) fullpath)
1376 (let* ((ns (cdr ange-ftp-path-format))
1377 (elt (nth 2 ns)))
1378 (concat (substring fullpath 0 (match-beginning elt))
1379 path
1380 (substring fullpath (match-end elt)))))))
1381
1382 ;;;; ------------------------------------------------------------
1383 ;;;; Miscellaneous utils.
1384 ;;;; ------------------------------------------------------------
1385
1386 (setq ange-ftp-tmp-keymap (make-sparse-keymap))
1387 (define-key ange-ftp-tmp-keymap "\C-m" 'exit-minibuffer)
1388
1389 (defun ange-ftp-repaint-minibuffer ()
1390 "Gross hack to set minibuf_message = 0, so that the contents of the
1391 minibuffer will show."
1392 (if (eq (selected-window) (minibuffer-window))
1393 (if (string-match "XEmacs" emacs-version)
1394 (message nil)
1395 ;; v18 GNU Emacs
1396 (let ((unread-command-char ?\C-m)
1397 (enable-recursive-minibuffers t))
1398 (read-from-minibuffer "" nil ange-ftp-tmp-keymap nil)))))
1399
1400 (defun ange-ftp-ftp-process-buffer (host user)
1401 "Return the name of the buffer that collects output from the ftp process
1402 connected to the given HOST and USER pair."
1403 (concat "*ftp " user "@" host "*"))
1404
1405 (defun ange-ftp-error (host user msg)
1406 "Display the last chunk of output from the ftp process for the given HOST
1407 USER pair, and signal an error including MSG in the text."
1408 (let ((cur (selected-window))
1409 (pop-up-windows t))
1410 (pop-to-buffer
1411 (get-buffer-create
1412 (ange-ftp-ftp-process-buffer host user)))
1413 (goto-char (point-max))
1414 (select-window cur))
1415 (signal 'ftp-error (list (format "FTP Error: %s" msg))))
1416
1417 (defun ange-ftp-set-buffer-mode ()
1418 "Set the correct modes for the current buffer if it is visiting a remote
1419 file."
1420 (if (and (stringp buffer-file-name)
1421 (ange-ftp-ftp-path buffer-file-name))
1422 (progn
1423 (auto-save-mode ange-ftp-auto-save)
1424 (make-variable-buffer-local 'revert-buffer-function)
1425 (setq revert-buffer-function 'ange-ftp-revert-buffer))))
1426
1427 (defun ange-ftp-kill-ftp-process (buffer)
1428 "If the BUFFER's visited filename or default-directory is an ftp filename
1429 then kill the related ftp process."
1430 (interactive "bKill FTP process associated with buffer: ")
1431 (if (null buffer)
1432 (setq buffer (current-buffer)))
1433 (let ((file (or (buffer-file-name) default-directory)))
1434 (if file
1435 (let ((parsed (ange-ftp-ftp-path (expand-file-name file))))
1436 (if parsed
1437 (let ((host (nth 0 parsed))
1438 (user (nth 1 parsed)))
1439 (kill-buffer (ange-ftp-ftp-process-buffer host user))))))))
1440
1441 (defun ange-ftp-quote-string (string)
1442 "Quote any characters in STRING that may confuse the ftp process."
1443 (apply (function concat)
1444 (mapcar (function
1445 (lambda (char)
1446 (if (or (<= char ? )
1447 (> char ?\~)
1448 (= char ?\")
1449 (= char ?\\))
1450 (vector ?\\ char)
1451 (vector char))))
1452 string)))
1453
1454 (defun ange-ftp-barf-if-not-directory (directory)
1455 (or (file-directory-p directory)
1456 (signal 'file-error
1457 (list "Opening directory"
1458 (if (file-exists-p directory)
1459 "not a directory"
1460 "no such file or directory")
1461 directory))))
1462
1463 ;;;; ------------------------------------------------------------
1464 ;;;; FTP process filter support.
1465 ;;;; ------------------------------------------------------------
1466
1467 (defun ange-ftp-process-handle-line (line proc)
1468 "Look at the given LINE from the ftp process PROC. Try to catagorize it
1469 into one of four categories: good, skip, fatal, or unknown."
1470 (cond ((string-match ange-ftp-xfer-size-msgs line)
1471 (setq ange-ftp-xfer-size
1472 (ash (string-to-int (substring line
1473 (match-beginning 1)
1474 (match-end 1)))
1475 -10)))
1476 ((string-match ange-ftp-skip-msgs line)
1477 (setq ange-ftp-process-multi-skip nil) ;; XEmacs patch (Bob Weiner)
1478 t)
1479 ((string-match ange-ftp-good-msgs line)
1480 (setq ange-ftp-process-busy nil
1481 ange-ftp-process-result t
1482 ange-ftp-process-result-line line))
1483 ((string-match ange-ftp-fatal-msgs line)
1484 (delete-process proc)
1485 (setq ange-ftp-process-busy nil
1486 ange-ftp-process-result-line line))
1487 ((string-match ange-ftp-multi-msgs line)
1488 (setq ange-ftp-process-multi-skip t))
1489 (ange-ftp-process-multi-skip
1490 t)
1491 (t
1492 (setq ange-ftp-process-busy nil
1493 ange-ftp-process-result-line line))))
1494
1495 (defun ange-ftp-process-log-string (proc str)
1496 "For a given PROCESS, log the given STRING at the end of its
1497 associated buffer."
1498 (let ((old-buffer (current-buffer)))
1499 (unwind-protect
1500 (let (moving)
1501 (set-buffer (process-buffer proc))
1502 (setq moving (= (point) (process-mark proc)))
1503 (save-excursion
1504 ;; Insert the text, moving the process-marker.
1505 (goto-char (process-mark proc))
1506 (insert str)
1507 (set-marker (process-mark proc) (point)))
1508 (if moving (goto-char (process-mark proc))))
1509 (set-buffer old-buffer))))
1510
1511 (defun ange-ftp-set-xfer-size (host user bytes)
1512 "Set the size of the next FTP transfer in bytes."
1513 (let ((proc (ange-ftp-get-process host user)))
1514 (if proc
1515 (let ((buf (process-buffer proc)))
1516 (if buf
1517 (save-excursion
1518 (set-buffer buf)
1519 (setq ange-ftp-xfer-size (ash bytes -10))))))))
1520
1521 (defun ange-ftp-process-handle-hash (str)
1522 "Remove hash marks from STRING and display count so far."
1523 (setq str (concat (substring str 0 (match-beginning 0))
1524 (substring str (match-end 0)))
1525 ange-ftp-hash-mark-count (+ (- (match-end 0)
1526 (match-beginning 0))
1527 ange-ftp-hash-mark-count))
1528 (and ange-ftp-process-msg
1529 ange-ftp-process-verbose
1530 (not (eq (selected-window) (minibuffer-window)))
1531 (not (boundp 'search-message)) ;screws up isearch otherwise
1532 (not cursor-in-echo-area) ;screws up y-or-n-p otherwise
1533 (let ((kbytes (ash (* ange-ftp-hash-mark-unit
1534 ange-ftp-hash-mark-count)
1535 -6)))
1536 (if (zerop ange-ftp-xfer-size)
1537 (ange-ftp-lazy-message "%s...%dk" ange-ftp-process-msg kbytes)
1538 (let ((percent (/ (* 100 kbytes) ange-ftp-xfer-size)))
1539 ;; cut out the redisplay of identical %-age messages.
1540 (if (not (eq percent ange-ftp-last-percent))
1541 (progn
1542 (setq ange-ftp-last-percent percent)
1543 (ange-ftp-lazy-message "%s...%d%%"
1544 ange-ftp-process-msg percent)))))))
1545 str)
1546
1547 (defun ange-ftp-call-cont (cont result line)
1548 "Call the function specified by CONT. CONT can be either a function or a
1549 list of a function and some args. The first two parameters passed to the
1550 function will be RESULT and LINE. The remaining args will be taken from CONT
1551 if a list was passed."
1552 (if cont
1553 (if (and (listp cont)
1554 (not (eq (car cont) 'lambda)))
1555 (apply (car cont) result line (cdr cont))
1556 (funcall cont result line))))
1557
1558 (defun ange-ftp-process-filter (proc str)
1559 "Build up a complete line of output from the ftp PROCESS and pass it
1560 on to ange-ftp-process-handle-line to deal with."
1561 (let ((buffer (process-buffer proc))
1562 (old-buffer (current-buffer)))
1563
1564 ;; see if the buffer is still around... it could have been deleted.
1565 (if (buffer-name buffer)
1566 (unwind-protect
1567 (ange-ftp-save-match-data
1568 (set-buffer (process-buffer proc))
1569
1570 ;; handle hash mark printing
1571 (and ange-ftp-hash-mark-unit
1572 ange-ftp-process-busy
1573 (string-match "^#+$" str)
1574 (setq str (ange-ftp-process-handle-hash str)))
1575 (ange-ftp-process-log-string proc str)
1576 (if ange-ftp-process-busy
1577 (progn
1578 (setq ange-ftp-process-string (concat ange-ftp-process-string
1579 str))
1580
1581 ;; if we gave an empty password to the USER command earlier
1582 ;; then we should send a null password now.
1583 (if (string-match "Password: *$" ange-ftp-process-string)
1584 (send-string proc "\n"))))
1585 (while (and ange-ftp-process-busy
1586 (string-match "\n" ange-ftp-process-string))
1587 (let ((line (substring ange-ftp-process-string
1588 0
1589 (match-beginning 0))))
1590 (setq ange-ftp-process-string (substring ange-ftp-process-string
1591 (match-end 0)))
1592 (while (string-match "^ftp> *" line)
1593 (setq line (substring line (match-end 0))))
1594 (ange-ftp-process-handle-line line proc)))
1595
1596 ;; has the ftp client finished? if so then do some clean-up
1597 ;; actions.
1598 (if (not ange-ftp-process-busy)
1599 (progn
1600 ;; reset the xfer size
1601 (setq ange-ftp-xfer-size 0)
1602
1603 ;; issue the "done" message since we've finished.
1604 (if (and ange-ftp-process-msg
1605 ange-ftp-process-verbose
1606 ange-ftp-process-result)
1607 (progn
1608 (ange-ftp-message "%s...done" ange-ftp-process-msg)
1609 (ange-ftp-repaint-minibuffer)
1610 (setq ange-ftp-process-msg nil)))
1611
1612 ;; is there a continuation we should be calling? if so,
1613 ;; we'd better call it, making sure we only call it once.
1614 (if ange-ftp-process-continue
1615 (let ((cont ange-ftp-process-continue))
1616 (setq ange-ftp-process-continue nil)
1617 (ange-ftp-call-cont cont
1618 ange-ftp-process-result
1619 ange-ftp-process-result-line))))))
1620 (set-buffer old-buffer)))))
1621
1622 (defun ange-ftp-process-sentinel (proc str)
1623 "When ftp process changes state, nuke all file-entries in cache."
1624 (ange-ftp-save-match-data
1625 (let ((name (process-name proc)))
1626 (if (string-match "\\*ftp \\([^@]+\\)@\\([^*]+\\)*" name)
1627 (let ((user (substring name (match-beginning 1) (match-end 1)))
1628 (host (substring name (match-beginning 2) (match-end 2))))
1629 (ange-ftp-wipe-file-entries host user))))
1630 (setq ange-ftp-ls-cache-file nil)))
1631
1632 ;;;; ------------------------------------------------------------
1633 ;;;; Gateway support.
1634 ;;;; ------------------------------------------------------------
1635
1636 (defun ange-ftp-use-gateway-p (host)
1637 "Returns whether to access this host via a normal (non-smart) gateway."
1638 ;; yes, I know that I could simplify the following expression, but it is
1639 ;; clearer (to me at least) this way.
1640 (and (not ange-ftp-smart-gateway)
1641 (ange-ftp-save-match-data
1642 (not (string-match ange-ftp-local-host-regexp host)))))
1643
1644 (defun ange-ftp-use-smart-gateway-p (host)
1645 "Returns whether to access this host via a smart gateway."
1646 (and ange-ftp-smart-gateway
1647 (ange-ftp-save-match-data
1648 (not (string-match ange-ftp-local-host-regexp host)))))
1649
1650
1651 ;;; ------------------------------------------------------------
1652 ;;; Temporary file location and deletion...
1653 ;;; ------------------------------------------------------------
1654
1655 (defvar ange-ftp-tmp-name-files ())
1656 (defvar ange-ftp-tmp-name-hashtable (ange-ftp-make-hashtable 10))
1657 (defvar ange-ftp-pid nil)
1658
1659 (defun ange-ftp-get-pid ()
1660 "Half-hearted attempt to get the current process's id."
1661 (setq ange-ftp-pid (substring (make-temp-name "") 1)))
1662
1663 (defun ange-ftp-make-tmp-name (host)
1664 "This routine will return the name of a new file."
1665 (let* ((template (if (ange-ftp-use-gateway-p host)
1666 ange-ftp-gateway-tmp-name-template
1667 ange-ftp-tmp-name-template))
1668 (pid (or ange-ftp-pid (ange-ftp-get-pid)))
1669 (start ?a)
1670 file entry)
1671 (while
1672 (progn
1673 (setq file (format "%s%c%s" template start pid))
1674 (setq entry (intern file ange-ftp-tmp-name-hashtable))
1675 (or (memq entry ange-ftp-tmp-name-files)
1676 (ange-ftp-real-file-exists-p file)))
1677 (if (> (setq start (1+ start)) ?z)
1678 (progn
1679 (setq template (concat template "X"))
1680 (setq start ?a))))
1681 (setq ange-ftp-tmp-name-files
1682 (cons entry ange-ftp-tmp-name-files))
1683 file))
1684
1685 (defun ange-ftp-del-tmp-name (temp)
1686 (setq ange-ftp-tmp-name-files
1687 (delq (intern temp ange-ftp-tmp-name-hashtable)
1688 ange-ftp-tmp-name-files))
1689 (condition-case ()
1690 (ange-ftp-real-delete-file temp)
1691 (error nil)))
1692
1693 ;;;; ------------------------------------------------------------
1694 ;;;; Interactive gateway program support.
1695 ;;;; ------------------------------------------------------------
1696
1697 (defvar ange-ftp-gwp-running t)
1698 (defvar ange-ftp-gwp-status nil)
1699
1700 (defun ange-ftp-gwp-sentinel (proc str)
1701 (setq ange-ftp-gwp-running nil))
1702
1703 (defun ange-ftp-gwp-filter (proc str)
1704 (ange-ftp-save-match-data
1705 (ange-ftp-process-log-string proc str)
1706 (cond ((string-match "login: *$" str)
1707 (send-string proc
1708 (concat
1709 (let ((ange-ftp-default-user t))
1710 (ange-ftp-get-user ange-ftp-gateway-host))
1711 "\n")))
1712 ((string-match "Password: *$" str)
1713 (send-string proc
1714 (concat
1715 (ange-ftp-get-passwd ange-ftp-gateway-host
1716 (ange-ftp-get-user
1717 ange-ftp-gateway-host))
1718 "\n")))
1719 ((string-match ange-ftp-gateway-fatal-msgs str)
1720 (delete-process proc)
1721 (setq ange-ftp-gwp-running nil))
1722 ((string-match ange-ftp-gateway-prompt-pattern str)
1723 (setq ange-ftp-gwp-running nil
1724 ange-ftp-gwp-status t)))))
1725
1726 (defun ange-ftp-gwp-start (host user name args)
1727 "Login to the gateway machine and fire up an ftp process."
1728 (let* ((gw-user (ange-ftp-get-user ange-ftp-gateway-host))
1729 (proc (start-process name name
1730 ange-ftp-gateway-program
1731 ange-ftp-gateway-host))
1732 (ftp (mapconcat (function identity) args " ")))
1733 (process-kill-without-query proc)
1734 (set-process-sentinel proc (function ange-ftp-gwp-sentinel))
1735 (set-process-filter proc (function ange-ftp-gwp-filter))
1736 (set-marker (process-mark proc) (point))
1737 (setq ange-ftp-gwp-running t
1738 ange-ftp-gwp-status nil)
1739 (ange-ftp-message "Connecting to gateway %s..." ange-ftp-gateway-host)
1740 (while ange-ftp-gwp-running ;perform login sequence
1741 (accept-process-output proc))
1742 (if (not ange-ftp-gwp-status)
1743 (ange-ftp-error host user "unable to login to gateway"))
1744 (ange-ftp-message "Connecting to gateway %s...done" ange-ftp-gateway-host)
1745 (setq ange-ftp-gwp-running t
1746 ange-ftp-gwp-status nil)
1747 (process-send-string proc ange-ftp-gateway-setup-term-command)
1748 (while ange-ftp-gwp-running ;zap ^M's and double echoing.
1749 (accept-process-output proc))
1750 (if (not ange-ftp-gwp-status)
1751 (ange-ftp-error host user "unable to set terminal modes on gateway"))
1752 (setq ange-ftp-gwp-running t
1753 ange-ftp-gwp-status nil)
1754 (process-send-string proc (concat "exec " ftp "\n")) ;spawn ftp process
1755 proc))
1756
1757 ;;;; ------------------------------------------------------------
1758 ;;;; Support for sending commands to the ftp process.
1759 ;;;; ------------------------------------------------------------
1760
1761 (defun ange-ftp-raw-send-cmd (proc cmd &optional msg cont nowait)
1762 "Low-level routine to send the given ftp CMD to the ftp PROCESS.
1763 MSG is an optional message to output before and after the command.
1764 If CONT is non-NIL then it is either a function or a list of function and
1765 some arguments. The function will be called when the ftp command has completed.
1766 If CONT is NIL then this routine will return \( RESULT . LINE \) where RESULT
1767 is whether the command was successful, and LINE is the line from the FTP
1768 process that caused the command to complete.
1769 If NOWAIT is given then the routine will return immediately the command has
1770 been queued with no result. CONT will still be called, however."
1771 (if (memq (process-status proc) '(run open))
1772 (save-excursion
1773 (set-buffer (process-buffer proc))
1774 (while ange-ftp-process-busy
1775 (accept-process-output))
1776 (setq ange-ftp-process-string ""
1777 ange-ftp-process-result-line ""
1778 ange-ftp-process-busy t
1779 ange-ftp-process-result nil
1780 ange-ftp-process-multi-skip nil
1781 ange-ftp-process-msg msg
1782 ange-ftp-process-continue cont
1783 ange-ftp-hash-mark-count 0
1784 ange-ftp-last-percent -1
1785 cmd (concat cmd "\n"))
1786 (and msg ange-ftp-process-verbose (ange-ftp-message "%s..." msg))
1787 (goto-char (point-max))
1788 ; (move-marker last-input-start (point))
1789 ;; don't insert the password into the buffer on the USER command.
1790 (ange-ftp-save-match-data
1791 (if (string-match "^user \"[^\"]*\"" cmd)
1792 (insert (substring cmd 0 (match-end 0)) " Turtle Power!\n")
1793 (insert cmd)))
1794 ; (move-marker last-input-end (point))
1795 (send-string proc cmd)
1796 (set-marker (process-mark proc) (point))
1797 (if nowait
1798 nil
1799 ;; hang around for command to complete
1800 (while ange-ftp-process-busy
1801 (accept-process-output proc))
1802 (if cont
1803 nil ;cont has already been called
1804 (cons ange-ftp-process-result ange-ftp-process-result-line))))))
1805
1806 (defun ange-ftp-nslookup-host (host)
1807 "Attempt to resolve the given HOSTNAME using nslookup if possible."
1808 (interactive "sHost: ")
1809 (if ange-ftp-nslookup-program
1810 (let ((proc (start-process " *nslookup*" " *nslookup*"
1811 ange-ftp-nslookup-program host))
1812 (res host))
1813 (process-kill-without-query proc)
1814 (save-excursion
1815 (set-buffer (process-buffer proc))
1816 (while (memq (process-status proc) '(run open))
1817 (accept-process-output proc))
1818 (goto-char (point-min))
1819 (if (re-search-forward "Name:.*\nAddress: *\\(.*\\)$" nil t)
1820 (setq res (buffer-substring (match-beginning 1)
1821 (match-end 1))))
1822 (kill-buffer (current-buffer)))
1823 res)
1824 host))
1825
1826 (defun ange-ftp-start-process (host user name)
1827 "Spawn a new ftp process ready to connect to machine HOST and give it NAME.
1828 If HOST is only ftp-able through a gateway machine then spawn a shell
1829 on the gateway machine to do the ftp instead."
1830 (let* ((use-gateway (ange-ftp-use-gateway-p host))
1831 (ftp-prog (if use-gateway
1832 ange-ftp-gateway-ftp-program-name
1833 ange-ftp-ftp-program-name))
1834 (args (append (list ftp-prog) ange-ftp-ftp-program-args))
1835 (saved-term-var (getenv "TERM"))
1836 proc)
1837 ;; fix problems in losing Linux FTP's, which like to output
1838 ;; ESC sequences to highlight the ftp prompt, which messes things up
1839 (unwind-protect
1840 (progn
1841 (setenv "TERM" "dumb")
1842 (if use-gateway
1843 (if ange-ftp-gateway-program-interactive
1844 (setq proc (ange-ftp-gwp-start host user name args))
1845 (setq proc (apply 'start-process name name
1846 (append (list ange-ftp-gateway-program
1847 ange-ftp-gateway-host)
1848 args))))
1849 (setq proc (apply 'start-process name name args)))
1850 (process-kill-without-query proc)
1851 (set-process-sentinel proc (function ange-ftp-process-sentinel))
1852 (set-process-filter proc (function ange-ftp-process-filter)))
1853 (setenv "TERM" saved-term-var))
1854 ;; jwz: turn on shell mode after setting the proc filter for the
1855 ;; benefit of shell-font.
1856 (require 'shell)
1857 (save-excursion
1858 (set-buffer (process-buffer proc))
1859 (ange-ftp-shell-mode))
1860 (accept-process-output proc) ;wait for ftp startup message
1861 proc))
1862
1863 (defun ange-ftp-smart-login (host user pass account proc)
1864 "Connect to the FTP-server on HOST as USER using PASSWORD and ACCOUNT.
1865 PROC is the FTP-client's process. This routine uses the smart-gateway
1866 host specified in ``ange-ftp-gateway-host''."
1867 (let ((result (ange-ftp-raw-send-cmd
1868 proc
1869 (format "open %s %s"
1870 (ange-ftp-nslookup-host ange-ftp-gateway-host)
1871 ange-ftp-smart-gateway-port)
1872 (format "Opening FTP connection to %s via %s"
1873 host
1874 ange-ftp-gateway-host))))
1875 (or (car result)
1876 (ange-ftp-error host user
1877 (concat "OPEN request failed: "
1878 (cdr result))))
1879 (setq result (ange-ftp-raw-send-cmd
1880 proc (format "user \"%s\"@%s %s %s"
1881 user
1882 (ange-ftp-nslookup-host host)
1883 pass
1884 account)
1885 (format "Logging in as user %s@%s"
1886 user host)))
1887 (or (car result)
1888 (progn
1889 (ange-ftp-set-passwd host user nil) ; reset password
1890 (ange-ftp-set-account host user nil) ; reset account
1891 (ange-ftp-error host user
1892 (concat "USER request failed: "
1893 (cdr result)))))))
1894
1895 (defun ange-ftp-normal-login (host user pass account proc)
1896 "Connect to the FTP-server on HOST as USER using PASSWORD and ACCOUNT.
1897 PROC is the process to the FTP-client."
1898 (let ((result (ange-ftp-raw-send-cmd
1899 proc
1900 (format "open %s" (ange-ftp-nslookup-host host))
1901 (format "Opening FTP connection to %s" host))))
1902 (or (car result)
1903 (ange-ftp-error host user
1904 (concat "OPEN request failed: "
1905 (cdr result))))
1906 (setq result (ange-ftp-raw-send-cmd
1907 proc
1908 (format "user \"%s\" %s %s" user pass account)
1909 (format "Logging in as user %s@%s" user host)))
1910 (or (car result)
1911 (progn
1912 (ange-ftp-set-passwd host user nil) ;reset password.
1913 (ange-ftp-set-account host user nil) ;reset account.
1914 (ange-ftp-error host user
1915 (concat "USER request failed: "
1916 (cdr result)))))))
1917
1918 (defvar ange-ftp-hash-mark-msgs
1919 "[hH]ash mark [^0-9]*\\([0-9]+\\)"
1920 "*Regexp matching the FTP client's output upon doing a HASH command.")
1921
1922 (defun ange-ftp-guess-hash-mark-size (proc)
1923 (if ange-ftp-send-hash
1924 (save-excursion
1925 (set-buffer (process-buffer proc))
1926 (let* ((status (ange-ftp-raw-send-cmd proc "hash"))
1927 (result (car status))
1928 (line (cdr status)))
1929 (ange-ftp-save-match-data
1930 (if (string-match ange-ftp-hash-mark-msgs line)
1931 (let ((size (string-to-int
1932 (substring line
1933 (match-beginning 1)
1934 (match-end 1)))))
1935 (setq ange-ftp-ascii-hash-mark-size size
1936 ange-ftp-hash-mark-unit (ash size -4))
1937
1938 ;; if a default value for this is set, use that value.
1939 (or ange-ftp-binary-hash-mark-size
1940 (setq ange-ftp-binary-hash-mark-size size)))))))))
1941
1942 (defun ange-ftp-get-process (host user)
1943 "Return the process object for a FTP process connected to HOST and
1944 logged in as USER. Create a new process if needed."
1945 (let* ((name (ange-ftp-ftp-process-buffer host user))
1946 (proc (get-process name)))
1947 (if (and proc (memq (process-status proc) '(run open)))
1948 proc
1949 (let ((pass (ange-ftp-quote-string
1950 (ange-ftp-get-passwd host user)))
1951 (account (ange-ftp-quote-string
1952 (ange-ftp-get-account host user))))
1953 ;; grab a suitable process.
1954 (setq proc (ange-ftp-start-process host user name))
1955
1956 ;; login to FTP server.
1957 (if (ange-ftp-use-smart-gateway-p host)
1958 (ange-ftp-smart-login host user pass account proc)
1959 (ange-ftp-normal-login host user pass account proc))
1960
1961 ;; Tell client to send back hash-marks as progress. It isn't usually
1962 ;; fatal if this command fails.
1963 (ange-ftp-guess-hash-mark-size proc)
1964
1965 ;; Guess at the host type.
1966 (ange-ftp-guess-host-type host user)
1967
1968 ;; Run any user-specified hooks. Note that proc, host and user are
1969 ;; dynamically bound at this point.
1970 (run-hooks 'ange-ftp-process-startup-hook))
1971 proc)))
1972
1973 ;; Variables for caching host and host-type
1974 (defvar ange-ftp-host-cache nil)
1975 (defvar ange-ftp-host-type-cache nil)
1976
1977 ;; If ange-ftp-host-type is called with the optional user
1978 ;; argument, it will attempt to guess the host type by connecting
1979 ;; as user, if necessary. For efficiency, I have tried to give this
1980 ;; optional second argument only when necessary. Have I missed any calls
1981 ;; to ange-ftp-host-type where it should have been supplied?
1982
1983 (defun ange-ftp-host-type (host &optional user)
1984 "Return a symbol which represents the type of the HOST given.
1985 If the optional argument USER is given, attempts to guess the
1986 host-type by logging in as USER."
1987 (if (eq host ange-ftp-host-cache)
1988 ange-ftp-host-type-cache
1989 ;; Trigger an ftp connection, in case we need to guess at the host type.
1990 (if (and user (ange-ftp-get-process host user) (eq host ange-ftp-host-cache))
1991 ange-ftp-host-type-cache
1992 (setq ange-ftp-host-cache host
1993 ange-ftp-host-type-cache
1994 (cond ((ange-ftp-dumb-unix-host host)
1995 'dumb-unix)
1996 ((and (fboundp 'ange-ftp-vos-host)
1997 (ange-ftp-vos-host host))
1998 'vos)
1999 ((and (fboundp 'ange-ftp-vms-host)
2000 (ange-ftp-vms-host host))
2001 'vms)
2002 ((and (fboundp 'ange-ftp-mts-host)
2003 (ange-ftp-mts-host host))
2004 'mts)
2005 ((and (fboundp 'ange-ftp-cms-host)
2006 (ange-ftp-cms-host host))
2007 'cms)
2008 (t
2009 'unix))))))
2010
2011 ;; It would be nice to abstract the functions ange-ftp-TYPE-host and
2012 ;; ange-ftp-add-TYPE-host. The trick is to abstract these functions
2013 ;; without sacrificing speed. Also, having separate variables
2014 ;; ange-ftp-TYPE-regexp is more user friendly then requiring the user to
2015 ;; set an alist to indicate that a host is of a given type. Even with
2016 ;; automatic host type recognition, setting a regexp is still a good idea
2017 ;; (for efficiency) if you log into a particular non-UNIX host frequently.
2018
2019 (defvar ange-ftp-fix-path-func-alist nil
2020 "Association list of \( TYPE \. FUNC \) pairs, where FUNC is a routine
2021 which can change a UNIX path into a path more suitable for a host of type
2022 TYPE.")
2023
2024 (defvar ange-ftp-fix-dir-path-func-alist nil
2025 "Association list of \( TYPE \. FUNC \) pairs, where FUNC is a routine
2026 which can change UNIX directory path into a directory path more suitable
2027 for a host of type TYPE.")
2028
2029 ;; *** Perhaps the sense of this variable should be inverted, since there
2030 ;; *** is only 1 host type that can take ls-style listing options.
2031 (defvar ange-ftp-dumb-host-types '(dumb-unix)
2032 "List of host types that can't take UNIX ls-style listing options.")
2033
2034 (defun ange-ftp-send-cmd (host user cmd &optional msg cont nowait)
2035 "Find an ftp process connected to HOST logged in as USER and send it CMD.
2036 MSG is an optional status message to be output before and after issuing the
2037 command.
2038 See the documentation for ange-ftp-raw-send-cmd for a description of CONT
2039 and NOWAIT."
2040 ;; Handle conversion to remote pathname syntax and remote ls option
2041 ;; capability.
2042 (let ((cmd0 (car cmd))
2043 (cmd1 (nth 1 cmd))
2044 cmd2 cmd3 host-type fix-pathname-func)
2045
2046 (cond
2047
2048 ;; pwd case (We don't care what host-type.)
2049 ((null cmd1))
2050
2051 ;; cmd == 'dir "remote-path" "local-path" "ls-switches"
2052 ((progn
2053 (setq cmd2 (nth 2 cmd)
2054 host-type (ange-ftp-host-type host user))
2055 ;; This will trigger an FTP login, if one doesn't exist
2056 (eq cmd0 'dir))
2057 (setq cmd1 (funcall
2058 (or (cdr (assq host-type ange-ftp-fix-dir-path-func-alist))
2059 'identity)
2060 cmd1)
2061 cmd3 (nth 3 cmd))
2062 ;; Need to deal with the HP-UX ftp bug. This should also allow
2063 ;; us to resolve symlinks to directories on SysV machines. (Sebastian will
2064 ;; be happy.)
2065 (and (eq host-type 'unix)
2066 (string-match "/$" cmd1)
2067 (not (string-match "R" cmd3))
2068 (setq cmd1 (concat cmd1 ".")))
2069 ;; If the remote ls can take switches, put them in
2070 (or (memq host-type ange-ftp-dumb-host-types)
2071 (setq cmd0 'ls
2072 cmd1 (format "\"%s %s\"" cmd3 cmd1))))
2073
2074 ;; First argument is the remote pathname
2075 ((progn
2076 (setq fix-pathname-func (or (cdr (assq host-type
2077 ange-ftp-fix-path-func-alist))
2078 'identity))
2079 (memq cmd0 '(get delete mkdir rmdir cd)))
2080 (setq cmd1 (funcall fix-pathname-func cmd1)))
2081
2082 ;; Second argument is the remote pathname
2083 ((memq cmd0 '(append put chmod))
2084 (setq cmd2 (funcall fix-pathname-func cmd2)))
2085
2086 ;; Both arguments are remote pathnames
2087 ((eq cmd0 'rename)
2088 (setq cmd1 (funcall fix-pathname-func cmd1)
2089 cmd2 (funcall fix-pathname-func cmd2))))
2090
2091 ;; Turn the command into one long string
2092 (setq cmd0 (symbol-name cmd0))
2093 (setq cmd (concat cmd0
2094 (and cmd1 (concat " " cmd1))
2095 (and cmd2 (concat " " cmd2))))
2096
2097 ;; Actually send the resulting command.
2098 (let (afsc-result
2099 afsc-line)
2100 (ange-ftp-raw-send-cmd
2101 (ange-ftp-get-process host user)
2102 cmd
2103 msg
2104 (list
2105 (function (lambda (result line host user
2106 cmd msg cont nowait)
2107 (or cont
2108 (setq afsc-result result
2109 afsc-line line))
2110 (if result
2111 (ange-ftp-call-cont cont result line)
2112 (ange-ftp-raw-send-cmd
2113 (ange-ftp-get-process host user)
2114 cmd
2115 msg
2116 (list
2117 (function (lambda (result line cont)
2118 (or cont
2119 (setq afsc-result result
2120 afsc-line line))
2121 (ange-ftp-call-cont cont result line)))
2122 cont)
2123 nowait))))
2124 host user cmd msg cont nowait)
2125 nowait)
2126
2127 (if nowait
2128 nil
2129 (if cont
2130 nil
2131 (cons afsc-result afsc-line))))))
2132
2133 ;; It might be nice to message users about the host type identified,
2134 ;; but there is so much other messaging going on, it would not be
2135 ;; seen. No point in slowing things down just so users can read
2136 ;; a host type message.
2137
2138 (defconst ange-ftp-cms-path-template
2139 (concat
2140 "^[-A-Z0-9$*][-A-Z0-9$*]?[-A-Z0-9$*]?[-A-Z0-9$*]?[-A-Z0-9$*]?"
2141 "[-A-Z0-9$*]?[-A-Z0-9$*]?[-A-Z0-9$*]?\\.[0-9][0-9][0-9A-Z]$"))
2142 (defconst ange-ftp-vms-path-template
2143 "^[-A-Z0-9_$]+:\\[[-A-Z0-9_$]+\\(\\.[-A-Z0-9_$]+\\)*\\]$")
2144 (defconst ange-ftp-mts-path-template
2145 "^[A-Z0-9._][A-Z0-9._][A-Z0-9._][A-Z0-9._]:$")
2146
2147 (defun ange-ftp-guess-host-type (host user)
2148 "Guess at the the host type of HOST by doing a pwd, and examining
2149 the directory syntax."
2150 (let ((host-type (ange-ftp-host-type host))
2151 (key (concat host "/" user "/~")))
2152 (if (eq host-type 'unix)
2153 ;; Note that ange-ftp-host-type returns unix as the default value.
2154 (ange-ftp-save-match-data
2155 (let* ((result (ange-ftp-get-pwd host user))
2156 (dir (car result))
2157 fix-path-func)
2158 (cond ((null dir)
2159 (message "Warning! Unable to get home directory")
2160 (sit-for 1)
2161 (if (string-match
2162 "^450 No current working directory defined$"
2163 (cdr result))
2164
2165 ;; We'll assume that if pwd bombs with this
2166 ;; error message, then it's CMS.
2167 (progn
2168 (ange-ftp-add-cms-host host)
2169 (setq ange-ftp-host-cache host
2170 ange-ftp-host-type-cache 'cms))))
2171
2172 ;; try for VMS
2173 ((string-match ange-ftp-vms-path-template dir)
2174 (ange-ftp-add-vms-host host)
2175 ;; The add-host functions clear the host type cache.
2176 ;; Therefore, need to set the cache afterwards.
2177 (setq ange-ftp-host-cache host
2178 ange-ftp-host-type-cache 'vms))
2179
2180 ;; try for MTS
2181 ((string-match ange-ftp-mts-path-template dir)
2182 (ange-ftp-add-mts-host host)
2183 (setq ange-ftp-host-cache host
2184 ange-ftp-host-type-cache 'mts))
2185
2186 ;; try for CMS
2187 ((string-match ange-ftp-cms-path-template dir)
2188 (ange-ftp-add-cms-host host)
2189 (setq ange-ftp-host-cache host
2190 ange-ftp-host-type-cache 'cms))
2191
2192 ;; assume UN*X
2193 (t
2194 (setq ange-ftp-host-cache host
2195 ange-ftp-host-type-cache 'unix)))
2196
2197 ;; Now that we have done a pwd, might as well put it in
2198 ;; the expand-dir hashtable.
2199 (setq fix-path-func (cdr (assq ange-ftp-host-type-cache
2200 ange-ftp-fix-path-func-alist)))
2201 (if fix-path-func
2202 (setq dir (funcall fix-path-func dir 'reverse)))
2203 (ange-ftp-put-hash-entry key dir
2204 ange-ftp-expand-dir-hashtable))))
2205
2206 ;; In the special case of CMS make sure that know the
2207 ;; expansion of the home minidisk now, because we will
2208 ;; be doing a lot of cd's.
2209 (if (and (eq host-type 'cms)
2210 (not (ange-ftp-hash-entry-exists-p
2211 key ange-ftp-expand-dir-hashtable)))
2212 (let ((dir (car (ange-ftp-get-pwd host user))))
2213 (if dir
2214 (ange-ftp-put-hash-entry key (concat "/" dir)
2215 ange-ftp-expand-dir-hashtable)
2216 (message "Warning! Unable to get home directory")
2217 (sit-for 1))))))
2218
2219
2220 ;;;; ------------------------------------------------------------
2221 ;;;; Simple FTP process shell support.
2222 ;;;; ------------------------------------------------------------
2223
2224 (defvar ange-ftp-shell-mode-map nil)
2225
2226 (defun ange-ftp-shell-mode ()
2227 "Major mode for interacting with an FTP process.
2228 Return at end of buffer sends line as input.
2229 Return not at end copies rest of line to end and sends it.
2230
2231 The following commands imitate the usual Unix interrupt and editing
2232 control characters:
2233 \\{ange-ftp-shell-mode-map}
2234 Runs ange-ftp-shell-mode-hook if not nil."
2235 (interactive)
2236 (let ((proc (get-buffer-process (current-buffer))))
2237 (kill-all-local-variables)
2238 (shell-mode)
2239 (if (null ange-ftp-shell-mode-map)
2240 (progn
2241 (setq ange-ftp-shell-mode-map (make-sparse-keymap))
2242 (set-keymap-parent ange-ftp-shell-mode-map shell-mode-map)
2243 (set-keymap-name ange-ftp-shell-mode-map 'ange-ftp-shell-mode-map)))
2244 (use-local-map ange-ftp-shell-mode-map)
2245 (setq major-mode 'ange-ftp-shell-mode)
2246 (setq mode-name "ange-ftp")
2247 (goto-char (point-max))
2248 (set-marker (process-mark proc) (point))
2249 (set (make-local-variable 'ange-ftp-process-string) nil)
2250 (setq ange-ftp-process-string "")
2251 (set (make-local-variable 'ange-ftp-process-busy) nil)
2252 (set (make-local-variable 'ange-ftp-process-result) nil)
2253 (set (make-local-variable 'ange-ftp-process-msg) nil)
2254 (set (make-local-variable 'ange-ftp-process-multi-skip) nil)
2255 (set (make-local-variable 'ange-ftp-process-result-line) nil)
2256 (set (make-local-variable 'ange-ftp-process-continue) nil)
2257 (set (make-local-variable 'ange-ftp-hash-mark-count) nil)
2258 (set (make-local-variable 'ange-ftp-binary-hash-mark-size) nil)
2259 (set (make-local-variable 'ange-ftp-ascii-hash-mark-size) nil)
2260 (set (make-local-variable 'ange-ftp-hash-mark-unit) nil)
2261 (set (make-local-variable 'ange-ftp-xfer-size) nil)
2262 (set (make-local-variable 'ange-ftp-last-percent) nil)
2263 (setq ange-ftp-hash-mark-count 0)
2264 (setq ange-ftp-xfer-size 0)
2265 (setq ange-ftp-process-result-line "")
2266 (run-hooks 'ange-ftp-shell-mode-hook)))
2267
2268 ;;;; ------------------------------------------------------------
2269 ;;;; Remote file and directory listing support.
2270 ;;;; ------------------------------------------------------------
2271
2272 (defun ange-ftp-dumb-unix-host (host)
2273 "Returns whether HOST's FTP server doesn't like \'ls\' or \'dir\' commands
2274 to take switch arguments."
2275 (and ange-ftp-dumb-unix-host-regexp
2276 (ange-ftp-save-match-data
2277 (string-match ange-ftp-dumb-unix-host-regexp host))))
2278
2279 (defun ange-ftp-add-dumb-unix-host (host)
2280 "Interactively adds a given HOST to ange-ftp-dumb-unix-host-regexp."
2281 (interactive
2282 (list (read-string "Host: "
2283 (let ((name (or (buffer-file-name)
2284 (and (eq major-mode 'dired-mode)
2285 dired-directory))))
2286 (and name (car (ange-ftp-ftp-path name)))))))
2287 (if (not (ange-ftp-dumb-unix-host host))
2288 (setq ange-ftp-dumb-unix-host-regexp
2289 (concat "^" (regexp-quote host) "$"
2290 (and ange-ftp-dumb-unix-host-regexp "\\|")
2291 ange-ftp-dumb-unix-host-regexp)
2292 ange-ftp-host-cache nil)))
2293
2294 (defvar ange-ftp-parse-list-func-alist nil
2295 "Association list of \( TYPE \. FUNC \) pairs. The FUNC is a routine
2296 which can parse the output from a DIR listing for a host of type TYPE.")
2297
2298 ;; With no-error nil, this function returns:
2299 ;; an error if file is not an ange-ftp-path
2300 ;; (This should never happen.)
2301 ;; an error if either the listing is unreadable or there is an ftp error.
2302 ;; the listing (a string), if everything works.
2303 ;;
2304 ;; With no-error t, it returns:
2305 ;; an error if not an ange-ftp-path
2306 ;; error if listing is unreable (most likely caused by a slow connection)
2307 ;; nil if ftp error (this is because although asking to list a nonexistent
2308 ;; directory on a remote unix machine usually (except
2309 ;; maybe for dumb hosts) returns an ls error, but no
2310 ;; ftp error, if the same is done on a VMS machine,
2311 ;; an ftp error is returned. Need to trap the error
2312 ;; so we can go on and try to list the parent.)
2313 ;; the listing, if everything works.
2314
2315 (defun ange-ftp-ls (file lsargs parse &optional no-error)
2316 "Return the output of an `DIR' or `ls' command done over ftp.
2317 FILE is the full name of the remote file, LSARGS is any args to pass to the
2318 `ls' command, and PARSE specifies that the output should be parsed and stored
2319 away in the internal cache."
2320 ;; If parse is t, we assume that file is a directory. i.e. we only parse
2321 ;; full directory listings.
2322 (setq file (ange-ftp-expand-file-name file))
2323 (let ((parsed (ange-ftp-ftp-path file)))
2324 (if parsed
2325 (let* ((host (nth 0 parsed))
2326 (user (nth 1 parsed))
2327 (path (ange-ftp-quote-string (nth 2 parsed)))
2328 (key (directory-file-name file))
2329 (host-type (ange-ftp-host-type host user))
2330 (dumb (memq host-type ange-ftp-dumb-host-types))
2331 result
2332 temp
2333 lscmd parse-func)
2334 (if (string-equal path "")
2335 (setq path
2336 (ange-ftp-real-file-name-as-directory
2337 (ange-ftp-expand-dir host user "~"))))
2338 (if (and ange-ftp-ls-cache-file
2339 (string-equal key ange-ftp-ls-cache-file)
2340 ;; Don't care about lsargs for dumb hosts.
2341 (or dumb (string-equal lsargs ange-ftp-ls-cache-lsargs)))
2342 ange-ftp-ls-cache-res
2343 (setq temp (ange-ftp-make-tmp-name host))
2344 (setq lscmd (list 'dir path temp lsargs))
2345 (unwind-protect
2346 (if (car (setq result (ange-ftp-send-cmd
2347 host
2348 user
2349 lscmd
2350 (format "Listing %s"
2351 (ange-ftp-abbreviate-filename
2352 file)))))
2353 (save-excursion
2354 (set-buffer (get-buffer-create
2355 ange-ftp-data-buffer-name))
2356 (erase-buffer)
2357 (if (ange-ftp-real-file-readable-p temp)
2358 (ange-ftp-real-insert-file-contents temp)
2359 (sleep-for ange-ftp-retry-time)
2360 ;wait for file to possibly appear
2361 (if (ange-ftp-real-file-readable-p temp)
2362 ;; Try again.
2363 (ange-ftp-real-insert-file-contents temp)
2364 (ange-ftp-error host user
2365 (format
2366 "list data file %s not readable"
2367 temp))))
2368 (if parse
2369 (ange-ftp-set-files
2370 file
2371 (if (setq
2372 parse-func
2373 (cdr (assq host-type
2374 ange-ftp-parse-list-func-alist)))
2375 (funcall parse-func)
2376 (ange-ftp-parse-dired-listing lsargs))))
2377 (setq ange-ftp-ls-cache-file key
2378 ange-ftp-ls-cache-lsargs lsargs
2379 ; For dumb hosts-types this is
2380 ; meaningless but harmless.
2381 ange-ftp-ls-cache-res (buffer-string))
2382 ;; (kill-buffer (current-buffer))
2383 ange-ftp-ls-cache-res)
2384 (if no-error
2385 nil
2386 (ange-ftp-error host user
2387 (concat "DIR failed: " (cdr result)))))
2388 (ange-ftp-del-tmp-name temp))))
2389 (error "Should never happen. Please report. Bug ref. no.: 1"))))
2390
2391 ;;;; ------------------------------------------------------------
2392 ;;;; Directory information caching support.
2393 ;;;; ------------------------------------------------------------
2394
2395 (defconst ange-ftp-date-regexp
2396 (concat
2397 " \\(Jan\\|Feb\\|Mar\\|Apr\\|May\\|Jun\\|Jul\\|Aug\\|Sep\\|Oct"
2398 "\\|Nov\\|Dec\\) +[0-3]?[0-9] "))
2399
2400 (defvar ange-ftp-add-file-entry-alist nil
2401 "Association list of pairs \( TYPE \. FUNC \), where FUNC
2402 is a function to be used to add a file entry for the OS TYPE. The
2403 main reason for this alist is to deal with file versions in VMS.")
2404
2405 (defvar ange-ftp-delete-file-entry-alist nil
2406 "Association list of pairs \( TYPE \. FUNC \), where FUNC
2407 is a function to be used to delete a file entry for the OS TYPE.
2408 The main reason for this alist is to deal with file versions in
2409 VMS.")
2410
2411 (defun ange-ftp-add-file-entry (path &optional dir-p)
2412 "Given a PATH, add the file entry for it, if its directory
2413 info exists."
2414 (funcall (or (cdr (assq (ange-ftp-host-type
2415 (car (ange-ftp-ftp-path path)))
2416 ange-ftp-add-file-entry-alist))
2417 'ange-ftp-internal-add-file-entry)
2418 path dir-p)
2419 (setq ange-ftp-ls-cache-file nil))
2420
2421 (defun ange-ftp-delete-file-entry (path &optional dir-p)
2422 "Given a PATH, delete the file entry for it, if its directory
2423 info exists."
2424 (funcall (or (cdr (assq (ange-ftp-host-type
2425 (car (ange-ftp-ftp-path path)))
2426 ange-ftp-delete-file-entry-alist))
2427 'ange-ftp-internal-delete-file-entry)
2428 path dir-p)
2429 (setq ange-ftp-ls-cache-file nil))
2430
2431 (defmacro ange-ftp-parse-filename ()
2432 ;;Extract the filename from the current line of a dired-like listing.
2433 (` (let ((eol (progn (end-of-line) (point))))
2434 (beginning-of-line)
2435 (if (re-search-forward ange-ftp-date-regexp eol t)
2436 (progn
2437 (skip-chars-forward " ")
2438 (skip-chars-forward "^ " eol)
2439 (skip-chars-forward " " eol)
2440 ;; We bomb on filenames starting with a space.
2441 (buffer-substring (point) eol))))))
2442
2443 ;; This deals with the F switch. Should also do something about
2444 ;; unquoting names obtained with the SysV b switch and the GNU Q
2445 ;; switch. See Sebastian's dired-get-filename.
2446
2447 (defmacro ange-ftp-ls-parser ()
2448 ;; Note that switches is dynamically bound.
2449 ;; Meant to be called by ange-ftp-parse-dired-listing
2450 (` (let ((tbl (ange-ftp-make-hashtable))
2451 (used-F (and (stringp switches)
2452 (string-match "F" switches)))
2453 file-type symlink directory file)
2454 (while (setq file (ange-ftp-parse-filename))
2455 (beginning-of-line)
2456 (skip-chars-forward "\t 0-9")
2457 (setq file-type (following-char)
2458 directory (eq file-type ?d))
2459 (if (eq file-type ?l)
2460 (if (string-match " -> " file)
2461 (setq symlink (substring file (match-end 0))
2462 file (substring file 0 (match-beginning 0)))
2463 ;; Shouldn't happen
2464 (setq symlink ""))
2465 (setq symlink nil))
2466 ;; Only do a costly regexp search if the F switch was used.
2467 (if (and used-F
2468 (not (string-equal file ""))
2469 (looking-at
2470 ".[-r][-w]\\([^ ]\\)[-r][-w]\\([^ ]\\)[-r][-w]\\([^ ]\\)"))
2471 (let ((socket (eq file-type ?s))
2472 (executable
2473 (and (not symlink) ; x bits don't mean a thing for symlinks
2474 (string-match "[xst]"
2475 (concat
2476 (buffer-substring
2477 (match-beginning 1)
2478 (match-end 1))
2479 (buffer-substring
2480 (match-beginning 2)
2481 (match-end 2))
2482 (buffer-substring
2483 (match-beginning 3)
2484 (match-end 3)))))))
2485 ;; Some ls's with the F switch mark symlinks with an @ (ULTRIX)
2486 ;; and others don't. (sigh...) Beware, that some Unix's don't
2487 ;; seem to believe in the F-switch
2488 (if (or (and symlink (string-match "@$" file))
2489 (and directory (string-match "/$" file))
2490 (and executable (string-match "*$" file))
2491 (and socket (string-match "=$" file)))
2492 (setq file (substring file 0 -1)))))
2493 (ange-ftp-put-hash-entry file (or symlink directory) tbl)
2494 (forward-line 1))
2495 (ange-ftp-put-hash-entry "." t tbl)
2496 (ange-ftp-put-hash-entry ".." t tbl)
2497 tbl)))
2498
2499 ;;; The dl stuff for descriptive listings
2500
2501 (defvar ange-ftp-dl-dir-regexp nil
2502 "Regexp matching directories which are listed in dl format. This regexp
2503 shouldn't be anchored with a trailing $ so that it will match subdirectories
2504 as well.")
2505
2506 (defun ange-ftp-add-dl-dir (dir)
2507 "Interactively adds a given directory to ange-ftp-dl-dir-regexp."
2508 (interactive
2509 (list (read-string "Directory: "
2510 (let ((name (or (buffer-file-name)
2511 (and (eq major-mode 'dired-mode)
2512 dired-directory))))
2513 (and name (ange-ftp-ftp-path name)
2514 (file-name-directory name))))))
2515 (if (not (and ange-ftp-dl-dir-regexp
2516 (string-match ange-ftp-dl-dir-regexp dir)))
2517 (setq ange-ftp-dl-dir-regexp
2518 (concat "^" (regexp-quote dir)
2519 (and ange-ftp-dl-dir-regexp "\\|")
2520 ange-ftp-dl-dir-regexp))))
2521
2522 (defmacro ange-ftp-dl-parser ()
2523 ;; Parse the current buffer, which is assumed to be a descriptive
2524 ;; listing, and return a hashtable.
2525 (` (let ((tbl (ange-ftp-make-hashtable)))
2526 (while (not (eobp))
2527 (ange-ftp-put-hash-entry
2528 (buffer-substring (point)
2529 (progn
2530 (skip-chars-forward "^ /\n")
2531 (point)))
2532 (eq (following-char) ?/)
2533 tbl)
2534 (forward-line 1))
2535 (ange-ftp-put-hash-entry "." t tbl)
2536 (ange-ftp-put-hash-entry ".." t tbl)
2537 tbl)))
2538
2539 (defun ange-ftp-parse-dired-listing (&optional switches)
2540 "Parse the current buffer which is assumed to be in a dired-like listing
2541 format, and return a hashtable as the result. If the listing is not really
2542 a listing, then return nil."
2543 (ange-ftp-save-match-data
2544 (cond
2545 ((looking-at "^total [0-9]+$")
2546 (forward-line 1)
2547 (ange-ftp-ls-parser))
2548 ((looking-at "[^\n]+\\( not found\\|: Not a directory\\)\n\\'")
2549 ;; It's an ls error message.
2550 nil)
2551 ((eobp) ; i.e. (zerop (buffer-size))
2552 ;; This could be one of:
2553 ;; (1) An Ultrix ls error message
2554 ;; (2) A listing with the A switch of an empty directory
2555 ;; on a machine which doesn't give a total line.
2556 ;; (3) The twilight zone.
2557 ;; We'll assume (1) for now.
2558 nil)
2559 ((re-search-forward ange-ftp-date-regexp nil t)
2560 (beginning-of-line)
2561 (ange-ftp-ls-parser))
2562 ((re-search-forward "^[^ \n\t]+ +\\([0-9]+\\|-\\|=\\) " nil t)
2563 ;; It's a dl listing (I hope).
2564 ;; file is bound by the call to ange-ftp-ls
2565 (ange-ftp-add-dl-dir file)
2566 (beginning-of-line)
2567 (ange-ftp-dl-parser))
2568 (t nil))))
2569
2570 (defun ange-ftp-set-files (directory files)
2571 "For a given DIRECTORY, set or change the associated FILES hashtable."
2572 (and files (ange-ftp-put-hash-entry (file-name-as-directory directory)
2573 files ange-ftp-files-hashtable)))
2574
2575 (defun ange-ftp-get-files (directory &optional no-error)
2576 "Given a given DIRECTORY, return a hashtable of file entries.
2577 This will give an error or return nil, depending on the value of
2578 NO-ERROR, if a listing for DIRECTORY cannot be obtained."
2579 (setq directory (file-name-as-directory directory)) ;normalize
2580 (or (ange-ftp-get-hash-entry directory ange-ftp-files-hashtable)
2581 (ange-ftp-save-match-data
2582 (and (ange-ftp-ls directory
2583 ;; This is an efficiency hack. We try to
2584 ;; anticipate what sort of listing dired
2585 ;; might want, and cache just such a listing.
2586 (if (and (boundp 'dired-actual-switches)
2587 (stringp dired-actual-switches)
2588 ;; We allow the A switch, which lists
2589 ;; all files except "." and "..".
2590 ;; This is OK because we manually
2591 ;; insert these entries
2592 ;; in the hash table.
2593 (string-match
2594 "[aA]" dired-actual-switches)
2595 (string-match
2596 "l" dired-actual-switches)
2597 (not (string-match
2598 "R" dired-actual-switches)))
2599 dired-actual-switches
2600 (if (and (boundp 'dired-listing-switches)
2601 (stringp dired-listing-switches)
2602 (string-match
2603 "[aA]" dired-listing-switches)
2604 (string-match
2605 "l" dired-listing-switches)
2606 (not (string-match
2607 "R" dired-listing-switches)))
2608 dired-listing-switches
2609 "-al"))
2610 t no-error)
2611 (ange-ftp-get-hash-entry
2612 directory ange-ftp-files-hashtable)))))
2613
2614 (defmacro ange-ftp-get-file-part (path)
2615 "Given PATH, return the file part that can be used for looking up the
2616 file's entry in a hashtable."
2617 (` (let ((file (file-name-nondirectory (, path))))
2618 (if (string-equal file "")
2619 "."
2620 file))))
2621
2622 (defmacro ange-ftp-allow-child-lookup (dir file)
2623 "Return whether ange-ftp-file-entry-p and ange-ftp-get-file-entry are
2624 allowed to determine if PATH is a sub-directory by listing it directly,
2625 rather than listing its parent directory. This is used for efficiency so
2626 that a wasted listing is not done:
2627 1. When looking for a .dired file in dired-x.el.
2628 2. The syntax of FILE and DIR make it impossible that FILE could be a valid
2629 subdirectory. This is of course an OS dependent judgement."
2630 (` (not
2631 (let* ((efile (, file)) ; expand once.
2632 (edir (, dir))
2633 (parsed (ange-ftp-ftp-path edir))
2634 (host-type (ange-ftp-host-type
2635 (car parsed))))
2636 (or
2637 ;; Deal with dired
2638 (and (boundp 'dired-local-variables-file)
2639 (stringp dired-local-variables-file)
2640 (string-equal dired-local-variables-file efile))
2641 ;; No dots in dir names in vms.
2642 (and (eq host-type 'vms)
2643 (string-match "\\." efile))
2644 ;; No subdirs in mts of cms.
2645 (and (memq host-type '(mts cms))
2646 (not (string-equal "/" (nth 2 parsed)))))))))
2647
2648 (defun ange-ftp-file-entry-p (path)
2649 "Given PATH, return whether there is a file entry for it."
2650 (let* ((path (directory-file-name path))
2651 (dir (file-name-directory path))
2652 (ent (ange-ftp-get-hash-entry dir ange-ftp-files-hashtable))
2653 (file (ange-ftp-get-file-part path)))
2654 (if ent
2655 (ange-ftp-hash-entry-exists-p file ent)
2656 (or (and (ange-ftp-allow-child-lookup dir file)
2657 (setq ent (ange-ftp-get-files path t))
2658 ;; Try a child lookup. i.e. try to list file as a
2659 ;; subdirectory of dir. This is a good idea because
2660 ;; we may not have read permission for file's parent. Also,
2661 ;; people tend to work down directory trees anyway. We use
2662 ;; no-error ;; because if file does not exist as a subdir.,
2663 ;; then dumb hosts will give an ftp error. Smart unix hosts
2664 ;; will simply send back the ls
2665 ;; error message.
2666 (ange-ftp-get-hash-entry "." ent))
2667 ;; Child lookup failed. Try the parent. If this bombs,
2668 ;; we are at wits end -- signal an error.
2669 ;; Problem: If this signals an error, the error message
2670 ;; may not have a lot to do with what went wrong.
2671 (ange-ftp-hash-entry-exists-p file
2672 (ange-ftp-get-files dir))))))
2673
2674 (defun ange-ftp-get-file-entry (path)
2675 "Given PATH, return the given file entry which will be either t for a
2676 directory, nil for a normal file, or a string for a symlink. If the file
2677 isn't in the hashtable, this also returns nil."
2678 (let* ((path (directory-file-name path))
2679 (dir (file-name-directory path))
2680 (ent (ange-ftp-get-hash-entry dir ange-ftp-files-hashtable))
2681 (file (ange-ftp-get-file-part path)))
2682 (if ent
2683 (ange-ftp-get-hash-entry file ent)
2684 (or (and (ange-ftp-allow-child-lookup dir file)
2685 (setq ent (ange-ftp-get-files path t))
2686 (ange-ftp-get-hash-entry "." ent))
2687 ;; i.e. it's a directory by child lookup
2688 (ange-ftp-get-hash-entry file
2689 (ange-ftp-get-files dir))))))
2690
2691 (defun ange-ftp-internal-delete-file-entry (path &optional dir-p)
2692 (if dir-p
2693 (progn
2694 (setq path (file-name-as-directory path))
2695 (ange-ftp-del-hash-entry path ange-ftp-files-hashtable)
2696 (setq path (directory-file-name path))))
2697 ;; Note that file-name-as-directory followed by directory-file-name
2698 ;; serves to canonicalize directory file names to their unix form.
2699 ;; i.e. in VMS, FOO.DIR -> FOO/ -> FOO
2700 (let ((files (ange-ftp-get-hash-entry (file-name-directory path)
2701 ange-ftp-files-hashtable)))
2702 (if files
2703 (ange-ftp-del-hash-entry (ange-ftp-get-file-part path)
2704 files))))
2705
2706 (defun ange-ftp-internal-add-file-entry (path &optional dir-p)
2707 (and dir-p
2708 (setq path (directory-file-name path)))
2709 (let ((files (ange-ftp-get-hash-entry (file-name-directory path)
2710 ange-ftp-files-hashtable)))
2711 (if files
2712 (ange-ftp-put-hash-entry (ange-ftp-get-file-part path)
2713 dir-p
2714 files))))
2715
2716 (defun ange-ftp-wipe-file-entries (host user)
2717 "Replace the file entry information hashtable with one that doesn't have any
2718 entries for the given HOST, USER pair."
2719 (let ((new-tbl (ange-ftp-make-hashtable (length ange-ftp-files-hashtable))))
2720 (ange-ftp-map-hashtable
2721 (function
2722 (lambda (key val)
2723 (let ((parsed (ange-ftp-ftp-path key)))
2724 (if parsed
2725 (let ((h (nth 0 parsed))
2726 (u (nth 1 parsed)))
2727 (or (and (equal host h) (equal user u))
2728 (ange-ftp-put-hash-entry key val new-tbl)))))))
2729 ange-ftp-files-hashtable)
2730 (setq ange-ftp-files-hashtable new-tbl)))
2731
2732 ;;;; ------------------------------------------------------------
2733 ;;;; File transfer mode support.
2734 ;;;; ------------------------------------------------------------
2735
2736 (defun ange-ftp-set-binary-mode (host user)
2737 "Tell the ftp process for the given HOST & USER to switch to binary mode."
2738 (let ((result (ange-ftp-send-cmd host user '(type "binary"))))
2739 (if (not (car result))
2740 (ange-ftp-error host user (concat "BINARY failed: " (cdr result)))
2741 (save-excursion
2742 (set-buffer (process-buffer (ange-ftp-get-process host user)))
2743 (and ange-ftp-binary-hash-mark-size
2744 (setq ange-ftp-hash-mark-unit (ash ange-ftp-binary-hash-mark-size -4)))))))
2745
2746 (defun ange-ftp-set-ascii-mode (host user)
2747 "Tell the ftp process for the given HOST & USER to switch to ascii mode."
2748 (let ((result (ange-ftp-send-cmd host user '(type "ascii"))))
2749 (if (not (car result))
2750 (ange-ftp-error host user (concat "ASCII failed: " (cdr result)))
2751 (save-excursion
2752 (set-buffer (process-buffer (ange-ftp-get-process host user)))
2753 (and ange-ftp-ascii-hash-mark-size
2754 (setq ange-ftp-hash-mark-unit (ash ange-ftp-ascii-hash-mark-size -4)))))))
2755
2756 ;;; ------------------------------------------------------------
2757 ;;; expand-file-name and friends...
2758 ;;; ------------------------------------------------------------
2759
2760 (defun ange-ftp-cd (host user dir)
2761 (let ((result (ange-ftp-send-cmd host user (list 'cd dir) "Doing CD")))
2762 (or (car result)
2763 (ange-ftp-error host user (concat "CD failed: " (cdr result))))))
2764
2765 (defun ange-ftp-get-pwd (host user)
2766 "Attempts to get the current working directory for the given HOST/USER pair.
2767 Returns \( DIR . LINE \) where DIR is either the directory or NIL if not found,
2768 and LINE is the relevant success or fail line from the FTP-client."
2769 (let* ((result (ange-ftp-send-cmd host user '(pwd) "Getting PWD"))
2770 (line (cdr result))
2771 dir)
2772 (if (car result)
2773 (ange-ftp-save-match-data
2774 (and (or (string-match "\"\\([^\"]*\\)\"" line)
2775 (string-match " \\([^ ]+\\) " line)) ; stone-age VMS servers!
2776 (setq dir (substring line
2777 (match-beginning 1)
2778 (match-end 1))))))
2779 (cons dir line)))
2780
2781 (defconst ange-ftp-expand-dir-hashtable (ange-ftp-make-hashtable))
2782
2783 (defconst ange-ftp-expand-dir-regexp "^5.0 \\([^: ]+\\):")
2784
2785 (defun ange-ftp-expand-dir (host user dir)
2786 "Return the result of doing a PWD in the current FTP session to machine HOST
2787 logged in as user USER and cd'd to directory DIR."
2788 (let* ((host-type (ange-ftp-host-type host user))
2789 ;; It is more efficient to call ange-ftp-host-type
2790 ;; before binding res, because ange-ftp-host-type sometimes
2791 ;; adds to the info in the expand-dir-hashtable.
2792 (fix-pathname-func
2793 (cdr (assq host-type ange-ftp-fix-path-func-alist)))
2794 (key (concat host "/" user "/" dir))
2795 (res (ange-ftp-get-hash-entry key ange-ftp-expand-dir-hashtable)))
2796 (or res
2797 (progn
2798 (or
2799 (string-equal user "anonymous")
2800 (string-equal user "ftp")
2801 (not (eq host-type 'unix))
2802 (let* ((ange-ftp-good-msgs (concat ange-ftp-expand-dir-regexp
2803 "\\|"
2804 ange-ftp-good-msgs))
2805 (result (ange-ftp-send-cmd host user
2806 (list 'get dir "/dev/null")
2807 (format "expanding %s" dir)))
2808 (line (cdr result)))
2809 (setq res
2810 (if (string-match ange-ftp-expand-dir-regexp line)
2811 (substring line
2812 (match-beginning 1)
2813 (match-end 1))))))
2814 (or res
2815 (if (string-equal dir "~")
2816 (setq res (car (ange-ftp-get-pwd host user)))
2817 (let ((home (ange-ftp-expand-dir host user "~")))
2818 (unwind-protect
2819 (and (ange-ftp-cd host user dir)
2820 (setq res (car (ange-ftp-get-pwd host user))))
2821 (ange-ftp-cd host user home)))))
2822 (if res
2823 (progn
2824 (if fix-pathname-func
2825 (setq res (funcall fix-pathname-func res 'reverse)))
2826 (ange-ftp-put-hash-entry
2827 key res ange-ftp-expand-dir-hashtable)))
2828 res))))
2829
2830 (defun ange-ftp-canonize-filename (n)
2831 "Take a string and short-circuit //, /. and /.."
2832 (if (string-match "[^:]+//" n) ;don't upset Apollo users
2833 (setq n (substring n (1- (match-end 0)))))
2834 (let ((parsed (ange-ftp-ftp-path n)))
2835 (if parsed
2836 (let ((host (car parsed))
2837 (user (nth 1 parsed))
2838 (path (nth 2 parsed)))
2839
2840 ;; See if remote path is absolute. If so then just expand it and
2841 ;; replace the path component of the overall path.
2842 (cond ((string-match "^/" path)
2843 path)
2844
2845 ;; Path starts with ~ or ~user. Resolve that part of the path
2846 ;; making it absolute then re-expand it.
2847 ((string-match "^~[^/]*" path)
2848 (let* ((tilda (substring path
2849 (match-beginning 0)
2850 (match-end 0)))
2851 (rest (substring path (match-end 0)))
2852 (dir (ange-ftp-expand-dir host user tilda)))
2853 (if dir
2854 (setq path (concat dir rest))
2855 (error "User \"%s\" is not known"
2856 (substring tilda 1)))))
2857
2858 ;; relative path. Tack on homedir and re-expand.
2859 (t
2860 (let ((dir (ange-ftp-expand-dir host user "~")))
2861 (if dir
2862 (setq path (concat
2863 (ange-ftp-real-file-name-as-directory dir)
2864 path))
2865 (error "Unable to obtain CWD")))))
2866
2867 (if (not (string-match "^//" path))
2868 (progn
2869 (setq path (ange-ftp-real-expand-file-name path))
2870
2871 (if (string-match "^//" path)
2872 (setq path (substring path 1)))))
2873
2874 ;; Now substitute the expanded path back into the overall filename.
2875 (ange-ftp-replace-path-component n path))
2876
2877 ;; non-ange-ftp path. Just expand normally.
2878 (if (eq (string-to-char n) ?/)
2879 (ange-ftp-real-expand-file-name n)
2880 (ange-ftp-real-expand-file-name
2881 (ange-ftp-real-file-name-nondirectory n)
2882 (ange-ftp-real-file-name-directory n))))))
2883
2884 (defun ange-ftp-expand-file-name (name &optional default)
2885 "Documented as original."
2886 (ange-ftp-save-match-data
2887 (if (eq (string-to-char name) ?/)
2888 (while (cond ((string-match "[^:]+//" name) ;don't upset Apollo users
2889 (setq name (substring name (1- (match-end 0)))))
2890 ((string-match "/~" name)
2891 (setq name (substring name (1- (match-end 0))))))))
2892 (cond ((eq (string-to-char name) ?~)
2893 (ange-ftp-real-expand-file-name name))
2894 ((eq (string-to-char name) ?/)
2895 (ange-ftp-canonize-filename name))
2896 ((zerop (length name))
2897 (ange-ftp-canonize-filename (or default default-directory)))
2898 ((ange-ftp-canonize-filename
2899 (concat (file-name-as-directory (or default default-directory))
2900 name))))))
2901
2902 ;;;; ------------------------------------------------------------
2903 ;;;; Redefinitions of standard GNU Emacs functions.
2904 ;;;; ------------------------------------------------------------
2905
2906 (defvar ange-ftp-file-name-as-directory-alist nil
2907 "Association list of \( TYPE \. FUNC \) pairs, where
2908 FUNC converts a filename to a directory name for the operating
2909 system TYPE.")
2910
2911 (defun ange-ftp-file-name-as-directory (name)
2912 "Documented as original."
2913 (let ((parsed (ange-ftp-ftp-path name)))
2914 (if parsed
2915 (if (string-equal (nth 2 parsed) "")
2916 name
2917 (funcall (or (cdr (assq
2918 (ange-ftp-host-type (car parsed))
2919 ange-ftp-file-name-as-directory-alist))
2920 'ange-ftp-real-file-name-as-directory)
2921 name))
2922 (ange-ftp-real-file-name-as-directory name))))
2923
2924 (defun ange-ftp-file-name-directory (name)
2925 "Documented as original."
2926 (let ((parsed (ange-ftp-ftp-path name)))
2927 (if parsed
2928 (let ((path (nth 2 parsed)))
2929 (if (ange-ftp-save-match-data
2930 (string-match "^~[^/]*$" path))
2931 name
2932 (ange-ftp-replace-path-component
2933 name
2934 (ange-ftp-real-file-name-directory path))))
2935 (ange-ftp-real-file-name-directory name))))
2936
2937 (defun ange-ftp-file-name-nondirectory (name)
2938 "Documented as original."
2939 (let ((parsed (ange-ftp-ftp-path name)))
2940 (if parsed
2941 (let ((path (nth 2 parsed)))
2942 (if (ange-ftp-save-match-data
2943 (string-match "^~[^/]*$" path))
2944 ""
2945 (ange-ftp-real-file-name-nondirectory path)))
2946 (ange-ftp-real-file-name-nondirectory name))))
2947
2948 (defun ange-ftp-directory-file-name (dir)
2949 "Documented as original."
2950 (let ((parsed (ange-ftp-ftp-path dir)))
2951 (if parsed
2952 (ange-ftp-replace-path-component
2953 dir
2954 (ange-ftp-real-directory-file-name (nth 2 parsed)))
2955 (ange-ftp-real-directory-file-name dir))))
2956
2957 (defun ange-ftp-binary-file (file)
2958 "Returns whether the given FILE is to be considered as a binary file for
2959 ftp transfers."
2960 (ange-ftp-save-match-data
2961 (string-match ange-ftp-binary-file-name-regexp file)))
2962
2963 (defun ange-ftp-write-region (start end filename &optional append visit
2964 lockname)
2965 "Documented as original."
2966 (interactive "r\nFWrite region to file: ")
2967 (setq filename (expand-file-name filename))
2968 (let ((parsed (ange-ftp-ftp-path filename)))
2969 (if parsed
2970 (let* ((host (nth 0 parsed))
2971 (user (nth 1 parsed))
2972 (path (ange-ftp-quote-string (nth 2 parsed)))
2973 (temp (ange-ftp-make-tmp-name host))
2974 (binary (ange-ftp-binary-file filename))
2975 (cmd (if append 'append 'put))
2976 (abbr (ange-ftp-abbreviate-filename filename)))
2977 (unwind-protect
2978 (progn
2979 (let ((executing-macro t)
2980 (filename (buffer-file-name))
2981 (mod-p (buffer-modified-p)))
2982 (unwind-protect
2983 (ange-ftp-real-write-region start end temp nil
2984 visit lockname)
2985 ;; cleanup forms
2986 (setq buffer-file-name filename)
2987 (if (fboundp 'compute-buffer-file-truename)
2988 (compute-buffer-file-truename))
2989 (set-buffer-modified-p mod-p)))
2990 (if binary
2991 (ange-ftp-set-binary-mode host user))
2992
2993 ;; tell the process filter what size the transfer will be.
2994 (let ((attr (file-attributes temp)))
2995 (if attr
2996 (ange-ftp-set-xfer-size host user (nth 7 attr))))
2997
2998 ;; put or append the file.
2999 (let ((result (ange-ftp-send-cmd host user
3000 (list cmd temp path)
3001 (format "Writing %s" abbr))))
3002 (or (car result)
3003 (signal 'ftp-error
3004 (list
3005 "Opening output file"
3006 (format "FTP Error: \"%s\"" (cdr result))
3007 filename)))))
3008 (ange-ftp-del-tmp-name temp)
3009 (if binary
3010 (ange-ftp-set-ascii-mode host user)))
3011 (if (eq visit t)
3012 (progn
3013 (ange-ftp-set-buffer-mode)
3014 (setq buffer-file-name filename)
3015 (if (fboundp 'compute-buffer-file-truename)
3016 (compute-buffer-file-truename))
3017 (set-buffer-modified-p nil)))
3018 (ange-ftp-message "Wrote %s" abbr)
3019 (ange-ftp-add-file-entry filename))
3020 (ange-ftp-real-write-region start end filename append visit lockname))))
3021
3022 (defun ange-ftp-insert-file-contents (filename &optional visit beg end replace)
3023 "Documented as original."
3024 (barf-if-buffer-read-only)
3025 (setq filename (expand-file-name filename))
3026 (let ((parsed (ange-ftp-ftp-path filename)))
3027 (if parsed
3028 (progn
3029 (if visit
3030 (progn
3031 (setq buffer-file-name filename)
3032 (if (fboundp 'compute-buffer-file-truename)
3033 (compute-buffer-file-truename))))
3034 (if (or (file-exists-p filename)
3035 (progn
3036 (setq ange-ftp-ls-cache-file nil)
3037 (ange-ftp-del-hash-entry (file-name-directory filename)
3038 ange-ftp-files-hashtable)
3039 (file-exists-p filename)))
3040 (let* ((host (nth 0 parsed))
3041 (user (nth 1 parsed))
3042 (path (ange-ftp-quote-string (nth 2 parsed)))
3043 (temp (ange-ftp-make-tmp-name host))
3044 (binary (ange-ftp-binary-file filename))
3045 (abbr (ange-ftp-abbreviate-filename filename))
3046 size)
3047 (unwind-protect
3048 (progn
3049 (if binary
3050 (ange-ftp-set-binary-mode host user))
3051 (let ((result (ange-ftp-send-cmd host user
3052 (list 'get path temp)
3053 (format "Retrieving %s" abbr))))
3054 (or (car result)
3055 (signal 'ftp-error
3056 (list
3057 "Opening input file"
3058 (format "FTP Error: \"%s\"" (cdr result))
3059 filename))))
3060 (if (or (ange-ftp-real-file-readable-p temp)
3061 (sleep-for ange-ftp-retry-time)
3062 ;; Wait for file to hopefully appear.
3063 (ange-ftp-real-file-readable-p temp))
3064 (setq
3065 size
3066 (nth 1 (progn
3067 (if replace ; kludge...
3068 (delete-region (point-min)
3069 (point-max)))
3070 (ange-ftp-real-insert-file-contents
3071 temp visit beg end nil))))
3072 (signal 'ftp-error
3073 (list
3074 "Opening input file:"
3075 (format
3076 "FTP Error: %s not arrived or readable"
3077 filename)))))
3078 (if binary
3079 (ange-ftp-set-ascii-mode host user))
3080 (ange-ftp-del-tmp-name temp))
3081 (if visit
3082 (progn
3083 (setq buffer-file-name filename)
3084 (if (fboundp 'compute-buffer-file-truename)
3085 (compute-buffer-file-truename))))
3086 (list filename size))
3087 (signal 'file-error
3088 (list
3089 "Opening input file"
3090 filename))))
3091 (ange-ftp-real-insert-file-contents filename visit beg end replace))))
3092
3093 (defun ange-ftp-revert-buffer (arg noconfirm)
3094 "Revert this buffer from a remote file using ftp."
3095 (let ((opoint (point)))
3096 (cond ((null buffer-file-name)
3097 (error "Buffer does not seem to be associated with any file"))
3098 ((or noconfirm
3099 (yes-or-no-p (format "Revert buffer from file %s? "
3100 buffer-file-name)))
3101 (let ((buffer-read-only nil))
3102 ;; Set buffer-file-name to nil
3103 ;; so that we don't try to lock the file.
3104 (let ((buffer-file-name nil))
3105 (unlock-buffer)
3106 (erase-buffer))
3107 (insert-file-contents buffer-file-name t))
3108 (goto-char (min opoint (point-max)))
3109 (after-find-file nil)
3110 t))))
3111
3112 (defun ange-ftp-expand-symlink (file dir)
3113 (if (file-name-absolute-p file)
3114 (ange-ftp-replace-path-component dir file)
3115 (expand-file-name file dir)))
3116
3117 (defun ange-ftp-file-symlink-p (file)
3118 "Documented as original."
3119 ;; call ange-ftp-expand-file-name rather than the normal
3120 ;; expand-file-name to stop loops when using a package that
3121 ;; redefines both file-symlink-p and expand-file-name.
3122 (setq file (ange-ftp-expand-file-name file))
3123 (if (ange-ftp-ftp-path file)
3124 (let ((file-ent
3125 (ange-ftp-get-hash-entry
3126 (ange-ftp-get-file-part file)
3127 (ange-ftp-get-files (file-name-directory file)))))
3128 (if (stringp file-ent)
3129 (if (file-name-absolute-p file-ent)
3130 (ange-ftp-replace-path-component
3131 (file-name-directory file) file-ent)
3132 file-ent)))
3133 (ange-ftp-real-file-symlink-p file)))
3134
3135 (defun ange-ftp-file-exists-p (path)
3136 "Documented as original."
3137 (setq path (expand-file-name path))
3138 (if (ange-ftp-ftp-path path)
3139 (if (ange-ftp-file-entry-p path)
3140 (let ((file-ent (ange-ftp-get-file-entry path)))
3141 (if (stringp file-ent)
3142 (file-exists-p
3143 (ange-ftp-expand-symlink file-ent
3144 (file-name-directory
3145 (directory-file-name path))))
3146 t)))
3147 (ange-ftp-real-file-exists-p path)))
3148
3149 (defun ange-ftp-file-directory-p (path)
3150 "Documented as original."
3151 (setq path (expand-file-name path))
3152 (if (ange-ftp-ftp-path path)
3153 ;; We do a file-name-as-directory on path here because some
3154 ;; machines (VMS) use a .DIR to indicate the filename associated
3155 ;; with a directory. This needs to be canonicalized.
3156 (let ((file-ent (ange-ftp-get-file-entry
3157 (ange-ftp-file-name-as-directory path))))
3158 (if (stringp file-ent)
3159 (file-directory-p
3160 (ange-ftp-expand-symlink file-ent
3161 (file-name-directory
3162 (directory-file-name path))))
3163 file-ent))
3164 (ange-ftp-real-file-directory-p path)))
3165
3166 (defun ange-ftp-directory-files (directory &optional full match
3167 &rest v19-args)
3168 "Documented as original."
3169 (setq directory (expand-file-name directory))
3170 (if (ange-ftp-ftp-path directory)
3171 (progn
3172 (ange-ftp-barf-if-not-directory directory)
3173 (let ((tail (ange-ftp-hash-table-keys
3174 (ange-ftp-get-files directory)))
3175 files f)
3176 (setq directory (file-name-as-directory directory))
3177 (ange-ftp-save-match-data
3178 (while tail
3179 (setq f (car tail)
3180 tail (cdr tail))
3181 (if (or (not match) (string-match match f))
3182 (setq files
3183 (cons (if full (concat directory f) f) files)))))
3184 (nreverse files)))
3185 (apply 'ange-ftp-real-directory-files directory full match v19-args)))
3186
3187 (defun ange-ftp-file-attributes (file)
3188 "Documented as original."
3189 (setq file (expand-file-name file))
3190 (let ((parsed (ange-ftp-ftp-path file)))
3191 (if parsed
3192 (let ((part (ange-ftp-get-file-part file))
3193 (files (ange-ftp-get-files (file-name-directory file))))
3194 (if (ange-ftp-hash-entry-exists-p part files)
3195 (let ((host (nth 0 parsed))
3196 (user (nth 1 parsed))
3197 (path (nth 2 parsed))
3198 (dirp (ange-ftp-get-hash-entry part files)))
3199 (list (if (and (stringp dirp) (file-name-absolute-p dirp))
3200 (ange-ftp-expand-symlink dirp
3201 (file-name-directory file))
3202 dirp) ;0 file type
3203 -1 ;1 link count
3204 -1 ;2 uid
3205 -1 ;3 gid
3206 '(0 0) ;4 atime
3207 '(0 0) ;5 mtime
3208 '(0 0) ;6 ctime
3209 -1 ;7 size
3210 (concat (if (stringp dirp) "l" (if dirp "d" "-"))
3211 "?????????") ;8 mode
3212 nil ;9 gid weird
3213 ;; Hack to give remote files a unique "inode number".
3214 ;; It's actually the sum of the characters in its name.
3215 (apply '+ (nconc (mapcar 'identity host)
3216 (mapcar 'identity user)
3217 (mapcar 'identity
3218 (directory-file-name path))))
3219 -1 ;11 device number [v19 only]
3220 ))))
3221 (ange-ftp-real-file-attributes file))))
3222
3223 (defun ange-ftp-file-writable-p (file)
3224 "Documented as original."
3225 (setq file (expand-file-name file))
3226 (if (ange-ftp-ftp-path file)
3227 (or (file-exists-p file) ;guess here for speed
3228 (file-directory-p (file-name-directory file)))
3229 (ange-ftp-real-file-writable-p file)))
3230
3231 (defun ange-ftp-file-readable-p (file)
3232 "Documented as original."
3233 (setq file (expand-file-name file))
3234 (if (ange-ftp-ftp-path file)
3235 (file-exists-p file)
3236 (ange-ftp-real-file-readable-p file)))
3237
3238 (defun ange-ftp-delete-file (file)
3239 "Documented as original."
3240 (interactive "fDelete file: ")
3241 (setq file (expand-file-name file))
3242 (let ((parsed (ange-ftp-ftp-path file)))
3243 (if parsed
3244 (let* ((host (nth 0 parsed))
3245 (user (nth 1 parsed))
3246 (path (ange-ftp-quote-string (nth 2 parsed)))
3247 (abbr (ange-ftp-abbreviate-filename file))
3248 (result (ange-ftp-send-cmd host user
3249 (list 'delete path)
3250 (format "Deleting %s" abbr))))
3251 (or (car result)
3252 (signal 'ftp-error
3253 (list
3254 "Removing old name"
3255 (format "FTP Error: \"%s\"" (cdr result))
3256 file)))
3257 (ange-ftp-delete-file-entry file))
3258 (ange-ftp-real-delete-file file))))
3259
3260 (defun ange-ftp-verify-visited-file-modtime (buf)
3261 "Documented as original."
3262 (let ((name (buffer-file-name buf)))
3263 (if (and (stringp name) (ange-ftp-ftp-path name))
3264 t
3265 (ange-ftp-real-verify-visited-file-modtime buf))))
3266
3267 (defun ange-ftp-backup-buffer ()
3268 "Documented as original."
3269 (let (parsed)
3270 (if (and
3271 (listp ange-ftp-make-backup-files)
3272 (stringp buffer-file-name)
3273 (setq parsed (ange-ftp-ftp-path buffer-file-name))
3274 (or
3275 (null ange-ftp-make-backup-files)
3276 (not
3277 (memq
3278 (ange-ftp-host-type
3279 (car parsed))
3280 ange-ftp-make-backup-files))))
3281 nil
3282 (ange-ftp-real-backup-buffer))))
3283
3284 ;;;; ------------------------------------------------------------
3285 ;;;; File copying support... totally re-written 6/24/92.
3286 ;;;; ------------------------------------------------------------
3287
3288 (defun ange-ftp-barf-or-query-if-file-exists (absname querystring interactive)
3289 (if (file-exists-p absname)
3290 (if (not interactive)
3291 (signal 'file-already-exists (list absname))
3292 (if (not (yes-or-no-p (format "File %s already exists; %s anyway? "
3293 absname querystring)))
3294 (signal 'file-already-exists (list absname))))))
3295
3296 ;; async local copy commented out for now since I don't seem to get
3297 ;; the process sentinel called for some processes.
3298 ;;
3299 ;; (defun ange-ftp-copy-file-locally (filename newname ok-if-already-exists
3300 ;; keep-date cont)
3301 ;; "Kludge to copy a local file and call a continuation when the copy
3302 ;; finishes."
3303 ;; ;; check to see if we can overwrite
3304 ;; (if (or (not ok-if-already-exists)
3305 ;; (numberp ok-if-already-exists))
3306 ;; (ange-ftp-barf-or-query-if-file-exists newname "copy to it"
3307 ;; (numberp ok-if-already-exists)))
3308 ;; (let ((proc (start-process " *copy*"
3309 ;; (generate-new-buffer "*copy*")
3310 ;; "cp"
3311 ;; filename
3312 ;; newname))
3313 ;; res)
3314 ;; (set-process-sentinel proc (function ange-ftp-copy-file-locally-sentinel))
3315 ;; (process-kill-without-query proc)
3316 ;; (save-excursion
3317 ;; (set-buffer (process-buffer proc))
3318 ;; (make-variable-buffer-local 'copy-cont)
3319 ;; (setq copy-cont cont))))
3320 ;;
3321 ;; (defun ange-ftp-copy-file-locally-sentinel (proc status)
3322 ;; (save-excursion
3323 ;; (set-buffer (process-buffer proc))
3324 ;; (let ((cont copy-cont)
3325 ;; (result (buffer-string)))
3326 ;; (unwind-protect
3327 ;; (if (and (string-equal status "finished\n")
3328 ;; (zerop (length result)))
3329 ;; (ange-ftp-call-cont cont t nil)
3330 ;; (ange-ftp-call-cont cont
3331 ;; nil
3332 ;; (if (zerop (length result))
3333 ;; (substring status 0 -1)
3334 ;; (substring result 0 -1))))
3335 ;; (kill-buffer (current-buffer))))))
3336
3337 ;; this is the extended version of ange-ftp-copy-file-internal that works
3338 ;; asyncronously if asked nicely.
3339 (defun ange-ftp-copy-file-internal (filename newname ok-if-already-exists
3340 keep-date &optional msg cont nowait)
3341 (setq filename (expand-file-name filename)
3342 newname (expand-file-name newname))
3343
3344 ;; canonicalize newname if a directory.
3345 (if (file-directory-p newname)
3346 (setq newname (expand-file-name (file-name-nondirectory filename) newname)))
3347
3348 (let ((f-parsed (ange-ftp-ftp-path filename))
3349 (t-parsed (ange-ftp-ftp-path newname)))
3350
3351 ;; local file to local file copy?
3352 (if (and (not f-parsed) (not t-parsed))
3353 (progn
3354 (ange-ftp-real-copy-file filename newname ok-if-already-exists
3355 keep-date)
3356 (if cont
3357 (ange-ftp-call-cont cont t "Copied locally")))
3358 ;; one or both files are remote.
3359 (let* ((f-host (and f-parsed (nth 0 f-parsed)))
3360 (f-user (and f-parsed (nth 1 f-parsed)))
3361 (f-path (and f-parsed (ange-ftp-quote-string (nth 2 f-parsed))))
3362 (f-abbr (ange-ftp-abbreviate-filename filename))
3363 (t-host (and t-parsed (nth 0 t-parsed)))
3364 (t-user (and t-parsed (nth 1 t-parsed)))
3365 (t-path (and t-parsed (ange-ftp-quote-string (nth 2 t-parsed))))
3366 (t-abbr (ange-ftp-abbreviate-filename newname filename))
3367 (binary (or (ange-ftp-binary-file filename)
3368 (ange-ftp-binary-file newname)))
3369 temp1
3370 temp2)
3371
3372 ;; check to see if we can overwrite
3373 (if (or (not ok-if-already-exists)
3374 (numberp ok-if-already-exists))
3375 (ange-ftp-barf-or-query-if-file-exists newname "copy to it"
3376 (numberp ok-if-already-exists)))
3377
3378 ;; do the copying.
3379 (if f-parsed
3380
3381 ;; filename was remote.
3382 (progn
3383 (if (or (ange-ftp-use-gateway-p f-host)
3384 t-parsed)
3385 ;; have to use intermediate file if we are getting via
3386 ;; gateway machine or we are doing a remote to remote copy.
3387 (setq temp1 (ange-ftp-make-tmp-name f-host)))
3388
3389 (if binary
3390 (ange-ftp-set-binary-mode f-host f-user))
3391
3392 (ange-ftp-send-cmd
3393 f-host
3394 f-user
3395 (list 'get f-path (or temp1 newname))
3396 (or msg
3397 (if (and temp1 t-parsed)
3398 (format "Getting %s" f-abbr)
3399 (format "Copying %s to %s" f-abbr t-abbr)))
3400 (list (function ange-ftp-cf1)
3401 filename newname binary msg
3402 f-parsed f-host f-user f-path f-abbr
3403 t-parsed t-host t-user t-path t-abbr
3404 temp1 temp2 cont nowait)
3405 nowait))
3406
3407 ;; filename wasn't remote. newname must be remote. call the
3408 ;; function which does the remainder of the copying work.
3409 (ange-ftp-cf1 t nil
3410 filename newname binary msg
3411 f-parsed f-host f-user f-path f-abbr
3412 t-parsed t-host t-user t-path t-abbr
3413 nil nil cont nowait))))))
3414
3415 ;; next part of copying routine.
3416 (defun ange-ftp-cf1 (result line
3417 filename newname binary msg
3418 f-parsed f-host f-user f-path f-abbr
3419 t-parsed t-host t-user t-path t-abbr
3420 temp1 temp2 cont nowait)
3421 (if line
3422 ;; filename must have been remote, and we must have just done a GET.
3423 (unwind-protect
3424 (or result
3425 ;; GET failed for some reason. Clean up and get out.
3426 (progn
3427 (and temp1 (ange-ftp-del-tmp-name temp1))
3428 (or cont
3429 (signal 'ftp-error (list "Opening input file"
3430 (format "FTP Error: \"%s\"" line)
3431 filename)))))
3432 ;; cleanup
3433 (if binary
3434 (ange-ftp-set-ascii-mode f-host f-user))))
3435
3436 (if result
3437 ;; We now have to copy either temp1 or filename to newname.
3438 (if t-parsed
3439
3440 ;; newname was remote.
3441 (progn
3442 (if (ange-ftp-use-gateway-p t-host)
3443 (setq temp2 (ange-ftp-make-tmp-name t-host)))
3444
3445 ;; make sure data is moved into the right place for the
3446 ;; outgoing transfer. gateway temporary files complicate
3447 ;; things nicely.
3448 (if temp1
3449 (if temp2
3450 (if (string-equal temp1 temp2)
3451 (setq temp1 nil)
3452 (ange-ftp-real-copy-file temp1 temp2 t))
3453 (setq temp2 temp1 temp1 nil))
3454 (if temp2
3455 (ange-ftp-real-copy-file filename temp2 t)))
3456
3457 (if binary
3458 (ange-ftp-set-binary-mode t-host t-user))
3459
3460 ;; tell the process filter what size the file is.
3461 (let ((attr (file-attributes (or temp2 filename))))
3462 (if attr
3463 (ange-ftp-set-xfer-size t-host t-user (nth 7 attr))))
3464
3465 (ange-ftp-send-cmd
3466 t-host
3467 t-user
3468 (list 'put (or temp2 filename) t-path)
3469 (or msg
3470 (if (and temp2 f-parsed)
3471 (format "Putting %s" newname)
3472 (format "Copying %s to %s" f-abbr t-abbr)))
3473 (list (function ange-ftp-cf2)
3474 newname t-host t-user binary temp1 temp2 cont)
3475 nowait))
3476
3477 ;; newname wasn't remote.
3478 (ange-ftp-cf2 t nil newname t-host t-user binary temp1 temp2 cont))
3479
3480 ;; first copy failed, tell caller
3481 (ange-ftp-call-cont cont result line)))
3482
3483 ;; last part of copying routine.
3484 (defun ange-ftp-cf2 (result line newname t-host t-user binary temp1 temp2 cont)
3485 (unwind-protect
3486 (if line
3487 ;; result from doing a local to remote copy.
3488 (unwind-protect
3489 (progn
3490 (or result
3491 (or cont
3492 (signal 'ftp-error
3493 (list "Opening output file"
3494 (format "FTP Error: \"%s\"" line)
3495 newname))))
3496
3497 (ange-ftp-add-file-entry newname))
3498
3499 ;; cleanup.
3500 (if binary
3501 (ange-ftp-set-ascii-mode t-host t-user)))
3502
3503 ;; newname was local.
3504 (if temp1
3505 (ange-ftp-real-copy-file temp1 newname t)))
3506
3507 ;; clean up
3508 (and temp1 (ange-ftp-del-tmp-name temp1))
3509 (and temp2 (ange-ftp-del-tmp-name temp2))
3510 (ange-ftp-call-cont cont result line)))
3511
3512 (defun ange-ftp-copy-file (filename newname &optional ok-if-already-exists
3513 keep-date)
3514 "Documented as original."
3515 (interactive "fCopy file: \nFCopy %s to file: \np")
3516 (ange-ftp-copy-file-internal filename
3517 newname
3518 ok-if-already-exists
3519 keep-date
3520 nil
3521 nil
3522 (interactive-p)))
3523
3524 ;;;; ------------------------------------------------------------
3525 ;;;; File renaming support.
3526 ;;;; ------------------------------------------------------------
3527
3528 (defun ange-ftp-rename-remote-to-remote (filename newname f-parsed t-parsed
3529 binary)
3530 "Rename remote file FILE to remote file NEWNAME."
3531 (let ((f-host (nth 0 f-parsed))
3532 (f-user (nth 1 f-parsed))
3533 (t-host (nth 0 t-parsed))
3534 (t-user (nth 1 t-parsed)))
3535 (if (and (string-equal f-host t-host)
3536 (string-equal f-user t-user))
3537 (let* ((f-path (ange-ftp-quote-string (nth 2 f-parsed)))
3538 (t-path (ange-ftp-quote-string (nth 2 t-parsed)))
3539 (cmd (list 'rename f-path t-path))
3540 (fabbr (ange-ftp-abbreviate-filename filename))
3541 (nabbr (ange-ftp-abbreviate-filename newname filename))
3542 (result (ange-ftp-send-cmd f-host f-user cmd
3543 (format "Renaming %s to %s"
3544 fabbr
3545 nabbr))))
3546 (or (car result)
3547 (signal 'ftp-error
3548 (list
3549 "Renaming"
3550 (format "FTP Error: \"%s\"" (cdr result))
3551 filename
3552 newname)))
3553 (ange-ftp-add-file-entry newname)
3554 (ange-ftp-delete-file-entry filename))
3555 (ange-ftp-copy-file-internal filename newname t nil)
3556 (delete-file filename))))
3557
3558 (defun ange-ftp-rename-local-to-remote (filename newname)
3559 "Rename local FILE to remote file NEWNAME."
3560 (let* ((fabbr (ange-ftp-abbreviate-filename filename))
3561 (nabbr (ange-ftp-abbreviate-filename newname filename))
3562 (msg (format "Renaming %s to %s" fabbr nabbr)))
3563 (ange-ftp-copy-file-internal filename newname t nil msg)
3564 (let (ange-ftp-process-verbose)
3565 (delete-file filename))))
3566
3567 (defun ange-ftp-rename-remote-to-local (filename newname)
3568 "Rename remote file FILE to local file NEWNAME."
3569 (let* ((fabbr (ange-ftp-abbreviate-filename filename))
3570 (nabbr (ange-ftp-abbreviate-filename newname filename))
3571 (msg (format "Renaming %s to %s" fabbr nabbr)))
3572 (ange-ftp-copy-file-internal filename newname t nil msg)
3573 (let (ange-ftp-process-verbose)
3574 (delete-file filename))))
3575
3576 (defun ange-ftp-rename-file (filename newname &optional ok-if-already-exists)
3577 "Documented as original."
3578 (interactive "fRename file: \nFRename %s to file: \np")
3579 (setq filename (expand-file-name filename))
3580 (setq newname (expand-file-name newname))
3581 (let* ((f-parsed (ange-ftp-ftp-path filename))
3582 (t-parsed (ange-ftp-ftp-path newname))
3583 (binary (if (or f-parsed t-parsed) (ange-ftp-binary-file filename))))
3584 (if (and (or f-parsed t-parsed)
3585 (or (not ok-if-already-exists)
3586 (numberp ok-if-already-exists)))
3587 (ange-ftp-barf-or-query-if-file-exists
3588 newname
3589 "rename to it"
3590 (numberp ok-if-already-exists)))
3591 (if f-parsed
3592 (if t-parsed
3593 (ange-ftp-rename-remote-to-remote filename newname f-parsed
3594 t-parsed binary)
3595 (ange-ftp-rename-remote-to-local filename newname))
3596 (if t-parsed
3597 (ange-ftp-rename-local-to-remote filename newname)
3598 (ange-ftp-real-rename-file filename newname ok-if-already-exists)))))
3599
3600 ;;;; ------------------------------------------------------------
3601 ;;;; Classic Dired support.
3602 ;;;; ------------------------------------------------------------
3603
3604 (defvar ange-ftp-dired-host-type nil
3605 "The host type associated with a dired buffer. (buffer local)")
3606 (make-variable-buffer-local 'ange-ftp-dired-host-type)
3607
3608 (defun ange-ftp-dired-readin (dirname buffer)
3609 "Documented as original."
3610 (let ((file (ange-ftp-abbreviate-filename dirname))
3611 (parsed (ange-ftp-ftp-path dirname)))
3612 (save-excursion
3613 (ange-ftp-message "Reading directory %s..." file)
3614 (set-buffer buffer)
3615 (let ((buffer-read-only nil))
3616 (widen)
3617 (erase-buffer)
3618 (setq dirname (expand-file-name dirname))
3619 (if parsed
3620 (let ((host-type (ange-ftp-host-type (car parsed))))
3621 (setq ange-ftp-dired-host-type host-type)
3622 (insert (ange-ftp-ls dirname dired-listing-switches t)))
3623 (if (ange-ftp-real-file-directory-p dirname)
3624 (call-process "ls" nil buffer nil
3625 dired-listing-switches dirname)
3626 (let ((default-directory
3627 (ange-ftp-real-file-name-directory dirname)))
3628 (call-process
3629 shell-file-name nil buffer nil
3630 "-c" (concat
3631 "ls " dired-listing-switches " "
3632 (ange-ftp-real-file-name-nondirectory dirname))))))
3633 (goto-char (point-min))
3634 (while (not (eobp))
3635 (insert " ")
3636 (forward-line 1))
3637 (goto-char (point-min))))
3638 (ange-ftp-message "Reading directory %s...done" file)))
3639
3640 (defun ange-ftp-dired-revert (&optional arg noconfirm)
3641 "Documented as original."
3642 (if (and dired-directory
3643 (ange-ftp-ftp-path (expand-file-name dired-directory)))
3644 (setq ange-ftp-ls-cache-file nil))
3645 (ange-ftp-real-dired-revert arg noconfirm))
3646
3647 ;;;; ------------------------------------------------------------
3648 ;;;; Tree Dired support (ange & Sebastian Kremer)
3649 ;;;; ------------------------------------------------------------
3650
3651 (defvar ange-ftp-dired-re-exe-alist nil
3652 "Association list of regexps \(strings\) which match file lines of
3653 executable files.")
3654
3655 (defvar ange-ftp-dired-re-dir-alist nil
3656 "Association list of regexps \(strings\) which match file lines of
3657 subdirectories.")
3658
3659 (defvar ange-ftp-dired-insert-headerline-alist nil
3660 "Association list of \(TYPE \. FUNC \) pairs, where FUNC is
3661 the function to be used by dired to insert the headerline of
3662 the dired buffer.")
3663
3664 (defvar ange-ftp-dired-move-to-filename-alist nil
3665 "Association list of \(TYPE \. FUNC \) pairs, where FUNC is
3666 the function to be used by dired to move to the beginning of a
3667 filename.")
3668
3669 (defvar ange-ftp-dired-move-to-end-of-filename-alist nil
3670 "Association list of \(TYPE \. FUNC \) pairs, where FUNC is
3671 the function to be used by dired to move to the end of a
3672 filename.")
3673
3674 (defvar ange-ftp-dired-get-filename-alist nil
3675 "Association list of \(TYPE \. FUNC \) pairs, where FUNC is
3676 the function to be used by dired to get a filename from the
3677 current line.")
3678
3679 (defvar ange-ftp-dired-between-files-alist nil
3680 "Association list of \(TYPE \. FUNC \) pairs, where FUNC is
3681 the function to be used by dired to determine when the point
3682 is on a line between files.")
3683
3684 (defvar ange-ftp-dired-ls-trim-alist nil
3685 "Association list of \( TYPE \. FUNC \) pairs, where FUNC is
3686 a function which trims extraneous lines from a directory listing.")
3687
3688 (defvar ange-ftp-dired-clean-directory-alist nil
3689 "Association list of \( TYPE \. FUNC \) pairs, where FUNC is
3690 a function which cleans out old versions of files in the OS TYPE.")
3691
3692 (defvar ange-ftp-dired-flag-backup-files-alist nil
3693 "Association list of \( TYPE \. FUNC \) pairs, where FUNC is
3694 a functions which flags the backup files for deletion in the OS TYPE.")
3695
3696 (defvar ange-ftp-dired-backup-diff-alist nil
3697 "Association list of \( TYPE \. FUNC \) pairs, where FUNC diffs
3698 a file with its backup. The backup file is determined according to
3699 the OS TYPE.")
3700
3701 ;; Could use dired-before-readin-hook here, instead of overloading
3702 ;; dired-readin. However, if people change this hook after ange-ftp
3703 ;; is loaded, they'll break things.
3704 ;; Also, why overload dired-readin rather than dired-mode?
3705 ;; Because I don't want to muck up virtual dired (see dired-x.el).
3706
3707 (defun ange-ftp-tree-dired-readin (dirname buffer)
3708 "Documented as original."
3709 (let ((parsed (ange-ftp-ftp-path dirname)))
3710 (if parsed
3711 (save-excursion
3712 (set-buffer buffer)
3713 (setq ange-ftp-dired-host-type
3714 (ange-ftp-host-type (car parsed)))
3715 (and ange-ftp-dl-dir-regexp
3716 (eq ange-ftp-dired-host-type 'unix)
3717 (string-match ange-ftp-dl-dir-regexp dirname)
3718 (setq ange-ftp-dired-host-type 'unix:dl))
3719 (let ((eentry (assq ange-ftp-dired-host-type
3720 ange-ftp-dired-re-exe-alist))
3721 (dentry (assq ange-ftp-dired-host-type
3722 ange-ftp-dired-re-dir-alist)))
3723 (if eentry
3724 (set (make-local-variable 'dired-re-exe) (cdr eentry)))
3725 (if dentry
3726 (set (make-local-variable 'dired-re-dir) (cdr dentry)))
3727 ;; No switches are sent to dumb hosts, so don't confuse dired.
3728 ;; I hope that dired doesn't get excited if it doesn't see the l
3729 ;; switch. If it does, then maybe fake things by setting this to
3730 ;; "-Al".
3731 (if (memq ange-ftp-dired-host-type ange-ftp-dumb-host-types)
3732 (setq dired-actual-switches "-Al"))))))
3733 (ange-ftp-real-dired-readin dirname buffer))
3734
3735 (defun ange-ftp-dired-insert-headerline (dir)
3736 "Documented as original."
3737 (funcall (or (and ange-ftp-dired-host-type
3738 (cdr (assq ange-ftp-dired-host-type
3739 ange-ftp-dired-insert-headerline-alist)))
3740 'ange-ftp-real-dired-insert-headerline)
3741 dir))
3742
3743 (defun ange-ftp-dired-move-to-filename (&optional raise-error eol)
3744 "Documented as original."
3745 (funcall (or (and ange-ftp-dired-host-type
3746 (cdr (assq ange-ftp-dired-host-type
3747 ange-ftp-dired-move-to-filename-alist)))
3748 'ange-ftp-real-dired-move-to-filename)
3749 raise-error eol))
3750
3751 (defun ange-ftp-dired-move-to-end-of-filename (&optional no-error)
3752 "Documented as original."
3753 (funcall (or (and ange-ftp-dired-host-type
3754 (cdr (assq ange-ftp-dired-host-type
3755 ange-ftp-dired-move-to-end-of-filename-alist)))
3756 'ange-ftp-real-dired-move-to-end-of-filename)
3757 no-error))
3758
3759 (defun ange-ftp-dired-get-filename (&optional localp no-error-if-not-filep)
3760 "Documented as original."
3761 (funcall (or (and ange-ftp-dired-host-type
3762 (cdr (assq ange-ftp-dired-host-type
3763 ange-ftp-dired-get-filename-alist)))
3764 'ange-ftp-real-dired-get-filename)
3765 localp no-error-if-not-filep))
3766
3767 (defun ange-ftp-dired-between-files ()
3768 "Documented as original."
3769 (funcall (or (and ange-ftp-dired-host-type
3770 (cdr (assq ange-ftp-dired-host-type
3771 ange-ftp-dired-between-files-alist)))
3772 'ange-ftp-real-dired-between-files)))
3773
3774 (defvar ange-ftp-bob-version-alist nil
3775 "Association list of pairs \( TYPE \. FUNC \), where FUNC is
3776 a function to be used to bob the version number off of a filename
3777 in OS TYPE.")
3778
3779 (defun ange-ftp-dired-find-file ()
3780 "Documented as original."
3781 (interactive)
3782 (find-file (funcall (or (and ange-ftp-dired-host-type
3783 (cdr (assq ange-ftp-dired-host-type
3784 ange-ftp-bob-version-alist)))
3785 'identity)
3786 (dired-get-filename))))
3787
3788 ;; Need the following functions for making filenames of compressed
3789 ;; files, because some OS's (unlike UNIX) do not allow a filename to
3790 ;; have two extensions.
3791
3792 (defvar ange-ftp-dired-compress-make-compressed-filename-alist nil
3793 "Association list of \( TYPE \. FUNC \) pairs, where FUNC converts a
3794 filename to the filename of the associated compressed file.")
3795
3796 ;;; this overwrites dired's `dired-compress-make-compressed-filename'
3797 (defun ange-ftp-dired-compress-make-compressed-filename (name &optional reverse)
3798 "Converts a filename to the filename of the associated compressed
3799 file. With an optional reverse argument, the reverse conversion is done.
3800
3801 Modified to work with gzip (GNU zip) files."
3802 (let ((parsed (ange-ftp-ftp-path name))
3803 conversion-func)
3804 (if (and parsed
3805 (setq conversion-func
3806 (cdr (assq (ange-ftp-host-type (car parsed))
3807 ange-ftp-dired-compress-make-compressed-filename-alist))))
3808 (funcall conversion-func name reverse)
3809 (if reverse
3810
3811 ;; uncompress...
3812 ;; return `nil' if no match found -- better than nothing
3813 (let (case-fold-search ; case-sensitive search
3814 (string
3815 (concat "\\.\\(g?z\\|" (regexp-quote dired-gzip-file-extension)
3816 "$\\|Z\\)$")))
3817
3818 (and (string-match string name)
3819 (substring name 0 (match-beginning 0))))
3820
3821 ;; add appropriate extension
3822 ;; note: it could be that `gz' is not the proper extension for gzip
3823 (concat name
3824 (if dired-use-gzip-instead-of-compress
3825 dired-gzip-file-extension ".Z"))))))
3826
3827 (defun ange-ftp-dired-clean-directory (keep)
3828 "Documented as original."
3829 (interactive "P")
3830 (funcall (or (and ange-ftp-dired-host-type
3831 (cdr (assq ange-ftp-dired-host-type
3832 ange-ftp-dired-clean-directory-alist)))
3833 'ange-ftp-real-dired-clean-directory)
3834 keep))
3835
3836 (defun ange-ftp-dired-backup-diff (&optional switches)
3837 "Documented as original."
3838 (interactive (list (if (fboundp 'diff-read-switches)
3839 (diff-read-switches "Diff with switches: "))))
3840 (funcall (or (and ange-ftp-dired-host-type
3841 (cdr (assq ange-ftp-dired-host-type
3842 ange-ftp-dired-backup-diff-alist)))
3843 'ange-ftp-real-dired-backup-diff)
3844 switches))
3845
3846
3847 (defun ange-ftp-dired-fixup-subdirs (start file)
3848 "Turn each subdir name into a valid ange-ftp filename."
3849
3850 ;; We haven't indented the listing yet.
3851 ;; Must be careful about filelines ending in a colon: exclude spaces!
3852 (let ((subdir-regexp "^\\([^ \n\r]+\\)\\(:\\)[\n\r]"))
3853 (save-restriction
3854 (save-excursion
3855 (narrow-to-region start (point))
3856 (goto-char start)
3857 (while (re-search-forward subdir-regexp nil t)
3858 (goto-char (match-beginning 1))
3859 (let ((name (buffer-substring (point)
3860 (match-end 1))))
3861 (delete-region (point) (match-end 1))
3862 (insert (ange-ftp-replace-path-component
3863 file
3864 name))))))))
3865
3866 (defun ange-ftp-dired-ls (file switches &optional wildcard full-directory-p)
3867 "Documented as original."
3868 (let ((parsed (ange-ftp-ftp-path file)))
3869 (if parsed
3870 (let* ((pt (point))
3871 (path (nth 2 parsed))
3872 (host-type (ange-ftp-host-type (car parsed)))
3873 (dumb (memq host-type ange-ftp-dumb-host-types))
3874 trim-func case-fold-search)
3875 ;; Make sure that case-fold-search is nil
3876 ;; so that we can look at the switches.
3877 (if wildcard
3878 (if (not (memq host-type '(unix dumb-unix)))
3879 (insert (ange-ftp-ls file switches nil))
3880 ;; Prevent ls from inserting subdirs, as the subdir header
3881 ;; line format would be wrong (it would have no "/user@host:"
3882 ;; prefix)
3883 (insert (ange-ftp-ls file (concat switches "d") nil))
3884
3885 ;; Quoting the path part of the file name seems to be a good
3886 ;; idea (using dired.el's shell-quote function), but ftpd
3887 ;; always globs ls args before passing them to /bin/ls or even
3888 ;; doing the ls formatting itself. --> So wildcard characters
3889 ;; in FILE lose. Sigh...
3890
3891 ;; When using wildcards, some ftpd's put the whole directory
3892 ;; name in front of each filename. Walk down the listing
3893 ;; generated and remove this stuff.
3894 (let ((dir (ange-ftp-real-file-name-directory path)))
3895 (if dir
3896 (let ((dirq (regexp-quote dir)))
3897 (save-restriction
3898 (save-excursion
3899 (narrow-to-region pt (point))
3900 (goto-char pt)
3901 (while (not (eobp))
3902 (if (dired-move-to-filename)
3903 (if (re-search-forward dirq nil t)
3904 (replace-match "")))
3905 (forward-line 1))))))))
3906
3907 ;;;;;;;;;;;;;;;;;;;;;;;;;;
3908 ;; Big issue here Andy! ;;
3909 ;;;;;;;;;;;;;;;;;;;;;;;;;;
3910 ;; In tree dired V5.245 Sebastian has used the following
3911 ;; trick to resolve symbolic links to directories. This causes
3912 ;; havoc with ange-ftp, because ange-ftp expands dots, with
3913 ;; expand-file-name before it sends them. This means that this
3914 ;; trick currently fails for remote SysV machines. But worse,
3915 ;; /vms:/DEV:/FOO/. expands to /vms:/DEV:/FOO, which converts
3916 ;; to DEV:FOO and not DEV:[FOO]. i.e it is only in UNIX that
3917 ;; we can play fast and loose with the difference between
3918 ;; directory names and their associated filenames.
3919 ;; My temporary fix is to knock Sebastian's dot off.
3920 ;; Maybe things can be made real clever in
3921 ;; the future, so that Sebastian can have his way with remote
3922 ;; SysV machines.
3923 ;; Sebastian in dired-readin-insert says:
3924
3925 ;; On SysV derived system, symbolic links to
3926 ;; directories are not resolved, while on BSD
3927 ;; derived it suffices to let DIRNAME end in slash.
3928 ;; We always let it end in "/." since it does no
3929 ;; harm on BSD and makes Dired work on such links on
3930 ;; SysV.
3931
3932 (if (string-match "/\\.$" path)
3933 (setq
3934 file
3935 (ange-ftp-replace-path-component
3936 file (substring path 0 -1))))
3937 (if (string-match "R" switches)
3938 (progn
3939 (insert (ange-ftp-ls file switches nil))
3940 ;; fix up the subdirectory names in the recursive
3941 ;; listing.
3942 (ange-ftp-dired-fixup-subdirs pt file))
3943 (insert
3944 (ange-ftp-ls file
3945 switches
3946 (and (or dumb (string-match "[aA]" switches))
3947 full-directory-p))))
3948 (if (and (null full-directory-p)
3949 (setq trim-func
3950 (cdr (assq host-type
3951 ange-ftp-dired-ls-trim-alist))))
3952 ;; If full-directory-p and wild-card are null, then only one
3953 ;; line per file must be inserted.
3954 ;; Some OS's (like VMS) insert other crap. Clean it out.
3955 (save-restriction
3956 (narrow-to-region pt (point))
3957 (funcall trim-func)))))
3958 (ange-ftp-real-dired-ls file switches wildcard full-directory-p))))
3959
3960 (defvar ange-ftp-remote-shell-file-name
3961 (if (memq system-type '(hpux usg-unix-v)) ; hope that's right
3962 "remsh"
3963 "rsh")
3964 "Remote shell used by ange-ftp.")
3965
3966 (defun ange-ftp-dired-run-shell-command (command &optional in-background)
3967 "Documented as original."
3968 (let* ((parsed (ange-ftp-ftp-path default-directory))
3969 (host (nth 0 parsed))
3970 (user (nth 1 parsed))
3971 (path (nth 2 parsed)))
3972 (if (not parsed)
3973 (ange-ftp-real-dired-run-shell-command command in-background)
3974 (if (> (length path) 0) ; else it's $HOME
3975 (setq command (concat "cd " path "; " command)))
3976 (setq command
3977 (format "%s %s \"%s\"" ; remsh -l USER does not work well
3978 ; on a hp-ux machine I tried
3979 ange-ftp-remote-shell-file-name host command))
3980 (ange-ftp-message "Remote command '%s' ..." command)
3981 ;; Cannot call ange-ftp-real-dired-run-shell-command here as it
3982 ;; would prepend "cd default-directory" --- which bombs because
3983 ;; default-directory is in ange-ftp syntax for remote path names.
3984 (if in-background
3985 (comint::background command)
3986 (shell-command command)))))
3987
3988 (defun ange-ftp-make-directory (dir &optional parents)
3989 "Documented as original."
3990 (interactive (list (let ((current-prefix-arg current-prefix-arg))
3991 (read-directory-name "Create directory: "))
3992 current-prefix-arg))
3993 (if (file-exists-p dir)
3994 (error "Cannot make directory %s: file already exists" dir)
3995 (let ((parsed (ange-ftp-ftp-path dir)))
3996 (if parsed
3997 (let* ((host (nth 0 parsed))
3998 (user (nth 1 parsed))
3999 ;; Some ftp's on unix machines (at least on Suns)
4000 ;; insist that mkdir take a filename, and not a
4001 ;; directory-name name as an arg. Argh!! This is a bug.
4002 ;; Non-unix machines will probably always insist
4003 ;; that mkdir takes a directory-name as an arg
4004 ;; (as the ftp man page says it should).
4005 (path (ange-ftp-quote-string
4006 (if (eq (ange-ftp-host-type host) 'unix)
4007 (ange-ftp-real-directory-file-name (nth 2 parsed))
4008 (ange-ftp-real-file-name-as-directory
4009 (nth 2 parsed)))))
4010 (abbr (ange-ftp-abbreviate-filename dir))
4011 (result (ange-ftp-send-cmd host user
4012 (list 'mkdir path)
4013 (format "Making directory %s"
4014 abbr))))
4015 (or (car result)
4016 (ange-ftp-error host user
4017 (format "Could not make directory %s: %s"
4018 dir
4019 (cdr result))))
4020 (ange-ftp-add-file-entry dir t))
4021 (ange-ftp-real-make-directory dir parents)))))
4022
4023 (defun ange-ftp-remove-directory (dir)
4024 "Documented as original."
4025 (interactive
4026 (list (expand-file-name (read-file-name "Remove directory: "
4027 nil nil 'confirm))))
4028 (if (file-directory-p dir)
4029 (let ((parsed (ange-ftp-ftp-path dir)))
4030 (if parsed
4031 (let* ((host (nth 0 parsed))
4032 (user (nth 1 parsed))
4033 ;; Some ftp's on unix machines (at least on Suns)
4034 ;; insist that rmdir take a filename, and not a
4035 ;; directory-name name as an arg. Argh!! This is a bug.
4036 ;; Non-unix machines will probably always insist
4037 ;; that rmdir takes a directory-name as an arg
4038 ;; (as the ftp man page says it should).
4039 (path (ange-ftp-quote-string
4040 (if (eq (ange-ftp-host-type host) 'unix)
4041 (ange-ftp-real-directory-file-name
4042 (nth 2 parsed))
4043 (ange-ftp-real-file-name-as-directory
4044 (nth 2 parsed)))))
4045 (abbr (ange-ftp-abbreviate-filename dir))
4046 (result (ange-ftp-send-cmd host user
4047 (list 'rmdir path)
4048 (format "Removing directory %s"
4049 abbr))))
4050 (or (car result)
4051 (ange-ftp-error host user
4052 (format "Could not remove directory %s: %s"
4053 dir
4054 (cdr result))))
4055 (ange-ftp-delete-file-entry dir t))
4056 (ange-ftp-real-remove-directory dir)))
4057 (error "Not a directory: %s" dir)))
4058
4059 ;; XEmacs change: This function isn't in the FSF version. Maybe
4060 ;; because there is no such function as diff-read-args. I can't find
4061 ;; where there ever _has_ been such a function. If you want this
4062 ;; functionality, write diff-read-args and uncomment this.
4063
4064 ;;(defun ange-ftp-diff (fn1 fn2 &optional switches)
4065 ;; "Documented as original."
4066 ;; (interactive (diff-read-args "Diff: " "Diff %s with: "
4067 ;; "Diff with switches: "))
4068 ;; (or (and (stringp fn1)
4069 ;; (stringp fn2))
4070 ;; (error "diff: arguments must be strings: %s %s" fn1 fn2))
4071 ;; (or switches
4072 ;; (setq switches (if (stringp diff-switches)
4073 ;; diff-switches
4074 ;; (if (listp diff-switches)
4075 ;; (mapconcat 'identity diff-switches " ")
4076 ;; ""))))
4077 ;; (let* ((fn1 (expand-file-name fn1))
4078 ;; (fn2 (expand-file-name fn2))
4079 ;; (pa1 (ange-ftp-ftp-path fn1))
4080 ;; (pa2 (ange-ftp-ftp-path fn2)))
4081 ;; (if (or pa1 pa2)
4082 ;; (let* ((tmp1 (and pa1 (ange-ftp-make-tmp-name (car pa1))))
4083 ;; (tmp2 (and pa2 (ange-ftp-make-tmp-name (car pa2))))
4084 ;; (bin1 (and pa1 (ange-ftp-binary-file fn1)))
4085 ;; (bin2 (and pa2 (ange-ftp-binary-file fn2)))
4086 ;; (dir1 (file-directory-p fn1))
4087 ;; (dir2 (file-directory-p fn2))
4088 ;; (old-dir default-directory)
4089 ;; (default-directory "/tmp")) ;fool FTP-smart compile.el
4090 ;; (unwind-protect
4091 ;; (progn
4092 ;; (if (and dir1 dir2)
4093 ;; (error "can't compare remote directories"))
4094 ;; (if dir1
4095 ;; (setq fn1 (expand-file-name (file-name-nondirectory fn2)
4096 ;; fn1)
4097 ;; pa1 (ange-ftp-ftp-path fn1)
4098 ;; bin1 (ange-ftp-binary-file fn1)))
4099 ;; (if dir2
4100 ;; (setq fn2 (expand-file-name (file-name-nondirectory fn1)
4101 ;; fn2)
4102 ;; pa2 (ange-ftp-ftp-path fn2)
4103 ;; bin2 (ange-ftp-binary-file fn2)))
4104 ;; (and pa1 (ange-ftp-copy-file-internal fn1 tmp1 t nil
4105 ;; (format "Getting %s" fn1)))
4106 ;; (and pa2 (ange-ftp-copy-file-internal fn2 tmp2 t nil
4107 ;; (format "Getting %s" fn2)))
4108 ;; (and ange-ftp-process-verbose
4109 ;; (ange-ftp-message "doing diff..."))
4110 ;; (sit-for 0)
4111 ;; (ange-ftp-real-diff (or tmp1 fn1) (or tmp2 fn2) switches)
4112 ;; (cond ((boundp 'compilation-process)
4113 ;; (while (and compilation-process
4114 ;; (eq (process-status compilation-process)
4115 ;; 'run))
4116 ;; (accept-process-output compilation-process)))
4117 ;; ((boundp 'compilation-last-buffer)
4118 ;; (while (and compilation-last-buffer
4119 ;; (buffer-name compilation-last-buffer)
4120 ;; (get-buffer-process
4121 ;; compilation-last-buffer)
4122 ;; (eq (process-status
4123 ;; (get-buffer-process
4124 ;; compilation-last-buffer))
4125 ;; 'run))
4126 ;; (accept-process-output))))
4127 ;; (and ange-ftp-process-verbose
4128 ;; (ange-ftp-message "doing diff...done"))
4129 ;; (save-excursion
4130 ;; (set-buffer (get-buffer-create "*compilation*"))
4131 ;;
4132 ;; ;; replace the default directory that we munged earlier.
4133 ;; (goto-char (point-min))
4134 ;; (if (search-forward (concat "cd " default-directory) nil t)
4135 ;; (replace-match (concat "cd " old-dir)))
4136 ;; (setq default-directory old-dir)
4137 ;;
4138 ;; ;; massage the diff output, replacing the temporary file-
4139 ;; ;; names with their original names.
4140 ;; (if tmp1
4141 ;; (let ((q1 (shell-quote tmp1)))
4142 ;; (goto-char (point-min))
4143 ;; (while (search-forward q1 nil t)
4144 ;; (replace-match fn1))))
4145 ;; (if tmp2
4146 ;; (let ((q2 (shell-quote tmp2)))
4147 ;; (goto-char (point-min))
4148 ;; (while (search-forward q2 nil t)
4149 ;; (replace-match fn2))))))
4150 ;; (and tmp1 (ange-ftp-del-tmp-name tmp1))
4151 ;; (and tmp2 (ange-ftp-del-tmp-name tmp2))))
4152 ;; (ange-ftp-real-diff fn1 fn2 switches))))
4153
4154 (defun ange-ftp-dired-call-process (program discard &rest arguments)
4155 "Documented as original."
4156 ;; PROGRAM is always one of those below in the cond in dired.el.
4157 ;; The ARGUMENTS are (nearly) always files.
4158 (if (ange-ftp-ftp-path default-directory)
4159 ;; Can't use ange-ftp-dired-host-type here because the current
4160 ;; buffer is *dired-check-process output*
4161 (condition-case oops
4162 (cond ((equal "compress" program)
4163 (ange-ftp-call-compress arguments))
4164 ((equal "uncompress" program)
4165 (ange-ftp-call-uncompress arguments))
4166 ((equal "chmod" program)
4167 (ange-ftp-call-chmod arguments))
4168 ;; ((equal "chgrp" program))
4169 ;; ((equal dired-chown-program program))
4170 (t (error "Unknown remote command: %s" program)))
4171 (ftp-error (insert (format "%s: %s, %s\n"
4172 (nth 1 oops)
4173 (nth 2 oops)
4174 (nth 3 oops))))
4175 (error (insert (format "%s\n" (nth 1 oops)))))
4176 (apply 'call-process program nil (not discard) nil arguments)))
4177
4178
4179 (defun ange-ftp-call-compress (args)
4180 "Perform a compress command on a remote file.
4181 Works by taking a copy of the file, compressing it and copying the file
4182 back."
4183 (if (or (not (= (length args) 2))
4184 (not (string-equal "-f" (car args))))
4185 (error
4186 "ange-ftp-call-compress: missing -f flag and/or missing filename: %s"
4187 args))
4188 (let* ((file (nth 1 args))
4189 (parsed (ange-ftp-ftp-path file))
4190 (tmp1 (ange-ftp-make-tmp-name (car parsed)))
4191 (tmp2 (ange-ftp-make-tmp-name (car parsed)))
4192 (abbr (ange-ftp-abbreviate-filename file))
4193 (nfile (ange-ftp-dired-compress-make-compressed-filename file))
4194 (nabbr (ange-ftp-abbreviate-filename nfile))
4195 (msg1 (format "Getting %s" abbr))
4196 (msg2 (format "Putting %s" nabbr)))
4197 (unwind-protect
4198 (progn
4199 (ange-ftp-copy-file-internal file tmp1 t nil msg1)
4200 (and ange-ftp-process-verbose
4201 (ange-ftp-message "Compressing %s..." abbr))
4202 (call-process-region (point)
4203 (point)
4204 shell-file-name
4205 nil
4206 t
4207 nil
4208 "-c"
4209 (format "compress -f -c < %s > %s" tmp1 tmp2))
4210 (and ange-ftp-process-verbose
4211 (ange-ftp-message "Compressing %s...done" abbr))
4212 (if (zerop (buffer-size))
4213 (progn
4214 (let (ange-ftp-process-verbose)
4215 (delete-file file))
4216 (ange-ftp-copy-file-internal tmp2 nfile t nil msg2))))
4217 (ange-ftp-del-tmp-name tmp1)
4218 (ange-ftp-del-tmp-name tmp2))))
4219
4220 (defun ange-ftp-call-uncompress (args)
4221 "Perform an uncompress command on a remote file.
4222 Works by taking a copy of the file, uncompressing it and copying the file
4223 back."
4224 (if (not (= (length args) 1))
4225 (error "ange-ftp-call-uncompress: missing filename: %s" args))
4226 (let* ((file (car args))
4227 (parsed (ange-ftp-ftp-path file))
4228 (tmp1 (ange-ftp-make-tmp-name (car parsed)))
4229 (tmp2 (ange-ftp-make-tmp-name (car parsed)))
4230 (abbr (ange-ftp-abbreviate-filename file))
4231 (nfile (ange-ftp-dired-compress-make-compressed-filename file 'reverse))
4232 (nabbr (ange-ftp-abbreviate-filename nfile))
4233 (msg1 (format "Getting %s" abbr))
4234 (msg2 (format "Putting %s" nabbr))
4235 ;; ;; Cheap hack because of problems with binary file transfers from
4236 ;; ;; VMS hosts.
4237 ;; (gbinary (not (eq 'vms (ange-ftp-host-type (car parsed)))))
4238 )
4239 (unwind-protect
4240 (progn
4241 (ange-ftp-copy-file-internal file tmp1 t nil msg1)
4242 (and ange-ftp-process-verbose
4243 (ange-ftp-message "Uncompressing %s..." abbr))
4244 (call-process-region (point)
4245 (point)
4246 shell-file-name
4247 nil
4248 t
4249 nil
4250 "-c"
4251 (format "uncompress -c < %s > %s" tmp1 tmp2))
4252 (and ange-ftp-process-verbose
4253 (ange-ftp-message "Uncompressing %s...done" abbr))
4254 (if (zerop (buffer-size))
4255 (progn
4256 (let (ange-ftp-process-verbose)
4257 (delete-file file))
4258 (ange-ftp-copy-file-internal tmp2 nfile t nil msg2))))
4259 (ange-ftp-del-tmp-name tmp1)
4260 (ange-ftp-del-tmp-name tmp2))))
4261
4262 (defvar ange-ftp-remote-shell "rsh"
4263 "Remote shell to use for chmod, if FTP server rejects the `chmod' command.")
4264
4265 (defun ange-ftp-call-chmod (args)
4266 (if (< (length args) 2)
4267 (error "ange-ftp-call-chmod: missing mode and/or filename: %s" args))
4268 (let ((mode (car args)))
4269 (mapcar
4270 (function
4271 (lambda (file)
4272 (setq file (expand-file-name file))
4273 (let ((parsed (ange-ftp-ftp-path file)))
4274 (if parsed
4275 (let* ((host (nth 0 parsed))
4276 (user (nth 1 parsed))
4277 (path (ange-ftp-quote-string (nth 2 parsed)))
4278 (abbr (ange-ftp-abbreviate-filename file))
4279 (result (ange-ftp-send-cmd host user
4280 (list 'chmod mode path)
4281 (format "doing chmod %s"
4282 abbr))))
4283 (or (car result)
4284 ;; if FTP server rejects chmod, try rsh chmod instead
4285 (call-process
4286 ange-ftp-remote-shell
4287 nil t nil host "chmod" mode path)))))))
4288 (cdr args)))
4289 (setq ange-ftp-ls-cache-file nil)) ;stop confusing dired
4290
4291 ;; Need to abstract the way dired computes the names of compressed files.
4292 ;; I feel badly about these two overloads.
4293
4294 (defun ange-ftp-dired-compress ()
4295 ;; Compress current file. Return nil for success, offending filename else.
4296 (let* (buffer-read-only
4297 (from-file (dired-get-filename))
4298 (to-file (ange-ftp-dired-compress-make-compressed-filename from-file)))
4299 (cond ((save-excursion (beginning-of-line)
4300 (looking-at dired-re-sym))
4301 (dired-log (concat "Attempt to compress a symbolic link:\n"
4302 from-file))
4303 (dired-make-relative from-file))
4304 ((dired-check-process (concat "Compressing " from-file)
4305 "compress" "-f" from-file)
4306 ;; errors from the process are already logged by
4307 ;; dired-check-process
4308 (dired-make-relative from-file))
4309 (t
4310 (dired-update-file-line to-file)
4311 nil))))
4312
4313 (defun ange-ftp-dired-uncompress ()
4314 ;; Uncompress current file. Return nil for success,
4315 ;; offending filename else.
4316 (let* (buffer-read-only
4317 (from-file (dired-get-filename))
4318 (to-file (ange-ftp-dired-compress-make-compressed-filename from-file 'reverse)))
4319 (if (dired-check-process (concat "Uncompressing " from-file)
4320 "uncompress" from-file)
4321 (dired-make-relative from-file)
4322 (dired-update-file-line to-file)
4323 nil)))
4324
4325 (defun ange-ftp-dired-flag-backup-files (&optional unflag-p)
4326 "Documented as original."
4327 (interactive "P")
4328 (funcall (or (and ange-ftp-dired-host-type
4329 (cdr (assq ange-ftp-dired-host-type
4330 ange-ftp-dired-flag-backup-files-alist)))
4331 'ange-ftp-real-dired-flag-backup-files)
4332 unflag-p))
4333
4334 ;;; ------------------------------------------------------------
4335 ;;; Noddy support for async copy-file within dired.
4336 ;;; ------------------------------------------------------------
4337
4338 (defun ange-ftp-dired-copy-file (from to ok-flag &optional cont nowait)
4339 "Documented as original."
4340 (dired-handle-overwrite to)
4341 (ange-ftp-copy-file-internal from to ok-flag dired-copy-preserve-time nil
4342 cont nowait))
4343
4344 (defun ange-ftp-dired-do-create-files (op-symbol file-creator operation arg
4345 &optional marker-char op1
4346 how-to)
4347 "Documented as original."
4348 ;; we need to let ange-ftp-dired-create-files know that we indirectly
4349 ;; called it rather than somebody else.
4350 (let ((ange-ftp-dired-do-create-files t)) ; tell who caller is
4351 (ange-ftp-real-dired-do-create-files op-symbol file-creator operation
4352 arg marker-char op1 how-to)))
4353
4354 (defun ange-ftp-dired-create-files (file-creator operation fn-list name-constructor
4355 &optional marker-char)
4356 "Documented as original."
4357 (if (and (boundp 'ange-ftp-dired-do-create-files)
4358 ;; called from ange-ftp-dired-do-create-files?
4359 ange-ftp-dired-do-create-files
4360 ;; any files worth copying?
4361 fn-list
4362 ;; we only support async copy-file at the mo.
4363 (eq file-creator 'dired-copy-file)
4364 ;; it is only worth calling the alternative function for remote files
4365 ;; as we tie ourself in recursive knots otherwise.
4366 (or (ange-ftp-ftp-path (car fn-list))
4367 ;; we can only call the name constructor for dired-do-create-files
4368 ;; since the one for regexps starts prompting here, there and
4369 ;; everywhere.
4370 (ange-ftp-ftp-path (funcall name-constructor (car fn-list)))))
4371 ;; use the process-filter driven routine rather than the iterative one.
4372 (ange-ftp-dcf-1 file-creator
4373 operation
4374 fn-list
4375 name-constructor
4376 (and (boundp 'target) target) ;dynamically bound
4377 marker-char
4378 (current-buffer)
4379 nil ;overwrite-query
4380 nil ;overwrite-backup-query
4381 nil ;failures
4382 nil ;skipped
4383 0 ;success-count
4384 (length fn-list) ;total
4385 )
4386 ;; normal case... use the interative routine... much cheaper.
4387 (ange-ftp-real-dired-create-files file-creator operation fn-list
4388 name-constructor marker-char)))
4389
4390 (defun ange-ftp-dcf-1 (file-creator operation fn-list name-constructor
4391 target marker-char buffer overwrite-query
4392 overwrite-backup-query failures skipped
4393 success-count total)
4394 (let ((old-buf (current-buffer)))
4395 (unwind-protect
4396 (progn
4397 (set-buffer buffer)
4398 (if (null fn-list)
4399 (ange-ftp-dcf-3 failures operation total skipped
4400 success-count buffer)
4401
4402 (let* ((from (car fn-list))
4403 (to (funcall name-constructor from)))
4404 (if (equal to from)
4405 (progn
4406 (setq to nil)
4407 (dired-log "Cannot %s to same file: %s\n"
4408 (downcase operation) from)))
4409 (if (not to)
4410 (ange-ftp-dcf-1 file-creator
4411 operation
4412 (cdr fn-list)
4413 name-constructor
4414 target
4415 marker-char
4416 buffer
4417 overwrite-query
4418 overwrite-backup-query
4419 failures
4420 (cons (dired-make-relative from) skipped)
4421 success-count
4422 total)
4423 (let* ((overwrite (file-exists-p to))
4424 (overwrite-confirmed ; for dired-handle-overwrite
4425 (and overwrite
4426 (let ((help-form '(format "\
4427 Type SPC or `y' to overwrite file `%s',
4428 DEL or `n' to skip to next,
4429 ESC or `q' to not overwrite any of the remaining files,
4430 `!' to overwrite all remaining files with no more questions." to)))
4431 (dired-query 'overwrite-query
4432 "Overwrite `%s'?" to))))
4433 ;; must determine if FROM is marked before file-creator
4434 ;; gets a chance to delete it (in case of a move).
4435 (actual-marker-char
4436 (cond ((integerp marker-char) marker-char)
4437 (marker-char (dired-file-marker from)) ; slow
4438 (t nil))))
4439 (condition-case err
4440 (funcall file-creator from to overwrite-confirmed
4441 (list (function ange-ftp-dcf-2)
4442 nil ;err
4443 file-creator operation fn-list
4444 name-constructor
4445 target
4446 marker-char actual-marker-char
4447 buffer to from
4448 overwrite
4449 overwrite-confirmed
4450 overwrite-query
4451 overwrite-backup-query
4452 failures skipped success-count
4453 total)
4454 t)
4455 (file-error ; FILE-CREATOR aborted
4456 (ange-ftp-dcf-2 nil ;result
4457 nil ;line
4458 err
4459 file-creator operation fn-list
4460 name-constructor
4461 target
4462 marker-char actual-marker-char
4463 buffer to from
4464 overwrite
4465 overwrite-confirmed
4466 overwrite-query
4467 overwrite-backup-query
4468 failures skipped success-count
4469 total))))))))
4470 (set-buffer old-buf))))
4471
4472 (defun ange-ftp-dcf-2 (result line err
4473 file-creator operation fn-list
4474 name-constructor
4475 target
4476 marker-char actual-marker-char
4477 buffer to from
4478 overwrite
4479 overwrite-confirmed
4480 overwrite-query
4481 overwrite-backup-query
4482 failures skipped success-count
4483 total)
4484 (let ((old-buf (current-buffer)))
4485 (unwind-protect
4486 (progn
4487 (set-buffer buffer)
4488 (if (or err (not result))
4489 (progn
4490 (setq failures (cons (dired-make-relative from) failures))
4491 (dired-log "%s `%s' to `%s' failed:\n%s\n"
4492 operation from to (or err line)))
4493 (if overwrite
4494 ;; If we get here, file-creator hasn't been aborted
4495 ;; and the old entry (if any) has to be deleted
4496 ;; before adding the new entry.
4497 (dired-remove-file to))
4498 (setq success-count (1+ success-count))
4499 (message "%s: %d of %d" operation success-count total)
4500 (dired-add-file to actual-marker-char))
4501
4502 (ange-ftp-dcf-1 file-creator operation (cdr fn-list)
4503 name-constructor
4504 target
4505 marker-char
4506 buffer
4507 overwrite-query
4508 overwrite-backup-query
4509 failures skipped success-count
4510 total))
4511 (set-buffer old-buf))))
4512
4513 (defun ange-ftp-dcf-3 (failures operation total skipped success-count
4514 buffer)
4515 (let ((old-buf (current-buffer)))
4516 (unwind-protect
4517 (progn
4518 (set-buffer buffer)
4519 (cond
4520 (failures
4521 (dired-log-summary
4522 (message "%s failed for %d of %d file%s %s"
4523 operation (length failures) total
4524 (dired-plural-s total) failures)))
4525 (skipped
4526 (dired-log-summary
4527 (message "%s: %d of %d file%s skipped %s"
4528 operation (length skipped) total
4529 (dired-plural-s total) skipped)))
4530 (t
4531 (message "%s: %s file%s."
4532 operation success-count (dired-plural-s success-count))))
4533 (dired-move-to-filename))
4534 (set-buffer old-buf))))
4535
4536 ;;;; -----------------------------------------------
4537 ;;;; Unix Descriptive Listing (dl) Support
4538 ;;;; -----------------------------------------------
4539
4540 (defconst ange-ftp-dired-dl-re-dir
4541 "^. [^ /]+/[ \n]"
4542 "Regular expression to use to search for dl directories.")
4543
4544 (or (assq 'unix:dl ange-ftp-dired-re-dir-alist)
4545 (setq ange-ftp-dired-re-dir-alist
4546 (cons (cons 'unix:dl ange-ftp-dired-dl-re-dir)
4547 ange-ftp-dired-re-dir-alist)))
4548
4549 (defun ange-ftp-dired-dl-move-to-filename (&optional raise-error eol)
4550 "In dired, move to the first character of the filename on this line."
4551 ;; This is the Unix dl version.
4552 (or eol (setq eol (progn (end-of-line) (point))))
4553 (let (case-fold-search)
4554 (beginning-of-line)
4555 (if (looking-at ". [^ ]+ +\\([0-9]+\\|-\\|=\\) ")
4556 (goto-char (+ (point) 2))
4557 (if raise-error
4558 (error "No file on this line")
4559 nil))))
4560
4561 (or (assq 'unix:dl ange-ftp-dired-move-to-filename-alist)
4562 (setq ange-ftp-dired-move-to-filename-alist
4563 (cons '(unix:dl . ange-ftp-dired-dl-move-to-filename)
4564 ange-ftp-dired-move-to-filename-alist)))
4565
4566 (defun ange-ftp-dired-dl-move-to-end-of-filename (&optional no-error eol)
4567 ;; Assumes point is at beginning of filename.
4568 ;; So, it should be called only after (dired-move-to-filename t).
4569 ;; On failure, signals an error or returns nil.
4570 ;; This is the Unix dl version.
4571 (let ((opoint (point))
4572 case-fold-search hidden)
4573 (or eol (setq eol (save-excursion (end-of-line) (point))))
4574 (setq hidden (and selective-display
4575 (save-excursion
4576 (search-forward "\r" eol t))))
4577 (if hidden
4578 (if no-error
4579 nil
4580 (error
4581 (substitute-command-keys
4582 "File line is hidden, type \\[dired-hide-subdir] to unhide")))
4583 (skip-chars-forward "^ /" eol)
4584 (if (eq opoint (point))
4585 (if no-error
4586 nil
4587 (error "No file on this line"))
4588 (point)))))
4589
4590 (or (assq 'unix:dl ange-ftp-dired-move-to-end-of-filename-alist)
4591 (setq ange-ftp-dired-move-to-end-of-filename-alist
4592 (cons '(unix:dl . ange-ftp-dired-dl-move-to-end-of-filename)
4593 ange-ftp-dired-move-to-end-of-filename-alist)))
4594
4595 ;;;; ------------------------------------------------------------
4596 ;;;; File name completion support.
4597 ;;;; ------------------------------------------------------------
4598
4599 (defun ange-ftp-file-entry-active-p (sym)
4600 "If the file entry is a symlink, returns whether the file pointed to exists.
4601 Note that DIR is dynamically bound."
4602 (let ((val (get sym 'val)))
4603 (or (not (stringp val))
4604 (file-exists-p (ange-ftp-expand-symlink val dir)))))
4605
4606 (defun ange-ftp-file-entry-not-ignored-p (sym)
4607 "If the file entry is not a directory (nor a symlink pointing to a directory)
4608 returns whether the file (or file pointed to by the symlink) is ignored
4609 by completion-ignored-extensions.
4610 Note that DIR and COMPLETION-IGNORED-PATTERN are dynamically bound."
4611 (let ((val (get sym 'val))
4612 (symname (symbol-name sym)))
4613 (if (stringp val)
4614 (let ((file (ange-ftp-expand-symlink val dir)))
4615 (or (file-directory-p file)
4616 (and (file-exists-p file)
4617 (not (string-match completion-ignored-pattern
4618 symname)))))
4619 (or val ; is a directory name
4620 (not (string-match completion-ignored-pattern symname))))))
4621
4622 (defun ange-ftp-file-name-all-completions (file dir)
4623 "Documented as original."
4624 (setq dir (expand-file-name dir))
4625 (if (ange-ftp-ftp-path dir)
4626 (progn
4627 (ange-ftp-barf-if-not-directory dir)
4628 (setq dir (ange-ftp-real-file-name-as-directory dir))
4629 (let* ((tbl (ange-ftp-get-files dir))
4630 (completions
4631 (all-completions file tbl
4632 (function ange-ftp-file-entry-active-p))))
4633
4634 ;; see whether each matching file is a directory or not...
4635 (mapcar
4636 (function
4637 (lambda (file)
4638 (let ((ent (ange-ftp-get-hash-entry file tbl)))
4639 (if (and ent
4640 (or (not (stringp ent))
4641 (file-directory-p
4642 (ange-ftp-expand-symlink ent dir))))
4643 (concat file "/")
4644 file))))
4645 completions)))
4646
4647 (if (string-equal "/" dir)
4648 (nconc (all-completions file (ange-ftp-generate-root-prefixes))
4649 (ange-ftp-real-file-name-all-completions file dir))
4650 (ange-ftp-real-file-name-all-completions file dir))))
4651
4652 (defun ange-ftp-file-name-completion (file dir)
4653 "Documented as original."
4654 (setq dir (expand-file-name dir))
4655 (if (ange-ftp-ftp-path dir)
4656 (progn
4657 (ange-ftp-barf-if-not-directory dir)
4658 (if (equal file "")
4659 ""
4660 (setq dir (ange-ftp-real-file-name-as-directory dir)) ;real?
4661 (let* ((tbl (ange-ftp-get-files dir))
4662 (completion-ignored-pattern
4663 (mapconcat (function
4664 (lambda (s) (if (stringp s)
4665 (concat (regexp-quote s) "$")
4666 "/"))) ; / never in filename
4667 completion-ignored-extensions
4668 "\\|")))
4669 (ange-ftp-save-match-data
4670 (or (ange-ftp-file-name-completion-1
4671 file tbl dir (function ange-ftp-file-entry-not-ignored-p))
4672 (ange-ftp-file-name-completion-1
4673 file tbl dir (function ange-ftp-file-entry-active-p)))))))
4674 (if (string-equal "/" dir)
4675 (try-completion
4676 file
4677 (nconc (ange-ftp-generate-root-prefixes)
4678 (mapcar 'list
4679 (ange-ftp-real-file-name-all-completions file "/"))))
4680 (ange-ftp-real-file-name-completion file dir))))
4681
4682
4683 (defun ange-ftp-file-name-completion-1 (file tbl dir predicate)
4684 "Internal subroutine for ange-ftp-file-name-completion. Do not call this."
4685 (let ((bestmatch (try-completion file tbl predicate)))
4686 (if bestmatch
4687 (if (eq bestmatch t)
4688 (if (file-directory-p (expand-file-name file dir))
4689 (concat file "/")
4690 t)
4691 (if (and (eq (try-completion bestmatch tbl predicate) t)
4692 (file-directory-p
4693 (expand-file-name bestmatch dir)))
4694 (concat bestmatch "/")
4695 bestmatch)))))
4696
4697 (defun ange-ftp-quote-filename (file)
4698 "Quote `$' as `$$' in FILE to get it past function `substitute-in-file-name.'"
4699 (let ((pos 0))
4700 (while (setq pos (string-match "\\$" file pos))
4701 (setq file (concat (substring file 0 pos)
4702 "$";; precede by escape character (also a $)
4703 (substring file pos))
4704 ;; add 2 instead 1 since another $ was inserted
4705 pos (+ 2 pos)))
4706 file))
4707
4708 ;; (defun ange-ftp-read-file-name-internal (string dir action)
4709 ;; "Documented as original."
4710 ;; (let (name realdir)
4711 ;; (if (eq action 'lambda)
4712 ;; (if (> (length string) 0)
4713 ;; (file-exists-p (substitute-in-file-name string)))
4714 ;; (if (zerop (length string))
4715 ;; (setq name string realdir dir)
4716 ;; (setq string (substitute-in-file-name string)
4717 ;; name (file-name-nondirectory string)
4718 ;; realdir (file-name-directory string))
4719 ;; (setq realdir (if realdir (expand-file-name realdir dir) dir)))
4720 ;; (if action
4721 ;; (file-name-all-completions name realdir)
4722 ;; (let ((specdir (file-name-directory string))
4723 ;; (val (file-name-completion name realdir)))
4724 ;; (if (and specdir (stringp val))
4725 ;; (ange-ftp-quote-filename (concat specdir val))
4726 ;; val))))))
4727
4728 ;; Put these lines uncommmented in your .emacs if you want C-r to refresh
4729 ;; ange-ftp's cache whilst doing filename completion.
4730 ;;
4731 ;;(define-key minibuffer-local-completion-map "\C-r" 'ange-ftp-re-read-dir)
4732 ;;(define-key minibuffer-local-must-match-map "\C-r" 'ange-ftp-re-read-dir)
4733
4734 (defun ange-ftp-re-read-dir (&optional dir)
4735 "Forces a re-read of the directory DIR. If DIR is omitted then it defaults
4736 to the directory part of the contents of the current buffer."
4737 (interactive)
4738 (if dir
4739 (setq dir (expand-file-name dir))
4740 (setq dir (file-name-directory (expand-file-name (buffer-string)))))
4741 (if (ange-ftp-ftp-path dir)
4742 (progn
4743 (setq ange-ftp-ls-cache-file nil)
4744 (ange-ftp-del-hash-entry dir ange-ftp-files-hashtable)
4745 (ange-ftp-get-files dir t))))
4746
4747 ;;;; ------------------------------------------------------------
4748 ;;;; Bits and bobs to bolt ange-ftp into GNU Emacs.
4749 ;;;; ------------------------------------------------------------
4750
4751 (defvar ange-ftp-overwrite-msg
4752 "Note: This function has been modified to work with ange-ftp.")
4753
4754 (defun ange-ftp-safe-documentation (fun)
4755 "A documentation function that isn't quite as fragile."
4756 (condition-case ()
4757 (documentation fun)
4758 (error nil)))
4759
4760 (defun ange-ftp-overwrite-fn (fun)
4761 "Replace FUN's function definition with ange-ftp-FUN's, saving the
4762 original definition as ange-ftp-real-FUN. The original documentation is
4763 placed on the new definition suitably augmented."
4764 (let* ((name (symbol-name fun))
4765 (saved (intern (concat "ange-ftp-real-" name)))
4766 (new (intern (concat "ange-ftp-" name)))
4767 (nfun (symbol-function new))
4768 (exec-directory (if (or (equal (nth 3 command-line-args) "dump")
4769 (equal (nth 4 command-line-args) "dump"))
4770 "../etc/"
4771 exec-directory)))
4772
4773 ;; *** This is unnecessary for any ange-ftp function (I think):
4774 (while (symbolp nfun)
4775 (setq nfun (symbol-function nfun)))
4776
4777 ;; Interpose the ange-ftp function between the function symbol and the
4778 ;; original definition of the function symbol AT TIME OF FIRST LOAD.
4779 ;; We must only redefine the symbol-function of FUN the very first
4780 ;; time, to avoid blowing away stuff that overloads FUN after this.
4781
4782 ;; We direct the function symbol to the ange-ftp's function symbol
4783 ;; rather than function definition to allow reloading of this file or
4784 ;; redefining of the individual function (e.g., during debugging)
4785 ;; later after some other code has been loaded on top of our stuff.
4786
4787 (or (fboundp saved)
4788 (progn
4789 (fset saved (symbol-function fun))
4790 (fset fun new)))
4791
4792 ;; Rewrite the doc string on the new ange-ftp function. This should
4793 ;; be done every time the file is loaded (or a function is redefined),
4794 ;; because the underlying overloaded function may have changed its doc
4795 ;; string.
4796
4797 (let* ((doc-str (ange-ftp-safe-documentation saved))
4798 (ndoc-str (concat doc-str (and doc-str "\n")
4799 ange-ftp-overwrite-msg)))
4800
4801 (cond ((listp nfun)
4802 ;; Probe to test whether function is in preloaded read-only
4803 ;; memory, and if so make writable copy:
4804 (condition-case nil
4805 (setcar nfun (car nfun))
4806 (error
4807 (setq nfun (copy-sequence nfun)) ; shallow copy only
4808 (fset new nfun)))
4809 (let ((ndoc-cdr (nthcdr 2 nfun)))
4810 (if (stringp (car ndoc-cdr))
4811 ;; Replace the existing docstring.
4812 (setcar ndoc-cdr ndoc-str)
4813 ;; There is no docstring. Insert the overwrite msg.
4814 (setcdr ndoc-cdr (cons (car ndoc-cdr) (cdr ndoc-cdr)))
4815 (setcar ndoc-cdr ange-ftp-overwrite-msg))))
4816 (t
4817 ;; it's an emacs19 compiled-code object
4818 (if (not (fboundp 'compiled-function-arglist))
4819 ;; the old way (typical emacs lack of abstraction)
4820 (let ((new-code (append nfun nil))) ; turn it into a list
4821 (if (nthcdr 4 new-code)
4822 (setcar (nthcdr 4 new-code) ndoc-str)
4823 (setcdr (nthcdr 3 new-code) (cons ndoc-str nil)))
4824 (fset new (apply 'make-byte-code new-code)))
4825 ;; the new way (marginally less random) for XEmacs 19.8+
4826 (fset new
4827 (apply 'make-byte-code
4828 (compiled-function-arglist nfun)
4829 (compiled-function-instructions nfun)
4830 (compiled-function-constants nfun)
4831 (compiled-function-stack-depth nfun)
4832 ndoc-str
4833 (if (commandp nfun)
4834 (list (nth 1 (compiled-function-interactive
4835 nfun)))
4836 nil)))
4837 ))))))
4838
4839 (defun ange-ftp-overwrite-dired ()
4840 (if (not (fboundp 'dired-ls)) ;dired should have been loaded by now
4841 (ange-ftp-overwrite-fn 'dired-readin) ; classic dired
4842 (ange-ftp-overwrite-fn 'make-directory) ; tree dired and v19 stuff
4843 (ange-ftp-overwrite-fn 'remove-directory)
4844 ;; XEmacs - not anymore because ange-ftp-diff is hosed
4845 ;; (ange-ftp-overwrite-fn 'diff)
4846 (ange-ftp-overwrite-fn 'dired-run-shell-command)
4847 (ange-ftp-overwrite-fn 'dired-ls)
4848 (ange-ftp-overwrite-fn 'dired-call-process)
4849 ;; Can't use (fset 'ange-ftp-dired-readin 'ange-ftp-tree-dired-readin)
4850 ;; here because it confuses ange-ftp-overwrite-fn.
4851 (fset 'ange-ftp-dired-readin (symbol-function 'ange-ftp-tree-dired-readin))
4852 (ange-ftp-overwrite-fn 'dired-readin)
4853 (ange-ftp-overwrite-fn 'dired-insert-headerline)
4854 (ange-ftp-overwrite-fn 'dired-move-to-filename)
4855 (ange-ftp-overwrite-fn 'dired-move-to-end-of-filename)
4856 (ange-ftp-overwrite-fn 'dired-get-filename)
4857 (ange-ftp-overwrite-fn 'dired-between-files)
4858 (ange-ftp-overwrite-fn 'dired-clean-directory)
4859 (ange-ftp-overwrite-fn 'dired-flag-backup-files)
4860 (ange-ftp-overwrite-fn 'dired-backup-diff)
4861 (if (fboundp 'dired-do-create-files)
4862 ;; dired 6.0 or later.
4863 (progn
4864 (ange-ftp-overwrite-fn 'dired-copy-file)
4865 (ange-ftp-overwrite-fn 'dired-create-files)
4866 (ange-ftp-overwrite-fn 'dired-do-create-files)))
4867 (if (fboundp 'dired-compress-make-compressed-filename)
4868 ;; it's V5.255 or later
4869 (ange-ftp-overwrite-fn 'dired-compress-make-compressed-filename)
4870 ;; ange-ftp-overwrite-fn confuses dired-mark-map here.
4871 (fset 'ange-ftp-real-dired-compress (symbol-function 'dired-compress))
4872 (fset 'dired-compress 'ange-ftp-dired-compress)
4873 (fset 'ange-ftp-real-dired-uncompress (symbol-function 'dired-uncompress))
4874 (fset 'dired-uncompress 'ange-ftp-dired-uncompress)))
4875
4876 (ange-ftp-overwrite-fn 'dired-find-file)
4877 (ange-ftp-overwrite-fn 'dired-revert))
4878
4879 ;; Attention!
4880 ;; It would be nice if ange-ftp-add-hook was generalized to
4881 ;; (defun ange-ftp-add-hook (hook-var hook-function &optional postpend),
4882 ;; where the optional postpend variable stipulates that hook-function
4883 ;; should be post-pended to the hook-var, rather than prepended.
4884 ;; Then, maybe we should overwrite dired with
4885 ;; (ange-ftp-add-hook 'dired-load-hook 'ange-ftp-overwrite-dired t).
4886 ;; This is because dired-load-hook is commonly used to add the dired extras
4887 ;; features (dired-x.el, dired-trns.el, dired-nstd.el, ...). Some of these
4888 ;; extras features overwrite functions in dired.el with fancier versions.
4889 ;; The "extras" overwrites would then clobber the ange-ftp overwrites.
4890 ;; As long as the ange-ftp overwrites are carefully written to use
4891 ;; ange-ftp-real-... when the directory is local, then doing the ange-ftp
4892 ;; overwrites after the extras overwites should be OK.
4893 ;; At the moment, I think that there aren't any conflicts between the extras
4894 ;; overwrites, and the ange-ftp overwrites. This may not last though.
4895
4896 (defun ange-ftp-add-hook (hook-var hook-function)
4897 "Prepend hook-function to hook-var's value, if it is not already an element.
4898 hook-var's value may be a single function or a list of functions."
4899 (if (boundp hook-var)
4900 (let ((value (symbol-value hook-var)))
4901 (if (and (listp value) (not (eq (car value) 'lambda)))
4902 (and (not (memq hook-function value))
4903 (set hook-var
4904 (if value (cons hook-function value) hook-function)))
4905 (and (not (eq hook-function value))
4906 (set hook-var
4907 (list hook-function value)))))
4908 (set hook-var hook-function)))
4909
4910 ;; To load ange-ftp and not dired (leaving it to autoload), define
4911 ;; dired-load-hook and make sure dired.el ends with:
4912 ;; (run-hooks 'dired-load-hook)
4913 ;;
4914 (if (and (boundp 'dired-load-hook)
4915 (not (featurep 'dired)))
4916 (ange-ftp-add-hook 'dired-load-hook 'ange-ftp-overwrite-dired)
4917 (require 'dired)
4918 (ange-ftp-overwrite-dired))
4919
4920 ;; In case v19 or emacs-19.el already loaded:
4921 ;; (Can't use fboundp to check if emacs-19.el is
4922 ;; loaded, because these functions are probably
4923 ;; bound to autoloads.)
4924
4925 (if (and (fboundp 'make-directory)
4926 (not (and (listp (symbol-function 'make-directory))
4927 (eq (car (symbol-function 'make-directory)) 'autoload))))
4928 (ange-ftp-overwrite-fn 'make-directory))
4929 (if (and (fboundp 'remove-directory)
4930 (not (and (listp (symbol-function 'remove-directory))
4931 (eq (car (symbol-function 'remove-directory)) 'autoload))))
4932 (ange-ftp-overwrite-fn 'remove-directory))
4933 ;; XEmacs change -- ange-ftp-diff is hosed
4934 ;;(if (and (fboundp 'diff)
4935 ;; (not (and (listp (symbol-function 'diff))
4936 ;; (eq (car (symbol-function 'diff)) 'autoload))))
4937 ;; (ange-ftp-overwrite-fn 'diff))
4938
4939 (ange-ftp-overwrite-fn 'insert-file-contents)
4940 (ange-ftp-overwrite-fn 'directory-files)
4941 (ange-ftp-overwrite-fn 'file-directory-p)
4942 (ange-ftp-overwrite-fn 'file-writable-p)
4943 (ange-ftp-overwrite-fn 'file-readable-p)
4944 (ange-ftp-overwrite-fn 'file-symlink-p)
4945 (ange-ftp-overwrite-fn 'delete-file)
4946 ;; (ange-ftp-overwrite-fn 'read-file-name-internal)
4947 (ange-ftp-overwrite-fn 'verify-visited-file-modtime)
4948 (ange-ftp-overwrite-fn 'file-exists-p)
4949 (ange-ftp-overwrite-fn 'write-region)
4950 (ange-ftp-overwrite-fn 'backup-buffer)
4951 (ange-ftp-overwrite-fn 'copy-file)
4952 (ange-ftp-overwrite-fn 'rename-file)
4953 (ange-ftp-overwrite-fn 'file-attributes)
4954 (ange-ftp-overwrite-fn 'file-name-directory)
4955 (ange-ftp-overwrite-fn 'file-name-nondirectory)
4956 (ange-ftp-overwrite-fn 'file-name-as-directory)
4957 (ange-ftp-overwrite-fn 'directory-file-name)
4958 (ange-ftp-overwrite-fn 'expand-file-name)
4959 (ange-ftp-overwrite-fn 'file-name-all-completions)
4960 (ange-ftp-overwrite-fn 'file-name-completion)
4961
4962 (or (memq 'ange-ftp-set-buffer-mode find-file-hooks)
4963 (setq find-file-hooks
4964 (cons 'ange-ftp-set-buffer-mode find-file-hooks)))
4965
4966
4967 ;;;; ------------------------------------------------------------
4968 ;;;; VOS support (VOS support is probably broken,
4969 ;;;; but I don't know anything about VOS.)
4970 ;;;; ------------------------------------------------------------
4971 ;
4972 ;(defun ange-ftp-fix-path-for-vos (path &optional reverse)
4973 ; (setq path (copy-sequence path))
4974 ; (let ((from (if reverse ?\> ?\/))
4975 ; (to (if reverse ?\/ ?\>))
4976 ; (i (1- (length path))))
4977 ; (while (>= i 0)
4978 ; (if (= (aref path i) from)
4979 ; (aset path i to))
4980 ; (setq i (1- i)))
4981 ; path))
4982 ;
4983 ;(or (assq 'vos ange-ftp-fix-path-func-alist)
4984 ; (setq ange-ftp-fix-path-func-alist
4985 ; (cons '(vos . ange-ftp-fix-path-for-vos)
4986 ; ange-ftp-fix-path-func-alist)))
4987 ;
4988 ;(or (memq 'vos ange-ftp-dumb-host-types)
4989 ; (setq ange-ftp-dumb-host-types
4990 ; (cons 'vos ange-ftp-dumb-host-types)))
4991 ;
4992 ;(defun ange-ftp-fix-dir-path-for-vos (dir-path)
4993 ; (ange-ftp-fix-path-for-vos
4994 ; (concat dir-path
4995 ; (if (eq ?/ (aref dir-path (1- (length dir-path))))
4996 ; "" "/")
4997 ; "*")))
4998 ;
4999 ;(or (assq 'vos ange-ftp-fix-dir-path-func-alist)
5000 ; (setq ange-ftp-fix-dir-path-func-alist
5001 ; (cons '(vos . ange-ftp-fix-dir-path-for-vos)
5002 ; ange-ftp-fix-dir-path-func-alist)))
5003 ;
5004 ;(defvar ange-ftp-vos-host-regexp nil
5005 ; "If a host matches this regexp then it is assumed to be running VOS.")
5006 ;
5007 ;(defun ange-ftp-vos-host (host)
5008 ; (and ange-ftp-vos-host-regexp
5009 ; (ange-ftp-save-match-data
5010 ; (string-match ange-ftp-vos-host-regexp host))))
5011 ;
5012 ;(defun ange-ftp-parse-vos-listing ()
5013 ; "Parse the current buffer which is assumed to be in VOS list -all
5014 ;format, and return a hashtable as the result."
5015 ; (let ((tbl (ange-ftp-make-hashtable))
5016 ; (type-list
5017 ; '(("^Files: [0-9]+ +Blocks: [0-9]+\n+" nil 40)
5018 ; ("^Dirs: [0-9]+\n+" t 30)))
5019 ; type-regexp type-is-dir type-col file)
5020 ; (goto-char (point-min))
5021 ; (ange-ftp-save-match-data
5022 ; (while type-list
5023 ; (setq type-regexp (car (car type-list))
5024 ; type-is-dir (nth 1 (car type-list))
5025 ; type-col (nth 2 (car type-list))
5026 ; type-list (cdr type-list))
5027 ; (if (re-search-forward type-regexp nil t)
5028 ; (while (eq (char-after (point)) ? )
5029 ; (move-to-column type-col)
5030 ; (setq file (buffer-substring (point)
5031 ; (progn
5032 ; (end-of-line 1)
5033 ; (point))))
5034 ; (ange-ftp-put-hash-entry file type-is-dir tbl)
5035 ; (forward-line 1))))
5036 ; (ange-ftp-put-hash-entry "." 'vosdir tbl)
5037 ; (ange-ftp-put-hash-entry ".." 'vosdir tbl))
5038 ; tbl))
5039 ;
5040 ;(or (assq 'vos ange-ftp-parse-list-func-alist)
5041 ; (setq ange-ftp-parse-list-func-alist
5042 ; (cons '(vos . ange-ftp-parse-vos-listing)
5043 ; ange-ftp-parse-list-func-alist)))
5044
5045 ;;;; ------------------------------------------------------------
5046 ;;;; VMS support.
5047 ;;;; ------------------------------------------------------------
5048
5049 (defun ange-ftp-fix-path-for-vms (path &optional reverse)
5050 "Convert PATH from UNIX-ish to VMS. If REVERSE given then convert from VMS
5051 to UNIX-ish."
5052 (ange-ftp-save-match-data
5053 (if reverse
5054 (if (string-match "^\\([^:]+:\\)?\\(\\[.*\\]\\)?\\([^][]*\\)$" path)
5055 (let (drive dir file)
5056 (if (match-beginning 1)
5057 (setq drive (substring path
5058 (match-beginning 1)
5059 (match-end 1))))
5060 (if (match-beginning 2)
5061 (setq dir
5062 (substring path (match-beginning 2) (match-end 2))))
5063 (if (match-beginning 3)
5064 (setq file
5065 (substring path (match-beginning 3) (match-end 3))))
5066 (and dir
5067 (setq dir (apply (function concat)
5068 (mapcar (function
5069 (lambda (char)
5070 (if (= char ?.)
5071 (vector ?/)
5072 (vector char))))
5073 (substring dir 1 -1)))))
5074 (concat (and drive
5075 (concat "/" drive "/"))
5076 dir (and dir "/")
5077 file))
5078 (error "path %s didn't match" path))
5079 (let (drive dir file tmp)
5080 (if (string-match "^/[^:]+:/" path)
5081 (setq drive (substring path 1
5082 (1- (match-end 0)))
5083 path (substring path (match-end 0))))
5084 (setq tmp (file-name-directory path))
5085 (if tmp
5086 (setq dir (apply (function concat)
5087 (mapcar (function
5088 (lambda (char)
5089 (if (= char ?/)
5090 (vector ?.)
5091 (vector char))))
5092 (substring tmp 0 -1)))))
5093 (setq file (file-name-nondirectory path))
5094 (concat drive
5095 (and dir (concat "[" (if drive nil ".") dir "]"))
5096 file)))))
5097
5098 ;; (ange-ftp-fix-path-for-vms "/PUB$:/ANONYMOUS/SDSCPUB/NEXT/Readme.txt;1")
5099 ;; (ange-ftp-fix-path-for-vms "/PUB$:[ANONYMOUS.SDSCPUB.NEXT]Readme.txt;1" t)
5100
5101 (or (assq 'vms ange-ftp-fix-path-func-alist)
5102 (setq ange-ftp-fix-path-func-alist
5103 (cons '(vms . ange-ftp-fix-path-for-vms)
5104 ange-ftp-fix-path-func-alist)))
5105
5106 (or (memq 'vms ange-ftp-dumb-host-types)
5107 (setq ange-ftp-dumb-host-types
5108 (cons 'vms ange-ftp-dumb-host-types)))
5109
5110 ;; It is important that this function barf for directories for which we know
5111 ;; that we cannot possibly get a directory listing, such as "/" and "/DEV:/".
5112 ;; This is because it saves an unnecessary FTP error, or possibly the listing
5113 ;; might succeed, but give erroneous info. This last case is particularly
5114 ;; likely for OS's (like MTS) for which we need to use a wildcard in order
5115 ;; to list a directory.
5116
5117 (defun ange-ftp-fix-dir-path-for-vms (dir-path)
5118 "Convert path from UNIX-ish to VMS ready for a DIRectory listing."
5119 ;; Should there be entries for .. -> [-] and . -> [] below. Don't
5120 ;; think so, because expand-filename should have already short-circuited
5121 ;; them.
5122 (cond ((string-equal dir-path "/")
5123 (error "Cannot get listing for fictitious \"/\" directory."))
5124 ((string-match "^/[-A-Z0-9_$]+:/$" dir-path)
5125 (error "Cannot get listing for device."))
5126 ((ange-ftp-fix-path-for-vms dir-path))))
5127
5128 (or (assq 'vms ange-ftp-fix-dir-path-func-alist)
5129 (setq ange-ftp-fix-dir-path-func-alist
5130 (cons '(vms . ange-ftp-fix-dir-path-for-vms)
5131 ange-ftp-fix-dir-path-func-alist)))
5132
5133 (defvar ange-ftp-vms-host-regexp nil)
5134
5135 (defun ange-ftp-vms-host (host)
5136 "Return whether HOST is running VMS."
5137 (and ange-ftp-vms-host-regexp
5138 (ange-ftp-save-match-data
5139 (string-match ange-ftp-vms-host-regexp host))))
5140
5141 ;; Because some VMS ftp servers convert filenames to lower case
5142 ;; we allow a-z in the filename regexp. I'm not too happy about this.
5143
5144 (defconst ange-ftp-vms-filename-regexp
5145 (concat
5146 "\\(\\([_A-Za-z0-9$]?\\|[_A-Za-z0-9$][_A-Za-z0-9$---]*\\)\\."
5147 "[_A-Za-z0-9$---]*;+[0-9]*\\)")
5148 "Regular expression to match for a valid VMS file name in Dired buffer.
5149 Stupid freaking bug! Position of _ and $ shouldn't matter but they do.
5150 Having [A-Z0-9$_] bombs on filename _$$CHANGE_LOG$.TXT$ and $CHANGE_LOG$.TX
5151 Other orders of $ and _ seem to all work just fine.")
5152
5153 ;; These parsing functions are as general as possible because the syntax
5154 ;; of ftp listings from VMS hosts is a bit erratic. What saves us is that
5155 ;; the VMS filename syntax is so rigid. If they bomb on a listing in the
5156 ;; standard VMS Multinet format, then this is a bug. If they bomb on a listing
5157 ;; from vms.weird.net, then too bad.
5158
5159 (defun ange-ftp-parse-vms-filename ()
5160 "Extract the next filename from a VMS dired-like listing."
5161 (if (re-search-forward
5162 ange-ftp-vms-filename-regexp
5163 nil t)
5164 (buffer-substring (match-beginning 0) (match-end 0))))
5165
5166 (defun ange-ftp-parse-vms-listing ()
5167 "Parse the current buffer which is assumed to be in MultiNet FTP dir
5168 format, and return a hashtable as the result."
5169 (let ((tbl (ange-ftp-make-hashtable))
5170 file)
5171 (goto-char (point-min))
5172 (ange-ftp-save-match-data
5173 (while (setq file (ange-ftp-parse-vms-filename))
5174 (if (string-match "\\.\\(DIR\\|dir\\);[0-9]+" file)
5175 ;; deal with directories
5176 (ange-ftp-put-hash-entry
5177 (substring file 0 (match-beginning 0)) t tbl)
5178 (ange-ftp-put-hash-entry file nil tbl)
5179 (if (string-match ";[0-9]+$" file) ; deal with extension
5180 ;; sans extension
5181 (ange-ftp-put-hash-entry
5182 (substring file 0 (match-beginning 0)) nil tbl)))
5183 (forward-line 1))
5184 ;; Would like to look for a "Total" line, or a "Directory" line to
5185 ;; make sure that the listing isn't complete garbage before putting
5186 ;; in "." and "..", but we can't even count on all VAX's giving us
5187 ;; either of these.
5188 (ange-ftp-put-hash-entry "." t tbl)
5189 (ange-ftp-put-hash-entry ".." t tbl))
5190 tbl))
5191
5192 (or (assq 'vms ange-ftp-parse-list-func-alist)
5193 (setq ange-ftp-parse-list-func-alist
5194 (cons '(vms . ange-ftp-parse-vms-listing)
5195 ange-ftp-parse-list-func-alist)))
5196
5197 ;; This version only deletes file entries which have
5198 ;; explicit version numbers, because that is all VMS allows.
5199
5200 ;; Can the following two functions be speeded up using file
5201 ;; completion functions?
5202
5203 (defun ange-ftp-vms-delete-file-entry (path &optional dir-p)
5204 (if dir-p
5205 (ange-ftp-internal-delete-file-entry path t)
5206 (ange-ftp-save-match-data
5207 (let ((file (ange-ftp-get-file-part path)))
5208 (if (string-match ";[0-9]+$" file)
5209 ;; In VMS you can't delete a file without an explicit
5210 ;; version number, or wild-card (e.g. FOO;*)
5211 ;; For now, we give up on wildcards.
5212 (let ((files (ange-ftp-get-hash-entry
5213 (file-name-directory path)
5214 ange-ftp-files-hashtable)))
5215 (if files
5216 (let* ((root (substring file 0
5217 (match-beginning 0)))
5218 (regexp (concat "^"
5219 (regexp-quote root)
5220 ";[0-9]+$"))
5221 versions)
5222 (ange-ftp-del-hash-entry file files)
5223 ;; Now we need to check if there are any
5224 ;; versions left. If not, then delete the
5225 ;; root entry.
5226 (mapatoms
5227 '(lambda (sym)
5228 (and (string-match regexp (get sym 'key))
5229 (setq versions t)))
5230 files)
5231 (or versions
5232 (ange-ftp-del-hash-entry root files))))))))))
5233
5234 (or (assq 'vms ange-ftp-delete-file-entry-alist)
5235 (setq ange-ftp-delete-file-entry-alist
5236 (cons '(vms . ange-ftp-vms-delete-file-entry)
5237 ange-ftp-delete-file-entry-alist)))
5238
5239 (defun ange-ftp-vms-add-file-entry (path &optional dir-p)
5240 (if dir-p
5241 (ange-ftp-internal-add-file-entry path t)
5242 (let ((files (ange-ftp-get-hash-entry
5243 (file-name-directory path)
5244 ange-ftp-files-hashtable)))
5245 (if files
5246 (let ((file (ange-ftp-get-file-part path)))
5247 (ange-ftp-save-match-data
5248 (if (string-match ";[0-9]+$" file)
5249 (ange-ftp-put-hash-entry
5250 (substring file 0 (match-beginning 0))
5251 nil files)
5252 ;; Need to figure out what version of the file
5253 ;; is being added.
5254 (let ((regexp (concat "^"
5255 (regexp-quote file)
5256 ";\\([0-9]+\\)$"))
5257 (version 0))
5258 (mapatoms
5259 '(lambda (sym)
5260 (let ((name (get sym 'key)))
5261 (and (string-match regexp name)
5262 (setq version
5263 (max version
5264 (string-to-int
5265 (substring name
5266 (match-beginning 1)
5267 (match-end 1))))))))
5268 files)
5269 (setq version (1+ version))
5270 (ange-ftp-put-hash-entry
5271 (concat file ";" (int-to-string version))
5272 nil files))))
5273 (ange-ftp-put-hash-entry file nil files))))))
5274
5275 (or (assq 'vms ange-ftp-add-file-entry-alist)
5276 (setq ange-ftp-add-file-entry-alist
5277 (cons '(vms . ange-ftp-vms-add-file-entry)
5278 ange-ftp-add-file-entry-alist)))
5279
5280
5281 (defun ange-ftp-add-vms-host (host)
5282 "Interactively adds a given HOST to ange-ftp-vms-host-regexp."
5283 (interactive
5284 (list (read-string "Host: "
5285 (let ((name (or (buffer-file-name)
5286 (and (eq major-mode 'dired-mode)
5287 dired-directory))))
5288 (and name (car (ange-ftp-ftp-path name)))))))
5289 (if (not (ange-ftp-vms-host host))
5290 (setq ange-ftp-vms-host-regexp
5291 (concat "^" (regexp-quote host) "$"
5292 (and ange-ftp-vms-host-regexp "\\|")
5293 ange-ftp-vms-host-regexp)
5294 ange-ftp-host-cache nil)))
5295
5296
5297 (defun ange-ftp-vms-file-name-as-directory (name)
5298 (ange-ftp-save-match-data
5299 (if (string-match "\\.\\(DIR\\|dir\\)\\(;[0-9]+\\)?$" name)
5300 (setq name (substring name 0 (match-beginning 0))))
5301 (ange-ftp-real-file-name-as-directory name)))
5302
5303 (or (assq 'vms ange-ftp-file-name-as-directory-alist)
5304 (setq ange-ftp-file-name-as-directory-alist
5305 (cons '(vms . ange-ftp-vms-file-name-as-directory)
5306 ange-ftp-file-name-as-directory-alist)))
5307
5308 ;;; Tree dired support:
5309
5310 ;; For this code I have borrowed liberally from Sebastian Kremer's
5311 ;; dired-vms.el
5312
5313
5314 ;; These regexps must be anchored to beginning of line.
5315 ;; Beware that the ftpd may put the device in front of the filename.
5316
5317 (defconst ange-ftp-dired-vms-re-exe "^. [^ \t.]+\\.\\(EXE\\|exe\\)[; ]"
5318 "Regular expression to use to search for VMS executable files.")
5319
5320 (defconst ange-ftp-dired-vms-re-dir "^. [^ \t.]+\\.\\(DIR\\|dir\\)[; ]"
5321 "Regular expression to use to search for VMS directories.")
5322
5323 (or (assq 'vms ange-ftp-dired-re-exe-alist)
5324 (setq ange-ftp-dired-re-exe-alist
5325 (cons (cons 'vms ange-ftp-dired-vms-re-exe)
5326 ange-ftp-dired-re-exe-alist)))
5327
5328 (or (assq 'vms ange-ftp-dired-re-dir-alist)
5329 (setq ange-ftp-dired-re-dir-alist
5330 (cons (cons 'vms ange-ftp-dired-vms-re-dir)
5331 ange-ftp-dired-re-dir-alist)))
5332
5333 (defun ange-ftp-dired-vms-insert-headerline (dir)
5334 ;; VMS inserts a headerline. I would prefer the headerline
5335 ;; to be in ange-ftp format. This version tries to
5336 ;; be careful, because we can't count on a headerline
5337 ;; over ftp, and we wouldn't want to delete anything
5338 ;; important.
5339 (save-excursion
5340 (if (looking-at "^ wildcard ")
5341 (forward-line 1))
5342 (if (looking-at "^[ \n\t]*[^\n]+\\][ \t]*\n")
5343 (delete-region (point) (match-end 0))))
5344 (ange-ftp-real-dired-insert-headerline dir))
5345
5346 (or (assq 'vms ange-ftp-dired-insert-headerline-alist)
5347 (setq ange-ftp-dired-insert-headerline-alist
5348 (cons '(vms . ange-ftp-dired-vms-insert-headerline)
5349 ange-ftp-dired-insert-headerline-alist)))
5350
5351 (defun ange-ftp-dired-vms-move-to-filename (&optional raise-error eol)
5352 "In dired, move to first char of filename on this line.
5353 Returns position (point) or nil if no filename on this line."
5354 ;; This is the VMS version.
5355 (let (case-fold-search)
5356 (or eol (setq eol (progn (end-of-line) (point))))
5357 (beginning-of-line)
5358 (if (re-search-forward ange-ftp-vms-filename-regexp eol t)
5359 (goto-char (match-beginning 1))
5360 (if raise-error
5361 (error "No file on this line")
5362 nil))))
5363
5364 (or (assq 'vms ange-ftp-dired-move-to-filename-alist)
5365 (setq ange-ftp-dired-move-to-filename-alist
5366 (cons '(vms . ange-ftp-dired-vms-move-to-filename)
5367 ange-ftp-dired-move-to-filename-alist)))
5368
5369 (defun ange-ftp-dired-vms-move-to-end-of-filename (&optional no-error eol)
5370 ;; Assumes point is at beginning of filename.
5371 ;; So, it should be called only after (dired-move-to-filename t).
5372 ;; case-fold-search must be nil, at least for VMS.
5373 ;; On failure, signals an error or returns nil.
5374 ;; This is the VMS version.
5375 (let (opoint hidden case-fold-search)
5376 (setq opoint (point))
5377 (or eol (setq eol (save-excursion (end-of-line) (point))))
5378 (setq hidden (and selective-display
5379 (save-excursion (search-forward "\r" eol t))))
5380 (if hidden
5381 nil
5382 (re-search-forward ange-ftp-vms-filename-regexp eol t))
5383 (or no-error
5384 (not (eq opoint (point)))
5385 (error
5386 (if hidden
5387 (substitute-command-keys
5388 "File line is hidden, type \\[dired-hide-subdir] to unhide")
5389 "No file on this line")))
5390 (if (eq opoint (point))
5391 nil
5392 (point))))
5393
5394 (or (assq 'vms ange-ftp-dired-move-to-end-of-filename-alist)
5395 (setq ange-ftp-dired-move-to-end-of-filename-alist
5396 (cons '(vms . ange-ftp-dired-vms-move-to-end-of-filename)
5397 ange-ftp-dired-move-to-end-of-filename-alist)))
5398
5399 (defun ange-ftp-dired-vms-between-files ()
5400 (save-excursion
5401 (beginning-of-line)
5402 (or (equal (following-char) 10) ; newline
5403 (equal (following-char) 9) ; tab
5404 (progn (forward-char 2)
5405 (or (looking-at "Total of")
5406 (equal (following-char) 32))))))
5407
5408 (or (assq 'vms ange-ftp-dired-between-files-alist)
5409 (setq ange-ftp-dired-between-files-alist
5410 (cons '(vms . ange-ftp-dired-vms-between-files)
5411 ange-ftp-dired-between-files-alist)))
5412
5413 ;; Beware! In VMS filenames must be of the form "FILE.TYPE".
5414 ;; Therefore, we cannot just append a ".Z" to filenames for
5415 ;; compressed files. Instead, we turn "FILE.TYPE" into
5416 ;; "FILE.TYPE-Z". Hope that this is a reasonable thing to do.
5417
5418 (defun ange-ftp-vms-make-compressed-filename (name &optional reverse)
5419 (if reverse
5420 (cond
5421 ((string-match "-Z;[0-9]+$" name)
5422 (substring name 0 (match-beginning 0)))
5423 ((string-match ";[0-9]+$" name)
5424 (substring name 0 (match-beginning 0)))
5425 ((string-match "-Z$" name)
5426 (substring name 0 -2))
5427 (t name))
5428 (if (string-match ";[0-9]+$" name)
5429 (concat (substring name 0 (match-beginning 0))
5430 "-Z")
5431 (concat name "-Z"))))
5432
5433 (or (assq 'vms ange-ftp-dired-compress-make-compressed-filename-alist)
5434 (setq ange-ftp-dired-compress-make-compressed-filename-alist
5435 (cons '(vms . ange-ftp-vms-make-compressed-filename)
5436 ange-ftp-dired-compress-make-compressed-filename-alist)))
5437
5438 ;; When the filename is too long, VMS will use two lines to list a file
5439 ;; (damn them!) This will confuse dired. To solve this, need to convince
5440 ;; Sebastian to use a function dired-go-to-end-of-file-line, instead of
5441 ;; (forward-line 1). This would require a number of changes to dired.el.
5442 ;; If dired gets confused, revert-buffer will fix it.
5443
5444 (defun ange-ftp-dired-vms-ls-trim ()
5445 (goto-char (point-min))
5446 (let ((case-fold-search nil))
5447 (re-search-forward ange-ftp-vms-filename-regexp))
5448 (beginning-of-line)
5449 (delete-region (point-min) (point))
5450 (forward-line 1)
5451 (delete-region (point) (point-max)))
5452
5453
5454 (or (assq 'vms ange-ftp-dired-ls-trim-alist)
5455 (setq ange-ftp-dired-ls-trim-alist
5456 (cons '(vms . ange-ftp-dired-vms-ls-trim)
5457 ange-ftp-dired-ls-trim-alist)))
5458
5459 (defun ange-ftp-vms-bob-version (name)
5460 (ange-ftp-save-match-data
5461 (if (string-match ";[0-9]+$" name)
5462 (substring name 0 (match-beginning 0))
5463 name)))
5464
5465 (or (assq 'vms ange-ftp-bob-version-alist)
5466 (setq ange-ftp-bob-version-alist
5467 (cons '(vms . ange-ftp-vms-bob-version)
5468 ange-ftp-bob-version-alist)))
5469
5470 ;;; The vms version of clean-directory has 2 more optional args
5471 ;;; than the usual dired version. This is so that it can be used by
5472 ;;; ange-ftp-dired-vms-flag-backup-files.
5473
5474 (defun ange-ftp-dired-vms-clean-directory (keep &optional marker msg)
5475 "Flag numerical backups for deletion.
5476 Spares `dired-kept-versions' latest versions, and `kept-old-versions' oldest.
5477 Positive prefix arg KEEP overrides `dired-kept-versions';
5478 Negative prefix arg KEEP overrides `kept-old-versions' with KEEP made positive.
5479
5480 To clear the flags on these files, you can use \\[dired-flag-backup-files]
5481 with a prefix argument."
5482 ; (interactive "P") ; Never actually called interactively.
5483 (setq keep (max 1 (if keep (prefix-numeric-value keep) dired-kept-versions)))
5484 (let ((early-retention (if (< keep 0) (- keep) kept-old-versions))
5485 ;; late-retention must NEVER be allowed to be less than 1 in VMS!
5486 ;; This could wipe ALL copies of the file.
5487 (late-retention (max 1 (if (<= keep 0) dired-kept-versions keep)))
5488 (action (or msg "Cleaning"))
5489 (trample-marker (or marker dired-del-marker))
5490 (file-version-assoc-list ()))
5491 (message (concat action
5492 " numerical backups (keeping %d late, %d old)...")
5493 late-retention early-retention)
5494 ;; Look at each file.
5495 ;; If the file has numeric backup versions,
5496 ;; put on file-version-assoc-list an element of the form
5497 ;; (FILENAME . VERSION-NUMBER-LIST)
5498 (dired-map-dired-file-lines (function
5499 ange-ftp-dired-vms-collect-file-versions))
5500 ;; Sort each VERSION-NUMBER-LIST,
5501 ;; and remove the versions not to be deleted.
5502 (let ((fval file-version-assoc-list))
5503 (while fval
5504 (let* ((sorted-v-list (cons 'q (sort (cdr (car fval)) '<)))
5505 (v-count (length sorted-v-list)))
5506 (if (> v-count (+ early-retention late-retention))
5507 (rplacd (nthcdr early-retention sorted-v-list)
5508 (nthcdr (- v-count late-retention)
5509 sorted-v-list)))
5510 (rplacd (car fval)
5511 (cdr sorted-v-list)))
5512 (setq fval (cdr fval))))
5513 ;; Look at each file. If it is a numeric backup file,
5514 ;; find it in a VERSION-NUMBER-LIST and maybe flag it for deletion.
5515 (dired-map-dired-file-lines
5516 (function
5517 ange-ftp-dired-vms-trample-file-versions mark))
5518 (message (concat action " numerical backups...done"))))
5519
5520 (or (assq 'vms ange-ftp-dired-clean-directory-alist)
5521 (setq ange-ftp-dired-clean-directory-alist
5522 (cons '(vms . ange-ftp-dired-vms-clean-directory)
5523 ange-ftp-dired-clean-directory-alist)))
5524
5525 (defun ange-ftp-dired-vms-collect-file-versions (fn)
5526 ;; "If it looks like file FN has versions, return a list of the versions.
5527 ;;That is a list of strings which are file names.
5528 ;;The caller may want to flag some of these files for deletion."
5529 (let ((path (nth 2 (ange-ftp-ftp-path fn))))
5530 (if (string-match ";[0-9]+$" path)
5531 (let* ((path (substring path 0 (match-beginning 0)))
5532 (fn (ange-ftp-replace-path-component fn path)))
5533 (if (not (assq fn file-version-assoc-list))
5534 (let* ((base-versions
5535 (concat (file-name-nondirectory path) ";"))
5536 (bv-length (length base-versions))
5537 (possibilities (file-name-all-completions
5538 base-versions
5539 (file-name-directory fn)))
5540 (versions (mapcar
5541 '(lambda (arg)
5542 (if (and (string-match
5543 "[0-9]+$" arg bv-length)
5544 (= (match-beginning 0) bv-length))
5545 (string-to-int (substring arg bv-length))
5546 0))
5547 possibilities)))
5548 (if versions
5549 (setq
5550 file-version-assoc-list
5551 (cons (cons fn versions)
5552 file-version-assoc-list)))))))))
5553
5554 (defun ange-ftp-dired-vms-trample-file-versions (fn)
5555 (let* ((start-vn (string-match ";[0-9]+$" fn))
5556 base-version-list)
5557 (and start-vn
5558 (setq base-version-list ; there was a base version to which
5559 (assoc (substring fn 0 start-vn) ; this looks like a
5560 file-version-assoc-list)) ; subversion
5561 (not (memq (string-to-int (substring fn (1+ start-vn)))
5562 base-version-list)) ; this one doesn't make the cut
5563 (progn (beginning-of-line)
5564 (delete-char 1)
5565 (insert trample-marker)))))
5566
5567 (defun ange-ftp-dired-vms-flag-backup-files (&optional unflag-p)
5568 (let ((dired-kept-versions 1)
5569 (kept-old-versions 0)
5570 marker msg)
5571 (if unflag-p
5572 (setq marker ?\040 msg "Unflagging")
5573 (setq marker dired-del-marker msg "Cleaning"))
5574 (ange-ftp-dired-vms-clean-directory nil marker msg)))
5575
5576 (or (assq 'vms ange-ftp-dired-flag-backup-files-alist)
5577 (setq ange-ftp-dired-flag-backup-files-alist
5578 (cons '(vms . ange-ftp-dired-vms-flag-backup-files)
5579 ange-ftp-dired-flag-backup-files-alist)))
5580
5581 (defun ange-ftp-dired-vms-backup-diff (&optional switches)
5582 (let ((file (dired-get-filename 'no-dir))
5583 bak)
5584 (if (and (string-match ";[0-9]+$" file)
5585 ;; Find most recent previous version.
5586 (let ((root (substring file 0 (match-beginning 0)))
5587 (ver
5588 (string-to-int (substring file (1+ (match-beginning 0)))))
5589 found)
5590 (setq ver (1- ver))
5591 (while (and (> ver 0) (not found))
5592 (setq bak (concat root ";" (int-to-string ver)))
5593 (and (file-exists-p bak) (setq found t))
5594 (setq ver (1- ver)))
5595 found))
5596 (if switches
5597 (diff (expand-file-name bak) (expand-file-name file) switches)
5598 (diff (expand-file-name bak) (expand-file-name file)))
5599 (error "No previous version found for %s" file))))
5600
5601 (or (assq 'vms ange-ftp-dired-backup-diff-alist)
5602 (setq ange-ftp-dired-backup-diff-alist
5603 (cons '(vms . ange-ftp-dired-vms-backup-diff)
5604 ange-ftp-dired-backup-diff-alist)))
5605
5606
5607 ;;;; ------------------------------------------------------------
5608 ;;;; MTS support
5609 ;;;; ------------------------------------------------------------
5610
5611
5612 (defun ange-ftp-fix-path-for-mts (path &optional reverse)
5613 "Convert PATH from UNIX-ish to MTS. If REVERSE given then convert from
5614 MTS to UNIX-ish."
5615 (ange-ftp-save-match-data
5616 (if reverse
5617 (if (string-match "^\\([^:]+:\\)?\\(.*\\)$" path)
5618 (let (acct file)
5619 (if (match-beginning 1)
5620 (setq acct (substring path 0 (match-end 1))))
5621 (if (match-beginning 2)
5622 (setq file (substring path
5623 (match-beginning 2) (match-end 2))))
5624 (concat (and acct (concat "/" acct "/"))
5625 file))
5626 (error "path %s didn't match" path))
5627 (if (string-match "^/\\([^:]+:\\)/\\(.*\\)$" path)
5628 (concat (substring path 1 (match-end 1))
5629 (substring path (match-beginning 2) (match-end 2)))
5630 ;; Let's hope that mts will recognize it anyway.
5631 path))))
5632
5633 (or (assq 'mts ange-ftp-fix-path-func-alist)
5634 (setq ange-ftp-fix-path-func-alist
5635 (cons '(mts . ange-ftp-fix-path-for-mts)
5636 ange-ftp-fix-path-func-alist)))
5637
5638 (defun ange-ftp-fix-dir-path-for-mts (dir-path)
5639 "Convert path from UNIX-ish to MTS ready for a DIRectory listing.
5640 Remember that there are no directories in MTS."
5641 (if (string-equal dir-path "/")
5642 (error "Cannot get listing for fictitious \"/\" directory.")
5643 (let ((dir-path (ange-ftp-fix-path-for-mts dir-path)))
5644 (cond
5645 ((string-equal dir-path "")
5646 "?")
5647 ((string-match ":$" dir-path)
5648 (concat dir-path "?"))
5649 (dir-path))))) ; It's just a single file.
5650
5651 (or (assq 'mts ange-ftp-fix-dir-path-func-alist)
5652 (setq ange-ftp-fix-dir-path-func-alist
5653 (cons '(mts . ange-ftp-fix-dir-path-for-mts)
5654 ange-ftp-fix-dir-path-func-alist)))
5655
5656 (or (memq 'mts ange-ftp-dumb-host-types)
5657 (setq ange-ftp-dumb-host-types
5658 (cons 'mts ange-ftp-dumb-host-types)))
5659
5660 (defvar ange-ftp-mts-host-regexp nil)
5661
5662 (defun ange-ftp-mts-host (host)
5663 "Return whether HOST is running MTS."
5664 (and ange-ftp-mts-host-regexp
5665 (ange-ftp-save-match-data
5666 (string-match ange-ftp-mts-host-regexp host))))
5667
5668 (defun ange-ftp-parse-mts-listing ()
5669 "Parse the current buffer which is assumed to be in
5670 mts ftp dir format."
5671 (let ((tbl (ange-ftp-make-hashtable)))
5672 (goto-char (point-min))
5673 (ange-ftp-save-match-data
5674 (while (re-search-forward ange-ftp-date-regexp nil t)
5675 (end-of-line)
5676 (skip-chars-backward " ")
5677 (let ((end (point)))
5678 (skip-chars-backward "-A-Z0-9_.!")
5679 (ange-ftp-put-hash-entry (buffer-substring (point) end) nil tbl))
5680 (forward-line 1)))
5681 ;; Don't need to bother with ..
5682 (ange-ftp-put-hash-entry "." t tbl)
5683 tbl))
5684
5685 (or (assq 'mts ange-ftp-parse-list-func-alist)
5686 (setq ange-ftp-parse-list-func-alist
5687 (cons '(mts . ange-ftp-parse-mts-listing)
5688 ange-ftp-parse-list-func-alist)))
5689
5690 (defun ange-ftp-add-mts-host (host)
5691 "Interactively adds a given HOST to ange-ftp-mts-host-regexp."
5692 (interactive
5693 (list (read-string "Host: "
5694 (let ((name (or (buffer-file-name)
5695 (and (eq major-mode 'dired-mode)
5696 dired-directory))))
5697 (and name (car (ange-ftp-ftp-path name)))))))
5698 (if (not (ange-ftp-mts-host host))
5699 (setq ange-ftp-mts-host-regexp
5700 (concat "^" (regexp-quote host) "$"
5701 (and ange-ftp-mts-host-regexp "\\|")
5702 ange-ftp-mts-host-regexp)
5703 ange-ftp-host-cache nil)))
5704
5705 ;;; Tree dired support:
5706
5707 ;; There aren't too many systems left that use MTS. This dired support will
5708 ;; work for the implementation of ftp on mtsg.ubc.ca. I hope other mts systems
5709 ;; implement ftp in the same way. If not, it might be necessary to make the
5710 ;; following more flexible.
5711
5712 (defun ange-ftp-dired-mts-move-to-filename (&optional raise-error eol)
5713 "In dired, move to first char of filename on this line.
5714 Returns position (point) or nil if no filename on this line."
5715 ;; This is the MTS version.
5716 (or eol (setq eol (progn (end-of-line) (point))))
5717 (beginning-of-line)
5718 (if (re-search-forward
5719 ange-ftp-date-regexp eol t)
5720 (progn
5721 (skip-chars-forward " ") ; Eat blanks after date
5722 (skip-chars-forward "0-9:" eol) ; Eat time or year
5723 (skip-chars-forward " " eol) ; one space before filename
5724 ;; When listing an account other than the users own account it appends
5725 ;; ACCT: to the beginning of the filename. Skip over this.
5726 (and (looking-at "[A-Z0-9_.]+:")
5727 (goto-char (match-end 0)))
5728 (point))
5729 (if raise-error
5730 (error "No file on this line")
5731 nil)))
5732
5733 (or (assq 'mts ange-ftp-dired-move-to-filename-alist)
5734 (setq ange-ftp-dired-move-to-filename-alist
5735 (cons '(mts . ange-ftp-dired-mts-move-to-filename)
5736 ange-ftp-dired-move-to-filename-alist)))
5737
5738 (defun ange-ftp-dired-mts-move-to-end-of-filename (&optional no-error eol)
5739 ;; Assumes point is at beginning of filename.
5740 ;; So, it should be called only after (dired-move-to-filename t).
5741 ;; On failure, signals an error or returns nil.
5742 ;; This is the MTS version.
5743 (let (opoint hidden case-fold-search)
5744 (setq opoint (point)
5745 eol (save-excursion (end-of-line) (point))
5746 hidden (and selective-display
5747 (save-excursion (search-forward "\r" eol t))))
5748 (if hidden
5749 nil
5750 (skip-chars-forward "-A-Z0-9._!" eol))
5751 (or no-error
5752 (not (eq opoint (point)))
5753 (error
5754 (if hidden
5755 (substitute-command-keys
5756 "File line is hidden, type \\[dired-hide-subdir] to unhide")
5757 "No file on this line")))
5758 (if (eq opoint (point))
5759 nil
5760 (point))))
5761
5762 (or (assq 'mts ange-ftp-dired-move-to-end-of-filename-alist)
5763 (setq ange-ftp-dired-move-to-end-of-filename-alist
5764 (cons '(mts . ange-ftp-dired-mts-move-to-end-of-filename)
5765 ange-ftp-dired-move-to-end-of-filename-alist)))
5766
5767 ;;;; ------------------------------------------------------------
5768 ;;;; CMS support
5769 ;;;; ------------------------------------------------------------
5770
5771 ;; Since CMS doesn't have any full pathname syntax, we have to fudge
5772 ;; things with cd's. We actually send too many cd's, but is dangerous
5773 ;; to try to remember the current minidisk, because if the connection
5774 ;; is closed and needs to be reopened, we will find ourselves back in
5775 ;; the default minidisk. This is fairly likely since CMS ftp servers
5776 ;; usually close the connection after 5 minutes of inactivity.
5777
5778 ;; Have I got the filename character set right?
5779
5780 (defun ange-ftp-fix-path-for-cms (path &optional reverse)
5781 "Convert PATH from UNIX-ish to CMS. If REVERSE is given, convert
5782 from CMS to UNIX. Actually, CMS doesn't have a full pathname syntax,
5783 so we fudge things by sending cd's."
5784 (ange-ftp-save-match-data
5785 (if reverse
5786 ;; Since we only convert output from a pwd in this direction,
5787 ;; we'll assume that it's a minidisk, and make it into a
5788 ;; directory file name. Note that the expand-dir-hashtable
5789 ;; stores directories without the trailing /. Is this
5790 ;; consistent?
5791 (concat "/" path)
5792 (if (string-match "^/\\([-A-Z0-9$*._]+\\)/\\([-A-Z0-9$._]+\\)?$"
5793 path)
5794 (let ((minidisk (substring path 1 (match-end 1))))
5795 (if (match-beginning 2)
5796 (let ((file (substring path (match-beginning 2)
5797 (match-end 2)))
5798 (cmd (concat "cd " minidisk))
5799
5800 ;; Note that host and user are bound in the call
5801 ;; to ange-ftp-send-cmd
5802 (proc (ange-ftp-get-process host user)))
5803
5804 ;; Must use ange-ftp-raw-send-cmd here to avoid
5805 ;; an infinite loop.
5806 (if (car (ange-ftp-raw-send-cmd proc cmd msg))
5807 file
5808 ;; failed... try ONCE more.
5809 (setq proc (ange-ftp-get-process host user))
5810 (let ((result (ange-ftp-raw-send-cmd proc cmd msg)))
5811 (if (car result)
5812 file
5813 ;; failed. give up.
5814 (ange-ftp-error host user
5815 (format "cd to minidisk %s failed: %s"
5816 minidisk (cdr result)))))))
5817 ;; return the minidisk
5818 minidisk))
5819 (error "Invalid CMS filename")))))
5820
5821 (or (assq 'cms ange-ftp-fix-path-func-alist)
5822 (setq ange-ftp-fix-path-func-alist
5823 (cons '(cms . ange-ftp-fix-path-for-cms)
5824 ange-ftp-fix-path-func-alist)))
5825
5826 (or (memq 'cms ange-ftp-dumb-host-types)
5827 (setq ange-ftp-dumb-host-types
5828 (cons 'cms ange-ftp-dumb-host-types)))
5829
5830 (defun ange-ftp-fix-dir-path-for-cms (dir-path)
5831 "Convert path from UNIX-ish to VMS ready for a DIRectory listing."
5832 (cond
5833 ((string-equal "/" dir-path)
5834 (error "Cannot get listing for fictitious \"/\" directory."))
5835 ((string-match "^/\\([-A-Z0-9$*._]+\\)/\\([-A-Z0-9$._]+\\)?$" dir-path)
5836 (let* ((minidisk (substring dir-path (match-beginning 1) (match-end 1)))
5837 ;; host and user are bound in the call to ange-ftp-send-cmd
5838 (proc (ange-ftp-get-process host user))
5839 (cmd (concat "cd " minidisk))
5840 (file (if (match-beginning 2)
5841 ;; it's a single file
5842 (substring path (match-beginning 2)
5843 (match-end 2))
5844 ;; use the wild-card
5845 "*")))
5846 (if (car (ange-ftp-raw-send-cmd proc cmd))
5847 file
5848 ;; try again...
5849 (setq proc (ange-ftp-get-process host user))
5850 (let ((result (ange-ftp-raw-send-cmd proc cmd)))
5851 (if (car result)
5852 file
5853 ;; give up
5854 (ange-ftp-error host user
5855 (format "cd to minidisk %s failed: "
5856 minidisk (cdr result))))))))
5857 (t (error "Invalid CMS pathname"))))
5858
5859 (or (assq 'cms ange-ftp-fix-dir-path-func-alist)
5860 (setq ange-ftp-fix-dir-path-func-alist
5861 (cons '(cms . ange-ftp-fix-dir-path-for-cms)
5862 ange-ftp-fix-dir-path-func-alist)))
5863
5864 (defvar ange-ftp-cms-host-regexp nil
5865 "Regular expression to match hosts running the CMS operating system.")
5866
5867 (defun ange-ftp-cms-host (host)
5868 "Return whether the host is running CMS."
5869 (and ange-ftp-cms-host-regexp
5870 (ange-ftp-save-match-data
5871 (string-match ange-ftp-cms-host-regexp host))))
5872
5873 (defun ange-ftp-add-cms-host (host)
5874 "Interactively adds a given HOST to ange-ftp-cms-host-regexp."
5875 (interactive
5876 (list (read-string "Host: "
5877 (let ((name (or (buffer-file-name)
5878 (and (eq major-mode 'dired-mode)
5879 dired-directory))))
5880 (and name (car (ange-ftp-ftp-path name)))))))
5881 (if (not (ange-ftp-cms-host host))
5882 (setq ange-ftp-cms-host-regexp
5883 (concat "^" (regexp-quote host) "$"
5884 (and ange-ftp-cms-host-regexp "\\|")
5885 ange-ftp-cms-host-regexp)
5886 ange-ftp-host-cache nil)))
5887
5888 (defun ange-ftp-parse-cms-listing ()
5889 "Parse the current buffer which is assumed to be a CMS directory listing."
5890 ;; If we succeed in getting a listing, then we will assume that the minidisk
5891 ;; exists. file is bound by the call to ange-ftp-ls. This doesn't work
5892 ;; because ange-ftp doesn't know that the root hashtable has only part of
5893 ;; the info. It will assume that if a minidisk isn't in it, then it doesn't
5894 ;; exist. It would be nice if completion worked for minidisks, as we
5895 ;; discover them.
5896 ; (let* ((dir-file (directory-file-name file))
5897 ; (root (file-name-directory dir-file))
5898 ; (minidisk (ange-ftp-get-file-part dir-file))
5899 ; (root-tbl (ange-ftp-get-hash-entry root ange-ftp-files-hashtable)))
5900 ; (if root-tbl
5901 ; (ange-ftp-put-hash-entry minidisk t root-tbl)
5902 ; (setq root-tbl (ange-ftp-make-hashtable))
5903 ; (ange-ftp-put-hash-entry minidisk t root-tbl)
5904 ; (ange-ftp-put-hash-entry "." t root-tbl)
5905 ; (ange-ftp-set-files root root-tbl)))
5906 ;; Now do the usual parsing
5907 (let ((tbl (ange-ftp-make-hashtable)))
5908 (goto-char (point-min))
5909 (ange-ftp-save-match-data
5910 (while
5911 (re-search-forward
5912 "^\\([-A-Z0-9$_]+\\) +\\([-A-Z0-9$_]+\\) +[VF] +[0-9]+ " nil t)
5913 (ange-ftp-put-hash-entry
5914 (concat (buffer-substring (match-beginning 1)
5915 (match-end 1))
5916 "."
5917 (buffer-substring (match-beginning 2)
5918 (match-end 2)))
5919 nil tbl)
5920 (forward-line 1))
5921 (ange-ftp-put-hash-entry "." t tbl))
5922 tbl))
5923
5924 (or (assq 'cms ange-ftp-parse-list-func-alist)
5925 (setq ange-ftp-parse-list-func-alist
5926 (cons '(cms . ange-ftp-parse-cms-listing)
5927 ange-ftp-parse-list-func-alist)))
5928
5929 ;;; Tree dired support:
5930
5931 (defconst ange-ftp-dired-cms-re-exe
5932 "^. [-A-Z0-9$_]+ +EXEC "
5933 "Regular expression to use to search for CMS executables.")
5934
5935 (or (assq 'cms ange-ftp-dired-re-exe-alist)
5936 (setq ange-ftp-dired-re-exe-alist
5937 (cons (cons 'cms ange-ftp-dired-cms-re-exe)
5938 ange-ftp-dired-re-exe-alist)))
5939
5940
5941 (defun ange-ftp-dired-cms-insert-headerline (dir)
5942 ;; CMS has no total line, so we insert a blank line for
5943 ;; aesthetics.
5944 (insert "\n")
5945 (forward-char -1)
5946 (ange-ftp-real-dired-insert-headerline dir))
5947
5948 (or (assq 'cms ange-ftp-dired-insert-headerline-alist)
5949 (setq ange-ftp-dired-insert-headerline-alist
5950 (cons '(cms . ange-ftp-dired-cms-insert-headerline)
5951 ange-ftp-dired-insert-headerline-alist)))
5952
5953 (defun ange-ftp-dired-cms-move-to-filename (&optional raise-error eol)
5954 "In dired, move to the first char of filename on this line."
5955 ;; This is the CMS version.
5956 (or eol (setq eol (progn (end-of-line) (point))))
5957 (let (case-fold-search)
5958 (beginning-of-line)
5959 (if (re-search-forward " [-A-Z0-9$_]+ +[-A-Z0-9$_]+ +[VF] +[0-9]+ " eol t)
5960 (goto-char (1+ (match-beginning 0)))
5961 (if raise-error
5962 (error "No file on this line")
5963 nil))))
5964
5965 (or (assq 'cms ange-ftp-dired-move-to-filename-alist)
5966 (setq ange-ftp-dired-move-to-filename-alist
5967 (cons '(cms . ange-ftp-dired-cms-move-to-filename)
5968 ange-ftp-dired-move-to-filename-alist)))
5969
5970 (defun ange-ftp-dired-cms-move-to-end-of-filename (&optional no-error eol)
5971 ;; Assumes point is at beginning of filename.
5972 ;; So, it should be called only after (dired-move-to-filename t).
5973 ;; case-fold-search must be nil, at least for VMS.
5974 ;; On failure, signals an error or returns nil.
5975 ;; This is the CMS version.
5976 (let ((opoint (point))
5977 case-fold-search hidden)
5978 (or eol (setq eol (save-excursion (end-of-line) (point))))
5979 (setq hidden (and selective-display
5980 (save-excursion
5981 (search-forward "\r" eol t))))
5982 (if hidden
5983 (if no-error
5984 nil
5985 (error
5986 (substitute-command-keys
5987 "File line is hidden, type \\[dired-hide-subdir] to unhide")))
5988 (skip-chars-forward "-A-Z0-9$_" eol)
5989 (skip-chars-forward " " eol)
5990 (skip-chars-forward "-A-Z0-9$_" eol)
5991 (if (eq opoint (point))
5992 (if no-error
5993 nil
5994 (error "No file on this line"))
5995 (point)))))
5996
5997 (or (assq 'cms ange-ftp-dired-move-to-end-of-filename-alist)
5998 (setq ange-ftp-dired-move-to-end-of-filename-alist
5999 (cons '(cms . ange-ftp-dired-cms-move-to-end-of-filename)
6000 ange-ftp-dired-move-to-end-of-filename-alist)))
6001
6002 (defun ange-ftp-cms-make-compressed-filename (name &optional reverse)
6003 (if reverse
6004 (if (string-match "-Z$" name)
6005 (substring name 0 -2)
6006 name)
6007 (concat name "-Z")))
6008
6009 (or (assq 'cms ange-ftp-dired-compress-make-compressed-filename-alist)
6010 (setq ange-ftp-dired-compress-make-compressed-filename-alist
6011 (cons '(cms . ange-ftp-cms-make-compressed-filename)
6012 ange-ftp-dired-compress-make-compressed-filename-alist)))
6013
6014 (defun ange-ftp-dired-cms-get-filename (&optional localp no-error-if-not-filep)
6015 (let ((name (ange-ftp-real-dired-get-filename localp no-error-if-not-filep)))
6016 (and name
6017 (if (string-match "^\\([^ ]+\\) +\\([^ ]+\\)$" name)
6018 (concat (substring name 0 (match-end 1))
6019 "."
6020 (substring name (match-beginning 2) (match-end 2)))
6021 name))))
6022
6023 (or (assq 'cms ange-ftp-dired-get-filename-alist)
6024 (setq ange-ftp-dired-get-filename-alist
6025 (cons '(cms . ange-ftp-dired-cms-get-filename)
6026 ange-ftp-dired-get-filename-alist)))
6027
6028 ;;;; ------------------------------------------------------------
6029 ;;;; Finally provide package.
6030 ;;;; ------------------------------------------------------------
6031
6032 ;; This is so that VC doesn't need to be hacked up. I think the fsf way is
6033 ;; a bit cleaner. (Forgive me, as I have sinned...) The great side-effect
6034 ;; of this change is that ange-ftp will now autoload...even w/o being fully
6035 ;; converted to use the filename-handler-alist. --Stig
6036
6037 ;; Turn off RCS/SCCS processing to save time.
6038 ;; This returns nil for any file name as argument.
6039 (put 'vc-registered 'ange-ftp 'null)
6040 ^L
6041 ;;; Define ways of getting at unmodified Emacs primitives,
6042 ;;; turning off our handler.
6043
6044 (defun ange-ftp-run-real-handler (operation args)
6045 (let ((inhibit-file-name-handlers
6046 (cons 'ange-ftp-hook-function
6047 (cons 'ange-ftp-completion-hook-function
6048 (and (eq inhibit-file-name-operation operation)
6049 inhibit-file-name-handlers))))
6050 (inhibit-file-name-operation operation))
6051 (apply operation args)))
6052
6053 ;;;###autoload
6054 (defun ange-ftp-hook-function (operation &rest args)
6055 (let ((fn (get operation 'ange-ftp)))
6056 (if fn (apply fn args)
6057 (ange-ftp-run-real-handler operation args))))
6058
6059 ;;;###autoload
6060 (or (assoc (car ange-ftp-path-format) file-name-handler-alist)
6061 (setq file-name-handler-alist
6062 (cons (cons (car ange-ftp-path-format) 'ange-ftp-hook-function)
6063 file-name-handler-alist)))
6064
6065 ;; ;;; This regexp recognizes and absolute filenames with only one component,
6066 ;; ;;; for the sake of hostname completion.
6067 ;; ;;;###autoload
6068 ;; (or (assoc "^/[^/:]*\\'" file-name-handler-alist)
6069 ;; (setq file-name-handler-alist
6070 ;; (cons '("^/[^/:]*\\'" . ange-ftp-completion-hook-function)
6071 ;; file-name-handler-alist)))
6072
6073 (provide 'ange-ftp)