[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Emacs-diffs] emacs/lisp/net tramp-imap.el
From: |
Michael Albinus |
Subject: |
[Emacs-diffs] emacs/lisp/net tramp-imap.el |
Date: |
Mon, 28 Sep 2009 11:59:22 +0000 |
CVSROOT: /sources/emacs
Module name: emacs
Changes by: Michael Albinus <albinus> 09/09/28 11:59:22
Added files:
lisp/net : tramp-imap.el
Log message:
* net/tramp-imap.el: New package.
CVSWeb URLs:
http://cvs.savannah.gnu.org/viewcvs/emacs/lisp/net/tramp-imap.el?cvsroot=emacs&rev=1.1
Patches:
Index: tramp-imap.el
===================================================================
RCS file: tramp-imap.el
diff -N tramp-imap.el
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ tramp-imap.el 28 Sep 2009 11:59:22 -0000 1.1
@@ -0,0 +1,801 @@
+;;; tramp-imap.el --- Tramp interface to IMAP through imap.el
+
+;; Copyright (C) 2009 Free Software Foundation, Inc.
+
+;; Author: Teodor Zlatanov <address@hidden>
+;; Keywords: mail, comm
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Package to provide Tramp over IMAP
+
+;;; Setup:
+
+;; just load and open files, e.g.
+;; /imaps:address@hidden:/INBOX.test/1
+;; or
+;; /imap:address@hidden:/INBOX.test/1
+
+;; where `imap' goes over IMAP, while `imaps' goes over IMAP+SSL
+
+;; This module will use imap-hash.el to access the IMAP mailbox.
+
+;; This module will use auth-source.el to authenticate against the
+;; IMAP server, PLUS it will use auth-source.el to get your passphrase
+;; for the symmetrically encrypted messages. For the former, use the
+;; usual IMAP ports. For the latter, use the port "tramp-imap".
+
+;; example .authinfo / .netrc file:
+
+;; machine yourhosthere.com port tramp-imap login USER password
SYMMETRIC-PASSPHRASE
+
+;; note above is the symmetric encryption passphrase for GPG
+;; below is the regular password for IMAP itself and other things on that host
+
+;; machine yourhosthere.com login USER password NORMAL-PASSWORD
+
+
+;;; Code:
+
+(require 'assoc)
+(require 'tramp)
+(require 'tramp-compat)
+(require 'message)
+(require 'imap-hash)
+(require 'epa)
+(autoload 'auth-source-user-or-password "auth-source")
+
+;; Define Tramp IMAP method ...
+(defconst tramp-imap-method "imap"
+ "*Method to connect via IMAP protocol.")
+
+(add-to-list 'tramp-methods (list tramp-imap-method '(tramp-default-port 143)))
+
+;; Add a default for `tramp-default-user-alist'. Default is the local user.
+(add-to-list 'tramp-default-user-alist
+ `(,tramp-imap-method nil ,(user-login-name)))
+
+;; Define Tramp IMAPS method ...
+(defconst tramp-imaps-method "imaps"
+ "*Method to connect via secure IMAP protocol.")
+
+;; ... and add it to the method list.
+(add-to-list 'tramp-methods (list tramp-imaps-method '(tramp-default-port
993)))
+
+;; Add a default for `tramp-default-user-alist'. Default is the local user.
+(add-to-list 'tramp-default-user-alist
+ `(,tramp-imaps-method nil ,(user-login-name)))
+
+;; Add completion function for IMAP method.
+;; (tramp-set-completion-function
+;; tramp-imap-method tramp-completion-function-alist-ssh) ; TODO: test this
+;; tramp-imaps-method tramp-completion-function-alist-ssh) ; TODO: test this
+
+;; New handlers should be added here.
+(defconst tramp-imap-file-name-handler-alist
+ '(
+ ;; `access-file' performed by default handler
+ (add-name-to-file . ignore)
+ ;; `byte-compiler-base-file-name' performed by default handler
+ (copy-file . tramp-imap-handle-copy-file)
+ (delete-directory . ignore) ;; tramp-imap-handle-delete-directory)
+ (delete-file . tramp-imap-handle-delete-file)
+ ;; `diff-latest-backup-file' performed by default handler
+ (directory-file-name . tramp-handle-directory-file-name)
+ (directory-files . tramp-handle-directory-files)
+ (directory-files-and-attributes
+ . tramp-imap-handle-directory-files-and-attributes)
+ ;; `dired-call-process' performed by default handler
+ ;; `dired-compress-file' performed by default handler
+ ;; `dired-uncache' performed by default handler
+ (expand-file-name . tramp-imap-handle-expand-file-name)
+ ;; `file-accessible-directory-p' performed by default handler
+ (file-attributes . tramp-imap-handle-file-attributes)
+ (file-directory-p . tramp-imap-handle-file-directory-p)
+ (file-executable-p . tramp-imap-handle-file-executable-p)
+ (file-exists-p . tramp-imap-handle-file-exists-p)
+ (file-local-copy . tramp-imap-handle-file-local-copy)
+ (file-remote-p . tramp-handle-file-remote-p)
+ (file-modes . tramp-handle-file-modes)
+ (file-name-all-completions . tramp-imap-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-newer-than-file-p . tramp-imap-handle-file-newer-than-file-p)
+ (file-ownership-preserved-p . ignore)
+ (file-readable-p . tramp-imap-handle-file-readable-p)
+ (file-regular-p . tramp-handle-file-regular-p)
+ (file-symlink-p . tramp-handle-file-symlink-p)
+ ;; `file-truename' performed by default handler
+ (file-writable-p . tramp-imap-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
+ (insert-directory . tramp-imap-handle-insert-directory)
+ (insert-file-contents . tramp-imap-handle-insert-file-contents)
+ (load . tramp-handle-load)
+ (make-directory . ignore) ;; tramp-imap-handle-make-directory)
+ (make-directory-internal . ignore) ;;
tramp-imap-handle-make-directory-internal)
+ (make-symbolic-link . ignore)
+ (rename-file . tramp-imap-handle-rename-file)
+ (set-file-modes . ignore)
+ (set-file-times . ignore) ;; tramp-imap-handle-set-file-times)
+ (set-visited-file-modtime . ignore)
+ (shell-command . ignore)
+ (substitute-in-file-name . tramp-handle-substitute-in-file-name)
+ (unhandled-file-name-directory .
tramp-handle-unhandled-file-name-directory)
+ (vc-registered . ignore)
+ (verify-visited-file-modtime . ignore)
+ (write-region . tramp-imap-handle-write-region)
+ (executable-find . ignore)
+ (start-file-process . ignore)
+ (process-file . ignore)
+)
+ "Alist of handler functions for Tramp IMAP method.
+Operations not mentioned here will be handled by the default Emacs
primitives.")
+
+(defgroup tramp-imap nil
+ "Tramp over IMAP configuration."
+ :version "23.2"
+ :group 'applications)
+
+(defcustom tramp-imap-subject-marker "tramp-imap-subject-marker"
+ "The subject marker that Tramp-IMAP will use."
+ :type 'string
+ :version "23.2"
+ :group 'tramp-imap)
+
+;; TODO: these will be defcustoms later.
+(defvar tramp-imap-passphrase-cache nil) ;; can be t or 'never
+(defvar tramp-imap-passphrase nil)
+
+(defun tramp-imap-file-name-p (filename)
+ "Check if it's a filename for IMAP protocol."
+ (let ((v (tramp-dissect-file-name filename)))
+ (or
+ (string= (tramp-file-name-method v) tramp-imap-method)
+ (string= (tramp-file-name-method v) tramp-imaps-method))))
+
+(defun tramp-imap-file-name-handler (operation &rest args)
+ "Invoke the IMAP related OPERATION.
+First arg specifies the OPERATION, second arg is a list of arguments to
+pass to the OPERATION."
+ (let ((fn (assoc operation tramp-imap-file-name-handler-alist)))
+ (if fn
+ (save-match-data (apply (cdr fn) args))
+ (tramp-run-real-handler operation args))))
+
+(add-to-list 'tramp-foreign-file-name-handler-alist
+ (cons 'tramp-imap-file-name-p 'tramp-imap-file-name-handler))
+
+(defun tramp-imap-handle-copy-file
+ (filename newname &optional ok-if-already-exists keep-date preserve-uid-gid)
+ "Like `copy-file' for Tramp files."
+ (tramp-imap-do-copy-or-rename-file
+ 'copy filename newname ok-if-already-exists keep-date preserve-uid-gid))
+
+(defun tramp-imap-handle-rename-file
+ (filename newname &optional ok-if-already-exists)
+ "Like `rename-file' for Tramp files."
+ (tramp-imap-do-copy-or-rename-file
+ 'rename filename newname ok-if-already-exists t t))
+
+(defun tramp-imap-do-copy-or-rename-file
+ (op filename newname &optional ok-if-already-exists keep-date
preserve-uid-gid)
+ "Copy or rename a remote file.
+OP must be `copy' or `rename' and indicates the operation to perform.
+FILENAME specifies the file to copy or rename, NEWNAME is the name of
+the new file (for copy) or the new name of the file (for rename).
+OK-IF-ALREADY-EXISTS means don't barf if NEWNAME exists already.
+KEEP-DATE means to make sure that NEWNAME has the same timestamp
+as FILENAME. PRESERVE-UID-GID, when non-nil, instructs to keep
+the uid and gid if both files are on the same host.
+
+This function is invoked by `tramp-imap-handle-copy-file' and
+`tramp-imap-handle-rename-file'. It is an error if OP is neither
+of `copy' and `rename'."
+ (unless (memq op '(copy rename))
+ (error "Unknown operation `%s', must be `copy' or `rename'" op))
+ (setq filename (expand-file-name filename))
+ (setq newname (expand-file-name newname))
+ (when (file-directory-p newname)
+ (setq newname (expand-file-name (file-name-nondirectory filename)
newname)))
+
+ (let ((t1 (and (tramp-tramp-file-p filename)
+ (tramp-imap-file-name-p filename)))
+ (t2 (and (tramp-tramp-file-p newname)
+ (tramp-imap-file-name-p newname))))
+
+ (when (and (not ok-if-already-exists) (file-exists-p newname))
+ (with-parsed-tramp-file-name (if t1 filename newname) nil
+ (tramp-error
+ v 'file-already-exists "File %s already exists" newname)))
+
+ (with-parsed-tramp-file-name (if t1 filename newname) nil
+ (tramp-message v 0 "Transferring %s to %s..." filename newname))
+
+ ;; We just make a local copy of FILENAME, and write it then to
+ ;; NEWNAME. This must be optimized, when both files are located
+ ;; on the same IMAP server.
+ (with-temp-buffer
+ (if (and t1 t2)
+ ;; We don't encrypt.
+ (with-parsed-tramp-file-name newname nil
+ (insert (tramp-imap-get-file filename nil))
+ (tramp-imap-put-file
+ v (current-buffer)
+ (tramp-imap-file-name-name v)
+ (tramp-imap-get-file-inode newname)
+ nil))
+ ;; One of them is not located on a IMAP mailbox.
+ (insert-file-contents filename)
+ (write-region (point-min) (point-max) newname)))
+
+ (with-parsed-tramp-file-name (if t1 filename newname) nil
+ (tramp-message v 0 "Transferring %s to %s...done" filename newname))
+
+ (when (eq op 'rename)
+ (delete-file filename))))
+
+;; TODO: revise this much
+(defun tramp-imap-handle-expand-file-name (name &optional dir)
+ "Like `expand-file-name' for Tramp files."
+ ;; If DIR is not given, use DEFAULT-DIRECTORY or "/".
+ (setq dir (or dir default-directory "/"))
+ ;; Unless NAME is absolute, concat DIR and NAME.
+ (unless (file-name-absolute-p name)
+ (setq name (concat (file-name-as-directory dir) name)))
+ ;; If NAME is not a Tramp file, run the real handler.
+ (if (or (tramp-completion-mode-p) (not (tramp-tramp-file-p name)))
+ (tramp-drop-volume-letter
+ (tramp-run-real-handler 'expand-file-name (list name nil)))
+ ;; Dissect NAME.
+ (with-parsed-tramp-file-name name nil
+ (unless (tramp-run-real-handler 'file-name-absolute-p (list localname))
+ (setq localname (concat "/" localname)))
+ ;; There might be a double slash, for example when "~/"
+ ;; expands to "/". Remove this.
+ (while (string-match "//" localname)
+ (setq localname (replace-match "/" t t localname)))
+ ;; Do normal `expand-file-name' (this does "/./" and "/../").
+ ;; We bind `directory-sep-char' here for XEmacs on Windows,
+ ;; which would otherwise use backslash. `default-directory' is
+ ;; bound, because on Windows there would be problems with UNC
+ ;; shares or Cygwin mounts.
+ (let ((default-directory (tramp-compat-temporary-file-directory)))
+ (tramp-make-tramp-file-name
+ method user host
+ (tramp-drop-volume-letter
+ (tramp-run-real-handler
+ 'expand-file-name (list localname))))))))
+
+;; This function should return "foo/" for directories and "bar" for
+;; files.
+(defun tramp-imap-handle-file-name-all-completions (filename directory)
+ "Like `file-name-all-completions' for Tramp files."
+ (all-completions
+ filename
+ (with-parsed-tramp-file-name (expand-file-name directory) nil
+ (save-match-data
+ (let ((entries
+ (tramp-imap-get-file-entries v localname)))
+ (mapcar
+ (lambda (x)
+ (list
+ (if (string-match "d" (nth 9 x))
+ (file-name-as-directory (nth 0 x))
+ (nth 0 x))))
+ entries))))))
+
+(defun tramp-imap-get-file-entries (vec localname &optional exact)
+ "Read entries returned by IMAP server. EXACT limits to exact matches.
+Result is a list of (LOCALNAME LINK COUNT UID GID ATIME MTIME CTIME
+SIZE MODE WEIRD INODE DEVICE)."
+ (tramp-message vec 5 "working on %s" localname)
+ (let* ((name (tramp-imap-file-name-name vec))
+ (search-name (or name ""))
+ (search-name (if exact (concat search-name "$") search-name))
+ (iht (tramp-imap-make-iht vec search-name)))
+;; TODO: catch errors
+ ;; (tramp-error vec 'none "bad name %s or mailbox %s" name mbox))
+ (imap-hash-map (lambda (uid headers body)
+ (let ((subject (substring
+ (aget headers 'Subject "")
+ (length tramp-imap-subject-marker))))
+ (list
+ subject
+ nil
+ -1
+ 1
+ 1
+ '(0 0)
+ '(0 0)
+ '(0 0)
+ 1
+ "-rw-rw-rw-"
+ nil
+ uid
+ (tramp-get-device vec))))
+ iht t)))
+
+(defun tramp-imap-handle-write-region (start end filename &optional append
visit lockname confirm)
+ "Like `write-region' for Tramp files."
+ (setq filename (expand-file-name filename))
+ (with-parsed-tramp-file-name filename nil
+ ;; XEmacs takes a coding system as the seventh argument, not `confirm'.
+ (when (and (not (featurep 'xemacs))
+ confirm (file-exists-p filename))
+ (unless (y-or-n-p (format "File %s exists; overwrite anyway? "
+ filename))
+ (tramp-error v 'file-error "File not overwritten")))
+ (tramp-flush-file-property v localname)
+ (let* ((old-buffer (current-buffer))
+ (inode (tramp-imap-get-file-inode filename))
+ (min 1)
+ (max (point-max))
+ ;; Make sure we have good start and end values.
+ (start (or start min))
+ (end (or end max))
+ temp-buffer)
+ (with-temp-buffer
+ (setq temp-buffer (if (and (eq start min) (eq end max))
+ old-buffer
+ ;; If this is a region write, insert the substring.
+ (insert
+ (with-current-buffer old-buffer
+ (buffer-substring-no-properties start end)))
+ (current-buffer)))
+ (tramp-imap-put-file v
+ temp-buffer
+ (tramp-imap-file-name-name v)
+ inode
+ t)))
+ (when (eq visit t)
+ (set-visited-file-modtime))))
+
+(defun tramp-imap-handle-insert-directory
+ (filename switches &optional wildcard full-directory-p)
+ "Like `insert-directory' for Tramp files."
+ (setq filename (expand-file-name filename))
+ (when full-directory-p
+ ;; Called from `dired-add-entry'.
+ (setq filename (file-name-as-directory filename)))
+ (with-parsed-tramp-file-name filename nil
+ (save-match-data
+ (let ((base (file-name-nondirectory localname))
+ (entries (copy-sequence
+ (tramp-imap-get-file-entries
+ v (file-name-directory localname)))))
+
+ (when wildcard
+ (when (string-match "\\." base)
+ (setq base (replace-match "\\\\." nil nil base)))
+ (when (string-match "\\*" base)
+ (setq base (replace-match ".*" nil nil base)))
+ (when (string-match "\\?" base)
+ (setq base (replace-match ".?" nil nil base))))
+
+ ;; Filter entries.
+ (setq entries
+ (delq
+ nil
+ (if (or wildcard (zerop (length base)))
+ ;; Check for matching entries.
+ (mapcar
+ (lambda (x)
+ (when (string-match
+ (format "^%s" base) (nth 0 x))
+ x))
+ entries)
+ ;; We just need the only and only entry FILENAME.
+ (list (assoc base entries)))))
+
+ ;; Sort entries.
+ (setq entries
+ (sort
+ entries
+ (lambda (x y)
+ (if (string-match "t" switches)
+ ;; Sort by date.
+ (tramp-time-less-p (nth 6 y) (nth 6 x))
+ ;; Sort by name.
+ (string-lessp (nth 0 x) (nth 0 y))))))
+
+ ;; Handle "-F" switch.
+ (when (string-match "F" switches)
+ (mapc
+ (lambda (x)
+ (when (not (zerop (length (car x))))
+ (cond
+ ((char-equal ?d (string-to-char (nth 9 x)))
+ (setcar x (concat (car x) "/")))
+ ((char-equal ?x (string-to-char (nth 9 x)))
+ (setcar x (concat (car x) "*"))))))
+ entries))
+
+ ;; Print entries.
+ (mapcar
+ (lambda (x)
+ (when (not (zerop (length (nth 0 x))))
+ (insert
+ (format
+ "%10s %3d %-8s %-8s %8s %s "
+ (nth 9 x) ; mode
+ (nth 11 x) ; inode
+ "nobody" "nogroup"
+ (nth 8 x) ; size
+ (format-time-string
+ (if (tramp-time-less-p
+ (tramp-time-subtract (current-time) (nth 6 x))
+ tramp-half-a-year)
+ "%b %e %R"
+ "%b %e %Y")
+ (nth 6 x)))) ; date
+ ;; For the file name, we set the `dired-filename'
+ ;; property. This allows to handle file names with
+ ;; leading or trailing spaces as well.
+ (let ((pos (point)))
+ (insert (format "%s" (nth 0 x))) ; file name
+ (put-text-property pos (point) 'dired-filename t))
+ (insert "\n")
+ (forward-line)
+ (beginning-of-line)))
+ entries)))))
+
+(defun tramp-imap-handle-insert-file-contents
+ (filename &optional visit beg end replace)
+ "Like `insert-file-contents' for Tramp files."
+ (barf-if-buffer-read-only)
+ (when visit
+ (setq buffer-file-name (expand-file-name filename))
+ (set-visited-file-modtime)
+ (set-buffer-modified-p nil))
+ (with-parsed-tramp-file-name filename nil
+ (if (not (file-exists-p filename))
+ (tramp-error
+ v 'file-error "File `%s' not found on remote host" filename)
+ (let ((point (point))
+ size data)
+ (tramp-message v 4 "Fetching file %s..." filename)
+ (insert (tramp-imap-get-file filename t))
+ (setq size (- (point) point))
+;;; TODO: handle ranges.
+;;; (let ((beg (or beg (point-min)))
+;;; (end (min (or end (point-max)) (point-max))))
+;;; (setq size (- end beg))
+;;; (buffer-substring beg end))
+ (goto-char point)
+ (tramp-message v 4 "Fetching file %s...done" filename)
+ (list (expand-file-name filename) size)))))
+
+(defun tramp-imap-handle-file-exists-p (filename)
+ "Like `file-exists-p' for Tramp files."
+ (and (file-attributes filename) t))
+
+(defun tramp-imap-handle-file-directory-p (filename)
+ "Like `file-directory-p' for Tramp-IMAP files."
+ ;; We allow only mailboxes to be a directory.
+ (with-parsed-tramp-file-name (expand-file-name filename default-directory)
nil
+ (and (string-match "^/[^/]*$" (directory-file-name localname)) t)))
+
+(defun tramp-imap-handle-file-attributes (filename &optional id-format)
+ "Like `file-attributes' for Tramp-IMAP FILENAME."
+ (with-parsed-tramp-file-name (expand-file-name filename) nil
+ (cdr-safe (nth 0 (tramp-imap-get-file-entries v localname)))))
+
+(defun tramp-imap-get-file-inode (filename &optional id-format)
+ "Get inode equivalent \(actually the UID) for Tramp-IMAP FILENAME."
+ (nth 10 (tramp-compat-file-attributes filename id-format)))
+
+(defun tramp-imap-handle-file-executable-p (filename)
+ "Like `file-executable-p' for Tramp files. False for IMAP."
+ nil)
+
+(defun tramp-imap-handle-file-readable-p (filename)
+ "Like `file-readable-p' for Tramp files. True for IMAP."
+ (file-exists-p filename))
+
+(defun tramp-imap-handle-file-writable-p (filename)
+ "Like `file-writable-p' for Tramp files. True for IMAP."
+ ;; `file-exists-p' does not work yet for directories.
+ ;; (file-exists-p (file-name-directory filename)))
+ (file-directory-p (file-name-directory filename)))
+
+(defun tramp-imap-handle-delete-file (filename)
+ "Like `delete-file' for Tramp files."
+ (cond
+ ((not (file-exists-p filename)) nil)
+ (t (with-parsed-tramp-file-name (expand-file-name filename) nil
+ (let ((iht (tramp-imap-make-iht v)))
+ (imap-hash-rem (tramp-imap-get-file-inode filename) iht))))))
+
+(defun tramp-imap-handle-directory-files-and-attributes
+ (directory &optional full match nosort id-format)
+ "Like `directory-files-and-attributes' for Tramp files."
+ (mapcar
+ (lambda (x)
+ (cons x (tramp-compat-file-attributes
+ (if full x (expand-file-name x directory)) id-format)))
+ (directory-files directory full match nosort)))
+
+;; TODO: fix this in tramp-imap-get-file-entries.
+(defun tramp-imap-handle-file-newer-than-file-p (file1 file2)
+ "Like `file-newer-than-file-p' for Tramp files."
+ (cond
+ ((not (file-exists-p file1)) nil)
+ ((not (file-exists-p file2)) t)
+ (t (tramp-time-less-p (nth 5 (file-attributes file2))
+ (nth 5 (file-attributes file1))))))
+
+(defun tramp-imap-handle-file-local-copy (filename)
+ "Like `file-local-copy' for Tramp files."
+ (with-parsed-tramp-file-name (expand-file-name filename) nil
+ (unless (file-exists-p filename)
+ (tramp-error
+ v 'file-error
+ "Cannot make local copy of non-existing file `%s'" filename))
+ (let ((tmpfile (tramp-compat-make-temp-file filename)))
+ (tramp-message v 4 "Fetching %s to tmp file %s..." filename tmpfile)
+ (with-temp-buffer
+ (insert-file-contents filename)
+ (write-region (point-min) (point-max) tmpfile)
+ (tramp-message v 4 "Fetching %s to tmp file %s...done" filename tmpfile)
+ tmpfile))))
+
+(defun tramp-imap-put-file (vec filename-or-buffer &optional subject inode
encode)
+ "Write contents of FILENAME-OR-BUFFER to Tramp-IMAP file VEC with name
SUBJECT.
+When INODE is given, delete that old remote file after writing the new one
+\(normally this is the old file with the same name)."
+ ;; `tramp-current-host' is used in `tramp-imap-passphrase-callback-function'.
+ (let ((tramp-current-host (tramp-file-name-real-host vec))
+ (iht (tramp-imap-make-iht vec)))
+ (imap-hash-put (list
+ (list (cons
+ 'Subject
+ (format
+ "%s%s"
+ tramp-imap-subject-marker
+ (or subject "no subject"))))
+ (cond ((bufferp filename-or-buffer)
+ (with-current-buffer filename-or-buffer
+ (if encode
+ (tramp-imap-encode-buffer)
+ (buffer-string))))
+ ;; TODO: allow file names.
+ (t "No body available")))
+ iht
+ inode)))
+
+(defun tramp-imap-get-file (filename &optional decode)
+ ;; (debug (tramp-imap-get-file-inode filename))
+ (with-parsed-tramp-file-name (expand-file-name filename) nil
+ (condition-case ()
+ ;; `tramp-current-host' is used in
+ ;; `tramp-imap-passphrase-callback-function'.
+ (let* ((tramp-current-host (tramp-file-name-real-host v))
+ (iht (tramp-imap-make-iht v))
+ (inode (tramp-imap-get-file-inode filename))
+ (data (imap-hash-get inode iht t)))
+ (if decode
+ (with-temp-buffer
+ (insert (nth 1 data))
+ ;;(debug inode (buffer-string))
+ (tramp-imap-decode-buffer))
+ (nth 1 data)))
+ (error (tramp-error
+ v 'file-error "File `%s' could not be read" filename)))))
+
+(defun tramp-imap-passphrase-callback-function (context key-id handback)
+ "Called by EPG to get a passphrase for Tramp-IMAP.
+CONTEXT is the encryption/decryption EPG context.
+HANDBACK is just carried through.
+KEY-ID can be 'SYM or 'PIN among others."
+ (let* ((server tramp-current-host)
+ (port "tramp-imap") ; this is NOT the server password!
+ (auth-passwd
+ (auth-source-user-or-password "password" server port)))
+ (or
+ (copy-sequence auth-passwd)
+ ;; If we cache the passphrase and we have one.
+ (if (and (eq tramp-imap-passphrase-cache t)
+ tramp-imap-passphrase)
+ ;; Do we reuse it?
+ (if (y-or-n-p "Reuse the passphrase? ")
+ (copy-sequence tramp-imap-passphrase)
+ ;; Don't reuse: revert caching behavior to nil, erase passphrase,
+ ;; call ourselves again.
+ (setq tramp-imap-passphrase-cache nil)
+ (setq tramp-imap-passphrase nil)
+ (tramp-imap-passphrase-callback-function context key-id handback))
+ (let ((p (if (eq key-id 'SYM)
+ (read-passwd
+ "Tramp-IMAP passphrase for symmetric encryption: "
+ (eq (epg-context-operation context) 'encrypt)
+ tramp-imap-passphrase)
+ (read-passwd
+ (if (eq key-id 'PIN)
+ "Tramp-IMAP passphrase for PIN: "
+ (let ((entry (assoc key-id epg-user-id-alist)))
+ (if entry
+ (format "Tramp-IMAP passphrase for %s %s: "
+ key-id (cdr entry))
+ (format "Tramp-IMAP passphrase for %s: " key-id))))
+ nil
+ tramp-imap-passphrase))))
+
+ ;; If we have an answer, the passphrase has changed,
+ ;; the user hasn't declined keeping the passphrase,
+ ;; and they answer yes to keep it now...
+ (when (and
+ p
+ (not (equal tramp-imap-passphrase p))
+ (not (eq tramp-imap-passphrase-cache 'never))
+ (y-or-n-p "Keep the passphrase? "))
+ (setq tramp-imap-passphrase (copy-sequence p))
+ (setq tramp-imap-passphrase-cache t))
+
+ ;; If we still don't have a passphrase, the user didn't want
+ ;; to keep it.
+ (when (and
+ p
+ (not tramp-imap-passphrase))
+ (setq tramp-imap-passphrase-cache 'never))
+
+ p)))))
+
+(defun tramp-imap-encode-buffer ()
+ (let ((context (epg-make-context 'OpenPGP))
+ cipher)
+ (epg-context-set-armor context t)
+ (epg-context-set-passphrase-callback context
+
#'tramp-imap-passphrase-callback-function)
+ (epg-context-set-progress-callback context
+ (cons #'epa-progress-callback-function
+ "Encrypting..."))
+ (message "Encrypting...")
+ (setq cipher (epg-encrypt-string
+ context
+ (encode-coding-string (buffer-string) 'utf-8)
+ nil))
+ (message "Encrypting...done")
+ cipher))
+
+(defun tramp-imap-decode-buffer ()
+ (let ((context (epg-make-context 'OpenPGP))
+ plain)
+ (epg-context-set-passphrase-callback context
+
#'tramp-imap-passphrase-callback-function)
+ (epg-context-set-progress-callback context
+ (cons #'epa-progress-callback-function
+ "Decrypting..."))
+ (message "Decrypting...")
+ (setq plain (decode-coding-string
+ (epg-decrypt-string context (buffer-string))
+ 'utf-8))
+ (message "Decrypting...done")
+ plain))
+
+(defun tramp-imap-file-name-mailbox (vec)
+ (nth 0 (tramp-imap-file-name-parse vec)))
+
+(defun tramp-imap-file-name-name (vec)
+ (nth 1 (tramp-imap-file-name-parse vec)))
+
+(defun tramp-imap-file-name-localname (vec)
+ (nth 1 (tramp-imap-file-name-parse vec)))
+
+(defun tramp-imap-file-name-parse (vec)
+ (let ((name (substring-no-properties (tramp-file-name-localname vec))))
+ (if (string-match "^/\\([^/]+\\)/?\\(.*\\)$" name)
+ (list (match-string 1 name)
+ (match-string 2 name))
+ nil)))
+
+(defun tramp-imap-make-iht (vec &optional needed-subject)
+ "Translate the Tramp vector VEC to the imap-hash structure.
+With NEEDED-SUBJECT, alters the imap-hash test accordingly."
+ (let* ((mbox (tramp-imap-file-name-mailbox vec))
+ (server (tramp-file-name-real-host vec))
+ (method (tramp-file-name-method vec))
+ (user (tramp-file-name-user vec))
+ (ssl (string-equal method tramp-imaps-method))
+ (port (or (tramp-file-name-port vec)
+ (tramp-get-method-parameter method 'tramp-default-port)))
+ (result (imap-hash-make server port mbox)))
+ ;; Return the IHT with a test override to look for the subject
+ ;; marker. Set also user and ssl tags.
+ (setq result (plist-put result :user user)
+ result (plist-put result :ssl ssl)
+ result (plist-put
+ result
+ :test (format "^%s%s"
+ tramp-imap-subject-marker
+ (if needed-subject needed-subject ""))))))
+
+;;; TODO:
+
+;; * Implement `tramp-imap-handle-delete-directory',
+;; `tramp-imap-handle-make-directory',
+;; `tramp-imap-handle-make-directory-internal',
+;; `tramp-imap-handle-set-file-times'.
+
+;; * Encode the subject. If the filename has trailing spaces (like
+;; "test "), those characters get lost, for example in dired listings.
+
+;; * When opening a dired buffer, like "/imap::INBOX.test", there are
+;; several error messages:
+;; "Buffer has a running process; kill it? (yes or no) "
+;; "error in process filter: Internal error, tag 6 status BAD code nil text
No mailbox selected."
+;; Afterwards, everything seems to be fine.
+
+;; * imaps works for local IMAP servers. Accessing
+;; "/imaps:imap.gmail.com:/INBOX.test/" results in error
+;; "error in process filter: Internal error, tag 5 status BAD code nil text
UNSELECT not allowed now.
+
+(provide 'tramp-imap)
+;;; tramp-imap.el ends here
+
+;; Ignore, for testing only.
+
+;;; (setq tramp-imap-subject-marker "T")
+;;; (tramp-imap-get-file-entries (tramp-dissect-file-name
"/imap:yourhosthere.com:/INBOX.test/4") t)
+;;; (tramp-imap-get-file-entries (tramp-dissect-file-name
"/imap:yourhosthere.com:/INBOX.test/") t)
+;;; (tramp-imap-get-file-entries (tramp-dissect-file-name
"/imap:yourhosthere.com:/test/4") t)
+;;; (tramp-imap-get-file-entries (tramp-dissect-file-name
"/imap:yourhosthere.com:/test/") t)
+;;; (tramp-imap-get-file-entries (tramp-dissect-file-name
"/imap:yourhosthere.com:/test/welcommen") t)
+;;; (tramp-imap-get-file-entries (tramp-dissect-file-name
"/imap:yourhosthere.com:/test/welcommen") t t)
+;;;(tramp-imap-get-file-inode "/imap:yourhosthere.com:/test/welcome")
+;;; (dired-copy-file "/etc/fstab" "/imap:yourhosthere.com:/test/welcome" t)
+;;; (write-region 1 100 "/imap:yourhosthere.com:/test/welcome")
+;;; (tramp-imap-get-file "/imap:yourhosthere.com:/test/welcome" t)
+;;(with-temp-buffer (insert "hello") (write-file
"/imap:yourhosthere.com:/test/welcome"))
+;;(with-temp-buffer (insert "hello") (write-file
"/imap:yourhosthere.com:/test/welcome2"))
+;;(file-writable-p "/imap:yourhosthere.com:/test/welcome2")
+;;(file-name-directory "/imap:yourhosthere.com:/test/welcome2")
+;;(with-temp-buffer (insert "hello") (delete-file "/tmp/hellotest")
(write-file "/tmp/hellotest") (write-file
"/imap:yourhosthere.com:/test/welcome2"))
+;;;(file-exists-p "/imap:yourhosthere.com:/INBOX.test/4")
+;;;(file-attributes "/imap:yourhosthere.com:/INBOX.test/4")
+;;;(setq vec (tramp-dissect-file-name "/imap:yourhosthere.com:/INBOX.test/4"))
+;;;(tramp-imap-handle-file-attributes "/imap:yourhosthere.com:/INBOX.test/4")
+;;; (tramp-imap-handle-insert-file-contents
"/imap:address@hidden:/INBOX.test/4" nil nil nil nil)
+;;;(insert-file-contents "/imap:yourhosthere.com:/INBOX.test/4")
+;;;(file-attributes "/imap:yourhosthere.com:/test/welcommen")
+;;;(insert-file-contents "/imap:yourhosthere.com:/test/welcome")
+;;;(file-exists-p "/imap:yourhosthere.com:/test/welcome2")
+;;;(tramp-imap-handle-file-attributes "/imap:yourhosthere.com:/test/welcome")
+;;;(tramp-imap-get-file-inode "/imap:yourhosthere.com:/test/welcommen")
+;;;(tramp-imap-get-file-inode "/imap:yourhosthere.com:/test/welcome")
+;;;(file-writable-p "/imap:yourhosthere.com:/test/welcome2")
+;;; (delete-file "/imap:yourhosthere.com:/test/welcome")
+;;; (tramp-imap-get-file "/imap:yourhosthere.com:/test/welcommen" t)
+;;; (tramp-imap-get-file "/imap:yourhosthere.com:/test/welcome" t)
+;;;(tramp-imap-file-name-mailbox (tramp-dissect-file-name
"/imap:yourhosthere.com:/INBOX.test"))
+;;;(tramp-imap-file-name-mailbox (tramp-dissect-file-name
"/imap:yourhosthere.com:/INBOX.test/new/old"))
+;;;(tramp-imap-file-name-mailbox (tramp-dissect-file-name
"/imap:yourhosthere.com:/INBOX.test/new"))
+;;;(tramp-imap-file-name-parse (tramp-dissect-file-name
"/imap:yourhosthere.com:/INBOX.test/new/two"))
+;;;(tramp-imap-file-name-parse (tramp-dissect-file-name
"/imap:yourhosthere.com:/INBOX.test/new/one"))
+;;;(tramp-imap-file-name-parse (tramp-dissect-file-name
"/imap:yourhosthere.com:/INBOX.test"))
+;;; (tramp-imap-file-name-parse (tramp-dissect-file-name
"/imap:yourhosthere.com:/test/4"))
+;;; (tramp-imap-file-name-parse (tramp-dissect-file-name
"/imap:yourhosthere.com:/test/"))
+;;; (tramp-imap-file-name-parse (tramp-dissect-file-name
"/imap:yourhosthere.com:/test/welcommen"))
+;;; (tramp-imap-file-name-parse (tramp-dissect-file-name
"/imap:yourhosthere.com:/test/welcommen"))
+;;; (tramp-imap-make-iht (tramp-dissect-file-name
"/imap:yourhosthere.com:/test/welcommen"))
+;;; (tramp-imap-make-iht (tramp-dissect-file-name
"/imap:yourhosthere.com:/INBOX.test/4"))
+;;; (tramp-imap-make-iht (tramp-dissect-file-name
"/imap:yourhosthere.com:/INBOX.test/4") "extra")
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [Emacs-diffs] emacs/lisp/net tramp-imap.el,
Michael Albinus <=