X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/7ae3ea65e3ee04b8afe7cd364c7029856597a56a..38d50547c2a8195bed0aaeafbbc4c0f277d4e416:/lisp/net/tramp-gvfs.el diff --git a/lisp/net/tramp-gvfs.el b/lisp/net/tramp-gvfs.el index aaba49e815..cd2bab26f4 100644 --- a/lisp/net/tramp-gvfs.el +++ b/lisp/net/tramp-gvfs.el @@ -1,9 +1,10 @@ ;;; tramp-gvfs.el --- Tramp access functions for GVFS daemon -;; Copyright (C) 2009 Free Software Foundation, Inc. +;; Copyright (C) 2009, 2010 Free Software Foundation, Inc. ;; Author: Michael Albinus ;; Keywords: comm, processes +;; Package: tramp ;; This file is part of GNU Emacs. @@ -23,12 +24,19 @@ ;;; Commentary: ;; Access functions for the GVFS daemon from Tramp. Tested with GVFS -;; 1.0.2 (Ubuntu 8.10, Gnome 2.24). +;; 1.0.2 (Ubuntu 8.10, Gnome 2.24). It has been reported also to run +;; with GVFS 0.2.5 (Ubuntu 8.04, Gnome 2.22), but there is an +;; 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. -;; Consequently, GNU Emacs 23.0.90 with enabled D-Bus bindings is a +;; Consequently, GNU Emacs 23.1 with enabled D-Bus bindings is a ;; precondition. ;; The GVFS D-Bus interface is said to be instable. There are even no @@ -36,11 +44,11 @@ ;; development time, is given in respective comments. ;; The customer option `tramp-gvfs-methods' contains the list of -;; supported connection methods. Per default, these are "dav", "davs" -;; and "obex". Note that with "obex" it might be necessary to pair -;; with the other bluetooth device, if it hasn't been done already. -;; There might be also some few seconds delay in discovering available -;; bluetooth devices. +;; supported connection methods. Per default, these are "dav", +;; "davs", "obex" and "synce". Note that with "obex" it might be +;; necessary to pair with the other bluetooth device, if it hasn't +;; been done already. There might be also some few seconds delay in +;; discovering available bluetooth devices. ;; Other possible connection methods are "ftp", "sftp" and "smb". ;; When one of these methods is added to the list, the remote access @@ -65,11 +73,12 @@ ;; drop me a note. ;; For hostname completion, information is retrieved either from the -;; bluez daemon (for the "obex" method), or from the zeroconf daemon -;; (for the "dav", "davs", and "sftp" methods). The zeroconf daemon -;; is pre-configured to discover services in the "local" domain. If -;; another domain shall be used for discovering services, the customer -;; option `tramp-gvfs-zeroconf-domain' can be set accordingly. +;; bluez daemon (for the "obex" method), the hal daemon (for the +;; "synce" method), or from the zeroconf daemon (for the "dav", +;; "davs", and "sftp" methods). The zeroconf daemon is pre-configured +;; to discover services in the "local" domain. If another domain +;; shall be used for discovering services, the customer option +;; `tramp-gvfs-zeroconf-domain' can be set accordingly. ;; Restrictions: @@ -94,13 +103,21 @@ (require 'custom)) (require 'tramp) + +;; We call several `tramp-handle-*' functions directly. So we must +;; reqire that package as well. +(require 'tramp-sh) + (require 'dbus) (require 'url-parse) +(require 'url-util) (require 'zeroconf) +;;;###tramp-autoload (defcustom tramp-gvfs-methods '("dav" "davs" "obex" "synce") "*List of methods for remote files, accessed with GVFS." :group 'tramp + :version "23.2" :type '(repeat (choice (const "dav") (const "davs") (const "ftp") @@ -109,22 +126,24 @@ (const "smb") (const "synce")))) +;; Add a default for `tramp-default-user-alist'. Rule: For the SYNCE +;; method, no user is chosen. +(add-to-list 'tramp-default-user-alist + '("synce" nil nil)) + (defcustom tramp-gvfs-zeroconf-domain "local" "*Zeroconf domain to be used for discovering services, like host names." :group 'tramp + :version "23.2" :type 'string) ;; Add the methods to `tramp-methods', in order to allow minibuffer ;; completion. -(eval-after-load "tramp-gvfs" - '(when (featurep 'tramp-gvfs) - (dolist (elt tramp-gvfs-methods) - (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.") +;;;###tramp-autoload +(when (featurep 'dbusbind) + (dolist (elt tramp-gvfs-methods) + (unless (assoc elt tramp-methods) + (add-to-list 'tramp-methods (cons elt nil))))) (defconst tramp-gvfs-path-tramp (concat dbus-path-emacs "/Tramp") "The preceeding object path for own objects.") @@ -132,10 +151,12 @@ (defconst tramp-gvfs-service-daemon "org.gtk.vfs.Daemon" "The well known name of the GVFS daemon.") -;; Check that GVFS is available. -(unless (dbus-ping :session tramp-gvfs-service-daemon) - (message "GVFS daemon not running") - (throw 'tramp-loading nil)) +;; Check that GVFS is available. D-Bus integration is available since +;; Emacs 23 on some system types. We don't call `dbus-ping', because +;; this would load dbus.el. +(unless (and (tramp-compat-funcall 'dbus-get-unique-name :session) + (tramp-compat-process-running-p "gvfs-fuse-daemon")) + (error "Package `tramp-gvfs' not supported")) (defconst tramp-gvfs-path-mounttracker "/org/gtk/vfs/mounttracker" "The object path of the GVFS daemon.") @@ -146,7 +167,7 @@ ;; ;; ;; ;; ;; @@ -156,11 +177,11 @@ ;; ;; ;; +;; type='{sosssssbay{aya{say}}ay}'/> ;; ;; ;; +;; type='{sosssssbay{aya{say}}ay}'/> ;; ;; ;; @@ -169,7 +190,7 @@ ;; OBJECT_PATH object_path ;; STRING display_name ;; STRING stable_name -;; STRING x_content_types +;; STRING x_content_types Since GVFS 1.0 only !!! ;; STRING icon ;; STRING prefered_filename_encoding ;; BOOLEAN user_visible @@ -180,13 +201,14 @@ ;; STRUCT mount_spec_item ;; STRING key (server, share, type, user, host, port) ;; ARRAY BYTE value +;; ARRAY BYTE default_location Since GVFS 1.5 only !!! (defconst tramp-gvfs-interface-mountoperation "org.gtk.vfs.MountOperation" "Used by the dbus-proxying implementation of GMountOperation.") ;; ;; -;; +;; ;; ;; ;; @@ -327,6 +349,7 @@ A value of 0 would require an immediate discovery during hostname completion, nil means to use always cached values for discovered devices." :group 'tramp + :version "23.2" :type '(choice (const nil) integer)) (defvar tramp-bluez-discovery nil @@ -337,16 +360,29 @@ It keeps the timestamp of last discovery.") "Alist of detected bluetooth devices. Every entry is a list (NAME ADDRESS).") +(defconst tramp-hal-service "org.freedesktop.Hal" + "The well known name of the HAL service.") + +(defconst tramp-hal-path-manager "/org/freedesktop/Hal/Manager" + "The object path of the HAL daemon manager.") + +(defconst tramp-hal-interface-manager "org.freedesktop.Hal.Manager" + "The manager interface of the HAL daemon.") + +(defconst tramp-hal-interface-device "org.freedesktop.Hal.Device" + "The device interface of the HAL daemon.") + + ;; New handlers should be added here. (defconst tramp-gvfs-file-name-handler-alist '( (access-file . ignore) (add-name-to-file . tramp-gvfs-handle-copy-file) - ;; `byte-compiler-base-file-name' performed by default handler + ;; `byte-compiler-base-file-name' performed by default handler. (copy-file . tramp-gvfs-handle-copy-file) (delete-directory . tramp-gvfs-handle-delete-directory) (delete-file . tramp-gvfs-handle-delete-file) - ;; `diff-latest-backup-file' performed by default handler + ;; `diff-latest-backup-file' performed by default handler. (directory-file-name . tramp-handle-directory-file-name) (directory-files . tramp-gvfs-handle-directory-files) (directory-files-and-attributes @@ -354,41 +390,46 @@ Every entry is a list (NAME ADDRESS).") (dired-call-process . ignore) (dired-compress-file . ignore) (dired-uncache . tramp-handle-dired-uncache) + ;; `executable-find' is not official yet. performed by default handler. (expand-file-name . tramp-gvfs-handle-expand-file-name) - ;; `file-accessible-directory-p' performed by default handler + ;; `file-accessible-directory-p' performed by default handler. (file-attributes . tramp-gvfs-handle-file-attributes) - (file-directory-p . tramp-smb-handle-file-directory-p) + (file-directory-p . tramp-gvfs-handle-file-directory-p) (file-executable-p . tramp-gvfs-handle-file-executable-p) (file-exists-p . tramp-gvfs-handle-file-exists-p) (file-local-copy . tramp-gvfs-handle-file-local-copy) - (file-remote-p . tramp-handle-file-remote-p) - ;; `file-modes' performed by default handler + ;; `file-modes' performed by default handler. (file-name-all-completions . tramp-gvfs-handle-file-name-all-completions) (file-name-as-directory . tramp-handle-file-name-as-directory) (file-name-completion . tramp-handle-file-name-completion) (file-name-directory . tramp-handle-file-name-directory) (file-name-nondirectory . tramp-handle-file-name-nondirectory) - ;; `file-name-sans-versions' performed by default handler + ;; `file-name-sans-versions' performed by default handler. (file-newer-than-file-p . tramp-handle-file-newer-than-file-p) (file-ownership-preserved-p . ignore) (file-readable-p . tramp-gvfs-handle-file-readable-p) (file-regular-p . tramp-handle-file-regular-p) + (file-remote-p . tramp-handle-file-remote-p) + (file-selinux-context . tramp-gvfs-handle-file-selinux-context) (file-symlink-p . tramp-handle-file-symlink-p) - ;; `file-truename' performed by default handler + ;; `file-truename' performed by default handler. (file-writable-p . tramp-gvfs-handle-file-writable-p) (find-backup-file-name . tramp-handle-find-backup-file-name) - ;; `find-file-noselect' performed by default handler - ;; `get-file-buffer' performed by default handler + ;; `find-file-noselect' performed by default handler. + ;; `get-file-buffer' performed by default handler. (insert-directory . tramp-gvfs-handle-insert-directory) (insert-file-contents . tramp-gvfs-handle-insert-file-contents) (load . tramp-handle-load) (make-directory . tramp-gvfs-handle-make-directory) (make-directory-internal . ignore) (make-symbolic-link . ignore) + (process-file . tramp-gvfs-handle-process-file) (rename-file . tramp-gvfs-handle-rename-file) (set-file-modes . tramp-gvfs-handle-set-file-modes) + (set-file-selinux-context . tramp-gvfs-handle-set-file-selinux-context) (set-visited-file-modtime . tramp-gvfs-handle-set-visited-file-modtime) - (shell-command . ignore) + (shell-command . tramp-gvfs-handle-shell-command) + (start-file-process . tramp-gvfs-handle-start-file-process) (substitute-in-file-name . tramp-handle-substitute-in-file-name) (unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory) (vc-registered . ignore) @@ -399,13 +440,15 @@ Every entry is a list (NAME ADDRESS).") "Alist of handler functions for Tramp GVFS method. Operations not mentioned here will be handled by the default Emacs primitives.") -(defun tramp-gvfs-file-name-p (filename) +;;;###tramp-autoload +(defsubst tramp-gvfs-file-name-p (filename) "Check if it's a filename handled by the GVFS daemon." (and (tramp-tramp-file-p filename) (let ((method (tramp-file-name-method (tramp-dissect-file-name filename)))) (and (stringp method) (member method tramp-gvfs-methods))))) +;;;###tramp-autoload (defun tramp-gvfs-file-name-handler (operation &rest args) "Invoke the GVFS related OPERATION. First arg specifies the OPERATION, second arg is a list of arguments to @@ -417,8 +460,21 @@ pass to the OPERATION." ;; This might be moved to tramp.el. It shall be the first file name ;; handler. -(add-to-list 'tramp-foreign-file-name-handler-alist - (cons 'tramp-gvfs-file-name-p 'tramp-gvfs-file-name-handler)) +;;;###tramp-autoload +(when (featurep 'dbusbind) + (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) @@ -437,12 +493,13 @@ will be traced by Tramp with trace level 6." 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) (put 'with-tramp-dbus-call-method 'edebug-form-spec '(form symbolp body)) -(font-lock-add-keywords 'emacs-lisp-mode '("\\")) +(tramp-compat-font-lock-add-keywords + 'emacs-lisp-mode '("\\")) (defmacro with-tramp-gvfs-error-message (filename handler &rest args) "Apply a Tramp GVFS `handler'. @@ -451,7 +508,7 @@ In case of an error, modify the error message by replacing `(let ((fuse-file-name (regexp-quote (tramp-gvfs-fuse-file-name ,filename))) elt) (condition-case err - (apply ,handler (list ,@args)) + (tramp-compat-funcall ,handler ,@args) (error (setq elt (cdr err)) (while elt @@ -463,7 +520,8 @@ In case of an error, modify the error message by replacing (put 'with-tramp-gvfs-error-message 'lisp-indent-function 2) (put 'with-tramp-gvfs-error-message 'edebug-form-spec '(form symbolp body)) -(font-lock-add-keywords 'emacs-lisp-mode '("\\")) +(tramp-compat-font-lock-add-keywords + 'emacs-lisp-mode '("\\")) (defvar tramp-gvfs-dbus-event-vector nil "Current Tramp file name to be used, as vector. @@ -472,9 +530,10 @@ is no information where to trace the message.") (defun tramp-gvfs-dbus-event-error (event err) "Called when a D-Bus error message arrives, see `dbus-event-error-hooks'." -; (tramp-cleanup-connection tramp-gvfs-dbus-event-vector) - (tramp-message tramp-gvfs-dbus-event-vector 1 "%S" event) - (tramp-error tramp-gvfs-dbus-event-vector 'file-error "%s" (cadr err))) + (when tramp-gvfs-dbus-event-vector + ;(tramp-cleanup-connection tramp-gvfs-dbus-event-vector) + (tramp-message tramp-gvfs-dbus-event-vector 10 "%S" event) + (tramp-error tramp-gvfs-dbus-event-vector 'file-error "%s" (cadr err)))) (add-hook 'dbus-event-error-hooks 'tramp-gvfs-dbus-event-error) @@ -482,24 +541,56 @@ is no information where to trace the message.") ;; File name primitives. (defun tramp-gvfs-handle-copy-file - (filename newname &optional ok-if-already-exists keep-date preserve-uid-gid) + (filename newname &optional ok-if-already-exists keep-date + preserve-uid-gid preserve-selinux-context) "Like `copy-file' for Tramp files." - (copy-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 keep-date preserve-uid-gid)) - -(defun tramp-gvfs-handle-delete-directory (directory) + (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 + (let ((args + (append (if (or keep-date preserve-uid-gid) + (list "--preserve") + nil) + (list + (tramp-gvfs-url-file-name filename) + (tramp-gvfs-url-file-name newname))))) + (apply 'tramp-gvfs-send-command v "gvfs-copy" args))) + ;; 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." - (delete-directory (tramp-gvfs-fuse-file-name directory))) + (tramp-compat-delete-directory + (tramp-gvfs-fuse-file-name directory) recursive)) -(defun tramp-gvfs-handle-delete-file (filename) +(defun tramp-gvfs-handle-delete-file (filename &optional trash) "Like `delete-file' for Tramp files." - (delete-file (tramp-gvfs-fuse-file-name filename))) + (tramp-compat-delete-file (tramp-gvfs-fuse-file-name filename) trash)) (defun tramp-gvfs-handle-directory-files (directory &optional full match nosort) @@ -536,6 +627,14 @@ is no information where to trace the message.") (tramp-run-real-handler 'expand-file-name (list name nil)) ;; Dissect NAME. (with-parsed-tramp-file-name name nil + ;; If there is a default location, expand tilde. + (when (string-match "\\`\\(~\\)\\(/\\|\\'\\)" localname) + (save-match-data + (tramp-gvfs-maybe-open-connection (vector method user host "/"))) + (setq localname + (replace-match + (tramp-get-file-property v "/" "default-location" "~") + nil t localname 1))) ;; Tilde expansion is not possible. (when (string-match "\\`\\(~[^/]*\\)\\(.*\\)\\'" localname) (tramp-error @@ -563,6 +662,10 @@ is no information where to trace the message.") "Like `file-attributes' for Tramp files." (file-attributes (tramp-gvfs-fuse-file-name filename) id-format)) +(defun tramp-gvfs-handle-file-directory-p (filename) + "Like `file-directory-p' for Tramp files." + (file-directory-p (tramp-gvfs-fuse-file-name filename))) + (defun tramp-gvfs-handle-file-executable-p (filename) "Like `file-executable-p' for Tramp files." (file-executable-p (tramp-gvfs-fuse-file-name filename))) @@ -591,6 +694,11 @@ is no information where to trace the message.") "Like `file-readable-p' for Tramp files." (file-readable-p (tramp-gvfs-fuse-file-name filename))) +(defun tramp-gvfs-handle-file-selinux-context (filename) + "Like `file-selinux-context' for Tramp files." + (tramp-compat-funcall + 'file-selinux-context (tramp-gvfs-fuse-file-name filename))) + (defun tramp-gvfs-handle-file-writable-p (filename) "Like `file-writable-p' for Tramp files." (file-writable-p (tramp-gvfs-fuse-file-name filename))) @@ -616,42 +724,92 @@ is no information where to trace the message.") (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) + "Like `process-file' for Tramp files." + (let ((default-directory (tramp-gvfs-fuse-file-name default-directory))) + (apply 'call-process program infile destination display args))) (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." (with-tramp-gvfs-error-message filename 'set-file-modes (tramp-gvfs-fuse-file-name filename) mode)) +(defun tramp-gvfs-handle-set-file-selinux-context (filename context) + "Like `set-file-selinux-context' for Tramp files." + (with-tramp-gvfs-error-message filename 'set-file-selinux-context + (tramp-gvfs-fuse-file-name filename) context)) + (defun tramp-gvfs-handle-set-visited-file-modtime (&optional time-list) "Like `set-visited-file-modtime' for Tramp files." (let ((buffer-file-name (tramp-gvfs-fuse-file-name (buffer-file-name)))) (set-visited-file-modtime time-list))) +(defun tramp-gvfs-handle-shell-command + (command &optional output-buffer error-buffer) + "Like `shell-command' for Tramp files." + (let ((default-directory (tramp-gvfs-fuse-file-name default-directory))) + (shell-command command output-buffer error-buffer))) + +(defun tramp-gvfs-handle-start-file-process (name buffer program &rest args) + "Like `start-file-process' for Tramp files." + (let ((default-directory (tramp-gvfs-fuse-file-name default-directory))) + (apply 'start-process name buffer program args))) + (defun tramp-gvfs-handle-verify-visited-file-modtime (buf) "Like `verify-visited-file-modtime' for Tramp files." (with-current-buffer buf @@ -667,19 +825,20 @@ is no information where to trace the message.") 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)) + (set-visited-file-modtime (nth 5 (file-attributes filename)))) ;; The end. (when (or (eq visit t) (null visit) (stringp visit)) @@ -691,16 +850,20 @@ is no information where to trace the message.") (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." @@ -715,15 +878,19 @@ is no information where to trace the message.") "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." @@ -807,112 +974,149 @@ ADDRESS can have the form \"xx:xx:xx:xx:xx:xx\" or \"[xx:xx:xx:xx:xx:xx]\"." ;; there is only the question whether to accept an unknown ;; host signature. (with-temp-buffer - (insert message) - (pop-to-buffer (current-buffer)) - (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. + ;; Preserve message for `progress-reporter'. + (tramp-compat-with-temp-message "" + (insert message) + (pop-to-buffer (current-buffer)) + (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 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. nil ;; no abort of D-Bus. choice)) - ;; When QUIT is raised, we shall return this information to D-Bus. - (quit (list nil t 0)))))) + ;; When QUIT is raised, we shall return this information to D-Bus. + (quit (list nil t 0)))))) (defun tramp-gvfs-handler-mounted-unmounted (mount-info) "Signal handler for the \"org.gtk.vfs.MountTracker.mounted\" and \"org.gtk.vfs.MountTracker.unmounted\" signals." (ignore-errors - (let* ((signal-name (dbus-event-member-name last-input-event)) - (mount-spec (nth 1 (nth 9 mount-info))) - (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")) - (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))) - (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-set-file-property v "/" "list-mounts" 'undef) - (if (string-equal signal-name "unmounted") - (tramp-set-file-property v "/" "fuse-mountpoint" nil) - (tramp-set-file-property - v "/" "fuse-mountpoint" - (file-name-nondirectory - (dbus-byte-array-to-string (nth 8 mount-info))))))))) - -(dbus-register-signal - :session nil tramp-gvfs-path-mounttracker - tramp-gvfs-interface-mounttracker "mounted" - 'tramp-gvfs-handler-mounted-unmounted) - -(dbus-register-signal - :session nil tramp-gvfs-path-mounttracker - tramp-gvfs-interface-mounttracker "unmounted" - 'tramp-gvfs-handler-mounted-unmounted) - -(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 (nth 1 (nth 9 elt))) + (let ((signal-name (dbus-event-member-name last-input-event)) + (elt mount-info)) + ;; Jump over the first elements of the mount info. Since there + ;; were changes in the antries, we cannot access dedicated + ;; elements. + (while (stringp (car elt)) (setq elt (cdr elt))) + (let* ((fuse-mountpoint (dbus-byte-array-to-string (cadr elt))) + (mount-spec (caddr elt)) + (default-location (dbus-byte-array-to-string (cadddr elt))) (method (dbus-byte-array-to-string - (cadr (assoc "type" mount-spec)))) + (cadr (assoc "type" (cadr mount-spec))))) (user (dbus-byte-array-to-string - (cadr (assoc "user" mount-spec)))) + (cadr (assoc "user" (cadr mount-spec))))) (domain (dbus-byte-array-to-string - (cadr (assoc "domain" mount-spec)))) + (cadr (assoc "domain" (cadr 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))))) + (cadr (or (assoc "host" (cadr mount-spec)) + (assoc "server" (cadr mount-spec)))))) + (port (dbus-byte-array-to-string + (cadr (assoc "port" (cadr mount-spec))))) + (ssl (dbus-byte-array-to-string + (cadr (assoc "ssl" (cadr mount-spec))))) + (prefix (concat (dbus-byte-array-to-string (car mount-spec)) + (dbus-byte-array-to-string + (cadr (assoc "share" (cadr 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 (nth 8 elt)))) - (throw 'mounted t)))))) + (with-parsed-tramp-file-name + (tramp-make-tramp-file-name method user host "") nil + (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, mountpoint and location. + (unless (string-equal prefix "/") + (tramp-set-file-property v "/" "prefix" prefix)) + (tramp-set-file-property v "/" "fuse-mountpoint" fuse-mountpoint) + (tramp-set-file-property + v "/" "default-location" default-location))))))) + +(dbus-register-signal + :session nil tramp-gvfs-path-mounttracker + tramp-gvfs-interface-mounttracker "mounted" + 'tramp-gvfs-handler-mounted-unmounted) + +(dbus-register-signal + :session nil tramp-gvfs-path-mounttracker + tramp-gvfs-interface-mounttracker "unmounted" + 'tramp-gvfs-handler-mounted-unmounted) + +(defun tramp-gvfs-connection-mounted-p (vec) + "Check, whether the location is already mounted." + (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) + ;; Jump over the first elements of the mount info. Since there + ;; were changes in the antries, we cannot access dedicated + ;; elements. + (while (stringp (car elt)) (setq elt (cdr elt))) + (let* ((fuse-mountpoint (dbus-byte-array-to-string (cadr elt))) + (mount-spec (caddr elt)) + (default-location (dbus-byte-array-to-string (cadddr elt))) + (method (dbus-byte-array-to-string + (cadr (assoc "type" (cadr mount-spec))))) + (user (dbus-byte-array-to-string + (cadr (assoc "user" (cadr mount-spec))))) + (domain (dbus-byte-array-to-string + (cadr (assoc "domain" (cadr mount-spec))))) + (host (dbus-byte-array-to-string + (cadr (or (assoc "host" (cadr mount-spec)) + (assoc "server" (cadr mount-spec)))))) + (port (dbus-byte-array-to-string + (cadr (assoc "port" (cadr mount-spec))))) + (ssl (dbus-byte-array-to-string + (cadr (assoc "ssl" (cadr mount-spec))))) + (prefix (concat (dbus-byte-array-to-string (car mount-spec)) + (dbus-byte-array-to-string + (cadr (assoc "share" (cadr 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, mountpoint and location. + (unless (string-equal prefix "/") + (tramp-set-file-property vec "/" "prefix" prefix)) + (tramp-set-file-property vec "/" "fuse-mountpoint" fuse-mountpoint) + (tramp-set-file-property vec "/" "default-location" default-location) + (throw 'mounted t))))))) (defun tramp-gvfs-mount-spec (vec) "Return a mount-spec for \"org.gtk.vfs.MountTracker.mountLocation\"." @@ -923,7 +1127,8 @@ ADDRESS can have the form \"xx:xx:xx:xx:xx:xx\" or \"[xx:xx:xx:xx:xx:xx]\"." (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 @@ -966,8 +1171,12 @@ ADDRESS can have the form \"xx:xx:xx:xx:xx:xx\" or \"[xx:xx:xx:xx:xx:xx]\"." `(: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 @@ -999,72 +1208,79 @@ connection if a previous connection has died for some reason." (tramp-gvfs-object-path (tramp-make-tramp-file-name method user host "")))) - (if (zerop (length (tramp-file-name-user vec))) - (tramp-message - vec 3 "Opening connection for %s using %s..." host method) - (tramp-message - vec 3 "Opening connection for %s@%s using %s..." user host method)) - - ;; Enable auth-sorce and password-cache. - (tramp-set-connection-property - (tramp-get-connection-process vec) "first-password-request" t) - - ;; There will be a callback of "askPassword", when a password is - ;; needed. - (dbus-register-method - :session dbus-service-emacs object-path - tramp-gvfs-interface-mountoperation "askPassword" - 'tramp-gvfs-handler-askpassword) - - ;; There could be a callback of "askQuestion", when adding fingerprint. - (dbus-register-method - :session dbus-service-emacs object-path - tramp-gvfs-interface-mountoperation "askQuestion" - 'tramp-gvfs-handler-askquestion) - - ;; The call must be asynchronously, because of the "askPassword" - ;; or "askQuestion"callbacks. - (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) - :object-path object-path) - - ;; We must wait, until the mount is applied. This will be - ;; indicated by the "mounted" signal, i.e. the "fuse-mountpoint" - ;; file property. - (with-timeout - (60 - (if (zerop (length (tramp-file-name-user vec))) + (with-progress-reporter + vec 3 + (if (zerop (length user)) + (format "Opening connection for %s using %s" host method) + (format "Opening connection for %s@%s using %s" user host method)) + + ;; Enable auth-sorce and password-cache. + (tramp-set-connection-property vec "first-password-request" t) + + ;; There will be a callback of "askPassword", when a password is + ;; needed. + (dbus-register-method + :session dbus-service-emacs object-path + tramp-gvfs-interface-mountoperation "askPassword" + 'tramp-gvfs-handler-askpassword) + + ;; There could be a callback of "askQuestion", when adding fingerprint. + (dbus-register-method + :session dbus-service-emacs object-path + tramp-gvfs-interface-mountoperation "askQuestion" + 'tramp-gvfs-handler-askquestion) + + ;; The call must be asynchronously, because of the "askPassword" + ;; or "askQuestion"callbacks. + (with-tramp-dbus-call-method vec nil + :session tramp-gvfs-service-daemon tramp-gvfs-path-mounttracker + tramp-gvfs-interface-mounttracker "mountLocation" + (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 + ;; indicated by the "mounted" signal, i.e. the "fuse-mountpoint" + ;; file property. + (with-timeout + (60 + (if (zerop (length (tramp-file-name-user vec))) + (tramp-error + vec 'file-error + "Timeout reached mounting %s using %s" host method) (tramp-error vec 'file-error - "Timeout reached mounting %s using %s" host method) - (tramp-error - vec 'file-error - "Timeout reached mounting %s@%s using %s" user host method))) - (while (not (tramp-get-file-property vec "/" "fuse-mountpoint" nil)) - (sit-for 0.1))) - - ;; 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) - - (if (zerop (length (tramp-file-name-user vec))) - (tramp-message - vec 3 "Opening connection for %s using %s...done" host method) - (tramp-message - vec 3 - "Opening connection for %s@%s using %s...done" user host method))))) + "Timeout reached mounting %s@%s using %s" user host method))) + (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. (defun tramp-bluez-list-devices () - "Returns all discovered bluetooth devices as list. + "Return all discovered bluetooth devices as list. Every entry is a list (NAME ADDRESS). If `tramp-bluez-discover-devices-timeout' is an integer, and the last @@ -1132,7 +1348,7 @@ be used." (tramp-bluez-list-devices))) ;; Add completion function for OBEX method. -(when (dbus-ping :system tramp-bluez-service) +(when (member tramp-bluez-service (dbus-list-known-names :system)) (tramp-set-completion-function "obex" '((tramp-bluez-parse-device-names "")))) @@ -1165,7 +1381,7 @@ be used." (zeroconf-list-services "_webdav._tcp"))) ;; Add completion function for DAV and DAVS methods. -(when (dbus-ping :system zeroconf-service-avahi) +(when (member zeroconf-service-avahi (dbus-list-known-names :system)) (zeroconf-init tramp-gvfs-zeroconf-domain) (tramp-set-completion-function "sftp" '((tramp-zeroconf-parse-workstation-device-names ""))) @@ -1178,22 +1394,21 @@ be used." ;; D-Bus SYNCE functions. (defun tramp-synce-list-devices () - "Returns all discovered synce devices as list." + "Return all discovered synce devices as list. +They are retrieved from the hal daemon." (let (tramp-synce-devices) (dolist (device (with-tramp-dbus-call-method tramp-gvfs-dbus-event-vector t - :system "org.freedesktop.Hal" - "/org/freedesktop/Hal/Manager" - "org.freedesktop.Hal.Manager" "GetAllDevices")) + :system tramp-hal-service tramp-hal-path-manager + tramp-hal-interface-manager "GetAllDevices")) (when (with-tramp-dbus-call-method tramp-gvfs-dbus-event-vector t - :system "org.freedesktop.Hal" device - "org.freedesktop.Hal.Device" "PropertyExists" "sync.plugin") + :system tramp-hal-service device tramp-hal-interface-device + "PropertyExists" "sync.plugin") (add-to-list 'tramp-synce-devices (with-tramp-dbus-call-method tramp-gvfs-dbus-event-vector t - :system "org.freedesktop.Hal" device - "org.freedesktop.Hal.Device" - "GetPropertyString" "pda.pocketpc.name")))) + :system tramp-hal-service device tramp-hal-interface-device + "GetPropertyString" "pda.pocketpc.name")))) (tramp-message tramp-gvfs-dbus-event-vector 10 "%s" tramp-synce-devices) tramp-synce-devices)) @@ -1207,12 +1422,14 @@ be used." (tramp-set-completion-function "synce" '((tramp-synce-parse-device-names ""))) +(add-hook 'tramp-unload-hook + (lambda () + (unload-feature 'tramp-gvfs 'force))) + (provide 'tramp-gvfs) ;;; TODO: -;; * process-file and start-file-process on the local machine, but -;; with remote files. ;; * Host name completion via smb-server or smb-network. ;; * Check, how two shares of the same SMB server can be mounted in ;; parallel.