Skip to content

corecode/eimap

Repository files navigation

;;; -*- mode: emacs-lisp; -*-

;;; WHAT IS THIS?

; eimap is an experiment to approach IMAP processing from a new angle.
;
; the protocol parser and generator are both formally generated from
; grammars.
;
; instead of using a request-reply approach (which does not work well
; with IMAP), eimap uses a streaming data model:
;
; data from the server is automatically parsed into usable lisp data
; structures and directly handed to a handler for the incoming data
; (not shown in the sample code below).
;
; if the application requires other data that has not been streamed in
; yet, it can request the server to send this data, using the
; `eimap-request' method (see example below).

;;; USING THE APPLICATION

; (add-to-list 'load-path default-directory)
;
; (require 'eimap)
;
; (eimap
;  "0x2c.org" :user "2"
;  ;; default is IMAP+STARTTLS.
;  ;; pass :port "imaps" for SSL connection to port 993
;  )
;


;;; USING THE BACKEND LIBRARY

(add-to-list 'load-path default-directory)
(require 'eimap)

;; this table will dispatch all the incoming messages
;;
(eimap-declare-dispatch-table eimap-README)

;; upcalls are passed the `upcall-data' supplied to `eimap-open' and a
;; plist from the parsed message.  `eimap-parse.el' contains the
;; formal grammar and self-documents the fields of each message.
;;
;; the upcall is called with the eimap connection buffer active
;;
(eimap-define-method eimap-README connection-state (upcall-data state-data)
  ;; connection-state is issued when the state changes.  Possible
  ;; `:state's are `connecting', `connected', `authenticating',
  ;; `authenticated', and `closed'.
  (when (eq 'authenticated (plist-get state-data :state))
    ;; `eimap-request' has to be called with the eimap connection
    ;; buffer active.  use `eimap-request*' to pass the connection
    ;; object explicitly.
    ;;
    ;; `eimap-request' takes a request element; see
    ;; `eimap-generate.el' for the self-documenting formal grammar.
    ;;
    ;; it also takes a `:cbdata' callback data argument that will be
    ;; passed to the `:barrier' and `:done' callbacks.  There is no
    ;; data callback, because replies generally can not be associated
    ;; with the originating requests.  The presence of a `:barrier'
    ;; also drains the request stream before the request is issued.
    (eimap-request
     '(:method SELECT :mailbox "INBOX")
     :cbdata upcall-data
     :done (lambda (respdata cbdata)
             ;; now that the SELECT is done, we can
             ;; sequence the FETCH.  Maybe there should
             ;; be a way to add a tail barrier?
             (eimap-request '(:method FETCH
                                      ;; `:to' can be a
                                      ;; number or `*'.
                                      :ids (:from 1 :to *)
                                      :attr (FLAGS UID)))))))


;; Depending on the server, there will be more or less unsolicited
;; data.  EXISTS is a mandatory response to SELECT.
(eimap-define-method eimap-README EXISTS (upcall-data imap-data)
  (message "This mailbox has %d messages" (plist-get imap-data :exists)))

;; The server may also send status responses.  All status responses
;; (`resp-text-code') are converted to their own upcalls.
(eimap-define-method eimap-README UIDVALIDITY (upcall-data imap-data)
  ;; in reality, we'd have to empty caches if the UIDVALIDITY changed.
  (message "This mailbox UID validity is %d" (plist-get imap-data :uidvalidity)))

;; this upcall will be issued for every FETCH response
;;
;; let's assume we're building something like offlineIMAP, just to
;; illustrate how async requests would work with overlapping requests.
;;
;; for a real application, you'd collect uids first and issue several
;; per request to reduce the protocol overhead.
(eimap-define-method eimap-README FETCH (upcall-data imap-data)
  ;; usually, you'd update local state with this information and then
  ;; issue new requests based on local state.
  (when (plist-get imap-data :flags)
    (message "flags for uid %d (msgid %d) are %s"
             (plist-get imap-data :uid)
             (plist-get imap-data :msgid)
             (pp-to-string (plist-get imap-data :flags))))
  ;; now, say we decide that this message is new and its body needs
  ;; to be fetched.  for ease of demonstration, we'll just fetch the
  ;; bodystructure instead.
  (if (plist-get imap-data :bodystructure)
      ;; jolly good, we already got the data.
      (message "message uid %d has structure %s"
               (plist-get imap-data :uid)
               (pp-to-string (plist-get imap-data :bodystructure)))
    ;; no bodystructure present, let's fetch it.
    ;; this just simulates us picking some messages
    (when (eq 1 (% (plist-get imap-data :msgid) 30))
      (eimap-request `(:method FETCH
                               :uid t
                               :ids ,(plist-get imap-data :uid)
                               :attr (UID BODYSTRUCTURE))))))

(eimap-open
 "0x2c.org" :user "2"
 ;; `:upcall' is where it's at.  All incoming data (and connection
 ;; state change) are communicated via `:upcall'.  If you want to use
 ;; the automatic dispatch, just use `eimap-create-dispatch' to do the
 ;; work for you.
 ;;
 ;; Otherwise, pass a function that takes (upcall-data method data) as
 ;; arguments.  `upcall-data' is what you pass in `:upcall-data';
 ;; `method' is the IMAP response `:method' (see `eimap-parse.el'),
 ;; and `data' is the corresponding response data.  For example,
 ;; `:upcall' might be called with:
 ;;
 ;; ("o hi" 'FETCH (:msgid 3 :flags ("\\seen" "\\answered") :uid 667))
 :upcall (eimap-create-dispatch eimap-README)
 :upcall-data "o hi")


;; check out (switch-to-buffer-other-window "*Messages*")
;; that's where all the parsed data arrived

About

imap for emacs - wip

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published