[geary/wip/728002-webkit2: 25/43] Use CID resources to display images for multipart/mixed messages.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/728002-webkit2: 25/43] Use CID resources to display images for multipart/mixed messages.
- Date: Fri, 25 Nov 2016 11:19:20 +0000 (UTC)
commit 038128bd42491aa86b0558cfddb45cd0417183e6
Author: Michael James Gratton <mike vee net>
Date: Tue Oct 11 00:58:52 2016 +1100
Use CID resources to display images for multipart/mixed messages.
* src/client/conversation-viewer/conversation-message.vala
(ConversationMessage): Remove ReplacedImage and related code.
(ConversationMessage::inline_image_replacer): Don't bother loading,
scaling, rotating and serialising the images, just add them as CID
resources.
* src/client/components/client-web-view.vala (ClientWebView): Modify the
cid_resources map contain memory buffers, not files, and update call
sites.
src/client/components/client-web-view.vala | 22 ++--
src/client/composer/composer-widget.vala | 5 +-
.../conversation-viewer/conversation-message.vala | 148 ++------------------
ui/conversation-web-view.css | 2 +-
4 files changed, 29 insertions(+), 148 deletions(-)
---
diff --git a/src/client/components/client-web-view.vala b/src/client/components/client-web-view.vala
index e0dfae0..e31c73f 100644
--- a/src/client/components/client-web-view.vala
+++ b/src/client/components/client-web-view.vala
@@ -8,6 +8,8 @@
public class ClientWebView : WebKit.WebView {
+ public const string CID_PREFIX = "cid:";
+
private const double ZOOM_DEFAULT = 1.0;
private const double ZOOM_FACTOR = 0.1;
@@ -52,7 +54,8 @@ public class ClientWebView : WebKit.WebView {
set { if (zoom_level != (float)value) zoom_level = (float)value; }
}
- private Gee.Map<string,File> cid_resources = new Gee.HashMap<string,File>();
+ private Gee.Map<string,Geary.Memory.Buffer> cid_resources =
+ new Gee.HashMap<string,Geary.Memory.Buffer>();
/** Emitted when a user clicks a link in this web view. */
@@ -90,8 +93,8 @@ public class ClientWebView : WebKit.WebView {
/**
* Adds a resource that may be accessed via a cid:id url.
*/
- public void add_cid_resource(string id, File file) {
- this.cid_resources[id] = file;
+ public void add_cid_resource(string id, Geary.Memory.Buffer buf) {
+ this.cid_resources[id] = buf;
}
/**
@@ -131,16 +134,11 @@ public class ClientWebView : WebKit.WebView {
}
internal void handle_cid_request(WebKit.URISchemeRequest request) {
- const string CID_PREFIX = "cid:";
-
string cid = request.get_uri().substring(CID_PREFIX.length);
- File? file = this.cid_resources[cid];
- if (file != null) {
- try {
- request.finish(file.read(), -1, null);
- } catch (Error err) {
- request.finish_error(err);
- }
+ Geary.Memory.Buffer? buf = this.cid_resources[cid];
+ if (buf != null) {
+ request.finish(buf.get_input_stream(), buf.size, null);
+ attachment_loaded(cid);
} else {
request.finish_error(
new FileError.NOENT("Unknown CID: %s".printf(cid))
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index 990a467..9709023 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -1521,7 +1521,10 @@ public class ComposerWidget : Gtk.EventBox {
// attachment instead.
if (part.content_id != null) {
this.cid_files[part.content_id] = file;
- this.editor.add_cid_resource(part.content_id, file);
+ this.editor.add_cid_resource(
+ part.content_id,
+ new Geary.Memory.FileBuffer(file, true)
+ );
} else {
type = Geary.Mime.DispositionType.ATTACHMENT;
}
diff --git a/src/client/conversation-viewer/conversation-message.vala
b/src/client/conversation-viewer/conversation-message.vala
index ed0d983..ade1e01 100644
--- a/src/client/conversation-viewer/conversation-message.vala
+++ b/src/client/conversation-viewer/conversation-message.vala
@@ -19,7 +19,7 @@ public class ConversationMessage : Gtk.Grid {
private const string FROM_CLASS = "geary-from";
- private const string DATA_IMAGE_CLASS = "geary_data_inline_image";
+ private const string REPLACED_CID_TEMPLATE = "replaced_%02u@geary";
private const string REPLACED_IMAGE_CLASS = "geary_replaced_inline_image";
@@ -124,21 +124,6 @@ public class ConversationMessage : Gtk.Grid {
}
- // Internal class to associate inline image buffers (replaced by
- // rotated scaled versions of them) so they can be saved intact if
- // the user requires it
- private class ReplacedImage : Geary.BaseObject {
- public string id;
- public string filename;
- public Geary.Memory.Buffer buffer;
-
- public ReplacedImage(int replaced_number, string filename, Geary.Memory.Buffer buffer) {
- id = "%X".printf(replaced_number);
- this.filename = filename;
- this.buffer = buffer;
- }
- }
-
private const string[] INLINE_MIME_TYPES = {
"image/png",
"image/gif",
@@ -250,10 +235,6 @@ public class ConversationMessage : Gtk.Grid {
// Should any remote messages be always loaded and displayed?
private bool always_load_remote_images;
- private int next_replaced_buffer_number = 0;
- private Gee.HashMap<string, ReplacedImage> replaced_images = new Gee.HashMap<string, ReplacedImage>();
- private Gee.HashSet<string> replaced_content_ids = new Gee.HashSet<string>();
-
// Resource that have been loaded by the web view
private Gee.Map<string,WebKit.WebResource> resources =
new Gee.HashMap<string,WebKit.WebResource>();
@@ -263,6 +244,8 @@ public class ConversationMessage : Gtk.Grid {
/** Fired when an attachment is added for inline display. */
public signal void attachment_displayed_inline(string id);
+ private int next_replaced_buffer_number = 0;
+
/** Fired when the user requests remote images be loaded. */
public signal void flag_remote_images();
@@ -730,111 +713,20 @@ public class ConversationMessage : Gtk.Grid {
return null;
}
-
- // Even if the image doesn't need to be rotated, there's a win here: by reducing the size
- // of the image at load time, it reduces the amount of work that has to be done to insert
- // it into the HTML and then decoded and displayed for the user ... note that we currently
- // have the doucment set up to reduce the size of the image to fit in the viewport, and a
- // scaled load-and-deode is always faster than load followed by scale.
- Geary.Memory.Buffer rotated_image = buffer;
- string mime_type = content_type.get_mime_type();
- try {
- Gdk.PixbufLoader loader = new Gdk.PixbufLoader();
- loader.size_prepared.connect(on_inline_image_size_prepared);
-
- Geary.Memory.UnownedBytesBuffer? unowned_buffer = buffer as Geary.Memory.UnownedBytesBuffer;
- if (unowned_buffer != null)
- loader.write(unowned_buffer.to_unowned_uint8_array());
- else
- loader.write(buffer.get_uint8_array());
- loader.close();
-
- Gdk.Pixbuf? pixbuf = loader.get_pixbuf();
- if (pixbuf != null) {
- pixbuf = pixbuf.apply_embedded_orientation();
-
- // trade-off here between how long it takes to compress the data and how long it
- // takes to turn it into Base-64 (coupled with how long it takes WebKit to then
- // Base-64 decode and uncompress it)
- uint8[] image_data;
- pixbuf.save_to_buffer(out image_data, "png", "compression", "5");
-
- // Save length before transferring ownership (which frees the array)
- int image_length = image_data.length;
- rotated_image = new Geary.Memory.ByteBuffer.take((owned) image_data, image_length);
- mime_type = "image/png";
- }
- } catch (Error err) {
- debug("Unable to load and rotate image %s for display: %s", filename, err.message);
- }
-
- // store so later processing of the message doesn't replace this element with the original
- // MIME part
- string? escaped_content_id = null;
- if (!Geary.String.is_empty(content_id)) {
- replaced_content_ids.add(content_id);
- escaped_content_id = Geary.HTML.escape_markup(content_id);
+
+ string id = content_id;
+ if (id == null) {
+ id = REPLACED_CID_TEMPLATE.printf(this.next_replaced_buffer_number++);
}
-
- // Store the original buffer and its filename in a local map so they can be recalled later
- // (if the user wants to save it) ... note that Content-ID is optional and there's no
- // guarantee that filename will be unique, even in the same message, so need to generate
- // a unique identifier for each object
- ReplacedImage replaced_image = new ReplacedImage(next_replaced_buffer_number++, filename,
- buffer);
- replaced_images.set(replaced_image.id, replaced_image);
-
- return "<img alt=\"%s\" class=\"%s %s\" src=\"%s\" replaced-id=\"%s\" %s />".printf(
- Geary.HTML.escape_markup(filename),
- DATA_IMAGE_CLASS, REPLACED_IMAGE_CLASS,
- assemble_data_uri(mime_type, rotated_image),
- Geary.HTML.escape_markup(replaced_image.id),
- escaped_content_id != null ? @"cid=\"$escaped_content_id\"" : "");
- }
- // Returns a URI suitable for an IMG SRC attribute (or elsewhere, potentially) that is the
- // memory buffer unpacked into a Base-64 encoded data: URI
- private string assemble_data_uri(string mimetype, Geary.Memory.Buffer buffer) {
- // attempt to use UnownedBytesBuffer to avoid memcpying a potentially huge buffer only to
- // free it when the encoding operation is completed
- string base64;
- Geary.Memory.UnownedBytesBuffer? unowned_bytes = buffer as Geary.Memory.UnownedBytesBuffer;
- if (unowned_bytes != null)
- base64 = Base64.encode(unowned_bytes.to_unowned_uint8_array());
- else
- base64 = Base64.encode(buffer.get_uint8_array());
-
- return "data:%s;base64,%s".printf(mimetype, base64);
- }
+ this.web_view.add_cid_resource(id, buffer);
- // Called by Gdk.PixbufLoader when the image's size has been determined but not loaded yet ...
- // this allows us to load the image scaled down, for better performance when manipulating and
- // writing the data URI for WebKit
- private static void on_inline_image_size_prepared(Gdk.PixbufLoader loader, int width, int height) {
- // easier to use as local variable than have the const listed everywhere in the code
- // IN ALL SCREAMING CAPS
- int scale = MAX_INLINE_IMAGE_MAJOR_DIM;
-
- // Borrowed liberally from Shotwell's Dimensions.get_scaled() method
-
- // check for existing fit
- if (width <= scale && height <= scale)
- return;
-
- int adj_width, adj_height;
- if ((width - scale) > (height - scale)) {
- double aspect = (double) scale / (double) width;
-
- adj_width = scale;
- adj_height = (int) Math.round((double) height * aspect);
- } else {
- double aspect = (double) scale / (double) height;
-
- adj_width = (int) Math.round((double) width * aspect);
- adj_height = scale;
- }
-
- loader.set_size(adj_width, adj_height);
+ return "<img alt=\"%s\" class=\"%s\" src=\"%s%s\" />".printf(
+ Geary.HTML.escape_markup(filename),
+ REPLACED_IMAGE_CLASS,
+ ClientWebView.CID_PREFIX,
+ Geary.HTML.escape_markup(id)
+ );
}
private void show_images(bool remember) {
@@ -926,18 +818,6 @@ public class ConversationMessage : Gtk.Grid {
// }
// }
- private ReplacedImage? get_replaced_image() {
- ReplacedImage? image = null;
- // string? replaced_id = this.context_menu_element.get_attribute(
- // "replaced-id"
- // );
- // this.context_menu_element = null;
- // if (!Geary.String.is_empty(replaced_id)) {
- // image = replaced_images.get(replaced_id);
- // }
- return image;
- }
-
private inline void set_revealer(Gtk.Revealer revealer,
bool expand,
bool use_transition) {
diff --git a/ui/conversation-web-view.css b/ui/conversation-web-view.css
index 438b85e..5405c46 100644
--- a/ui/conversation-web-view.css
+++ b/ui/conversation-web-view.css
@@ -85,7 +85,7 @@ pre {
.geary_replaced_inline_image {
display: block;
max-width: 100%;
- margin-top: 1em;
+ margin: 1em 0;
}
/* Inline collapsable quote blocks */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]