[Xfce4-commits] <postler:master> Render all messages in conversation style

Christian Dywan noreply at xfce.org
Sun Jun 26 03:20:01 CEST 2011


Updating branch refs/heads/master
         to 9829b764ae34a8185c7651b52a3627c2eec1c071 (commit)
       from 1482b7ecb0f8f99354579b35207ab50196994efa (commit)

commit 9829b764ae34a8185c7651b52a3627c2eec1c071
Author: Christian Dywan <christian at twotoasts.de>
Date:   Sat Jun 25 22:59:43 2011 +0200

    Render all messages in conversation style
    
    Display messages in terms of message objects, not files.
    Merge parse_message and display_part into display.
    Display sender and date above each reply.
    Render attachments below each reply.
    Accept message/ thread id's on the command line.

 postler/postler-content.vala  |  366 ++++++++++++++++-------------------------
 postler/postler-message.vala  |   49 +++++-
 postler/postler-messages.vala |    2 +-
 postler/postler-reader.vala   |   20 ++-
 postler/postler-viewer.vala   |   21 +--
 5 files changed, 206 insertions(+), 252 deletions(-)

diff --git a/postler/postler-content.vala b/postler/postler-content.vala
index 55eb739..81c03c6 100644
--- a/postler/postler-content.vala
+++ b/postler/postler-content.vala
@@ -17,9 +17,6 @@ struct Postler.EmoticonMapping {
 public class Postler.Content : WebKit.WebView {
     AccountInfo? selected_account;
     public Message message { get; private set; }
-    string charset;
-    string carbon_copy;
-    string blind_copy;
     string reply;
 
     string? save_folder = null;
@@ -35,8 +32,6 @@ public class Postler.Content : WebKit.WebView {
     public unowned List<MessagePart> message_parts { public get; set; }
     public MessagePart? current_part { get; private set; }
     public int current_part_index { get; private set; }
-    public MessagePart? html_part { get; private set; }
-    public MessagePart? text_part { get; private set; }
 
     Gtk.Settings gtk_settings;
     GLib.Settings glib_settings;
@@ -69,6 +64,9 @@ public class Postler.Content : WebKit.WebView {
             background-color: ButtonFace; color: ButtonText;
             padding: 1em; margin: 1em;
         }
+        .sender {
+            margin-bottom: 0.5em;
+        }
         .project_reply .signature {
             display: none;
         }
@@ -420,7 +418,14 @@ public class Postler.Content : WebKit.WebView {
             body.append_c ('\n');
             var contents = File.new_for_path (location);
             try {
-                parse_message(location);
+                last_location = location;
+                message = new Message.from_file (contents);
+                message_id = message.id;
+                subject = message.subject;
+                message.parse_body ();
+                message_parts = message.parts.copy ();
+                notify_property ("message-parts");
+
                 bool last_line_empty = true;
                 StringBuilder b_temp = new StringBuilder ();
                 if (excerpt != null)
@@ -529,9 +534,9 @@ public class Postler.Content : WebKit.WebView {
         return g_content_type_is_a (mime_type, "text/plain");
     }
 
-    public void display_source (string location) {
-        last_location = location;
-        subject = _("Source Code: %s").printf (location);
+    public async void display_source (Message message) {
+        last_location = message.get_path ();
+        subject = _("Source Code: %s").printf (last_location);
 
         string line;
         string content_type = null;
@@ -539,7 +544,7 @@ public class Postler.Content : WebKit.WebView {
         string charset = null;
         string[] parts;
 
-        var contents = File.new_for_path (location);
+        var contents = File.new_for_path (last_location);
         GLib.StringBuilder body = new GLib.StringBuilder ();
 
         try {
@@ -593,111 +598,134 @@ public class Postler.Content : WebKit.WebView {
         return selected_account.address.split (",")[0];
     }
 
-    void parse_message (string location) throws GLib.Error {
-        last_location = location;
-        message = new Message.from_file (File.new_for_path (location));
-        message_id = message.id;
-        subject = message.subject;
-        charset = message.get_charset ();
-        string arguments = "?subject=Re: " + subject;
-        string? chosen_from = choose_from ();
-        if (chosen_from != null)
-            arguments += "?from=" + html_escape (chosen_from);
-        sender = linkify_address (message.sender ?? _("Unknown"), arguments);
-        carbon_copy = message.get_field ("cc");
-        if (carbon_copy != null)
-            carbon_copy = linkify_address (carbon_copy, arguments);
-        blind_copy = message.get_field ("bcc");
-        if (blind_copy != null)
-            blind_copy = linkify_address (blind_copy, arguments);
-        reply = message.reply_to;
-        if (reply != null)
-            reply = linkify_address (reply, arguments);
-        list_post = message.get_field ("list-post");
-        if (list_post != null)
-            list_post = Postler.Contact.address_from_string (list_post);
-        list_unsubscribe = message.get_field ("list-unsubscribe");
-        if (list_unsubscribe != null) {
-            list_unsubscribe = Postler.Contact.address_from_string (list_unsubscribe);
-            list_unsubscribe = linkify_address (list_unsubscribe, null);
-        }
-
-        message.parse_body ();
-        message_parts = message.parts.copy ();
-        notify_property ("message-parts");
-    }
-
-    public async bool display (string location, AccountInfo? account_info=null) {
+    public async bool display (Message the_message, AccountInfo? account_info=null) {
         selected_account = account_info;
         try {
-            parse_message (location);
-            var client = new Postler.Client ();
-            GLib.List<Message> thread = yield client.get_thread (message);
-            if (thread.nth_data (0) != null) {
-                text_part = null;
-                html_part = new MessagePart ("text/html");
+            GLib.List<Message> thread;
+            if (the_message.stream != null) {
+                thread = new GLib.List<Message> ();
+                thread.append (the_message);
+            }
+            else {
+                var client = new Postler.Client ();
+                thread = yield client.get_thread (the_message);
+            }
+            var body_chunk = new StringBuilder ();
+            foreach (var child in thread) {
+                MessagePart? html_part = null;
+                MessagePart? text_part = null;
+
+                try {
+                    if (child.uri == "error:")
+                        throw new GLib.FileError.FAILED (child.subject);
+                    child.parse_body ();
+                    child.parse_text ();
+                    html_part = child.html_part;
+                    text_part = child.text_part;
+                }
+                catch (GLib.Error parse_error) {
+                    html_part = null;
+                    text_part = new MessagePart ("text/plain");
+                    text_part.body.append (parse_error.message);
+                }
+
                 string reply_class = "reply";
-                if (message.project != null)
+                if (child.project != null)
                     reply_class = "project_reply";
-                foreach (var child in thread) {
-                    message = new Message.from_file (File.new_for_path (child.get_path ()));
-                    message.parse_body ();
-                    foreach (var thread_part in message.parts) {
-                        if (mime_type_is_text (thread_part.mime_type)) {
-                            html_part.body.append ("<div class=\"%s\">%s</div>"
-                                .printf (reply_class,
-                                         render_plain_text (thread_part.body.str)));
-                            break;
-                        }
-                    }
+                body_chunk.append_printf ("""
+                    <div class="%s"><div class="sender">%s<div style="float: right;">%s</div></div>
+                    """,
+                    reply_class, linkify_address (child.sender, null), format_date (child.date));
+                if (html_part != null)
+                    body_chunk.append (html_part.body.str);
+                else if (text_part != null) {
+                    if (child.project != null)
+                         reply_class += " plain_text";
+                    body_chunk.append (render_plain_text (text_part.body.str));
                 }
-                message_parts = new List<MessagePart> ();
-                message_parts.append (html_part);
-                notify_property ("message-parts");
-                display_part (html_part);
-                return false;
+                else
+                    body_chunk.append (text_part.body.str);
+
+                foreach (var attachment_part in child.parts) {
+                    if (attachment_part != html_part && attachment_part != text_part
+                     && !attachment_part.mime_type.has_prefix ("multipart/"))
+                        body_chunk.append_printf ("""<p>
+                            <b>%s</b>
+                            <a href="message-part:open:%d">%s</a>
+                            <a href="message-part:save:%d">%s</a></p>
+                            """,
+                            attachment_part.filename ?? attachment_part.mime_type,
+                            child.parts.position (child.parts.find (attachment_part)),
+                            _("Open file"),
+                            child.parts.position (child.parts.find (attachment_part)),
+                            _("Save As..."));
+                }
+
+                body_chunk.append ("</div>");
             }
 
-            /* Look for an HTML part, or otherwise plain text */
-            html_part = null;
-            text_part = null;
-            foreach (var part in message_parts) {
-                /* Ignore empty parts inserted by faulty clients */
-                if (part.body.str.strip () == "")
-                    continue;
-                /* Select part, merge "inline" parts, commonly signatures */
-                if (part.mime_type == "text/html") {
-                    if (html_part == null)
-                        html_part = part;
-                    else if (part.content_disposition == "inline") {
-                        html_part.body.append (part.body.str);
-                        message_parts.remove (part);
-                    }
-                }
-                if (part.mime_type == "text/plain") {
-                    if (html_part != null && part.content_disposition == "inline") {
-                        html_part.body.append (part.body.str);
-                        message_parts.remove (part);
-                    }
-                    if (text_part == null)
-                        text_part = part;
-                    else if (part.content_disposition == "inline") {
-                        text_part.body.append (part.body.str);
-                        message_parts.remove (part);
-                    }
-                }
+            message = thread.nth_data (0);
+            message_parts = message.parts.copy ();
+            last_location = message.get_path ();
+            subject = message.subject;
+            string arguments = "?subject=Re: " + subject;
+            string? chosen_from = choose_from ();
+            if (chosen_from != null)
+                arguments += "?from=" + html_escape (chosen_from);
+            list_post = message.get_field ("list-post");
+            if (list_post != null)
+                list_post = Postler.Contact.address_from_string (list_post);
+            list_unsubscribe = message.get_field ("list-unsubscribe");
+            if (list_unsubscribe != null) {
+                list_unsubscribe = Postler.Contact.address_from_string (list_unsubscribe);
+                list_unsubscribe = linkify_address (list_unsubscribe, null);
             }
             notify_property ("message-parts");
-            if (html_part != null)
-                display_part (html_part);
-            else if (text_part != null)
-                display_part (text_part);
-            else {
-                text_part = message_parts.nth_data (0);
-                display_part (text_part);
+
+            /* Emoticons */
+            /* foreach (var emoticon in emoticons) { */
+            for (int i = 0; i < emoticons.length; i++) {
+                var emoticon = emoticons[i];
+                try {
+                    var escaped = GLib.Regex.escape_string (" " + emoticon.token);
+                    var regex = new GLib.Regex (escaped);
+                    body_chunk.str = regex.replace_eval (body_chunk.str, -1, 0, 0,
+                        (match_info, result) => { evaluate_emoticon (match_info, result, emoticon); });
+                }
+                catch (GLib.RegexError regex_error) { }
             }
+
+            load_string (
+                """
+                <span class="headers">
+                <b>%s</b><br>
+                %s
+                <span style="float: right;"><a href="#more" id="more">%s</a></span>
+                <span id="extra_headers">
+                %s
+                %s
+                %s
+                %s
+                </span>
+                </span>
+                <style text="text/css">%s</style><p class="body">%s</p>
+                """
+                .printf (
+                         message.subject,
+                         /* FIXME: Merge all recipients here? */
+                         linkify_address (message.recipients, arguments),
+                         reply != null || message.organization != null
+                                       || message.application != null
+                                       || list_unsubscribe != null
+                             ? "%s »".printf (_("More")) : "",
+                         format_header (_("Reply To:"), reply),
+                         format_header (_("Organization:"), message.organization),
+                         format_header (_("Application:"), message.application),
+                         format_header (_("Unsubscribe:"), list_unsubscribe),
+                         themed_style_sheet (), body_chunk.str),
+                "text/html", "UTF-8", "about:blank");
         } catch (GLib.Error error) {
-            display_error  (error.message);
+            display_error (error.message);
         }
         return false;
     }
@@ -754,6 +782,17 @@ public class Postler.Content : WebKit.WebView {
                 line = line.substring (1);
             new_body.append (line + "<br>");
         }
+
+        /* Linkify */
+        try {
+            foreach (var link_format in link_formats) {
+                var regex = new GLib.Regex (link_format);
+                new_body.str = regex.replace (new_body.str, -1, 0,
+                    "<a href=\"\\1\">\\1</a>");
+            }
+        }
+        catch (GLib.RegexError regex_error) { }
+
         return new_body.str;
     }
 
@@ -777,115 +816,6 @@ public class Postler.Content : WebKit.WebView {
             "Link", color_to_rgb (*((Gdk.Color*)link_color)));
     }
 
-    public void display_part (MessagePart message_part) {
-        if (current_part != message_part) {
-            current_part = message_part;
-            current_part_index = message_parts.index (message_part);
-        }
-
-        string body_chunk;
-        string? html_or_text = "";
-        if (message_part == html_part) {
-            body_chunk = message_part.body.str;
-            if (text_part != null)
-                html_or_text = "<a href=\"#text\">" + _("View as plain text") + "</a>";
-        }
-        else if (message_part == text_part) {
-            body_chunk = render_plain_text (message_part.body.str);
-            if (html_part != null)
-                html_or_text = "<a href=\"#html\">" + _("View as HTML") + "</a>";
-            if (message.project != null)
-                body_chunk = "<p><span class=\"plain_text\">%s</span>".
-                    printf (body_chunk);
-        }
-        else {
-            body_chunk = """
-                <b>%s</b>
-                <a href="message-part:open:%d">%s</a>
-                <a href="message-part:save:%d">%s</a>
-                """.
-                printf (
-                message_part.filename ?? message_part.mime_type,
-                message_parts.position (message_parts.find (message_part)),
-                _("Open file"),
-                message_parts.position (message_parts.find (message_part)),
-                _("Save As..."));
-            if (mime_type_is_text (message_part.mime_type))
-                body_chunk += "<p><span class=\"plain_text\">%s</span>".
-                    printf (message_part.body.str);
-            else if (message_part.mime_type.has_prefix ("image/"))
-                body_chunk += "<p><img src=\"data:image/jpg;base64,%s\">".
-                    printf (message_part.body.str);
-        }
-
-        try {
-            /* Linkify */
-            if (message_part.plain_text) {
-                foreach (var link_format in link_formats) {
-                    var regex = new GLib.Regex (link_format);
-                    body_chunk = regex.replace (body_chunk, -1, 0,
-                        "<a href=\"\\1\">\\1</a>");
-                }
-            }
-
-            /* Emoticons */
-            /* foreach (var emoticon in emoticons) { */
-            for (int i = 0; i < emoticons.length; i++) {
-                var emoticon = emoticons[i];
-                try {
-                    var escaped = GLib.Regex.escape_string (" " + emoticon.token);
-                    var regex = new GLib.Regex (escaped);
-                    body_chunk = regex.replace_eval (body_chunk, -1, 0, 0,
-                        (match_info, result) => { evaluate_emoticon (match_info, result, emoticon); });
-                }
-                catch (GLib.RegexError error) { }
-            }
-
-            load_string ("""
-                <style text="text/css">%s</style>
-                <span class="headers">
-                <div style="float: right;">%s</div>
-                <b>%s</b> %s<br>
-                %s
-                %s
-                %s <span style="float: right;"><a href="#more" id="more">%s</a></span>
-                <b>%s</b> %s <br>
-                <span id="extra_headers">
-                %s
-                %s
-                %s
-                %s
-                %s
-                </span>
-                </span>
-                <p class="body">%s</p>
-                """.
-                printf (themed_style_sheet (),
-                        format_date (message.date),
-                        _("From:"), sender,
-                        format_header (_("To:"), message.get_field ("to")),
-                        format_header (_("Copy:"), carbon_copy),
-                        format_header (_("Blind Copy:"), blind_copy),
-                        reply != null || message.organization != null
-                                      || message.application != null
-                                      || list_unsubscribe != null
-                                      || html_or_text != ""
-                            ? "%s »".printf (_("More")) : "",
-                        _("Subject:"), subject,
-                        /* TODO: Sender:? */
-                        format_header (_("Reply To:"), reply),
-                        format_header (_("Organization:"), message.organization),
-                        format_header (_("Application:"), message.application),
-                        format_header (_("Unsubscribe:"), list_unsubscribe),
-                        html_or_text,
-                        body_chunk),
-                "text/html", "UTF-8", "about:blank");
-        } catch (GLib.Error contents_error) {
-            display_error (_("Failed to display message part \"%s\": %s").printf (
-                message_part.mime_type, contents_error.message));
-        }
-    }
-
     void display_error (string message) {
         load_string ("""
             <style text="text/css">%s</style>
@@ -919,16 +849,6 @@ public class Postler.Content : WebKit.WebView {
             settings.set ("enable-scripts", false);
             return true;
         }
-        else if (uri.has_suffix ("#html")) {
-            decision.ignore ();
-            display_part (html_part);
-            return true;
-        }
-        else if (uri.has_suffix ("#text")) {
-            decision.ignore ();
-            display_part (text_part);
-            return true;
-        }
 
         if (uri != null && uri.has_prefix ("about:"))
             return false;
diff --git a/postler/postler-message.vala b/postler/postler-message.vala
index 9c6ae4e..83a3324 100644
--- a/postler/postler-message.vala
+++ b/postler/postler-message.vala
@@ -107,7 +107,9 @@ namespace Postler {
         GLib.HashTable<string,string> fields = new GLib.HashTable<string,string> (str_hash, str_equal);
         public string get_field (string field) { return fields.lookup (field); }
         public GLib.List<MessagePart>? parts = null;
-        GLib.DataInputStream? stream = null;
+        public GLib.DataInputStream? stream = null;
+        public MessagePart? html_part = null;
+        public MessagePart? text_part = null;
 
         bool init (GLib.Cancellable? cancellable = null) throws GLib.Error {
             return false;
@@ -214,6 +216,11 @@ namespace Postler {
 
         public Message.from_file (GLib.File file,
             GLib.Cancellable? cancellable = null) throws GLib.Error {
+            read_from_file (file, cancellable);
+        }
+
+        void read_from_file (GLib.File file,
+            GLib.Cancellable? cancellable = null) throws GLib.Error {
 
             uri = file.get_uri ();
             read_flags (file.get_path ());
@@ -401,8 +408,11 @@ namespace Postler {
             return unquoted;
         }
 
-        public void parse_body () throws GLib.Error
-            requires (stream != null) {
+        public void parse_body (GLib.Cancellable? cancellable=null) throws GLib.Error {
+            if (stream == null) {
+                return_if_fail (uri != null);
+                read_from_file (GLib.File.new_for_uri (uri), cancellable);
+            }
 
             parts = new GLib.List<MessagePart> ();
             string? content_encoding = get_field ("content-transfer-encoding");
@@ -532,6 +542,39 @@ namespace Postler {
                 }
             }
         }
+
+        public void parse_text (GLib.Cancellable? cancellable=null) throws GLib.Error {
+            if (parts == null)
+                parse_body (cancellable);
+
+            var child_parts = new List<MessagePart> ();
+            foreach (var thread_part in parts) {
+                /* Ignore empty parts inserted by faulty clients */
+                if (thread_part.body.str.strip () == "")
+                    continue;
+                /* Select part, merge "inline" parts, commonly signatures */
+                if (thread_part.mime_type == "text/html") {
+                    if (html_part == null)
+                        html_part = thread_part;
+                    else if (thread_part.content_disposition == "inline") {
+                        html_part.body.append (thread_part.body.str);
+                        child_parts.remove (thread_part);
+                    }
+                }
+                else if (thread_part.mime_type == "text/plain") {
+                    if (html_part != null && thread_part.content_disposition == "inline") {
+                        html_part.body.append (thread_part.body.str);
+                        child_parts.remove (thread_part);
+                    }
+                    if (text_part == null)
+                        text_part = thread_part;
+                    else if (thread_part.content_disposition == "inline") {
+                        text_part.body.append (thread_part.body.str);
+                        child_parts.remove (thread_part);
+                    }
+                }
+            }
+        }
     }
 }
 
diff --git a/postler/postler-messages.vala b/postler/postler-messages.vala
index b25890f..0a4e702 100644
--- a/postler/postler-messages.vala
+++ b/postler/postler-messages.vala
@@ -595,7 +595,7 @@ public class Postler.Messages : Gtk.TreeView {
 
     void display_message (Gtk.TreeIter iter) {
         toggle_message_flag (iter, MessageFlags.MARK_READ, (message, result) => {
-            content.display ((message as Message).get_path (), account_info);
+            content.display (message as Message, account_info);
         });
     }
 
diff --git a/postler/postler-reader.vala b/postler/postler-reader.vala
index 8ac4a38..d9736a9 100644
--- a/postler/postler-reader.vala
+++ b/postler/postler-reader.vala
@@ -145,17 +145,25 @@ public class Postler.Reader {
                 shelf.add (new Postler.Viewer (content));
 
                 if (filenames != null && filenames[0] != null) {
-                    string filename;
+                    Message message;
                     try {
-                       filename = Filename.from_uri (filenames[0], null);
+                        string filename = filenames[0];
+                        if (filename.has_prefix ("file://"))
+                            message = new Message.from_file (GLib.File.new_for_uri (filename));
+                        else if (FileUtils.test (filename, FileTest.EXISTS))
+                            message = new Message.from_file (GLib.File.new_for_path (filename));
+                        else
+                            message = new Message.from_id (filename);
                     } catch (GLib.Error error) {
-                        filename = filenames[0];
+                        GLib.error (error.message);
                     }
+                    content.notify["subject"].connect ((object, pspec) => {
+                        window.set_title (content.subject);
+                    });
                     if (module == "source")
-                        content.display_source (filename);
+                        content.display_source (message);
                     else
-                        content.display (filename);
-                    window.set_title (content.subject);
+                        content.display (message);
                 }
 
                 shelf.show_all ();
diff --git a/postler/postler-viewer.vala b/postler/postler-viewer.vala
index 1decf0d..65fe1c7 100644
--- a/postler/postler-viewer.vala
+++ b/postler/postler-viewer.vala
@@ -84,29 +84,12 @@ public class Postler.Viewer : Gtk.VBox {
     void infobar_response (Gtk.InfoBar infobar, int response) {
         infobar.hide ();
         allow_external_images = true;
-        content.display_part (content.current_part);
+        content.display (content.message);
     }
 
     void notify_message_parts (GLib.Object object, GLib.ParamSpec? pspec) {
         infobar.hide ();
         allow_external_images = false;
-
-        if (content.message_parts.length () < 2) {
-            attachments.hide ();
-            return;
-        }
-
-        (attachments.model as Gtk.ListStore).clear ();
-        foreach (var part in content.message_parts) {
-            if (part.mime_type.has_prefix ("multipart/"))
-                continue;
-            if (part == content.html_part || part == content.text_part)
-                continue;
-
-            attachments.add_part (part);
-            if (part == content.current_part)
-                attachments.select (content.current_part);
-        }
     }
 
     void notify_current_part () {
@@ -116,7 +99,7 @@ public class Postler.Viewer : Gtk.VBox {
     void part_selected () {
         MessagePart? part = attachments.get_selected_part ();
         if (part != null)
-            content.display_part (part);
+            content.display (content.message);
     }
 
     bool key_pressed (Gdk.EventKey event) {



More information about the Xfce4-commits mailing list