[RFC][alot] design decisions

Patrick Totzke patricktotzke at googlemail.com
Wed Mar 21 02:07:22 PDT 2012


Hi all,

with alot 0.3 released, I started thinking seriously about proper™ MIME-display and gnupg
integration for alot. I have to make a few design decisions here and could really use
some informed opinions from more experienced UI developers. My question in particular is: 

 How do I best integrate calls to external mime-handlers into my MVC event driven code?

I mostly know how to implement the individual parts but I struggle coming up with a
consistent way of organizing it all without compromising my current layout.


Background
----------

Model. Alot uses an abstraction layer over notmuch's thread, message and database objects
to make them less fragile and generally behave more OOP-y: You can e.g. directly call
message.add_tags(['foo','bar']) and so on. This is made possible by a "DatabaseManager"
that maintains centralized and queued access to the notmuch index and deals with notmuch
exceptions like a locked index [0].

View/Controler. We use an event-driven interface (urwid.TwistedEventLoop, [1]) that
handles user input and maintains a tree of widgets that knows how to render itself.
Twisted offers a neat concept of "Deferreds" to organize call- and errbacks that we
already make use of [2].  Alot has a "MessageWidget" that displays a single message.

We can easily access and parse email messages into MIME-trees (msg.get_email() returns a
email.message object; pythons email module allows extensive and RFC compliant dealing with
this [3]).

We already offer full compatibility with the mailcap protocol to constuct shell commands
of external MIME handlers.

We know how to call external commands asynchronously; `alot.helper.call_cmd_async` does this
and returns a Deferred to which we can connect callback functions.


Objective
---------
We want to turn a email.message into nicely rendered text and display it as a widget.
Calls to renderers should be done asynchronously, so that displaying large threads
does not block the interface unnecessarily.
The text representation should be stored in a way that can be incrementally updated
for use with message parts decrypted at a later point.


Bad Solutions
-------------

1. Current solution: walk the MIME-tree depth-first, use blocking calls to handlers
and create a fixed "body" string that is displayed in the widget.
A silly and hackish solution that we want to get rid of for obvious reasons.

2. Use a "PartWidget" that is able to display a node in the MIME-tree:
Its constructor creates the representation in a RFC compliant way, that is,
it decodes 'text/plain' parts, stacks other widgets of the same kind on top of each other
for multipart parts or calls external renderer to get a text representation.

Cons:
 * widgets should not be clever and contain/construct any kind of additional info
 * its hard to update these widgets when a part gets decrypted; 
 * decrypted parts are not available outside the displaying widget, widgets get
   replaced completely when one rebuilds the buffer (refresh cmd)
Pros:
 * we can use Deferreds relatively painlessly in the ui (as opposed to the db-wrapper)

3. Accumulate rendered text in a tree structure *inside* message-objects and let that
massage update the widget that displays it. One could let the widget register
some update-callback with the widget that rebuilds the widgets content.
These callbacks are called after the external handler has finished and the message-
internal text-representation has been updated.

Pros: 
 * rendered text/decrypted message parts end up in the message and are available to process
   and redisplay at will
Cons:
 * the calls to MIME-handlers are dealt with as Deferreds which depend on the twisted event
   loop and indirectly on urwid. This is exactly the kind of tight coupling I'm trying to avoid:
   Messages should be independent of the interface.
 * Messages with multiple externally rendered parts will rebuild their widgets more than once
 * references to old widgets (replaced e.g. after a buffer rebuild) will be kept, which keeps
   them from being garbage-collected

I'm surely missing an easy and elegant solution here.
Any pointers or comments are much appreciated.
Thanks,
/p


[0]: http://alot.readthedocs.org/en/latest/api/database.html
[1]: http://excess.org/urwid/
[2]: http://twistedmatrix.com/documents/current/core/howto/defer.html
[3]: http://docs.python.org/library/email


More information about the notmuch mailing list