# HG changeset patch # User Michael Albinus # Date 1275472380 -7200 # Node ID 608a4139730189cf01c4c06749537950e2df080c # Parent da77a7326f79b73c4a1b1ca93c1c96079aa58bfb * net/tramp-gvfs.el (top): Require url-util. (tramp-gvfs-mount-point): Removed. (tramp-gvfs-stringify-dbus-message, tramp-gvfs-send-command): New defuns. (with-tramp-dbus-call-method): Format trace message. (tramp-gvfs-handle-copy-file, tramp-gvfs-handle-rename-file): Implement backup call, when operation on local files fails. Use progress reporter. Flush properties of changed files. (tramp-gvfs-handle-make-directory): Make more traces. (tramp-gvfs-url-file-name): Hexify file name in url. (tramp-gvfs-fuse-file-name): Take also prefix (like dav shares) into account for the resulting file name. (tramp-gvfs-handler-askquestion): Return dummy mountpoint, when the answer is "no". See `tramp-gvfs-maybe-open-connection'. (tramp-gvfs-handler-mounted-unmounted) (tramp-gvfs-connection-mounted-p): Test also for new mountspec attribute "default_location". Set "prefix" property. (tramp-gvfs-mount-spec): Return both prefix and mountspec. (tramp-gvfs-maybe-open-connection): Test, whether mountpoint exists. Raise an error, if not (due to a corresponding answer "no" in interactive questions, for example). diff -r da77a7326f79 -r 608a41397301 lisp/ChangeLog --- a/lisp/ChangeLog Tue Jun 01 21:45:46 2010 -0700 +++ b/lisp/ChangeLog Wed Jun 02 11:53:00 2010 +0200 @@ -1,4 +1,28 @@ -2010-06-02 Dan Nicolaescu +2010-06-02 Michael Albinus + + * net/tramp-gvfs.el (top): Require url-util. + (tramp-gvfs-mount-point): Removed. + (tramp-gvfs-stringify-dbus-message, tramp-gvfs-send-command): New + defuns. + (with-tramp-dbus-call-method): Format trace message. + (tramp-gvfs-handle-copy-file, tramp-gvfs-handle-rename-file): + Implement backup call, when operation on local files fails. Use + progress reporter. Flush properties of changed files. + (tramp-gvfs-handle-make-directory): Make more traces. + (tramp-gvfs-url-file-name): Hexify file name in url. + (tramp-gvfs-fuse-file-name): Take also prefix (like dav shares) + into account for the resulting file name. + (tramp-gvfs-handler-askquestion): Return dummy mountpoint, when + the answer is "no". See `tramp-gvfs-maybe-open-connection'. + (tramp-gvfs-handler-mounted-unmounted) + (tramp-gvfs-connection-mounted-p): Test also for new mountspec + attribute "default_location". Set "prefix" property. + (tramp-gvfs-mount-spec): Return both prefix and mountspec. + (tramp-gvfs-maybe-open-connection): Test, whether mountpoint + exists. Raise an error, if not (due to a corresponding answer + "no" in interactive questions, for example). + +22010-06-02 Dan Nicolaescu * log-edit.el (log-edit-font-lock-keywords): Make group 4 match lax. diff -r da77a7326f79 -r 608a41397301 lisp/net/tramp-gvfs.el --- a/lisp/net/tramp-gvfs.el Tue Jun 01 21:45:46 2010 -0700 +++ b/lisp/net/tramp-gvfs.el Wed Jun 02 11:53:00 2010 +0200 @@ -28,6 +28,10 @@ ;; incompatibility with the mount_info structure, which has been ;; worked around. +;; It has also been tested with GVFS 1.6.2 (Ubuntu 10.04, Gnome 2.30), +;; where the default_location has been added to mount_info (see +;; . + ;; All actions to mount a remote location, and to retrieve mount ;; information, are performed by D-Bus messages. File operations ;; themselves are performed via the mounted filesystem in ~/.gvfs. @@ -100,6 +104,7 @@ (require 'tramp) (require 'dbus) (require 'url-parse) +(require 'url-util) (require 'zeroconf) (defcustom tramp-gvfs-methods '("dav" "davs" "obex" "synce") @@ -133,10 +138,6 @@ (unless (assoc elt tramp-methods) (add-to-list 'tramp-methods (cons elt nil)))))) -(defconst tramp-gvfs-mount-point - (file-name-as-directory (expand-file-name ".gvfs" "~/")) - "The directory name, fuses mounts remote ressources.") - (defconst tramp-gvfs-path-tramp (concat dbus-path-emacs "/Tramp") "The preceeding object path for own objects.") @@ -190,6 +191,7 @@ ;; STRUCT mount_spec_item ;; STRING key (server, share, type, user, host, port) ;; ARRAY BYTE value +;; STRING default_location Since GVFS 1.5 only !!! (defconst tramp-gvfs-interface-mountoperation "org.gtk.vfs.MountOperation" "Used by the dbus-proxying implementation of GMountOperation.") @@ -449,6 +451,17 @@ (add-to-list 'tramp-foreign-file-name-handler-alist (cons 'tramp-gvfs-file-name-p 'tramp-gvfs-file-name-handler)) +(defun tramp-gvfs-stringify-dbus-message (message) + "Convert a D-Bus message into readable UTF8 strings, used for traces." + (cond + ((and (consp message) (characterp (car message))) + (format "%S" (dbus-byte-array-to-string message))) + ((consp message) + (mapcar 'tramp-gvfs-stringify-dbus-message message)) + ((stringp message) + (format "%S" message)) + (t message))) + (defmacro with-tramp-dbus-call-method (vec synchronous bus service path interface method &rest args) "Apply a D-Bus call on bus BUS. @@ -466,7 +479,7 @@ result) (tramp-message ,vec 6 "%s %s" func args) (setq result (apply func args)) - (tramp-message ,vec 6 "\n%s" result) + (tramp-message ,vec 6 "%s" (tramp-gvfs-stringify-dbus-message result)) result)) (put 'with-tramp-dbus-call-method 'lisp-indent-function 2) @@ -480,7 +493,7 @@ `(let ((fuse-file-name (regexp-quote (tramp-gvfs-fuse-file-name ,filename))) elt) (condition-case err - (apply ,handler (list ,@args)) + (funcall ,handler ,@args) (error (setq elt (cdr err)) (while elt @@ -515,18 +528,41 @@ (filename newname &optional ok-if-already-exists keep-date preserve-uid-gid preserve-selinux-context) "Like `copy-file' for Tramp files." - (let ((args - (list - (if (tramp-gvfs-file-name-p filename) - (tramp-gvfs-fuse-file-name filename) - filename) - (if (tramp-gvfs-file-name-p newname) - (tramp-gvfs-fuse-file-name newname) - newname) - ok-if-already-exists keep-date preserve-uid-gid))) - (when preserve-selinux-context - (setq args (append args (list preserve-selinux-context)))) - (apply 'copy-file args))) + (with-parsed-tramp-file-name + (if (tramp-tramp-file-p filename) filename newname) nil + (with-progress-reporter + v 0 (format "Copying %s to %s" filename newname) + (condition-case err + (let ((args + (list + (if (tramp-gvfs-file-name-p filename) + (tramp-gvfs-fuse-file-name filename) + filename) + (if (tramp-gvfs-file-name-p newname) + (tramp-gvfs-fuse-file-name newname) + newname) + ok-if-already-exists keep-date preserve-uid-gid))) + (when preserve-selinux-context + (setq args (append args (list preserve-selinux-context)))) + (apply 'copy-file args)) + + ;; Error case. Let's try it with the GVFS utilities. + (error + (tramp-message v 4 "`copy-file' failed, trying `gvfs-copy'") + (unless + (zerop + (tramp-gvfs-send-command + v "gvfs-copy" + (if (or keep-date preserve-uid-gid) "--preserve" "") + (tramp-gvfs-url-file-name filename) + (tramp-gvfs-url-file-name newname))) + ;; Propagate the error. + (tramp-error v (car err) "%s" (cdr err))))))) + + (when (file-remote-p newname) + (with-parsed-tramp-file-name newname nil + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-file-property v localname)))) (defun tramp-gvfs-handle-delete-directory (directory &optional recursive) "Like `delete-directory' for Tramp files." @@ -657,19 +693,20 @@ (defun tramp-gvfs-handle-make-directory (dir &optional parents) "Like `make-directory' for Tramp files." - (condition-case err - (with-tramp-gvfs-error-message dir 'make-directory - (tramp-gvfs-fuse-file-name dir) parents) - ;; Error case. Let's try it with the GVFS utilities. - (error - (with-parsed-tramp-file-name dir nil + (with-parsed-tramp-file-name dir nil + (condition-case err + (with-tramp-gvfs-error-message dir 'make-directory + (tramp-gvfs-fuse-file-name dir) parents) + + ;; Error case. Let's try it with the GVFS utilities. + (error (tramp-message v 4 "`make-directory' failed, trying `gvfs-mkdir'") (unless (zerop - (tramp-local-call-process - "gvfs-mkdir" nil (tramp-get-buffer v) nil - (tramp-gvfs-url-file-name dir))) - (signal (car err) (cdr err))))))) + (tramp-gvfs-send-command + v "gvfs-mkdir" (tramp-gvfs-url-file-name dir))) + ;; Propagate the error. + (tramp-error v (car err) "%s" (cdr err))))))) (defun tramp-gvfs-handle-process-file (program &optional infile destination display &rest args) @@ -680,14 +717,41 @@ (defun tramp-gvfs-handle-rename-file (filename newname &optional ok-if-already-exists) "Like `rename-file' for Tramp files." - (rename-file - (if (tramp-gvfs-file-name-p filename) - (tramp-gvfs-fuse-file-name filename) - filename) - (if (tramp-gvfs-file-name-p newname) - (tramp-gvfs-fuse-file-name newname) - newname) - ok-if-already-exists)) + (with-parsed-tramp-file-name + (if (tramp-tramp-file-p filename) filename newname) nil + (with-progress-reporter + v 0 (format "Renaming %s to %s" filename newname) + (condition-case err + (rename-file + (if (tramp-gvfs-file-name-p filename) + (tramp-gvfs-fuse-file-name filename) + filename) + (if (tramp-gvfs-file-name-p newname) + (tramp-gvfs-fuse-file-name newname) + newname) + ok-if-already-exists) + + ;; Error case. Let's try it with the GVFS utilities. + (error + (tramp-message v 4 "`rename-file' failed, trying `gvfs-move'") + (unless + (zerop + (tramp-gvfs-send-command + v "gvfs-move" + (tramp-gvfs-url-file-name filename) + (tramp-gvfs-url-file-name newname))) + ;; Propagate the error. + (tramp-error v (car err) "%s" (cdr err))))))) + + (when (file-remote-p filename) + (with-parsed-tramp-file-name filename nil + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-file-property v localname))) + + (when (file-remote-p newname) + (with-parsed-tramp-file-name newname nil + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-file-property v localname)))) (defun tramp-gvfs-handle-set-file-modes (filename mode) "Like `set-file-modes' for Tramp files." @@ -730,19 +794,16 @@ start end (tramp-gvfs-fuse-file-name filename) append visit lockname confirm) - ;; Error case. Let's try it with the GVFS utilities. + ;; Error case. Let's try rename. (error (let ((tmpfile (tramp-compat-make-temp-file filename))) - (tramp-message v 4 "`write-region' failed, trying `gvfs-save'") + (tramp-message v 4 "`write-region' failed, trying `rename-file'") (write-region start end tmpfile) - (unwind-protect - (unless - (zerop - (tramp-local-call-process - "gvfs-save" tmpfile (tramp-get-buffer v) nil - (tramp-gvfs-url-file-name filename))) - (signal (car err) (cdr err))) - (delete-file tmpfile))))) + (condition-case nil + (rename-file tmpfile filename) + (error + (delete-file tmpfile) + (tramp-error v (car err) "%s" (cdr err))))))) ;; Set file modification time. (when (or (eq visit t) (stringp visit)) @@ -758,16 +819,20 @@ (defun tramp-gvfs-url-file-name (filename) "Return FILENAME in URL syntax." - (url-recreate-url - (if (tramp-tramp-file-p filename) - (with-parsed-tramp-file-name (file-truename filename) nil - (when (string-match tramp-user-with-domain-regexp user) - (setq user - (concat (match-string 2 user) ";" (match-string 2 user)))) - (url-parse-make-urlobj - method user nil - (tramp-file-name-real-host v) (tramp-file-name-port v) localname)) - (url-parse-make-urlobj "file" nil nil nil nil (file-truename filename))))) + ;; "/" must NOT be hexlified. + (let ((url-unreserved-chars (append '(?/) url-unreserved-chars))) + (url-recreate-url + (if (tramp-tramp-file-p filename) + (with-parsed-tramp-file-name (file-truename filename) nil + (when (string-match tramp-user-with-domain-regexp user) + (setq user + (concat (match-string 2 user) ";" (match-string 2 user)))) + (url-parse-make-urlobj + method user nil + (tramp-file-name-real-host v) (tramp-file-name-port v) + (url-hexify-string localname))) + (url-parse-make-urlobj + "file" nil nil nil nil (url-hexify-string (file-truename filename))))))) (defun tramp-gvfs-object-path (filename) "Create a D-Bus object path from FILENAME." @@ -782,15 +847,19 @@ "Return FUSE file name, which is directly accessible." (with-parsed-tramp-file-name (expand-file-name filename) nil (tramp-gvfs-maybe-open-connection v) - (let ((fuse-mountpoint + (let ((prefix (tramp-get-file-property v "/" "prefix" "")) + (fuse-mountpoint (tramp-get-file-property v "/" "fuse-mountpoint" nil))) (unless fuse-mountpoint (tramp-error v 'file-error "There is no FUSE mount point for `%s'" filename)) - ;; We must remove the share from the local name. - (when (and (string-equal "smb" method) (string-match "/[^/]+" localname)) + ;; We must hide the prefix, if any. + (when (string-match (concat "^" (regexp-quote prefix)) localname) (setq localname (replace-match "" t t localname))) - (concat tramp-gvfs-mount-point fuse-mountpoint localname)))) + (tramp-message + v 10 "remote file `%s' is local file `%s'" + filename (concat fuse-mountpoint localname)) + (concat fuse-mountpoint localname)))) (defun tramp-bluez-address (device) "Return bluetooth device address from a given bluetooth DEVICE name." @@ -881,10 +950,10 @@ (setq choice (if (yes-or-no-p (concat (car choices) " ")) 0 1)) (tramp-message v 6 "%d" choice))) - ;; When the choice is "no", we set an empty - ;; fuse-mountpoint in order to leave the timeout. + ;; When the choice is "no", we set a dummy fuse-mountpoint + ;; in order to leave the timeout. (unless (zerop choice) - (tramp-set-file-property v "/" "fuse-mountpoint" "")) + (tramp-set-file-property v "/" "fuse-mountpoint" "/")) (list t ;; handled. @@ -898,6 +967,10 @@ "Signal handler for the \"org.gtk.vfs.MountTracker.mounted\" and \"org.gtk.vfs.MountTracker.unmounted\" signals." (ignore-errors + ;; The last element could be the default location in newer gvfs + ;; versions. We must check this. + (unless (consp (car (last mount-info))) + (setq mount-info (butlast mount-info))) (let* ((signal-name (dbus-event-member-name last-input-event)) (mount-spec (cadar (last mount-info))) (method (dbus-byte-array-to-string (cadr (assoc "type" mount-spec)))) @@ -908,7 +981,10 @@ (cadr (or (assoc "host" mount-spec) (assoc "server" mount-spec))))) (port (dbus-byte-array-to-string (cadr (assoc "port" mount-spec)))) - (ssl (dbus-byte-array-to-string (cadr (assoc "ssl" mount-spec))))) + (ssl (dbus-byte-array-to-string (cadr (assoc "ssl" mount-spec)))) + (prefix (concat (dbus-byte-array-to-string (caar (last mount-info))) + (dbus-byte-array-to-string + (cadr (assoc "share" mount-spec)))))) (when (string-match "^smb" method) (setq method "smb")) (when (string-equal "obex" method) @@ -921,14 +997,17 @@ (setq host (concat host tramp-prefix-port-format port))) (with-parsed-tramp-file-name (tramp-make-tramp-file-name method user host "") nil - (tramp-message v 6 "%s %s" signal-name mount-info) + (tramp-message + v 6 "%s %s" signal-name (tramp-gvfs-stringify-dbus-message mount-info)) (tramp-set-file-property v "/" "list-mounts" 'undef) (if (string-equal signal-name "unmounted") (tramp-set-file-property v "/" "fuse-mountpoint" nil) + ;; Set prefix and mountpoint. + (unless (string-equal prefix "/") + (tramp-set-file-property v "/" "prefix" prefix)) (tramp-set-file-property v "/" "fuse-mountpoint" - (file-name-nondirectory - (dbus-byte-array-to-string (car (last mount-info 2)))))))))) + (dbus-byte-array-to-string (car (last mount-info 2))))))))) (dbus-register-signal :session nil tramp-gvfs-path-mounttracker @@ -942,47 +1021,60 @@ (defun tramp-gvfs-connection-mounted-p (vec) "Check, whether the location is already mounted." - (catch 'mounted - (dolist - (elt - (with-file-property vec "/" "list-mounts" - (with-tramp-dbus-call-method vec t - :session tramp-gvfs-service-daemon tramp-gvfs-path-mounttracker - tramp-gvfs-interface-mounttracker "listMounts")) - nil) - (let* ((mount-spec (cadar (last elt))) - (method (dbus-byte-array-to-string - (cadr (assoc "type" mount-spec)))) - (user (dbus-byte-array-to-string - (cadr (assoc "user" mount-spec)))) - (domain (dbus-byte-array-to-string - (cadr (assoc "domain" mount-spec)))) - (host (dbus-byte-array-to-string - (cadr (or (assoc "host" mount-spec) - (assoc "server" mount-spec))))) - (port (dbus-byte-array-to-string (cadr (assoc "port" mount-spec)))) - (ssl (dbus-byte-array-to-string (cadr (assoc "ssl" mount-spec))))) - (when (string-match "^smb" method) - (setq method "smb")) - (when (string-equal "obex" method) - (setq host (tramp-bluez-device host))) - (when (and (string-equal "dav" method) (string-equal "true" ssl)) - (setq method "davs")) - (when (and (string-equal "synce" method) (zerop (length user))) - (setq user (or (tramp-file-name-user vec) ""))) - (unless (zerop (length domain)) - (setq user (concat user tramp-prefix-domain-format domain))) - (unless (zerop (length port)) - (setq host (concat host tramp-prefix-port-format port))) - (when (and - (string-equal method (tramp-file-name-method vec)) - (string-equal user (or (tramp-file-name-user vec) "")) - (string-equal host (tramp-file-name-host vec))) - (tramp-set-file-property - vec "/" "fuse-mountpoint" - (file-name-nondirectory - (dbus-byte-array-to-string (car (last elt 2))))) - (throw 'mounted t)))))) + (or + (tramp-get-file-property vec "/" "fuse-mountpoint" nil) + (catch 'mounted + (dolist + (elt + (with-file-property vec "/" "list-mounts" + (with-tramp-dbus-call-method vec t + :session tramp-gvfs-service-daemon tramp-gvfs-path-mounttracker + tramp-gvfs-interface-mounttracker "listMounts")) + nil) + ;; The last element could be the default location in newer gvfs + ;; versions. We must check this. + (unless (consp (car (last elt))) (setq elt (butlast elt))) + (let* ((mount-spec (cadar (last elt))) + (method (dbus-byte-array-to-string + (cadr (assoc "type" mount-spec)))) + (user (dbus-byte-array-to-string + (cadr (assoc "user" mount-spec)))) + (domain (dbus-byte-array-to-string + (cadr (assoc "domain" mount-spec)))) + (host (dbus-byte-array-to-string + (cadr (or (assoc "host" mount-spec) + (assoc "server" mount-spec))))) + (port (dbus-byte-array-to-string + (cadr (assoc "port" mount-spec)))) + (ssl (dbus-byte-array-to-string (cadr (assoc "ssl" mount-spec)))) + (prefix (concat (dbus-byte-array-to-string (caar (last elt))) + (dbus-byte-array-to-string + (cadr (assoc "share" mount-spec)))))) + (when (string-match "^smb" method) + (setq method "smb")) + (when (string-equal "obex" method) + (setq host (tramp-bluez-device host))) + (when (and (string-equal "dav" method) (string-equal "true" ssl)) + (setq method "davs")) + (when (and (string-equal "synce" method) (zerop (length user))) + (setq user (or (tramp-file-name-user vec) ""))) + (unless (zerop (length domain)) + (setq user (concat user tramp-prefix-domain-format domain))) + (unless (zerop (length port)) + (setq host (concat host tramp-prefix-port-format port))) + (when (and + (string-equal method (tramp-file-name-method vec)) + (string-equal user (or (tramp-file-name-user vec) "")) + (string-equal host (tramp-file-name-host vec)) + (string-match (concat "^" (regexp-quote prefix)) + (tramp-file-name-localname vec))) + ;; Set prefix and mountpoint. + (unless (string-equal prefix "/") + (tramp-set-file-property vec "/" "prefix" prefix)) + (tramp-set-file-property + vec "/" "fuse-mountpoint" + (dbus-byte-array-to-string (car (last elt 2)))) + (throw 'mounted t))))))) (defun tramp-gvfs-mount-spec (vec) "Return a mount-spec for \"org.gtk.vfs.MountTracker.mountLocation\"." @@ -993,7 +1085,8 @@ (port (tramp-file-name-port vec)) (localname (tramp-file-name-localname vec)) (ssl (if (string-match "^davs" method) "true" "false")) - (mount-spec `(:array))) + (mount-spec '(:array)) + (mount-pref "/")) (setq mount-spec @@ -1036,8 +1129,12 @@ `(:struct "port" ,(dbus-string-to-byte-array (number-to-string port))) 'append)) + (when (and (string-match "^dav" method) + (string-match "^/?[^/]+" localname)) + (setq mount-pref (match-string 0 localname))) + ;; Return. - mount-spec)) + `(:struct ,(dbus-string-to-byte-array mount-pref) ,mount-spec))) ;; Connection functions @@ -1096,10 +1193,7 @@ (with-tramp-dbus-call-method vec nil :session tramp-gvfs-service-daemon tramp-gvfs-path-mounttracker tramp-gvfs-interface-mounttracker "mountLocation" - `(:struct - ,(dbus-string-to-byte-array "/") - ,(tramp-gvfs-mount-spec vec)) - (dbus-get-unique-name :session) + (tramp-gvfs-mount-spec vec) (dbus-get-unique-name :session) :object-path object-path) ;; We must wait, until the mount is applied. This will be @@ -1117,11 +1211,29 @@ (while (not (tramp-get-file-property vec "/" "fuse-mountpoint" nil)) (read-event nil nil 0.1))) + ;; If `tramp-gvfs-handler-askquestion' has returned "No", it + ;; is marked with the fuse-mountpoint "/". We shall react. + (when (string-equal + (tramp-get-file-property vec "/" "fuse-mountpoint" "") "/") + (tramp-error vec 'file-error "FUSE mount denied")) + ;; We set the connection property "started" in order to put the ;; remote location into the cache, which is helpful for further ;; completion. (tramp-set-connection-property vec "started" t))))) +(defun tramp-gvfs-send-command (vec command &rest args) + "Send the COMMAND with its ARGS to connection VEC. +COMMAND is usually a command from the gvfs-* utilities. +`call-process' is applied, and its return code is returned." + (let (result) + (with-current-buffer (tramp-get-buffer vec) + (erase-buffer) + (tramp-message vec 6 "%s %s" command (mapconcat 'identity args " ")) + (setq result (apply 'tramp-local-call-process command nil t nil args)) + (tramp-message vec 6 "%s" (buffer-string)) + result))) + ;; D-Bus BLUEZ functions.