]> code.delx.au - gnu-emacs/blobdiff - lisp/ange-ftp.el
Update copyright notice.
[gnu-emacs] / lisp / ange-ftp.el
index 8b09c7f45e7a9606d2a9ae994df9e3d417ff69fb..0002e3f3c77e3a327069b73235aa451ee7032b7f 100644 (file)
@@ -1,6 +1,6 @@
 ;;; ange-ftp.el --- transparent FTP support for GNU Emacs
 
-;; Copyright (C) 1989,90,91,92,93,94,95,96  Free Software Foundation, Inc.
+;; Copyright (C) 1989,90,91,92,93,94,95,96,98  Free Software Foundation, Inc.
 
 ;; Author: Andy Norman (ange@hplb.hpl.hp.com)
 ;; Maintainer: FSF
 ;;; Code:
 
 (require 'comint)
+;; Silence compiler:
+(eval-when-compile
+  (defvar comint-last-output-start nil)
+  (defvar comint-last-input-start nil)
+  (defvar comint-last-input-end nil))
 
 ;;;; ------------------------------------------------------------
 ;;;; User customization variables.
@@ -695,7 +700,8 @@ These mean that the FTP process should (or already has) been killed."
   :group 'ange-ftp
   :type 'regexp)
 
-(defcustom ange-ftp-tmp-name-template "/tmp/ange-ftp"
+(defcustom ange-ftp-tmp-name-template 
+  (expand-file-name "ange-ftp" temporary-file-directory)
   "*Template used to create temporary files."
   :group 'ange-ftp
   :type 'directory)
@@ -1010,6 +1016,12 @@ All HOST values should be in lower case.")
 (defvar ange-ftp-files-hashtable (ange-ftp-make-hashtable 97)
   "Hash table for storing directories and their respective files.")
 
+(defvar ange-ftp-inodes-hashtable (ange-ftp-make-hashtable 97)
+  "Hash table for storing file names and their \"inode numbers\".")
+
+(defvar ange-ftp-next-inode-number 1
+  "Next \"inode number\" value.  We give each file name a unique number.")
+
 (defvar ange-ftp-ls-cache-lsargs nil
   "Last set of args used by ange-ftp-ls.")
 
@@ -1296,11 +1308,11 @@ Optional DEFAULT is password to start with."
               (if (looking-at "machine\\>")
                   ;; Skip `machine' and the machine name that follows.
                   (progn
-                    (skip-chars-forward "^ \t\n")
-                    (skip-chars-forward " \t\n")
-                    (skip-chars-forward "^ \t\n"))
+                    (skip-chars-forward "^ \t\r\n")
+                    (skip-chars-forward " \t\r\n")
+                    (skip-chars-forward "^ \t\r\n"))
                 ;; Skip `default'.
-                (skip-chars-forward "^ \t\n"))
+                (skip-chars-forward "^ \t\r\n"))
               ;; Find start of the next `machine' or `default'
               ;; or the end of the buffer.
               (if (re-search-forward "machine\\>\\|default\\>" nil t)
@@ -1365,7 +1377,7 @@ Optional DEFAULT is password to start with."
                (mapcar 'funcall find-file-hooks)
                (setq buffer-file-name nil)
                (goto-char (point-min))
-               (skip-chars-forward " \t\n")
+               (skip-chars-forward " \t\r\n")
                (while (not (eobp))
                  (ange-ftp-parse-netrc-group))
                (kill-buffer (current-buffer)))
@@ -1488,7 +1500,7 @@ then kill the related ftp process."
          (if parsed
              (let ((host (nth 0 parsed))
                    (user (nth 1 parsed)))
-               (kill-buffer (ange-ftp-ftp-process-buffer host user))))))))
+               (kill-buffer (get-buffer (ange-ftp-ftp-process-buffer host user)))))))))
 
 (defun ange-ftp-quote-string (string)
   "Quote any characters in STRING that may confuse the ftp process."
@@ -1960,7 +1972,23 @@ on the gateway machine to do the ftp instead."
     (process-kill-without-query proc)
     (set-process-sentinel proc (function ange-ftp-process-sentinel))
     (set-process-filter proc (function ange-ftp-process-filter))
-    (accept-process-output proc)       ;wait for ftp startup message
+    ;; wait for ftp startup message
+    (if (not (eq system-type 'windows-nt))
+       (accept-process-output proc)
+      ;; On Windows, the standard ftp client behaves a little oddly,
+      ;; initially buffering its output (because stdin/out are pipe
+      ;; handles).  As a result, the startup message doesn't appear
+      ;; until enough output is generated to flush stdout, so a plain
+      ;; accept-process-output call at this point would hang
+      ;; indefinitely.  So if nothing appears within 2 seconds, we try
+      ;; sending an innocuous command ("help foo") that forces some
+      ;; output.  Curiously, once we start sending normal commands, the
+      ;; output no longer appears to be buffered, and everything works
+      ;; correctly (or at least appears to!).
+      (if (accept-process-output proc 2)
+         nil
+       (process-send-string proc "help foo\n")
+       (accept-process-output proc)))
     proc))
 
 (put 'internal-ange-ftp-mode 'mode-class 'special)
@@ -2511,16 +2539,24 @@ away in the internal cache."
 ;;;; ------------------------------------------------------------
 
 (defconst ange-ftp-date-regexp
-  (let* ((l "[A-Za-z\xa0-\xff]")
-        (k "[^\x00-\xff]")
+  (let* ((l "\\([A-Za-z]\\|[^\0-\177]\\)")
+        ;; In some locales, month abbreviations are as short as 2 letters,
+        ;; and they can be padded on the right with spaces.
+        (month (concat l l "+ *"))
+        ;; Recognize any non-ASCII character.  
+        ;; The purpose is to match a Kanji character.
+        (k "[^\0-\177]")
         (s " ")
         (mm "[ 0-1][0-9]")
         (dd "[ 0-3][0-9]")
-        (western (concat "\\(" l l "+ +" dd "\\|" dd s l l "+" "\\)"))
+        (western (concat "\\(" month s dd "\\|" dd s month "\\)"))
         (japanese (concat mm k s dd k)))
-    (concat s "\\(" western "\\|" japanese "\\)" s))
-  "Regular expression to recognize the date in a directory listing.
-This regular expression is designed to recognize dates
+        ;; Require the previous column to end in a digit.
+        ;; This avoids recognizing `1 may 1997' as a date in the line:
+        ;; -r--r--r--   1 may      1997        1168 Oct 19 16:49 README
+    (concat "[0-9]" s "\\(" western "\\|" japanese "\\)" s))
+  "Regular expression to match up to the column before the file name in a
+directory listing.  This regular expression is designed to recognize dates
 regardless of the language.")
 
 (defvar ange-ftp-add-file-entry-alist nil
@@ -3022,6 +3058,8 @@ logged in as user USER and cd'd to directory DIR."
           (ange-ftp-real-expand-file-name name))
          ((eq (string-to-char name) ?/)
           (ange-ftp-canonize-filename name))
+         ((and (eq system-type 'windows-nt) (string-match "^[a-zA-Z]:" name))
+          name) ; when on local drive, return it as-is
          ((zerop (length name))
           (ange-ftp-canonize-filename (or default default-directory)))
          ((ange-ftp-canonize-filename
@@ -3097,8 +3135,12 @@ system TYPE.")
               (user (nth 1 parsed))
               (name (ange-ftp-quote-string (nth 2 parsed)))
               (temp (ange-ftp-make-tmp-name host))
+              ;; What we REALLY need here is a way to determine if the mode
+              ;; of the transfer is irrelevant, i.e. we can use binary mode
+              ;; regardless. Maybe a system-type to host-type lookup?
               (binary (or (ange-ftp-binary-file filename)
-                          (eq (ange-ftp-host-type host user) 'unix)))
+                          (and (not (eq system-type 'windows-nt))
+                               (eq (ange-ftp-host-type host user) 'unix))))
               (cmd (if append 'append 'put))
               (abbr (ange-ftp-abbreviate-filename filename)))
          (unwind-protect
@@ -3161,7 +3203,8 @@ system TYPE.")
                     (name (ange-ftp-quote-string (nth 2 parsed)))
                     (temp (ange-ftp-make-tmp-name host))
                     (binary (or (ange-ftp-binary-file filename)
-                                (eq (ange-ftp-host-type host user) 'unix)))
+                                (and (not (eq system-type 'windows-nt))
+                                     (eq (ange-ftp-host-type host user) 'unix))))
                     (abbr (ange-ftp-abbreviate-filename filename))
                     size)
                (unwind-protect
@@ -3184,7 +3227,10 @@ system TYPE.")
                          (setq
                           size
                           (nth 1 (ange-ftp-real-insert-file-contents
-                                  temp visit beg end replace)))
+                                  temp visit beg end replace))
+                          ;; override autodetection of buffer file type
+                          ;; to ensure buffer is saved in DOS format
+                          buffer-file-type binary)
                        (signal 'ftp-error
                                (list
                                 "Opening input file:"
@@ -3286,7 +3332,13 @@ system TYPE.")
              (let ((host (nth 0 parsed))
                    (user (nth 1 parsed))
                    (name (nth 2 parsed))
-                   (dirp (ange-ftp-get-hash-entry part files)))
+                   (dirp (ange-ftp-get-hash-entry part files))
+                   (inode (ange-ftp-get-hash-entry
+                           file ange-ftp-inodes-hashtable)))
+               (unless inode
+                 (setq inode ange-ftp-next-inode-number
+                       ange-ftp-next-inode-number (1+ inode))
+                 (ange-ftp-put-hash-entry file inode ange-ftp-inodes-hashtable))
                (list (if (and (stringp dirp) (file-name-absolute-p dirp))
                          (ange-ftp-expand-symlink dirp
                                                   (file-name-directory file))
@@ -3301,12 +3353,7 @@ system TYPE.")
                      (concat (if (stringp dirp) "l" (if dirp "d" "-"))
                              "?????????") ;8 mode
                      nil               ;9 gid weird
-                     ;; Hack to give remote files a unique "inode number".
-                     ;; It's actually the sum of the characters in its name.
-                     (apply '+ (nconc (mapcar 'identity host)
-                                      (mapcar 'identity user)
-                                      (mapcar 'identity
-                                              (directory-file-name name))))
+                     inode             ;10 "inode number".
                      -1                ;11 device number [v19 only]
                      ))))
       (ange-ftp-real-file-attributes file))))
@@ -3442,7 +3489,8 @@ system TYPE.")
             (t-abbr (ange-ftp-abbreviate-filename newname filename))
             (binary (or (ange-ftp-binary-file filename)
                         (ange-ftp-binary-file newname)
-                        (and (eq (ange-ftp-host-type f-host f-user) 'unix)
+                        (and (not (eq system-type 'windows-nt))
+                             (eq (ange-ftp-host-type f-host f-user) 'unix)
                              (eq (ange-ftp-host-type t-host t-user) 'unix))))
             temp1
             temp2)
@@ -3730,7 +3778,9 @@ system TYPE.")
                    file))))
             completions)))
 
-      (if (string-equal "/" ange-ftp-this-dir)
+      (if (or (and (eq system-type 'windows-nt)
+                  (string-match "[^a-zA-Z]?[a-zA-Z]:[/\]" ange-ftp-this-dir))
+             (string-equal "/" ange-ftp-this-dir))
          (nconc (all-completions file (ange-ftp-generate-root-prefixes))
                 (ange-ftp-real-file-name-all-completions file
                                                          ange-ftp-this-dir))
@@ -4033,6 +4083,14 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
          (cons '("^/[^/:]*[^/:.]:" . ange-ftp-hook-function)
                file-name-handler-alist)))
 
+;;; Real ange-ftp file names prefixed with a drive letter.
+;;;###autoload
+(and (memq system-type '(ms-dos windows-nt))
+     (or (assoc "^[a-zA-Z]:/[^/:]*[^/:.]:" file-name-handler-alist)
+        (setq file-name-handler-alist
+              (cons '("^[a-zA-Z]:/[^/:]*[^/:.]:" . ange-ftp-hook-function)
+                    file-name-handler-alist))))
+
 ;;; This regexp recognizes and absolute filenames with only one component,
 ;;; for the sake of hostname completion.
 ;;;###autoload
@@ -4041,6 +4099,15 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
          (cons '("^/[^/:]*\\'" . ange-ftp-completion-hook-function)
                file-name-handler-alist)))
 
+;;; Absolute file names prefixed with a drive letter.
+;;;###autoload
+(and (memq system-type '(ms-dos windows-nt))
+     (or (assoc "^[a-zA-Z]:/[^/:]*\\'" file-name-handler-alist)
+        (setq file-name-handler-alist
+              (cons '("^[a-zA-Z]:/[^/:]*\\'" . 
+                      ange-ftp-completion-hook-function)
+                    file-name-handler-alist))))
+
 ;;; The above two forms are sufficient to cause this file to be loaded
 ;;; if the user ever uses a file name with a colon in it.
 
@@ -4096,6 +4163,7 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
 (put 'vc-registered 'ange-ftp 'null)
 
 (put 'dired-call-process 'ange-ftp 'ange-ftp-dired-call-process)
+(put 'shell-command 'ange-ftp 'ange-ftp-shell-command)
 \f
 ;;; Define ways of getting at unmodified Emacs primitives,
 ;;; turning off our handler.
@@ -4117,8 +4185,12 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
   (ange-ftp-run-real-handler 'file-name-as-directory args))
 (defun ange-ftp-real-directory-file-name (&rest args)
   (ange-ftp-run-real-handler 'directory-file-name args))
+(or (and (eq system-type 'windows-nt)
+        ;; Windows handler for [A-Z]: drive name on local disks
+        (defun ange-ftp-real-expand-file-name (&rest args)
+          (ange-ftp-run-real-handler 'ange-ftp-real-expand-file-name-actual args)))
 (defun ange-ftp-real-expand-file-name (&rest args)
-  (ange-ftp-run-real-handler 'expand-file-name args))
+  (ange-ftp-run-real-handler 'expand-file-name args)))
 (defun ange-ftp-real-make-directory (&rest args)
   (ange-ftp-run-real-handler 'make-directory args))
 (defun ange-ftp-real-delete-directory (&rest args)
@@ -4211,15 +4283,14 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
     (if func (funcall func file keep-backup-version)
       (ange-ftp-real-file-name-sans-versions file keep-backup-version))))
 
-;;; This doesn't work yet; a new hook needs to be created.
-;;; Maybe the new hook should be in call-process.
-(defun ange-ftp-shell-command (command)
+;; This is the handler for shell-command.
+(defun ange-ftp-shell-command (command &optional output-buffer)
   (let* ((parsed (ange-ftp-ftp-name default-directory))
         (host (nth 0 parsed))
         (user (nth 1 parsed))
         (name (nth 2 parsed)))
     (if (not parsed)
-       (ange-ftp-real-shell-command command)
+       (ange-ftp-real-shell-command command output-buffer)
       (if (> (length name) 0)          ; else it's $HOME
          (setq command (concat "cd " name "; " command)))
       (setq command
@@ -4230,7 +4301,7 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
       ;; Cannot call ange-ftp-real-dired-run-shell-command here as it
       ;; would prepend "cd default-directory" --- which bombs because
       ;; default-directory is in ange-ftp syntax for remote file names.
-      (ange-ftp-real-shell-command command))))
+      (ange-ftp-real-shell-command command output-buffer))))
 
 ;;; This is the handler for call-process.
 (defun ange-ftp-dired-call-process (program discard &rest arguments)
@@ -4240,7 +4311,7 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
       ;; Can't use ange-ftp-dired-host-type here because the current
       ;; buffer is *dired-check-process output*
       (condition-case oops
-         (cond ((equal "chmod" program)
+         (cond ((equal dired-chmod-program program)
                 (ange-ftp-call-chmod arguments))
                ;; ((equal "chgrp" program))
                ;; ((equal dired-chown-program program))
@@ -4263,7 +4334,10 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
 (defun ange-ftp-call-chmod (args)
   (if (< (length args) 2)
       (error "ange-ftp-call-chmod: missing mode and/or filename: %s" args))
-  (let ((mode (car args)))
+  (let ((mode (car args))
+       (rest (cdr args)))
+    (if (equal "--" (car rest))
+       (setq rest (cdr rest)))
     (mapcar
      (function
       (lambda (file)
@@ -4281,8 +4355,8 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
                (or (car result)
                    (call-process 
                     ange-ftp-remote-shell
-                    nil t nil host "chmod" mode name)))))))
-     (cdr args)))
+                    nil t nil host dired-chmod-program mode name)))))))
+     rest))
   (setq ange-ftp-ls-cache-file nil)    ;Stop confusing Dired.
   0)
 \f
@@ -5608,6 +5682,27 @@ Other orders of $ and _ seem to all work just fine.")
 ;;    (setq ange-ftp-dired-get-filename-alist
 ;;       (cons '(cms . ange-ftp-dired-cms-get-filename)
 ;;             ange-ftp-dired-get-filename-alist)))
+\f
+;;
+(and (eq system-type 'windows-nt)
+     (setq ange-ftp-disable-netrc-security-check t))
+
+;; If a drive letter has been added, remote it.  Otherwise, if the drive
+;; letter existed before, leave it.
+(defun ange-ftp-real-expand-file-name-actual (&rest args)
+  (let (old-name new-name final drive-letter)
+    (setq old-name (car args))
+    (setq new-name (ange-ftp-run-real-handler 'expand-file-name args))
+    (setq drive-letter (substring new-name 0 2))
+    ;; I'd like to distill the following lines into one (if) statement
+    ;;   removing the need for the temp final variable
+    (setq final new-name)
+    (if (not (equal (substring old-name 0 1) "~"))
+       (if (or (< (length old-name) 2)
+               (not (string-match "/[a-zA-Z]:" old-name)))
+           (setq final (substring new-name 2))))
+    final))
+
 \f
 ;;;; ------------------------------------------------------------
 ;;;; Finally provide package.