Change delete-by-moving-to-trash so Lisp calls explicitly request trashing.
[bpt/emacs.git] / lisp / net / tramp-gvfs.el
index 3737628..25dae77 100644 (file)
@@ -1,6 +1,6 @@
 ;;; 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 <michael.albinus@gmx.de>
 ;; Keywords: comm, processes
 ;;; 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.
 
 ;; 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
 (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")
 (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
   "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")
+(unless (dbus-ping :session tramp-gvfs-service-daemon 100)
   (throw 'tramp-loading nil))
 
 (defconst tramp-gvfs-path-mounttracker "/org/gtk/vfs/mounttracker"
 ;;   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
 
 ;; <interface name='org.gtk.vfs.MountOperation'>
 ;;   <method name='askPassword'>
-;;     <arg name='message' type='s'  direction='in'/>
+;;     <arg name='message'        type='s' direction='in'/>
 ;;     <arg name='default_user'   type='s' direction='in'/>
 ;;     <arg name='default_domain' type='s' direction='in'/>
 ;;     <arg name='flags'          type='u' direction='in'/>
@@ -333,6 +337,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
@@ -361,11 +366,11 @@ Every entry is a list (NAME ADDRESS).")
   '(
     (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
@@ -373,41 +378,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-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)
@@ -491,9 +501,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)
 
@@ -501,24 +512,30 @@ 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)
+  (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)))
+
+(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 tramp)
   "Like `delete-file' for Tramp files."
-  (delete-file (tramp-gvfs-fuse-file-name filename)))
+  (tramp-compat-delete-file (tramp-gvfs-fuse-file-name filename) tramp))
 
 (defun tramp-gvfs-handle-directory-files
   (directory &optional full match nosort)
@@ -610,6 +627,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)))
@@ -649,6 +671,12 @@ is no information where to trace the message.")
             (tramp-gvfs-url-file-name dir)))
         (signal (car err) (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."
@@ -666,11 +694,27 @@ is no information where to trace the message.")
   (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
@@ -698,7 +742,11 @@ is no information where to trace the message.")
                   "gvfs-save" tmpfile (tramp-get-buffer v) nil
                   (tramp-gvfs-url-file-name filename)))
               (signal (car err) (cdr err)))
-          (delete-file tmpfile)))))
+          (tramp-compat-delete-file tmpfile)))))
+
+    ;; 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))
@@ -826,10 +874,12 @@ 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))
+             ;; Preserve message for `progress-reporter'.
+             (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 an empty
            ;; fuse-mountpoint in order to leave the timeout.
@@ -841,15 +891,15 @@ ADDRESS can have the form \"xx:xx:xx:xx:xx:xx\" or \"[xx:xx:xx:xx:xx:xx]\"."
             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)))
+          (mount-spec (cadar (last 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
@@ -878,7 +928,7 @@ ADDRESS can have the form \"xx:xx:xx:xx:xx:xx\" or \"[xx:xx:xx:xx:xx:xx]\"."
          (tramp-set-file-property
           v "/" "fuse-mountpoint"
           (file-name-nondirectory
-           (dbus-byte-array-to-string (nth 8 mount-info)))))))))
+           (dbus-byte-array-to-string (car (last mount-info 2))))))))))
 
 (dbus-register-signal
  :session nil tramp-gvfs-path-mounttracker
@@ -900,7 +950,7 @@ ADDRESS can have the form \"xx:xx:xx:xx:xx:xx\" or \"[xx:xx:xx:xx:xx:xx]\"."
             :session tramp-gvfs-service-daemon tramp-gvfs-path-mounttracker
             tramp-gvfs-interface-mounttracker "listMounts"))
         nil)
-      (let* ((mount-spec (nth 1 (nth 9 elt)))
+      (let* ((mount-spec (cadar (last elt)))
             (method (dbus-byte-array-to-string
                      (cadr (assoc "type" mount-spec))))
             (user (dbus-byte-array-to-string
@@ -930,7 +980,8 @@ ADDRESS can have the form \"xx:xx:xx:xx:xx:xx\" or \"[xx:xx:xx:xx:xx:xx]\"."
               (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))))
+          (file-name-nondirectory
+           (dbus-byte-array-to-string (car (last elt 2)))))
          (throw 'mounted t))))))
 
 (defun tramp-gvfs-mount-spec (vec)
@@ -1018,72 +1069,64 @@ 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"
+         `(: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)))
+                (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)))
+
+       ;; 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)))))
 
 \f
 ;; 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
@@ -1151,7 +1194,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 ""))))
 
@@ -1184,7 +1227,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 "")))
@@ -1197,19 +1240,16 @@ 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 tramp-hal-service tramp-hal-path-manager
               tramp-hal-interface-manager "GetAllDevices"))
-      (when (and (with-tramp-dbus-call-method tramp-gvfs-dbus-event-vector t
-                  :system tramp-hal-service device tramp-hal-interface-device
-                  "PropertyExists" "sync.plugin")
-                (with-tramp-dbus-call-method tramp-gvfs-dbus-event-vector t
-                  :system tramp-hal-service device tramp-hal-interface-device
-                  "PropertyExists" "pda.pocketpc.name"))
+      (when (with-tramp-dbus-call-method tramp-gvfs-dbus-event-vector t
+             :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
@@ -1232,8 +1272,6 @@ They are retrieved from the hal daemon."
 
 ;;; 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.