So far, we’ve
covered every action button on list windows except for
Delete and the All checkbox. The All checkbox simply toggles from
selecting all messages at once or deselecting all (View, Delete, Reply,
Fwd, and Save action buttons apply to all currently selected messages).
PyMailGUI also lets us delete messages from the server permanently, so
that we won’t see them the next time we access our inbox.
Delete operations are kicked off the same way as Views and Saves;
just press the Delete button instead. In typical operation, I eventually
delete email I’m not interested in, and save and delete emails that are
important. We met Save earlier in this demo.
Like View, Save, and other operations, Delete can be applied to
one or more messages. Deletes happen immediately, and like all server
transfers, they are run in a nonblocking thread but are performed only
if you verify the operation in a pop up, such as the one shown in
Figure 14-35
. During the delete, a
progress dialog like those in Figures
14-8
and
14-9
provide status.
Figure 14-35. PyMailGUI delete verification on quit
By design, no mail is ever removed automatically: you will see the
same messages the next time PyMailGUI runs. It deletes mail from your
server only when you ask it to, and then only if verified in the last
pop up shown (this is your last chance to prevent permanent mail
removal). After the deletions are performed, the mail index is updated,
and the GUI session continues.
Deletions disable mail loads and other deletes while running and
cannot be run in parallel with loads or other deletes already in
progress because they may change POP message numbers and thus modify the
mail index list (they may also modify the email cache). Messages may
still be composed during a deletion, however, and offline save files may
be
processed.
By now, we’ve seen
all the basic functionality of PyMailGUI—enough to get you
started sending and receiving simple but typical text-based messages. In
the rest of this demo, we’re going to turn our attention to some of the
deeper concepts in this system, including inbox synchronization, HTML
mails, Internationalization, and multiple account configuration. Since
the first of these is related to the preceding section’s tour of mail
deletions, let’s begin here.
Though they might seem simple from an end-user perspective, it
turns out that deletions are complicated by POP’s message-numbering
scheme. We learned about the potential for synchronization errors
between the server’s inbox and the fetched email list in
Chapter 13
, when studying themailtools
package
PyMailGUI uses (near
Example 13-24
). In brief, POP assigns
each message a relative sequential number, starting from one, and these
numbers are passed to the server to fetch and delete messages. The
server’s inbox is normally locked while a connection is held so that a
series of deletions can be run as an atomic operation; no other inbox
changes occur until the connection is closed.
However, message number changes also have some implications for
the GUI itself. It’s never an issue if new mail arrives while we’re
displaying the result of a prior
download—
the new mail is assigned higher
numbers, beyond what is displayed on the client. But if we delete a
message in the middle of a mailbox after the index has been loaded from
the mail server, the numbers of all messages after the one deleted
change (they are decremented by one). As a result, some message numbers
might no longer be valid if deletions are made while viewing previously
loaded email.
To work around this, PyMailGUI adjusts all the displayed numbers
after a Delete by simply removing the entries for deleted mails from its
index list and mail cache. However, this adjustment is not enough to
keep the GUI in sync with the server’s inbox if the inbox is modified at
a position other than after the end, by deletions in another email
client (even in another PyMailGUI session), or by deletions performed by
the mail server itself (e.g., messages determined to be undeliverable
and automatically removed from the inbox). Such modifications outside
PyMailGUI’s scope are uncommon, but not impossible.
To handle these cases, PyMailGUI uses the safe deletion and
synchronization tests inmailtools
.
That module uses mail header matching to detect mail list and server
inbox synchronization errors. For instance, if another email client has
deleted a message prior to the one to be deleted by PyMailGUI,mailtools
catches the problem and cancels the
deletion, and an error pop up like the one in
Figure 14-36
is
displayed.
Figure 14-36. Safe deletion test detection of inbox difference
Similarly, both index list loads and individual message fetches
run a synchronization test inmailtools
, as well.
Figure 14-37
captures the error
generated on a fetch if a message has been deleted in another client
since we last loaded the server index window. The same error is issued
when this occurs during a load operation, but the first line reads “Load
failed.”
Figure 14-37. Synchronization error after delete in another client
In both synchronization error cases, the mail list is
automatically reloaded with the new inbox content by PyMailGUI
immediately after the error pop up is dismissed. This scheme ensures
that PyMailGUI won’t delete or display the wrong message, in the rare
case that the server’s inbox is changed without its knowledge. Seemailtools
in
Chapter 13
for more on synchronization tests;
these errors are detected and raised inmailtools
, but triggered by calls made in the
mail cache manager
here.
Up to this point,
we’ve seen PyMailGUI’s basic operation in the context of
plain-text emails. We’ve also seen it handling HTML part attachments,
but not the main text of HTML messages. Today, of course, HTML is common
for mail content too. Because the PyEdit mail display deployed by
PyMailGUI uses a
tkinterText
widget
oriented toward plain text, HTML content is handled specially:
For text/HTML alternative mails, PyMailGUI displays the plain
text part in its view window and includes a button for opening the
HTML rendition in a web browser on demand.
For HTML-only mails, the main text area shows plain text
extracted from the HTML by a simple parser (not the raw HTML), and
the HTML is also displayed in a web browser automatically.
In all cases, the web browser’s display of International character
set content in the HTML depends upon encoding information in tags in the
HTML, guesses, or user feedback. Well-formed HTML parts already have
“” tags in their “
Figure 14-38
gives
the scene when a text/HTML alternative mail is viewed, and
Figure 14-39
shows what happens when an
HTML-only email is viewed. The web browser in
Figure 14-38
was opened by
clicking the HTML part’s button; this is no different than the HTML
attachment example we saw earlier.
For HTML-only messages, though, behavior is new here: the view
window on the left in
Figure 14-39
reflects the results of extracting plain text from the HTML shown in the
popped-up web browser behind it. The HTML parser used for this is
something of a first-cut prototype, but any result it can give is an
improvement on displaying raw HTML in the view window for HTML-only
mails. For simpler HTML mails of the sort sent by individuals instead of
those sent by mass-mailing companies (like those shown here), the
results are generally good in tests run to date, though time will tell
how this prototype parser fares in today’s unforgiving HTML jungle of
nonstandard and nonconforming code—improve as desired.
Figure 14-38. Viewing text/HTML alternative mails
Figure 14-39. Viewing HTML-only mails
One caveat here: PyMailGUI can today display HTML in a web browser
and extract plain text from it, but it cannot display HTML directly in
its own window and has no support for editing it specially. These are
enhancements that will have to await further attention by other
programmers who may find them useful.
Our next advanced
feature is something of an inevitable consequence of the
Internet’s success. As described earlier when summarizing version 3.0
changes, PyMailGUI fully supports International character sets in mail
content—both text part payloads and email headers are decoded for
display and encoded when sent, according to email, MIME, and Unicode
standards. This may be the most significant change in this version of
the program. Regrettably, capturing this in screenshots is a bit of a
challenge and you can get a better feel for how this pans out by running
an Open on the following included mail save file, viewing its messages
in formatted and raw modes, starting replies and forwards for them, and
so on:
C:\...\PP4E\Internet\Email\PyMailGui\SavedMail\i18n-4E
To sample the flavor of this support here,
Figure 14-40
shows the scene
when this file is opened, shown for variety here with one of the
alternate account configurations described the next section. This
figure’s index list window and mail view windows capture Russian and
Chinese language messages sent to my email account (these were
unsolicited email of no particular significance, but suffice as
reasonable test cases). Notice how both message headers and text payload
parts are decoded for display in both the mail list window and the mail
view windows.
Figure 14-40. Internationalization support, headers and body decoded for
display
Figure 14-41
shows
portions of the raw text of the two fetched messages, obtained by
double-clicking their list entries (you can open these mails from the
save file listed earlier if you have trouble seeing their details as
shown in this book). Notice how the body text is encoded per both MIME
and Unicode conventions—the headers at the top and text at the bottom of
these windows show the actual Base64 and quoted-printable strings that
must be decoded to achieve the nicely displayed output in
Figure 14-40
.
For the text parts, the information in the part’s header describes
its content’s encoding schemes. For instance,charset="gb2312"
in the content type header
identifies a Chinese Unicode character set, and the transfer encoding
header gives the part’s MIME encoding type (e.g.base64
).
The headers are encoded per i18n standards here as well—their
content self-describes their MIME and Unicode encodings. For example,
the header prefix=?koi8-r?B
means
Russian text, Base64 encoded. PyMailGUI is clever enough to decode both
full headers and the name fields of addresses for display, whether they
are completely encoded (as shown here) or contain just encoded
substrings (as shown by other saved mails in the
version30-4E
file in this example’s
SavedMail
directory).
Figure 14-41. Raw text of fetched Internationalized mails, headers and body
encoded
As additional context,
Figure 14-42
shows how these
messages’ main parts appear when opened via their part buttons. Their
content is saved as raw post-MIME bytes in binary mode, but the PyEdit
pop ups decode according to passed-in encoding names obtained from the
raw message headers. As we learned in Chapters
9
and
11
, the underlying tkinter toolkit
generally renders decodedstr
better
than rawbytes
.
Figure 14-42. Main text parts of Internationalized mails, decoded in PyEdit
pop-ups
So far, we’ve displayed Internationalized emails, but PyMailGUI
allows us to send them as well, and handles any encoding tasks implied
by the text content.
Figure 14-43
shows the result
of running replies and forwards to the Russian language email, with the
To address changed to protect the innocent. Headers in the view window
were decoded for display, encoded when sent, and decoded back again for
display; text parts in the mail body were similarly decoded, encoded,
and re-decoded along the way and headers are also decoded within the
“>” quoted original text inserted at the end of the message.
Figure 14-43. Result of reply and forward with International character sets,
re-decoded
And finally,
Figure 14-44
shows a portion of
the raw text of the Russian language reply message that appears in the
lower right of the formatted view of
Figure 14-43
. Again,
double-click to see these details live. Notice how both headers and body
text have been encoded per email and MIME standards.
As configured, the body text is always MIME encoded to UTF-8 when
sent if it fails to encode as ASCII, the default setting in themailconfig
module. Other defaults can be used if desired and will be
encoded appropriately for sends; in fact, text that won’t work in the
full text of email is MIME encoded the same way as binary parts such as
images.
This is also true for non-Internationalized character sets—the
text part of a message written in English with any non-ASCII quotes, for
example, will be UTF-8 and Base64 encoded in the same way as the message
in
Figure 14-44
, and
assume that the recipient’s email reader will decode (any reasonable
modern email client will). This allows non-ASCII text to be embedded in
the full email text sent.
Message headers are similarly encoded per UTF-8 if they are
non-ASCII when sent, so they will work in the full email text. In fact,
if you study this closely you’ll find that the Subject here was
originally encoded per a Russian Unicode scheme but is UTF-8
now—
its new representation yields the same
characters (code points) when decoded for
display
.
Figure 14-44. Raw text of sent Russian reply, headers and body
re-encoded
In short, although the GUI itself is still in English (its labels
and the like), the content of emails displayed and sent support
arbitrary character sets. Decoding for display is done per content where
possible, using message headers for text payloads and content for
headers. Encoding for sends is performed according to user settings and
policies, using user settings or inputs, or a UTF-8 default. Required
MIME and email header encodings are implemented in a largely automatic
fashion by the underlyingemail
package.
Not shown here are the pop-up dialogs that may be issued to prompt
for text part encoding preferences on sends if so configured inmailconfig
, and PyEdit’s similar prompts under
certain user configurations. Some of these user configurations are meant
for illustration and generality; the presets seem to work well for most
scenarios I’ve run into, but your International mileage may vary. For
more details, experiment with the file’s messages on your own and see
the system’s source
code.