diff lisp/efs/efs-dired.el @ 22:8fc7fe29b841 r19-15b94

Import from CVS: tag r19-15b94
author cvs
date Mon, 13 Aug 2007 08:50:29 +0200
parents
children 4103f0995bd7
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/efs/efs-dired.el	Mon Aug 13 08:50:29 2007 +0200
@@ -0,0 +1,1645 @@
+;; -*-Emacs-Lisp-*-
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; File:         efs-dired.el
+;; Release:      $efs release: 1.15 $
+;; Version:      $Revision: 1.1 $
+;; RCS:          
+;; Description:  Extends much of Dired to work under efs.
+;; Authors:      Sebastian Kremer <sk@thp.uni-koeln.de>, 
+;;               Andy Norman <ange@hplb.hpl.hp.com>,
+;;               Sandy Rutherford <sandy@ibm550.sissa.it>
+;; Created:      Throughout the ages.
+;; Modified:     Sun Nov 27 12:19:46 1994 by sandy on gandalf
+;; Language:     Emacs-Lisp
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;; Provisions and requirements
+
+(provide 'efs-dired)
+(require 'efs)
+(require 'dired)
+(autoload 'dired-shell-call-process "dired-shell")
+
+(defconst efs-dired-version
+  (concat (substring "$efs release: 1.15 $" 14 -2)
+	  "/"
+	  (substring "$Revision: 1.1 $" 11 -2)))
+
+;;;; ----------------------------------------------------------------
+;;;; User Configuration Variables
+;;;; ----------------------------------------------------------------
+
+(defvar efs-dired-verify-modtime-host-regexp nil
+  "Regular expression determining on which hosts dired modtimes are checked.")
+
+(defvar efs-dired-verify-anonymous-modtime nil
+  "If non-nil, dired modtimes are checked for anonymous logins.")
+
+(defvar efs-remote-shell-file-name
+  (if (memq system-type '(hpux usg-unix-v)) ; hope that's right
+      "remsh"
+    "rsh")
+  "Remote shell used by efs.")
+
+(defvar efs-remote-shell-takes-user
+  (null (null (memq system-type '(aix-v3 hpux silicon-graphics-unix
+					 berkeley-unix))))
+  ;; Complete? Doubt it.
+  "Set to non-nil if your remote shell command takes \"-l USER\".")
+
+;;; Internal Variables
+
+(make-variable-buffer-local 'dired-ls-F-marks-symlinks)
+
+;;;; -----------------------------------------------------------
+;;;; Inserting Directories into Buffers
+;;;; -----------------------------------------------------------
+
+;; The main command for inserting a directory listing in a buffer.
+;; In Emacs 19 this is in files.el, and not specifically connected to
+;; dired. Since our version of it uses some dired functions, it is
+;; included here, but there is an autoload for it in efs.el.
+
+(defun efs-insert-directory (file switches &optional wildcard full-directory-p
+				  nowait marker-char)
+  ;; Inserts a remote directory. Can do this asynch.
+  (let* ((parsed (efs-ftp-path file))
+	 (mk (point-marker))
+	 (host (car parsed))
+	 (user (nth 1 parsed))
+	 (path (nth 2 parsed))
+	 (host-type (efs-host-type host))
+	 (dumb (memq host-type efs-dumb-host-types))
+	 (subdir (and (null (or full-directory-p wildcard))
+		      (condition-case nil
+			  (dired-current-directory)
+			(error nil))))
+	 (case-fold-search nil) ; for testing switches
+	 (parse (and full-directory-p (not wildcard)
+		     (or dumb (efs-parsable-switches-p switches))))
+	 ;; In case dired-omit-silent isn't defined.
+	 (dired-omit-silent (and (boundp 'dired-omit-silent)
+				 dired-omit-silent)))
+    
+    ;; Insert the listing. If it's not a wild-card, and not a full-dir,
+    ;; then we are updating a dired-line. Do this asynch.
+    ;; This way of doing the listing makes sure that the dired
+    ;; buffer is still around after the listing is obtained.
+    
+    (efs-ls
+     file switches t (if parse 'parse t) nil
+     ;; asynch, if we're inserting in a subdir. Do it nowait = 0, so
+     ;; updating the file line gets a high priority??
+     ;; Insert subdir listings NOWAIT = 0 also so 1-line
+     ;; updates don't toggle the mode line.
+     (if (and subdir nowait) 0 nowait)
+     (efs-cont (listing) (host user file path wildcard
+			       nowait marker-char
+			       mk subdir parse switches dired-omit-silent)
+       ;; We pass the value of dired-omit-silent from the caller to the cont.
+       (let ((host-type (efs-host-type host))
+	     (listing-type (efs-listing-type host user)))
+	 (if (marker-buffer mk)
+	     (efs-save-buffer-excursion
+	       (set-buffer (marker-buffer mk))
+	       ;; parsing a listing, sometimes updates info
+	       (if (and parse (eq major-mode 'dired-mode))
+		   (progn
+		     (setq efs-dired-host-type host-type
+			   efs-dired-listing-type listing-type
+			   efs-dired-listing-type-string
+			   (and efs-show-host-type-in-dired
+				(concat " "
+					(symbol-name
+					 efs-dired-listing-type))))
+		     (if (memq host-type '(bsd-unix next-unix))
+			 (setq dired-ls-F-marks-symlinks nil)
+		       (if (memq host-type '(sysV-unix apollo-unix))
+			   (setq dired-ls-F-marks-symlinks t)))))
+	       (if subdir
+		   ;; a 1-line re-list
+		   (save-excursion
+		     (efs-update-file-info
+		      host-type file efs-data-buffer-name)
+		     (goto-char mk)
+		     (let ((new-subdir (condition-case nil
+					   (dired-current-directory)
+					 (error nil)))
+			   buffer-read-only)
+		       (if (and new-subdir
+				(string-equal subdir new-subdir))
+			   (progn
+			     ;; Is there an existing entry?
+			     (if (dired-goto-file file)
+				 (progn
+				   (delete-region
+				    (save-excursion
+				      (skip-chars-backward "^\n\r")
+				      (1- (point)))
+				    (progn
+				      (skip-chars-forward "^\n\r")
+				      (point)))
+				   (goto-char mk)))
+			     (insert listing)
+			     (save-restriction
+			       (narrow-to-region mk (point))
+			       (efs-dired-fixup-listing
+				listing-type file path switches wildcard)
+			       (efs-dired-ls-trim
+				listing-type)
+			       ;; save-excursion loses if fixup had to
+			       ;; remove and re-add the region. Say for
+			       ;; sorting.
+			       (goto-char (point-max)))
+			     (if (and nowait (eq major-mode 'dired-mode))
+				 (dired-after-add-entry
+				  (marker-position mk)
+				  marker-char))))))
+		 (goto-char mk)
+		 (let (buffer-read-only)
+		   (insert listing)
+		   (save-restriction
+		     (narrow-to-region mk (point))
+		     (efs-dired-fixup-listing
+		      listing-type file path switches wildcard)
+		     (goto-char (point-max))))))))))
+     ;; Return 0 if synch, nil if asynch
+    (if nowait nil 0)))
+
+;;; Functions for cleaning listings.
+
+(efs-defun efs-dired-ls-trim nil ()
+  ;; Trims dir listings, so that the listing of a single file is one line.
+  nil)
+
+(efs-defun efs-dired-fixup-listing nil (file path &optional switches wildcard)
+  ;; FILE is in efs syntax.
+  ;; PATH is just the remote path.
+  ;; Some ftpd's put the whole directory name in front of each filename.
+  ;; Seems to depend in a strange way on server-client interaction.
+  ;; Walk down the listing generated and remove this stuff.
+  ;; SWITCHES is a string.
+  (if (memq efs-key efs-unix-host-types)
+      (let ((continue t)
+	    spot bol)
+	(goto-char (point-min))
+	(while (and (not (eobp)) continue)
+	  (and (setq bol (point)
+		     spot (dired-manual-move-to-filename nil bol))
+	       (setq continue (= (following-char) ?/))
+	       (dired-manual-move-to-end-of-filename t bol)
+	       (progn
+		 (skip-chars-backward "^/")
+		 (delete-region spot (point))))
+	  (forward-line 1))
+	(efs-save-match-data
+	  (if (and switches (string-match "R" switches)
+		   (not (string-match "d" switches)))
+	      (let ((subdir-regexp "^\\(/[^ \n\r]+\\):[\n\r]")
+		    name)
+		(goto-char (point-min))
+		(while (re-search-forward subdir-regexp nil t)
+		  (goto-char (match-beginning 0))
+		  ;; There may be /./ type nonsense.
+		  ;; expand-file-name will handle it.
+		  (setq name (expand-file-name
+			      (buffer-substring (point) (match-end 0))))
+		  (delete-region (point) (match-end 0))
+		  (insert (efs-replace-path-component file name)))))))))
+
+
+;;;; ------------------------------------------------------------
+;;;; Tree Dired support
+;;;; ------------------------------------------------------------
+
+;;; efs-dired keymap
+
+(defvar efs-dired-map nil
+  "Keymap for efs commands in dired buffers.")
+
+(if efs-dired-map
+    ()
+  (setq efs-dired-map (make-sparse-keymap))
+  (define-key efs-dired-map "c" 'efs-dired-close-ftp-process)
+  (define-key efs-dired-map "k" 'efs-dired-kill-ftp-process)
+  (define-key efs-dired-map "o" 'efs-dired-display-ftp-process-buffer)
+  (define-key efs-dired-map "p" 'efs-dired-ping-connection))
+
+(fset 'efs-dired-prefix efs-dired-map)
+
+;;; Functions for dealing with the FTP process
+
+(defun efs-dired-close-ftp-process ()
+  "Close the FTP process for the current dired buffer.
+Closing causes the connection to be dropped, but efs will retain its
+cached data for the connection.  This will make it more efficient to
+reopen the connection."
+  (interactive)
+  (or efs-dired-host-type
+      (error "Dired buffer is not for a remote directory."))
+  (efs-close-ftp-process (current-buffer))
+  (let ((parsed (efs-ftp-path default-directory)))
+    (message "Closed FTP connection for %s@%s." (nth 1 parsed) (car parsed))))
+
+(defun efs-dired-kill-ftp-process ()
+  "Kills the FTP process for the current dired buffer.
+Killing causes the connection to be closed, the process buffer to be killed,
+and most of efs's cached data to be wiped."
+  (interactive)
+  (or efs-dired-host-type
+      (error "Dired buffer is not for a remote directory."))
+  (efs-kill-ftp-process (current-buffer))
+  (let ((parsed (efs-ftp-path default-directory)))
+    (message "Killed FTP connection for %s@%s." (nth 1 parsed) (car parsed))))
+
+(defun efs-dired-display-ftp-process-buffer ()
+  "Displays in another window the FTP process buffer for a dired buffer."
+  (interactive)
+  (or efs-dired-host-type
+      (error "Dired buffer is not for a remote directory."))
+  (efs-display-ftp-process-buffer (current-buffer)))
+
+(defun efs-dired-ping-connection ()
+  "Pings FTP connection associated with current dired buffer."
+  (interactive)
+  (or efs-dired-host-type
+      (error "Dired buffer is not for a remote directory."))
+  (efs-ping-ftp-connection (current-buffer)))
+
+
+;;; Reading in dired buffers.
+
+(defun efs-dired-revert (&optional arg noconfirm)
+  (let ((efs-ls-uncache t))
+    (dired-revert arg noconfirm)))
+
+(defun efs-dired-default-dir-function ()
+  (let* ((cd (dired-current-directory))
+	 (parsed (efs-ftp-path cd)))
+    (if parsed
+	(efs-save-match-data
+	  (let ((tail directory-abbrev-alist))
+	    (while tail
+	      (if (string-match (car (car tail)) cd)
+		  (setq cd (concat (cdr (car tail))
+				   (substring cd (match-end 0)))
+			parsed nil))
+	      (setq tail (cdr tail)))
+	    (apply 'efs-unexpand-parsed-filename
+		   (or parsed (efs-ftp-path cd)))))
+      cd)))
+
+(defun efs-dired-before-readin ()
+  ;; Put in the dired-before-readin-hook.
+  (let ((parsed (efs-ftp-path default-directory)))
+    (if parsed
+	(let ((host (car parsed))
+	      (user (nth 1 parsed)))
+	  (setq efs-dired-listing-type (efs-listing-type host user)
+		efs-dired-host-type (efs-host-type host)
+		efs-dired-listing-type-string
+		(and efs-show-host-type-in-dired
+		     (concat " " (symbol-name efs-dired-listing-type))))
+	  (set (make-local-variable 'revert-buffer-function)
+	       (function efs-dired-revert))
+	  (set (make-local-variable 'default-directory-function)
+	       (function efs-dired-default-dir-function))
+	  (set (make-local-variable 'dired-verify-modtimes)
+	       (null (null (and
+			    efs-dired-verify-modtime-host-regexp
+			    (efs-save-match-data
+			      (let ((case-fold-search t))
+				(string-match
+				 efs-dired-verify-modtime-host-regexp host))
+				  (or efs-dired-verify-anonymous-modtime
+				      (not (efs-anonymous-p user))))))))
+	  ;; The hellsoft ftp server mixes up cases.
+	  ;; However, we may not be able to catch this until
+	  ;; after the first directory is listed. 
+	  (if (and
+	       (eq efs-dired-host-type 'hell)
+	       (not (string-equal default-directory
+				  (setq default-directory
+					(downcase default-directory)))))
+	      (or (string-equal (buffer-name) (downcase (buffer-name)))
+		  (rename-buffer (generate-new-buffer-name
+				  (directory-file-name default-directory)))))
+	  ;; Setup the executable and directory regexps
+	  (let ((eentry (assq efs-dired-listing-type
+			      efs-dired-re-exe-alist))
+		(dentry (assq efs-dired-listing-type
+			      efs-dired-re-dir-alist)))
+	    (if eentry
+		(set (make-local-variable 'dired-re-exe) (cdr eentry)))
+	    (if dentry
+		(set (make-local-variable 'dired-re-dir) (cdr dentry))))
+	  ;; No switches are sent to dumb hosts, so don't confuse dired.
+	  ;; I hope that dired doesn't get excited if it doesn't see the l
+	  ;; switch. If it does, then maybe fake things by setting this to
+	  ;; "-Al".
+	  (if (eq efs-dired-listing-type 'vms)
+	      (setq dired-internal-switches
+		    (delq ?F dired-internal-switches))
+	    (if (memq efs-dired-host-type efs-dumb-host-types)
+		(setq dired-internal-switches '(?l ?A)
+		      ;; Don't lie on the mode line
+		      dired-sort-mode "")))
+	  ;; If the remote file system is version-based, don't set
+	  ;; dired-kept-versions to 0. It will flag the most recent
+	  ;; copy of the file for deletion -- this isn't really a backup.
+	  (if (memq efs-dired-host-type efs-version-host-types)
+	      (set (make-local-variable 'dired-kept-versions)
+		   (max 1 dired-kept-versions)))))))
+
+(efs-defun efs-dired-insert-headerline (&use efs-dired-listing-type) (dir)
+  "Documented as original."
+  (efs-real-dired-insert-headerline dir))
+
+(defun efs-dired-uncache (file dir-p)
+  ;; Remove FILE from cache.
+  (if dir-p
+      (efs-del-from-ls-cache file nil t)
+    (efs-del-from-ls-cache file t nil)))
+
+;;; Checking modtimes of directories.
+;;
+;;  This only runs if efs-dired-verify-anonymous-modtime and
+;;  efs-verify-modtime-host-regexp turn it on.  Few (any?) FTP servers
+;;  support getting MDTM for directories.  As usual, we cache whether
+;;  this works, and don't keep senselessly trying it if it doesn't.
+
+(defun efs-dired-file-modtime (file)
+  ;; Returns the modtime.
+  (let* ((parsed (efs-ftp-path file))
+	 (host (car parsed))
+	 (user (nth 1 parsed))
+	 (rpath (nth 2 parsed)))
+    (and (null (efs-get-host-property host 'dir-mdtm-failed))
+	 (let ((result (efs-send-cmd host user (list 'quote 'mdtm rpath)
+				     (and (eq efs-verbose t)
+					  "Getting modtime")))
+	       mp)
+	   (if (and (null (car result))
+		    (setq mp (efs-parse-mdtime (nth 1 result))))
+	       (let ((ent (efs-get-file-entry file)))
+		 (if ent
+		     (setcdr ent (list (nth 1 ent) (nth 2 ent)
+				       (nth 3 ent) (nth 4 ent) mp)))
+		 parsed)
+	     (efs-set-host-property host 'dir-mdtm-failed t)
+	     nil)))))
+
+(defun efs-dired-set-file-modtime (file alist)
+  ;; This works asynch.
+  (let* ((parsed (efs-ftp-path file))
+	 (host (car parsed))
+	 (user (nth 1 parsed))
+	 (path (nth 2 parsed)))
+    (if (efs-get-host-property host 'dir-mdtm-failed)
+	(let ((elt (assoc file alist)))
+	  (if elt (setcar (nthcdr 4 elt) nil)))
+      (efs-send-cmd
+       host user (list 'quote 'mdtm path) nil nil
+       (efs-cont (result line cont-lines) (file alist host)
+	 (let ((elt (assoc file alist))
+	       modtime)
+	   (if (and (null result) (setq modtime (efs-parse-mdtime line)))
+	       (if elt (setcar (nthcdr 4 elt) modtime))
+	     (if elt (setcar (nthcdr 4 elt) nil))
+	     (efs-set-host-property host 'dir-mdtm-failed t))))
+       0)    ; Always do this NOWAIT = 0
+      nil))) ; return NIL
+
+;;; Asynch insertion of subdirs.  Used when renaming subdirs.
+
+(defun efs-dired-insert-subdir (dirname &optional noerror nowait)
+  (let ((buff (current-buffer))
+	(switches (delq ?R (copy-sequence dired-internal-switches))))
+    (efs-ls
+     dirname (dired-make-switches-string switches)
+     t nil noerror nowait
+     (efs-cont (listing) (dirname buff switches)
+       (if (and listing (get-buffer buff))
+	   (save-excursion
+	     (set-buffer buff)
+	     (save-excursion
+	       (let ((elt (assoc dirname dired-subdir-alist))
+		     mark-list)
+		 (if elt
+		     (setq mark-list (dired-insert-subdir-del elt))
+		   (dired-insert-subdir-newpos dirname))
+		 (dired-insert-subdir-doupdate
+		  dirname
+		  (efs-dired-insert-subdir-do-insert dirname listing)
+		  switches elt mark-list)))))))))
+
+(defun efs-dired-insert-subdir-do-insert (dirname listing)
+  (let ((begin (point))
+	indent-tabs-mode end)
+    (insert listing)
+    (setq end (point-marker))
+    (indent-rigidly begin end 2)
+    (goto-char begin)
+    (dired-insert-headerline dirname)
+    ;; If the listing has null lines `quote' them so that "\n\n" delimits
+    ;; subdirs.  This is OK, because we aren't inserting -R listings.
+    (save-excursion
+      (while (search-forward "\n\n" end t)
+	(forward-char -1)
+	(insert " ")))
+    ;; point is now like in dired-build-subdir-alist
+    (prog1
+	(list begin (marker-position end))
+      (set-marker end nil))))
+
+;;; Moving around in dired buffers.
+
+(efs-defun efs-dired-manual-move-to-filename (&use efs-dired-listing-type)
+  (&optional raise-error bol eol)
+  "Documented as original."
+  (efs-real-dired-manual-move-to-filename raise-error bol eol))
+
+(efs-defun efs-dired-manual-move-to-end-of-filename
+  (&use efs-dired-listing-type) (&optional no-error bol eol)
+  "Documented as original."
+  (efs-real-dired-manual-move-to-end-of-filename no-error bol eol))
+
+(efs-defun efs-dired-make-filename-string (&use efs-dired-listing-type)
+  (filename &optional reverse)
+  "Documented as original."
+  ;; This translates file names from the way that they are displayed
+  ;; in listings to the way that the user gives them in the minibuffer.
+  ;; For example, in CMS this should take "FOO BAR" to "FOO.BAR".
+  filename)
+
+(defun efs-dired-find-file ()
+  "Documented as original."
+  (interactive)
+  (find-file
+   (if (memq efs-dired-host-type efs-version-host-types)
+       (efs-internal-file-name-sans-versions
+	efs-dired-host-type (dired-get-filename) t)
+     (dired-get-filename))))
+
+(defun efs-dired-find-file-other-window (&optional display)
+  "Documented as original."
+  (interactive "P")
+  (if display
+      (dired-display-file)
+    (let ((file (dired-get-filename)))
+      (if (memq efs-dired-host-type efs-version-host-types)
+	  (setq file (efs-internal-file-name-sans-versions
+		      efs-dired-host-type file t)))
+      (find-file-other-window file))))
+
+(defun efs-dired-display-file ()
+  "Documented as original."
+  (interactive)
+  (let ((file (dired-get-filename)))
+    (if (memq efs-dired-host-type efs-version-host-types)
+	(setq file (efs-internal-file-name-sans-versions
+		    efs-dired-host-type file t)))
+    (display-buffer (find-file-noselect file))))
+
+(defun efs-dired-find-file-other-frame ()
+  "Documented as original."
+  (interactive)
+  (find-file-other-frame
+   (if (memq efs-dired-host-type efs-version-host-types)
+       (efs-internal-file-name-sans-versions
+	efs-dired-host-type (dired-get-filename) t)
+     (dired-get-filename))))
+
+;;; Creating and deleting new directories.
+
+(defun efs-dired-recursive-delete-directory (fn)
+  ;; Does recursive deletion of remote directories for dired.
+  (or (file-exists-p fn)
+      (signal 'file-error
+	      (list "Removing old file name" "no such directory" fn)))
+  (efs-dired-internal-recursive-delete-directory fn))
+
+(defun efs-dired-internal-recursive-delete-directory (fn)
+  (if (eq (car (file-attributes fn)) t)
+      (let ((files (efs-directory-files fn)))
+	(if files
+	    (mapcar (function
+		     (lambda (ent)
+		       (or (string-equal "." ent)
+			   (string-equal ".." ent)
+			   (efs-dired-internal-recursive-delete-directory
+			    (expand-file-name ent fn)))))
+		    files))
+	(efs-delete-directory fn))
+    (condition-case err
+	(efs-delete-file fn)
+      (ftp-error (if (and (nth 2 err) (stringp (nth 2 err))
+			  (efs-save-match-data
+			    (string-match "^FTP Error: \"550 " (nth 2 err))))
+		     (message "File %s already deleted." fn)
+		   (signal (car err) (cdr err)))))))
+
+;;; File backups and versions.
+
+(efs-defun efs-dired-flag-backup-files
+  (&use efs-dired-host-type) (&optional unflag-p)
+  "Documented as original."
+  (interactive "P")
+  (efs-real-dired-flag-backup-files unflag-p))
+
+(efs-defun efs-dired-collect-file-versions (&use efs-dired-host-type) ()
+  ;; If it looks like a file has versions, return a list of the versions.
+  ;; The return value is ((FILENAME . (VERSION1 VERSION2 ...)) ...)
+  (efs-real-dired-collect-file-versions))
+
+;;; Sorting dired buffers
+
+(defun efs-dired-file-name-lessp (name1 name2)
+  (if (and efs-dired-host-type
+	   (memq efs-dired-host-type efs-case-insensitive-host-types))
+      (string< (downcase name1) (downcase name2))
+    (string< name1 name2)))
+
+;;; Support for async file creators.
+
+(defun efs-dired-copy-file (from to ok-flag &optional cont nowait)
+  ;; Version of dired-copy-file for remote files.
+  ;; Assumes that filenames are already expanded.
+  (dired-handle-overwrite to)
+  (efs-copy-file-internal from (efs-ftp-path from) to (efs-ftp-path to)
+			  ok-flag dired-copy-preserve-time 0 cont nowait))
+
+(defun efs-dired-rename-file (from to ok-flag &optional cont nowait
+				   insert-subdir)
+  ;; Version of dired-rename-file for remote files.
+  (dired-handle-overwrite to)
+  (efs-rename-file-internal
+   from to ok-flag nil
+   (efs-cont (result line cont-lines) (from to cont insert-subdir)
+     (if result
+	 (if cont
+	     (efs-call-cont cont result line cont-lines)
+	   (signal 'ftp-error
+		   (list "Dired Renaming"
+			 (format "FTP Error: \"%s\"" line)
+			 from to)))
+       (dired-remove-file from)
+       ;; Silently rename the visited file of any buffer visiting this file.
+       ;; We do not maintain inserted subdirs for remote 
+       (efs-dired-rename-update-buffers from to insert-subdir)
+       (if cont (efs-call-cont cont result line cont-lines))))
+   nowait))
+
+(defun efs-dired-rename-update-buffers (from to &optional insert-subdir)
+  (if (get-file-buffer from)
+      (save-excursion
+	(set-buffer (get-file-buffer from))
+	(let ((modflag (buffer-modified-p)))
+	  (set-visited-file-name to)	; kills write-file-hooks
+	  (set-buffer-modified-p modflag)))
+    ;; It's a directory.  More work to do.
+    (let ((blist (buffer-list))
+	  (from-dir (file-name-as-directory from))
+	  (to-dir (file-name-as-directory to)))
+      (save-excursion
+	(while blist
+	  (set-buffer (car blist))
+	  (setq blist (cdr blist))
+	  (cond
+	   (buffer-file-name
+	    (if (dired-in-this-tree buffer-file-name from-dir)
+		(let ((modflag (buffer-modified-p)))
+		  (unwind-protect
+		      (set-visited-file-name
+		       (concat to-dir (substring buffer-file-name
+						 (length from-dir))))
+		    (set-buffer-modified-p modflag)))))
+	   (dired-directory
+	    (if (string-equal from-dir (expand-file-name default-directory))
+		;; If top level directory was renamed, lots of things
+		;; have to be updated.
+		(progn
+		  (dired-unadvertise from-dir)
+		  (setq default-directory to-dir
+			dired-directory
+			;; Need to beware of wildcards.
+			(expand-file-name 
+			 (file-name-nondirectory dired-directory)
+			 to-dir))
+		  (let ((new-name (file-name-nondirectory
+				   (directory-file-name dired-directory))))
+		    ;; Try to rename buffer, but just leave old name if new
+		    ;; name would already exist (don't try appending "<%d>")
+		    ;; Why?  --sandy 19-8-94
+		    (or (get-buffer new-name)
+			(rename-buffer new-name)))
+		  (dired-advertise))
+	      (and insert-subdir
+		   (assoc (file-name-directory (directory-file-name to))
+			  dired-subdir-alist)
+		   (if (efs-ftp-path to)
+		       (efs-dired-insert-subdir to t 1)
+		     (dired-insert-subdir to)))))))))))
+
+(defun efs-dired-make-relative-symlink (from to ok-flag &optional cont nowait)
+  ;; efs version of dired-make-relative-symlink
+  ;; Called as a file-name-handler when dired-make-relative-symlink is
+  ;; called interactively.
+  ;; efs-dired-create-files calls it directly to supply CONT
+  ;; and NOWAIT args.
+  (setq from (directory-file-name from)
+	to (directory-file-name to))
+  (efs-make-symbolic-link-internal
+   (dired-make-relative from (file-name-directory to) t)
+   to ok-flag cont nowait))
+
+(defun efs-dired-create-files (file-creator operation fn-list name-constructor
+					    &optional marker-char query
+					    implicit-to)
+  "Documented as original."
+  (if (catch 'found
+	(let ((list fn-list)
+	      val)
+	  (while list
+	    (if (setq val (efs-ftp-path (car list)))
+		(throw 'found val)
+	      (if (setq val (funcall name-constructor (car list)))
+		  (throw 'found (efs-ftp-path val))
+		(setq list (cdr list)))))))
+      (progn
+	(cond ((eq file-creator 'dired-copy-file)
+	       (setq file-creator 'efs-dired-copy-file))
+	      ((eq file-creator 'dired-rename-file)
+	       (setq file-creator 'efs-dired-rename-file))
+	      ((eq file-creator 'make-symbolic-link)
+	       (setq file-creator 'efs-make-symbolic-link-internal))
+	      ((eq file-creator 'add-name-to-file)
+	       (setq file-creator 'efs-add-name-to-file-internal))
+	      ((eq file-creator 'dired-make-relative-symlink)
+	       (setq file-creator 'efs-dired-make-relative-symlink))
+	      ((eq file-creator 'dired-compress-file)
+	       (setq file-creator 'efs-dired-compress-file))
+	      ((error "Unable to perform operation %s on remote hosts."
+		      file-creator)))
+	;; use the process-filter driven routine rather than the iterative one.
+	(efs-dcf-1 file-creator operation fn-list name-constructor
+		   (if (eq marker-char t)
+		       (mapcar 'dired-file-marker fn-list)
+		     marker-char)
+		   query (buffer-name (current-buffer))
+		   nil	;overwrite-query
+		   nil	;dired-overwrite-backup-query
+		   nil  ;dired-file-creator-query
+		   nil	;failures
+		   nil	;skipped
+		   0		;success-count
+		   (length fn-list) ;total
+		   implicit-to
+		   (and (eq file-creator 'efs-dired-rename-file)
+			(delq nil
+			      (mapcar
+			       (function
+				(lambda (x)
+				  (and (assoc (file-name-as-directory x)
+					      dired-subdir-alist)
+				       x)))
+			       fn-list)))))
+    ;; normal case... use the interative routine... much cheaper.
+    (efs-real-dired-create-files file-creator operation fn-list
+				 name-constructor marker-char query
+				 implicit-to)))
+
+(defun efs-dcf-1 (file-creator operation fn-list name-constructor
+			       markers query buffer-name overwrite-query 
+			       overwrite-backup-query file-creator-query
+			       failures skipped success-count total
+			       implicit-to insertions)
+  (if (null fn-list)
+      (efs-dcf-3 failures operation total skipped
+		 success-count buffer-name)
+    (let* ((from (car fn-list))
+	   ;; For dired-handle-overwrite and the file-creator-query,
+	   ;; need to set these 2 fluid vars according to the cont data.
+	   (dired-overwrite-backup-query overwrite-backup-query)
+	   (dired-file-creator-query file-creator-query)
+	   (to (funcall name-constructor from))
+	   (marker-char (if (consp markers)
+			    (prog1 (car markers)
+			      (setq markers (cdr markers)))
+			  markers))
+	   (fn-list (cdr fn-list)))
+      (if to
+	  (if (equal to from)
+	      (progn
+		(dired-log buffer-name "Cannot %s to same file: %s\n"
+			   (downcase operation) from)
+		(efs-dcf-1 file-creator operation fn-list name-constructor
+			   markers query buffer-name overwrite-query
+			   dired-overwrite-backup-query
+			   dired-file-creator-query failures
+			   (cons (dired-make-relative from nil t) skipped)
+			   success-count total implicit-to insertions))
+	    (if (or (null query)
+		    (funcall query from to))
+		(let* ((overwrite (let (jka-compr-enabled)
+				    ;; Don't let jka-compr fool us.
+				    (file-exists-p to)))
+		       (overwrite-confirmed ; for dired-handle-overwrite
+			(and overwrite
+			     (let ((help-form '(format "\
+Type SPC or `y' to overwrite file `%s',
+DEL or `n' to skip to next,
+ESC or `q' to not overwrite any of the remaining files,
+`!' to overwrite all remaining files with no more questions." to)))
+			       (dired-query 'overwrite-query
+					    "Overwrite `%s'?" to)))))
+		  (condition-case err
+		      (let ((dired-unhandle-add-files
+			     (cons to dired-unhandle-add-files)))
+			(if implicit-to
+			    (funcall file-creator from overwrite-confirmed
+				     (list (function efs-dcf-2)
+					   file-creator operation fn-list
+					   name-constructor markers
+					   query marker-char
+					   buffer-name to from overwrite
+					   overwrite-confirmed overwrite-query 
+					   dired-overwrite-backup-query
+					   dired-file-creator-query
+					   failures skipped success-count
+					   total implicit-to insertions)
+				     t)
+			  (apply file-creator from to overwrite-confirmed
+				 (list (function efs-dcf-2)
+				       file-creator operation fn-list
+				       name-constructor markers
+				       query marker-char
+				       buffer-name to from overwrite
+				       overwrite-confirmed overwrite-query 
+				       dired-overwrite-backup-query
+				       dired-file-creator-query
+				       failures skipped success-count total
+				       implicit-to insertions)
+				 (if insertions
+				     (list t insertions)
+				   '(t)))))
+		    (error      		; FILE-CREATOR aborted
+		     (efs-dcf-2 'failed ;result
+				(format "%s" err) ;line
+				"" file-creator operation fn-list
+				name-constructor markers query marker-char
+				buffer-name to from overwrite
+				overwrite-confirmed overwrite-query
+				dired-overwrite-backup-query
+				dired-file-creator-query failures skipped
+				success-count total implicit-to insertions))))
+	      (efs-dcf-1 file-creator operation fn-list name-constructor
+			 markers query buffer-name overwrite-query
+			 dired-overwrite-backup-query dired-file-creator-query
+			 failures
+			 (cons (dired-make-relative from nil t) skipped)
+			 success-count total implicit-to insertions)))
+	(efs-dcf-1 file-creator operation fn-list name-constructor
+		   markers query buffer-name overwrite-query
+		   dired-overwrite-backup-query dired-file-creator-query
+		   failures (cons (dired-make-relative from nil t) skipped)
+		   success-count total implicit-to insertions)))))
+
+(defun efs-dcf-2 (result line cont-lines file-creator operation fn-list
+			 name-constructor markers query marker-char
+			 buffer-name to from overwrite overwrite-confirmed
+			 overwrite-query overwrite-backup-query
+			 file-creator-query failures skipped success-count
+			 total implicit-to insertions)
+  (if result
+      (progn
+	(setq failures (cons (dired-make-relative from nil t) failures))
+	(dired-log buffer-name "%s `%s' to `%s' failed:\n%s\n"
+		   operation from to line))
+    (setq success-count (1+ success-count))
+    (message "%s: %d of %d" operation success-count total)
+    (let ((efs-ls-uncache t))
+      (dired-add-file to marker-char)))
+  ;; iterate again
+  (efs-dcf-1 file-creator operation fn-list name-constructor
+	     markers query buffer-name overwrite-query overwrite-backup-query
+	     file-creator-query failures skipped success-count total
+	     implicit-to insertions))
+
+(defun efs-dcf-3 (failures operation total skipped success-count buffer-name)
+  (cond
+   (failures
+    (dired-log-summary buffer-name (format "%s failed for %d of %d file%s"
+					   operation (length failures) total
+					   (dired-plural-s total)) failures))
+   (skipped
+    (dired-log-summary buffer-name (format "%s: %d of %d file%s skipped"
+					   operation (length skipped) total
+					   (dired-plural-s total)) skipped))
+   (t
+    (message "%s: %s file%s."
+	     operation success-count
+	     (dired-plural-s success-count)))))
+
+;;; Running remote shell commands
+
+;;; This support isn't very good. efs is really about a virtual file system,
+;;; and not remote processes. What is really required is low-level
+;;; support for start-process & call-process on remote hosts. This shouldn't
+;;; be part of efs, although.
+
+(defun efs-dired-shell-unhandle-file-name (filename)
+  ;; Puts remote file names into a form where they can be passed to remsh.
+  (nth 2 (efs-ftp-path filename)))
+
+(defun efs-dired-shell-call-process (command dir &optional in-background)
+  ;; Runs shell process on remote hosts.
+  (let* ((parsed (efs-ftp-path dir))
+	 (host (car parsed))
+	 (user (nth 1 parsed))
+	 (rdir (nth 2 parsed))
+	 (file-name-handler-alist nil))
+    (or (string-equal (efs-internal-directory-file-name dir)
+		      (efs-expand-tilde "~" (efs-host-type host) host user))
+	(string-match "^cd " command)
+	(setq command (concat "cd " rdir "; " command)))
+    (setq command
+	  (format  "%s %s%s \"%s\""	; remsh -l USER does not work well
+					; on a hp-ux machine I tried
+		   efs-remote-shell-file-name host
+		   (if efs-remote-shell-takes-user
+		       (concat " -l " user)
+		     "")
+		   command))
+    (message "Doing shell command on %s..." host)
+    (dired-shell-call-process
+     command (file-name-directory efs-tmp-name-template) in-background)))
+
+;;; Dired commands for running local processes on remote files.
+;;
+;;  Lots of things in this section need to be re-thunk.
+
+(defun efs-dired-call-process (program discard &rest arguments)
+  "Documented as original."
+  ;; PROGRAM is always one of those below in the cond in dired.el.
+  ;; The ARGUMENTS are (nearly) always files.
+  (if (efs-ftp-path default-directory)
+      ;; Can't use efs-dired-host-type here because the current
+      ;; buffer is *dired-check-process output*
+      (condition-case oops
+	  (cond
+	   ((string-equal "efs-call-compress" program)
+	    (apply 'efs-call-compress arguments))
+	   ((string-equal "chmod" program)
+	    (efs-call-chmod arguments))
+	   (t (error "Unknown remote command: %s" program)))
+	(ftp-error (dired-log (buffer-name (current-buffer))
+			      (format "%s: %s, %s\n"
+				      (nth 1 oops)
+				      (nth 2 oops)
+				      (nth 3 oops))))
+	(error (dired-log (buffer-name (current-buffer))
+			  (format "%s\n" (nth 1 oops)))))
+    (apply 'call-process program nil (not discard) nil arguments)))
+
+(defun efs-dired-make-compressed-filename (name &optional method)
+  ;; Version of dired-make-compressed-filename for efs.
+  ;; If NAME is in the syntax of a compressed file (according to
+  ;; dired-compression-method-alist), return the data (a list) from this
+  ;; alist on how to uncompress it. Otherwise, return a string, the
+  ;; uncompressed form of this file name. This is computed using the optional
+  ;; argument METHOD (a symbol). If METHOD is nil, the ambient value of
+  ;; dired-compression-method is used.
+  (let* ((host-type (efs-host-type (car (efs-ftp-path name))))
+	 (ef-alist (if (memq host-type efs-single-extension-host-types)
+		       (mapcar
+			(function
+			 (lambda (elt)
+			   (list (car elt)
+				 (mapconcat
+				  (function
+				   (lambda (char)
+				     (if (= char ?.)
+					 "-"
+				       (char-to-string char))))
+				  (nth 1 elt) "")
+				 (nth 2 elt)
+				 (nth 3 elt))))
+			dired-compression-method-alist)
+		     dired-compression-method-alist))
+	 (alist ef-alist)
+	 (len (length name))
+	 ext ext-len result)
+    (if (memq host-type efs-version-host-types)
+	(setq name (efs-internal-file-name-sans-versions host-type name)))
+    (if (memq host-type efs-case-insensitive-host-types)
+	(let ((name (downcase name)))
+	  (while alist
+	    (if (and (> len
+			(setq ext-len (length (setq ext (nth 1 (car alist))))))
+		     (string-equal (downcase ext)
+				   (substring name (- ext-len))))
+		(setq result (car alist)
+		      alist nil)
+	      (setq alist (cdr alist)))))
+      (while alist
+	(if (and (> len
+		    (setq ext-len (length (setq ext (nth 1 (car alist))))))
+		 (string-equal ext (substring name (- ext-len))))
+	    (setq result (car alist)
+		  alist nil)
+	  (setq alist (cdr alist)))))
+    (or result
+	(concat name
+		(nth 1 (or (assq (or method dired-compression-method)
+				 ef-alist)
+			   (error "Unknown compression method: %s"
+				  (or method dired-compression-method))))))))
+
+(defun efs-dired-compress-file (file ok-flag &optional cont nowait)
+  ;; Version of dired-compress-file for remote files.
+  (let* ((compressed-fn (efs-dired-make-compressed-filename file))
+	 (host (car (efs-ftp-path file)))
+	 (host-type (efs-host-type host)))
+    (cond ((file-symlink-p file)
+	   (if cont
+	       (efs-call-cont
+		cont 'failed
+		(format "Cannot compress %s, a symbolic link." file) "")
+	     (signal 'file-error (list "Compress error:" file
+				       "a symbolic link"))))
+	  ((listp compressed-fn)
+	   (let ((newname (substring (if (memq host-type
+					       efs-version-host-types)
+					 (efs-internal-file-name-sans-versions
+					  host-type file)
+				       file)
+				      0 (- (length (nth 1 compressed-fn)))))
+		 (program (nth 3 compressed-fn)))
+	     (if (and (memq host-type efs-unix-host-types)
+		      (null (efs-get-host-property host 'exec-failed))
+		      (null (eq (efs-get-host-property
+				 host
+				 (intern
+				  (concat
+				   "exec-"
+				   (efs-compress-progname (car program)))))
+				'failed)))
+		 (efs-call-remote-compress
+		  program file newname t ok-flag
+		  (efs-cont (result line cont-lines) (program file newname
+							      cont nowait)
+		    (if result
+			(if (eq result 'unsupported)
+			    (efs-call-compress program file newname
+					       t t cont nowait)
+			  (if cont
+			      (efs-call-cont cont result line cont-lines)
+			    (signal 'ftp-error
+				    (list "Uncompressing file"
+					  (format "FTP Error: \"%s\" " line)
+					  file))))
+		      (if cont (efs-call-cont cont result line cont-lines))))
+		  nowait)
+	       (efs-call-compress
+		program file newname t ok-flag cont nowait)
+	       newname)))
+	  ((stringp compressed-fn)
+	   (let ((program (nth 2 (assq dired-compression-method
+				       dired-compression-method-alist))))
+	     (if (and (memq host-type efs-unix-host-types)
+		      (null (efs-get-host-property host 'exec-failed))
+		      (null (eq (efs-get-host-property
+				 host
+				 (intern
+				  (concat
+				   "exec-"
+				   (efs-compress-progname (car program)))))
+				'failed)))
+		 (efs-call-remote-compress
+		  program file compressed-fn nil ok-flag
+		  (efs-cont (result line cont-lines) (program file
+							      compressed-fn
+							      cont nowait)
+		    (if result
+			(if (eq result 'unsupported)
+			    (efs-call-compress program file compressed-fn nil
+					       t cont nowait)
+			  (if cont
+			      (efs-call-cont cont result line cont-lines)
+			    (signal 'ftp-error
+				    (list "Compressing file"
+					  (format "FTP Error: \"%s\" " line)
+					  file))))
+		      (if cont (efs-call-cont cont result line cont-lines))))
+		  nowait)
+	       (efs-call-compress
+		program file compressed-fn nil ok-flag cont nowait)))
+	   compressed-fn)
+	  (t (error "Strange error in efs-dired-compress-file.")))))
+
+(defun efs-dired-print-file (command file)
+  ;; Version of dired-print-file for remote files.
+  (let ((command (dired-trans-command command (list file) "")))
+    ;; Only replace the first occurence of the file name?
+    (if (string-match (concat "[ ><|]\\(" (regexp-quote
+					   (dired-shell-quote file))
+			      "\\)\\($\\|[ |><&]\\)")
+		      command)
+	(setq command (concat (substring command 0 (match-beginning 1))
+			      "%s"
+			      (substring command (match-end 1))))
+      (error "efs-print-command: strange error"))
+  (efs-call-lpr file command)))
+
+;;;;----------------------------------------------------------------
+;;;; Support for `processes' run on remote files.
+;;;; Usually (but not necessarily) these are only called from dired.
+;;;;----------------------------------------------------------------
+
+(defun efs-compress-progname (program)
+  ;; Returns a canonicalized i.e. without the "un", version of a compress
+  ;; program name.
+  (efs-save-match-data
+    (if (string-equal program "gunzip")
+	"gzip"
+      (if (string-match "^un" program)
+	  (substring program (match-end 0))
+	program))))
+
+(defun efs-call-remote-compress (program filename newname &optional uncompress
+					 ok-if-already-exists cont nowait)
+  ;; Run a remote compress process using SITE EXEC.
+  (if (or (not ok-if-already-exists)
+	  (numberp ok-if-already-exists))
+      (efs-barf-or-query-if-file-exists
+       newname
+       (if uncompress
+	   "uncompress to it"
+	 "compress to it")
+       (numberp ok-if-already-exists)))
+  (let* ((filename (expand-file-name filename))
+	 (parsed (efs-ftp-path filename))
+	 (host (car parsed))
+	 (user (nth 1 parsed))
+	 (rpath (nth 2 parsed)))
+    (if (efs-get-host-property host 'exec-failed)
+	(if cont
+	    (efs-call-cont cont 'unsupported "SITE EXEC not supported" "")
+	  (signal 'ftp-error (list "Unable to SITE EXEC" host)))
+      (let* ((progname (efs-compress-progname (car program)))
+	     (propsym (intern  (concat "exec-" progname)))
+	     (prop (efs-get-host-property host propsym)))
+	(cond
+	 ((eq prop 'failed)
+	  (if cont
+	      (efs-call-cont cont 'unsupported
+			     (concat progname " not in FTP exec path") "")
+	    (signal 'ftp-error
+		    (list (concat progname " not in FTP exec path") host))))
+	 ((eq prop 'worked)
+	  (efs-send-cmd
+	   host user
+	   (list 'quote 'site 'exec
+		 (concat (mapconcat 'identity program " ") " " rpath))
+	   (concat (if uncompress "Uncompressing " "Compressing ") filename)
+	   nil
+	   (efs-cont (result line cont-lines) (host user filename cont)
+	     (if result
+		 (progn
+		   (efs-set-host-property host 'exec-failed t)
+		   (efs-error host user (concat "FTP exec Error: " line)))
+	       (efs-save-match-data
+		 (if (string-match "\n200-\\([^\n]*\\)" cont-lines)
+		     (let ((err (substring cont-lines (match-beginning 1)
+					   (match-end 1))))
+		       (if cont
+			   (efs-call-cont cont  'failed err cont-lines)
+			 (efs-error host user (concat "FTP Error: " err))))
+		   ;; This function only gets called for unix hosts, so
+		   ;; we'll use the default version of efs-delete-file-entry
+		   ;; and save a host-type lookup.
+		   (efs-delete-file-entry nil filename)
+		   (dired-remove-file filename)
+		   (if cont (efs-call-cont cont nil line cont-lines))))))
+	   nowait))
+	 (t ; (null prop)
+	  (efs-send-cmd
+	   host user
+	   (list 'quote 'site 'exec (concat progname " " "-V"))
+	   (format "Checking for %s executable" progname)
+	   nil
+	   (efs-cont (result line cont-lines) (propsym host program filename
+						       newname uncompress
+						       cont nowait)
+	     (efs-save-match-data
+	       (if (string-match "\n200-" cont-lines)
+		   (efs-set-host-property host propsym 'worked)
+		 (efs-set-host-property host propsym 'failed)))
+	     (efs-call-remote-compress program filename newname uncompress
+				       t ; already tested for overwrite
+				       cont nowait))
+	   nowait)))))))
+
+(defun efs-call-compress (program filename newname &optional uncompress
+				  ok-if-already-exists cont nowait)
+  "Perform a compress command on a remote file.
+PROGRAM is a list of the compression program and args. Works by taking a 
+copy of the file, compressing it and copying the file back. Returns 0 on
+success, 1 or 2 on failure. If UNCOMPRESS is non-nil, does this instead."
+  (let* ((filename (expand-file-name filename))
+	 (newname (expand-file-name newname))
+	 (parsed (efs-ftp-path filename))
+	 (tmp1 (car (efs-make-tmp-name nil (car parsed))))
+	 (tmp2 (car (efs-make-tmp-name nil (car parsed))))
+	 (program (mapconcat 'identity program " ")))
+    (efs-copy-file-internal
+     filename parsed tmp1 nil
+     t nil 2
+     (efs-cont (result line cont-lines) (filename newname tmp1 tmp2 program
+				       uncompress ok-if-already-exists
+				       cont nowait)
+       (if result
+	   (signal 'ftp-error
+		   (list "Opening input file"
+			 (format "FTP Error: \"%s\" " line) filename))
+	 (let ((err-buff (let ((default-major-mode 'fundamental-mode))
+			   (get-buffer-create
+			    (generate-new-buffer-name
+			     (format
+			      " efs-call-compress %s" filename))))))
+	   (save-excursion
+	     (set-buffer err-buff)
+	     (set (make-local-variable 'efs-call-compress-filename) filename)
+	     (set (make-local-variable 'efs-call-compress-newname) newname)
+	     (set (make-local-variable 'efs-call-compress-tmp1) tmp1)
+	     (set (make-local-variable 'efs-call-compress-tmp2) tmp2)
+	     (set (make-local-variable 'efs-call-compress-cont) cont)
+	     (set (make-local-variable 'efs-call-compress-nowait) nowait)
+	     (set (make-local-variable 'efs-call-compress-ok)
+		  ok-if-already-exists)
+	     (set (make-local-variable 'efs-call-compress-uncompress)
+		  uncompress)
+	     (set (make-local-variable 'efs-call-compress-abbr)
+		  (efs-relativize-filename filename))
+	     (if efs-verbose
+		 (efs-message
+		  (format "%s %s..."
+			  (if uncompress "Uncompressing" "Compressing")
+			  (symbol-value (make-local-variable
+					 'efs-call-compress-abbr)))))
+	     (set-process-sentinel
+	      (start-process (format "efs-call-compress %s" filename)
+			     err-buff shell-file-name
+			     "-c" (format "%s %s < %s > %s"
+					  program
+					  ;; Hope -c makes the compress
+					  ;; program write to std out.
+					  "-c"
+					  tmp1 tmp2))
+	      (function
+	       (lambda (proc str)
+		 (let ((buff (get-buffer (process-buffer proc))))
+		   (if buff
+		       (save-excursion
+			 (set-buffer buff)
+			 (if (/= (buffer-size) 0)
+			     (if cont
+				 (efs-call-cont
+				  (symbol-value
+				   (make-local-variable
+				    'efs-call-compress-cont))
+				  'failed
+				  (concat
+				   "failed to compress "
+				   (symbol-value (make-local-variable
+						  'efs-call-compress-filename))
+				   ", "
+				   (buffer-substring
+				    (point-min)
+				    (progn (goto-char (point-min))
+					   (end-of-line) (point))))))
+			   (efs-del-tmp-name (symbol-value
+					      (make-local-variable
+					       'efs-call-compress-tmp1)))
+			   (let ((tmp2 (symbol-value
+					(make-local-variable
+					 'efs-call-compress-tmp2)))
+				 (newname (symbol-value
+					   (make-local-variable
+					    'efs-call-compress-newname)))
+				 (filename (symbol-value
+					    (make-local-variable
+					     'efs-call-compress-filename)))
+				 (cont (symbol-value
+					(make-local-variable
+					 'efs-call-compress-cont)))
+				 (nowait (symbol-value
+					  (make-local-variable
+					   'efs-call-compress-nowait)))
+				 (ok (symbol-value
+				      (make-local-variable
+				       'efs-call-compress-ok)))
+				 (uncompress
+				  (symbol-value
+				   (make-local-variable
+				    'efs-call-compress-uncompress))))
+			     (if efs-verbose
+				 (efs-message
+				  (format "%s %s...done"
+					  (if uncompress
+					      "Uncompressing"
+					    "Compressing")
+					  (symbol-value
+					   (make-local-variable
+					    'efs-call-compress-abbr)))))
+			     (kill-buffer (current-buffer))
+			     (efs-copy-file-internal
+			      tmp2 nil newname (efs-ftp-path newname)
+			      ok nil 1
+			      (efs-cont (result line cont-lines) (cont
+								  tmp2
+								  filename)
+				(efs-del-tmp-name tmp2)
+				(or result
+				    (let (efs-verbose)
+				      (efs-delete-file filename)
+				      (dired-remove-file filename)))
+				(if cont
+				    (efs-call-cont cont result line
+						   cont-lines)))
+			      nowait (if uncompress nil 'image)))))
+		     (error "Strange error: %s" proc))))))))))
+     nowait (if uncompress 'image nil))))
+
+(defun efs-update-mode-string (perms modes)
+  ;; For PERMS of the form `u+w', and MODES a unix 9-character mode string,
+  ;; computes the new mode string.
+  ;; Doesn't call efs-save-match-data. The calling function should.
+  (or (string-match "^[augo]+\\([+-]\\)[rwxst]+$" perms)
+      (error "efs-update-mode-string: invalid perms %s" perms))
+  (let* ((who (substring perms 0 (match-beginning 1)))
+	 (add (= (aref perms (match-beginning 1)) ?+))
+	 (what (substring perms (match-end 1)))
+	 (newmodes (copy-sequence modes))
+	 (read (string-match "r" what))
+	 (write (string-match "w" what))
+	 (execute (string-match "x" what))
+	 (sticky (string-match "t" what))
+	 (suid (string-match "s" what)))
+    (if (string-match "a" who)
+	(if add
+	    (progn
+	      (if read
+		  (progn
+		    (aset newmodes 0 ?r)
+		    (aset newmodes 3 ?r)
+		    (aset newmodes 6 ?r)))
+	      (if write
+		  (progn
+		    (aset newmodes 1 ?w)
+		    (aset newmodes 4 ?w)
+		    (aset newmodes 7 ?w)))
+	      (if execute
+		  (let ((curr (aref newmodes 2)))
+		    (if (= curr ?-)
+			(aset newmodes 2 ?x)
+		      (if (= curr ?S)
+			  (aset newmodes 2 ?s)))
+		    (setq curr (aref newmodes 5))
+		    (if (= curr ?-)
+			(aset newmodes 5 ?x)
+		      (if (= curr ?S)
+			  (aset newmodes 5 ?s)))
+		    (setq curr (aref newmodes 8))
+		    (if (= curr ?-)
+			(aset newmodes 8 ?x)
+		      (if (= curr ?T)
+			  (aset newmodes 8 ?t)))))
+	      (if suid
+		  (let ((curr (aref newmodes 2)))
+		    (if (= curr ?-)
+			(aset newmodes 2 ?S)
+		      (if (= curr ?x)
+			  (aset newmodes 2 ?s)))
+		    (setq curr (aref newmodes 5))
+		    (if (= curr ?-)
+			(aset newmodes 5 ?S)
+		      (if (= curr ?x)
+			  (aset newmodes 5 ?s)))))
+	      (if sticky
+		  (let ((curr (aref newmodes 8)))
+		    (if (= curr ?-)
+			(aset newmodes 8 ?T)
+		      (if (= curr ?x)
+			  (aset newmodes 8 ?t))))))
+	  (if read
+	      (progn
+		(aset newmodes 0 ?-)
+		(aset newmodes 3 ?-)
+		(aset newmodes 6 ?-)))
+	  (if write
+	      (progn
+		(aset newmodes 1 ?-)
+		(aset newmodes 4 ?-)
+		(aset newmodes 7 ?-)))
+	  (if execute
+	      (let ((curr (aref newmodes 2)))
+		(if (= curr ?x)
+		    (aset newmodes 2 ?-)
+		  (if (= curr ?s)
+		      (aset newmodes 2 ?S)))
+		(setq curr (aref newmodes 5))
+		(if (= curr ?x)
+		    (aset newmodes 5 ?-)
+		  (if (= curr ?s)
+		      (aset newmodes 5 ?S)))
+		    (setq curr (aref newmodes 8))
+		    (if (= curr ?x)
+			(aset newmodes 8 ?-)
+		      (if (= curr ?t)
+			  (aset newmodes 8 ?T)))))
+	  (if suid
+	      (let ((curr (aref newmodes 2)))
+		(if (= curr ?s)
+		    (aset newmodes 2 ?x)
+		  (if (= curr ?S)
+		      (aset newmodes 2 ?-)))
+		(setq curr (aref newmodes 5))
+		(if (= curr ?s)
+		    (aset newmodes 5 ?x)
+		  (if (= curr ?S)
+		      (aset newmodes 5 ?-)))))
+	  (if sticky
+	      (let ((curr (aref newmodes 8)))
+		(if (= curr ?t)
+		    (aset newmodes 8 ?x)
+		  (if (= curr ?T)
+		      (aset newmodes 8 ?-))))))
+      (if (string-match "u" who)
+	  (if add
+	      (progn
+		(if read
+		    (aset newmodes 0 ?r))
+		(if write
+		    (aset newmodes 1 ?w))
+		(if execute
+		    (let ((curr (aref newmodes 2)))
+		      (if (= curr ?-)
+			  (aset newmodes 2 ?x)
+			(if (= curr ?S)
+			    (aset newmodes 2 ?s)))))
+		(if suid
+		    (let ((curr (aref newmodes 2)))
+		      (if (= curr ?-)
+			  (aset newmodes 2 ?S)
+			(if (= curr ?x)
+			    (aset newmodes 2 ?s))))))
+	    (if read
+		(aset newmodes 0 ?-))
+	    (if write
+		(aset newmodes 1 ?-))
+	    (if execute
+		(let ((curr (aref newmodes 2)))
+		  (if (= curr ?x)
+		      (aset newmodes 2 ?-)
+		    (if (= curr ?s)
+			(aset newmodes 2 ?S)))))
+	    (if suid
+		(let ((curr (aref newmodes 2)))
+		  (if (= curr ?s)
+		      (aset newmodes 2 ?x)
+		    (if (= curr ?S)
+			(aset newmodes 2 ?-)))))))
+      (if (string-match "g" who)
+	  (if add
+	      (progn
+		(if read
+		    (aset newmodes 3 ?r))
+		(if write
+		    (aset newmodes 4 ?w))
+		(if execute
+		    (let ((curr (aref newmodes 5)))
+		      (if (= curr ?-)
+			  (aset newmodes 5 ?x)
+			(if (= curr ?S)
+			    (aset newmodes 5 ?s)))))
+		(if suid
+		    (let ((curr (aref newmodes 5)))
+		      (if (= curr ?-)
+			  (aset newmodes 5 ?S)
+			(if (= curr ?x)
+			    (aset newmodes 5 ?s))))))
+	    (if read
+		(aset newmodes 3 ?-))
+	    (if write
+		(aset newmodes 4 ?-))
+	    (if execute
+		(let ((curr (aref newmodes 5)))
+		  (if (= curr ?x)
+		      (aset newmodes 5 ?-)
+		    (if (= curr ?s)
+			(aset newmodes 5 ?S)))))
+	    (if suid
+		(let ((curr (aref newmodes 5)))
+		  (if (= curr ?s)
+		      (aset newmodes 5 ?x)
+		    (if (= curr ?S)
+			(aset newmodes 5 ?-)))))))
+      (if (string-match "o" who)
+	  (if add
+	      (progn
+		(if read
+		    (aset newmodes 6 ?r))
+		(if write
+		    (aset newmodes 7 ?w))
+		(if execute
+		    (let ((curr (aref newmodes 8)))
+		      (if (= curr ?-)
+			  (aset newmodes 8 ?x)
+			(if (= curr ?T)
+			    (aset newmodes 8 ?t)))))
+		(if sticky
+		    (let ((curr (aref newmodes 8)))
+		      (if (= curr ?-)
+			  (aset newmodes 8 ?T)
+			(if (= curr ?x)
+			    (aset newmodes 5 ?t))))))
+	    (if read
+		(aset newmodes 6 ?-))
+	    (if write
+		(aset newmodes 7 ?-))
+	    (if execute
+		(let ((curr (aref newmodes 8)))
+		  (if (= curr ?x)
+		      (aset newmodes 8 ?-)
+		    (if (= curr ?t)
+			(aset newmodes 8 ?T)))))
+	    (if suid
+		(let ((curr (aref newmodes 8)))
+		  (if (= curr ?t)
+		      (aset newmodes 8 ?x)
+		    (if (= curr ?T)
+			(aset newmodes 8 ?-))))))))
+    newmodes))
+
+(defun efs-compute-chmod-arg (perms file)
+  ;; Computes the octal number, represented as a string, required to 
+  ;; modify the permissions PERMS of FILE.
+  (efs-save-match-data
+    (cond
+     ((string-match "^[0-7][0-7]?[0-7]?[0-7]?$" perms)
+      perms)
+     ((string-match "^[augo]+[-+][rwxst]+$" perms)
+      (let ((curr-mode (nth 3 (efs-get-file-entry file))))
+	(or (and curr-mode
+		 (stringp curr-mode)
+		 (= (length curr-mode) 10))
+	    (progn
+	      ;; Current buffer is process error buffer
+	      (insert "Require an octal integer to modify modes for "
+		      file ".\n")
+	      (error "Require an octal integer to modify modes for %s." file)))
+	(format "%o"
+		(efs-parse-mode-string
+		 (efs-update-mode-string perms
+					      (substring curr-mode 1))))))
+     (t
+      (insert "Don't know how to set modes " perms " for " file ".\n")
+      (error "Don't know how to set modes %s" perms)))))
+
+(defun efs-call-chmod (args)
+  ;; Sends an FTP CHMOD command.
+  (if (< (length args) 2)
+      (error "efs-call-chmod: missing mode and/or filename: %s" args))
+  (let ((mode (car args))
+	bombed)
+    (mapcar
+     (function
+      (lambda (file)
+	(setq file (expand-file-name file))
+	(let ((parsed (efs-ftp-path file)))
+	  (if parsed
+	      (condition-case nil
+		  (let* ((mode (efs-compute-chmod-arg mode file))
+			 (host (nth 0 parsed))
+			 (user (nth 1 parsed))
+			 (path (efs-quote-string
+				(efs-host-type host user) (nth 2 parsed)))
+			 (abbr (efs-relativize-filename file))
+			 (result (efs-send-cmd host user
+						    (list 'quote 'site 'chmod
+							  mode path)
+						    (format "doing chmod %s"
+							    abbr))))
+		    (efs-del-from-ls-cache file t)
+		    (if (car result)
+			(efs-error host user (format "chmod: %s: \"%s\"" file 
+						     (nth 1 result)))))
+		(error (setq bombed t)))))))
+     (cdr args))
+    (if bombed 1 0)))                      ; return code
+
+(defun efs-call-lpr (file command-format)
+  "Print remote file FILE. SWITCHES are passed to the print program."
+  ;; Works asynch.
+  (let* ((file (expand-file-name file))
+	 (parsed (efs-ftp-path file))
+	 (abbr (efs-relativize-filename file))
+	 (temp (car (efs-make-tmp-name nil (car parsed)))))
+    (efs-copy-file-internal
+     file parsed temp nil t nil 2
+     (efs-cont (result line cont-lines) (command-format file abbr temp)
+       (if result
+	   (signal 'ftp-error (list "Opening input file"
+				    (format "FTP Error: \"%s\" " line)
+				    file))
+	 (message "Spooling %s..." abbr)
+	 (set-process-sentinel
+	  (start-process (format "*print %s /// %s*" abbr temp)
+			 (generate-new-buffer-name " *print temp*")
+			 "sh" "-c" (format command-format temp))
+	  (function
+	   (lambda (proc status)
+	     (let ((buff (process-buffer proc))
+		   (name (process-name proc)))
+	       (if (and buff (get-buffer buff))
+		   (unwind-protect
+		       (save-excursion
+			 (set-buffer buff)
+			 (if (> (buffer-size) 0)
+			     (let ((log-buff (get-buffer-create
+					      "*Shell Command Output*")))
+			       (set-buffer log-buff)
+			       (goto-char (point-max))
+			       (or (bobp)
+				   (insert "\n"))
+			       (insert-buffer-substring buff)
+			       (goto-char (point-max))
+			       (display-buffer log-buff))))
+		     (condition-case nil (kill-buffer buff) (error nil))
+		     (efs-save-match-data
+		       (if (string-match "^\\*print \\(.*\\) /// \\(.*\\)\\*$"
+					 name)
+			   (let ((abbr (substring name (match-beginning 1)
+						  (match-end 1)))
+				 (temp (substring name (match-beginning 2)
+						  (match-end 2))))
+			     (or (= (match-beginning 2) (match-end 2))
+				 (efs-del-tmp-name temp))
+			     (message "Spooling %s...done" abbr))))))))))))
+     t)))
+
+;;;; --------------------------------------------------------------
+;;;; Attaching onto dired.
+;;;; --------------------------------------------------------------
+
+;;; Look out for MULE
+(if (or (boundp 'MULE) (featurep 'mule)) (load "efs-dired-mule"))
+
+;;; Magic file name hooks for dired.
+
+(put 'dired-print-file 'efs 'efs-dired-print-file)
+(put 'dired-make-compressed-filename 'efs 'efs-dired-make-compressed-filename)
+(put 'dired-compress-file 'efs 'efs-dired-compress-file)
+(put 'dired-recursive-delete-directory 'efs
+     'efs-dired-recursive-delete-directory)
+(put 'dired-uncache 'efs 'efs-dired-uncache)
+(put 'dired-shell-call-process 'efs 'efs-dired-shell-call-process)
+(put 'dired-shell-unhandle-file-name 'efs 'efs-dired-shell-unhandle-file-name)
+(put 'dired-file-modtime 'efs 'efs-dired-file-modtime)
+(put 'dired-set-file-modtime 'efs 'efs-dired-set-file-modtime)
+
+;;; Overwriting functions
+
+(efs-overwrite-fn "efs" 'dired-call-process)
+(efs-overwrite-fn "efs" 'dired-insert-headerline)
+(efs-overwrite-fn "efs" 'dired-manual-move-to-filename)
+(efs-overwrite-fn "efs" 'dired-manual-move-to-end-of-filename)
+(efs-overwrite-fn "efs" 'dired-make-filename-string)
+(efs-overwrite-fn "efs" 'dired-flag-backup-files)
+(efs-overwrite-fn "efs" 'dired-create-files)
+(efs-overwrite-fn "efs" 'dired-find-file)
+(efs-overwrite-fn "efs" 'dired-find-file-other-window)
+(efs-overwrite-fn "efs" 'dired-find-file-other-frame)
+(efs-overwrite-fn "efs" 'dired-collect-file-versions)
+(efs-overwrite-fn "efs" 'dired-file-name-lessp)
+
+;;; Hooks
+
+(add-hook 'dired-before-readin-hook 'efs-dired-before-readin)
+
+;;; Handle dired-grep.el too.
+
+(if (featurep 'dired-grep)
+    (efs-overwrite-fn "efs" 'dired-grep-delete-local-temp-file
+		      'efs-diff/grep-del-temp-file)
+  (add-hook 'dired-grep-load-hook
+	    (function
+	     (lambda ()
+	       (efs-overwrite-fn "efs" 'dired-grep-delete-local-temp-file
+				 'efs-diff/grep-del-temp-file)))))
+
+;;; end of efs-dired.el