Closes #3809. Attachments are now available through the message viewer and are saved as individual files outside of the database.
This commit is contained in:
parent
6fcf4cf6ce
commit
0258a20ad9
15 changed files with 842 additions and 239 deletions
15
sql/Version-002.sql
Normal file
15
sql/Version-002.sql
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
--
|
||||
-- MessageAttachmentTable
|
||||
--
|
||||
|
||||
CREATE TABLE MessageAttachmentTable (
|
||||
id INTEGER PRIMARY KEY,
|
||||
message_id INTEGER REFERENCES MessageTable ON DELETE CASCADE,
|
||||
filename TEXT,
|
||||
mime_type TEXT,
|
||||
filesize INTEGER
|
||||
);
|
||||
|
||||
CREATE INDEX MessageAttachmentTableMessageIDIndex ON MessageAttachmentTable(message_id);
|
||||
|
||||
|
|
@ -5,6 +5,7 @@ set(COMMON_SRC
|
|||
common/common-arrays.vala
|
||||
common/common-async.vala
|
||||
common/common-date.vala
|
||||
common/common-files.vala
|
||||
common/common-intl.vala
|
||||
common/common-yorba-application.vala
|
||||
)
|
||||
|
|
@ -12,6 +13,7 @@ common/common-yorba-application.vala
|
|||
set(ENGINE_SRC
|
||||
engine/api/geary-account.vala
|
||||
engine/api/geary-account-information.vala
|
||||
engine/api/geary-attachment.vala
|
||||
engine/api/geary-batch-operations.vala
|
||||
engine/api/geary-composed-email.vala
|
||||
engine/api/geary-conversation.vala
|
||||
|
|
@ -133,6 +135,8 @@ engine/sqlite/api/sqlite-folder.vala
|
|||
engine/sqlite/email/sqlite-folder-row.vala
|
||||
engine/sqlite/email/sqlite-folder-table.vala
|
||||
engine/sqlite/email/sqlite-mail-database.vala
|
||||
engine/sqlite/email/sqlite-message-attachment-row.vala
|
||||
engine/sqlite/email/sqlite-message-attachment-table.vala
|
||||
engine/sqlite/email/sqlite-message-location-row.vala
|
||||
engine/sqlite/email/sqlite-message-location-table.vala
|
||||
engine/sqlite/email/sqlite-message-row.vala
|
||||
|
|
|
|||
|
|
@ -112,6 +112,8 @@ public class GearyController {
|
|||
main_window.message_viewer.reply_all_message.connect(on_reply_all_message);
|
||||
main_window.message_viewer.forward_message.connect(on_forward_message);
|
||||
main_window.message_viewer.mark_message.connect(on_message_viewer_mark_message);
|
||||
main_window.message_viewer.open_attachment.connect(on_open_attachment);
|
||||
main_window.message_viewer.save_attachments.connect(on_save_attachments);
|
||||
|
||||
main_window.message_list_view.grab_focus();
|
||||
|
||||
|
|
@ -942,6 +944,54 @@ public class GearyController {
|
|||
set_busy(false);
|
||||
}
|
||||
|
||||
private void on_open_attachment(Geary.Attachment attachment) {
|
||||
open_uri("file://" + attachment.filepath);
|
||||
}
|
||||
|
||||
private void on_save_attachments(Gee.List<Geary.Attachment> attachments) {
|
||||
Gtk.FileChooserAction action = attachments.size == 1
|
||||
? Gtk.FileChooserAction.SAVE
|
||||
: Gtk.FileChooserAction.SELECT_FOLDER;
|
||||
Gtk.FileChooserDialog dialog = new Gtk.FileChooserDialog(null, main_window, action,
|
||||
Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL, Gtk.Stock.SAVE, Gtk.ResponseType.ACCEPT, null);
|
||||
dialog.set_filename(attachments[0].filepath);
|
||||
if (dialog.run() != Gtk.ResponseType.ACCEPT) {
|
||||
dialog.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the selected location.
|
||||
string filename = dialog.get_filename();
|
||||
debug("Saving attachment to: %s", filename);
|
||||
|
||||
// Save the attachments.
|
||||
// TODO Handle attachments with the same name being saved into the same directory.
|
||||
File destination = File.new_for_path(filename);
|
||||
if (attachments.size == 1) {
|
||||
File source = File.new_for_path(attachments[0].filepath);
|
||||
source.copy_async.begin(destination, FileCopyFlags.OVERWRITE, Priority.DEFAULT, null,
|
||||
null, on_save_completed);
|
||||
} else {
|
||||
foreach (Geary.Attachment attachment in attachments) {
|
||||
File dest_name = destination.get_child(attachment.filename);
|
||||
File source = File.new_for_path(attachment.filepath);
|
||||
debug("Saving %s to %s", source.get_path(), dest_name.get_path());
|
||||
source.copy_async.begin(dest_name, FileCopyFlags.OVERWRITE, Priority.DEFAULT, null,
|
||||
null, on_save_completed);
|
||||
}
|
||||
}
|
||||
|
||||
dialog.destroy();
|
||||
}
|
||||
|
||||
private void on_save_completed(Object? source, AsyncResult result) {
|
||||
try {
|
||||
((File) source).copy_async.end(result);
|
||||
} catch (Error error) {
|
||||
warning("Failed to copy attachment to destination: %s", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Opens a link in an external browser.
|
||||
private void open_uri(string _link) {
|
||||
string link = _link;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ public class MessageViewer : WebKit.WebView {
|
|||
| Geary.Email.Field.DATE
|
||||
| Geary.Email.Field.FLAGS
|
||||
| Geary.Email.Field.PREVIEW;
|
||||
|
||||
|
||||
private const int ATTACHMENT_PREVIEW_SIZE = 50;
|
||||
private const string MESSAGE_CONTAINER_ID = "message_container";
|
||||
private const string SELECTION_COUNTER_ID = "multiple_messages";
|
||||
private const string HTML_BODY = """
|
||||
|
|
@ -185,8 +186,9 @@ public class MessageViewer : WebKit.WebView {
|
|||
background-color: #e8e8e8
|
||||
}
|
||||
.email.hide:not(:last-of-type) .body,
|
||||
.email:not(.hide) .preview,
|
||||
.email:last-of-type .preview {
|
||||
.email.hide:not(:last-of-type) > .attachment_container,
|
||||
.email:not(.hide) .header_container .preview,
|
||||
.email:last-of-type .header_container .preview {
|
||||
display: none;
|
||||
}
|
||||
.email:not(:last-of-type) .header_container {
|
||||
|
|
@ -219,6 +221,76 @@ public class MessageViewer : WebKit.WebView {
|
|||
|
||||
}
|
||||
|
||||
.email:not(.attachment) .attachment.icon {
|
||||
display: none;
|
||||
}
|
||||
.email .header_container .attachment.icon {
|
||||
float: right;
|
||||
margin-top: 7px;
|
||||
}
|
||||
.email > .attachment_container {
|
||||
background-color: #ddd;
|
||||
border-radius: 4px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.email > .attachment_container > .top_border {
|
||||
border-bottom: 1px solid #999;
|
||||
border-radius: 0 0 4px 4px;
|
||||
height: 10px;
|
||||
background-color: white;
|
||||
margin-bottom: 5px;
|
||||
box-shadow: 0 3px 5px #c0c0c0;
|
||||
}
|
||||
.email > .attachment_container > .attachment {
|
||||
margin: 10px 10px 0 10px;
|
||||
padding: 2px;
|
||||
overflow: hidden;
|
||||
font-size: 10pt;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 5px;
|
||||
display: inline;
|
||||
}
|
||||
.email > .attachment_container > .attachment:hover,
|
||||
.email > .attachment_container > .attachment:active {
|
||||
border-color: #999;
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
.email > .attachment_container > .attachment:active {
|
||||
padding: 3px 1px 1px 3px;
|
||||
box-shadow: inset 3px 3px 5px #ccc, inset -1px -1px 3px #ccc;
|
||||
}
|
||||
.email > .attachment_container > .attachment .preview {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.email > .attachment_container > .attachment .preview img {
|
||||
max-width: 50px;
|
||||
max-height: 50px;
|
||||
}
|
||||
.email > .attachment_container > .attachment .preview .thumbnail {
|
||||
border: 1px solid #999;
|
||||
box-shadow: 0 0 5px #b8b8b8;
|
||||
background-size: 16px 16px;
|
||||
background-position:0 0, 8px 0, 8px -8px, 0px 8px;
|
||||
}
|
||||
.email > .attachment_container > .attachment:hover .preview .thumbnail {
|
||||
background-image:
|
||||
-webkit-linear-gradient(45deg, rgba(0, 0, 0, 0.1) 25%, transparent 25%, transparent),
|
||||
-webkit-linear-gradient(-45deg, rgba(0, 0, 0, 0.1) 25%, transparent 25%, transparent),
|
||||
-webkit-linear-gradient(45deg, transparent 75%, rgba(0, 0, 0, 0.1) 75%),
|
||||
-webkit-linear-gradient(-45deg, transparent 75%, rgba(0, 0, 0, 0.1) 75%);
|
||||
}
|
||||
.email > .attachment_container > .attachment .info {
|
||||
vertical-align: middle;
|
||||
padding-left: 5px;
|
||||
}
|
||||
.email > .attachment_container > .attachment .info > :not(.filename) {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.header {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
@ -314,7 +386,8 @@ public class MessageViewer : WebKit.WebView {
|
|||
width: auto;
|
||||
padding: 15px;
|
||||
}
|
||||
#email_template {
|
||||
#email_template,
|
||||
#attachment_template {
|
||||
display: none;
|
||||
}
|
||||
blockquote {
|
||||
|
|
@ -336,12 +409,23 @@ public class MessageViewer : WebKit.WebView {
|
|||
<div class="unstarred button"><img src="" class="icon" /></div>
|
||||
<div class="menu button"><img src="" class="icon" /></div>
|
||||
</div>
|
||||
<img src="" class="attachment icon" />
|
||||
<div class="header"></div>
|
||||
<div class="preview"></div>
|
||||
</div>
|
||||
<div class="body"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="attachment_template" class="attachment_container">
|
||||
<div class="top_border"></div>
|
||||
<table class="attachment"><tr>
|
||||
<td class="preview"><img src="" /></td>
|
||||
<td class="info">
|
||||
<div class="filename"></div>
|
||||
<div class="filesize"></div>
|
||||
</td>
|
||||
</tr></table>
|
||||
</div>
|
||||
</body></html>""";
|
||||
|
||||
// Fired when the user clicks a link.
|
||||
|
|
@ -362,10 +446,17 @@ public class MessageViewer : WebKit.WebView {
|
|||
// Fired when the user marks a message.
|
||||
public signal void mark_message(Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove);
|
||||
|
||||
// Fired when the user opens an attachment.
|
||||
public signal void open_attachment(Geary.Attachment attachment);
|
||||
|
||||
// Fired when the user wants to save one or more attachments.
|
||||
public signal void save_attachments(Gee.List<Geary.Attachment> attachment);
|
||||
|
||||
// List of emails in this view.
|
||||
public Gee.TreeSet<Geary.Email> messages { get; private set; default =
|
||||
new Gee.TreeSet<Geary.Email>((CompareFunc<Geary.Email>) Geary.Email.compare_date_ascending); }
|
||||
public Geary.Email? active_email = null;
|
||||
public Geary.Attachment? active_attachment = null;
|
||||
|
||||
// HTML element that contains message DIVs.
|
||||
private WebKit.DOM.HTMLDivElement container;
|
||||
|
|
@ -415,6 +506,7 @@ public class MessageViewer : WebKit.WebView {
|
|||
set_icon_src("#email_template .menu .icon", "down");
|
||||
set_icon_src("#email_template .starred .icon", "starred");
|
||||
set_icon_src("#email_template .unstarred .icon", "non-starred-grey");
|
||||
set_icon_src("#email_template .attachment.icon", "mail-attachment");
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -431,7 +523,7 @@ public class MessageViewer : WebKit.WebView {
|
|||
private void set_icon_src(string selector, string icon_name) {
|
||||
try {
|
||||
// Load the icon.
|
||||
string icon_filename = IconFactory.instance.lookup_icon(icon_name, 16, 0).get_filename();
|
||||
string icon_filename = IconFactory.instance.lookup_icon(icon_name, 16).get_filename();
|
||||
uint8[] icon_content;
|
||||
FileUtils.get_data(icon_filename, out icon_content);
|
||||
|
||||
|
|
@ -441,15 +533,56 @@ public class MessageViewer : WebKit.WebView {
|
|||
icon_content, out uncertain_content_type));
|
||||
|
||||
// Then set the source to a data url.
|
||||
WebKit.DOM.HTMLImageElement icon = get_dom_document().query_selector(selector)
|
||||
WebKit.DOM.HTMLImageElement img = Util.DOM.select(get_dom_document(), selector)
|
||||
as WebKit.DOM.HTMLImageElement;
|
||||
icon.set_attribute("src", "data:%s;base64,%s".printf(icon_mimetype,
|
||||
Base64.encode(icon_content)));
|
||||
set_data_url(img, icon_mimetype, icon_content);
|
||||
} catch (Error error) {
|
||||
warning("Failed to load icon '%s': %s", icon_name, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
private void set_image_src(WebKit.DOM.HTMLImageElement img, string mime_type, string filename,
|
||||
int maxwidth, int maxheight = -1) {
|
||||
if( maxheight == -1 ){
|
||||
maxheight = maxwidth;
|
||||
}
|
||||
|
||||
try {
|
||||
// If the file is an image, use it. Otherwise get the icon for this mime_type.
|
||||
uint8[] content;
|
||||
string content_type = ContentType.from_mime_type(mime_type);
|
||||
string icon_mime_type = mime_type;
|
||||
if (mime_type.has_prefix("image/")) {
|
||||
// Get a thumbnail for the image.
|
||||
// TODO Generate and save the thumbnail when extracting the attachments rather than
|
||||
// when showing them in the viewer.
|
||||
img.get_class_list().add("thumbnail");
|
||||
Gdk.Pixbuf image = new Gdk.Pixbuf.from_file_at_scale(filename, maxwidth, maxheight,
|
||||
true);
|
||||
image.save_to_buffer(out content, "png");
|
||||
icon_mime_type = "image/png";
|
||||
} else {
|
||||
// Load the icon for this mime type.
|
||||
ThemedIcon icon = ContentType.get_icon(content_type) as ThemedIcon;
|
||||
string icon_filename = IconFactory.instance.lookup_icon(icon.names[0], maxwidth)
|
||||
.get_filename();
|
||||
FileUtils.get_data(icon_filename, out content);
|
||||
icon_mime_type = ContentType.get_mime_type(ContentType.guess(icon_filename, content,
|
||||
null));
|
||||
}
|
||||
|
||||
// Then set the source to a data url.
|
||||
set_data_url(img, icon_mime_type, content);
|
||||
} catch (Error error) {
|
||||
warning("Failed to load image '%s': %s", filename, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
private void set_data_url(WebKit.DOM.HTMLImageElement img, string mime_type, uint8[] content)
|
||||
throws Error {
|
||||
img.set_attribute("src", "data:%s;base64,%s".printf(mime_type, Base64.encode(content)));
|
||||
}
|
||||
|
||||
// Removes all displayed e-mails from the view.
|
||||
public void clear() {
|
||||
// Remove all messages from DOM.
|
||||
|
|
@ -547,12 +680,10 @@ public class MessageViewer : WebKit.WebView {
|
|||
// </span>
|
||||
// </div>
|
||||
// </div>
|
||||
div_message = get_dom_document().get_element_by_id("email_template").clone_node(true)
|
||||
as WebKit.DOM.HTMLElement;
|
||||
div_message = Util.DOM.clone_select(get_dom_document(), "#email_template");
|
||||
div_message.set_attribute("id", message_id);
|
||||
container.insert_before(div_message, insert_before);
|
||||
div_email_container = div_message.query_selector("div.email_container")
|
||||
as WebKit.DOM.HTMLElement;
|
||||
div_email_container = Util.DOM.select(div_message, "div.email_container");
|
||||
if (email.is_unread() == Geary.Trillian.FALSE) {
|
||||
div_message.get_class_list().add("hide");
|
||||
}
|
||||
|
|
@ -598,7 +729,7 @@ public class MessageViewer : WebKit.WebView {
|
|||
|
||||
// Add the avatar.
|
||||
try {
|
||||
WebKit.DOM.HTMLImageElement icon = get_dom_document().query_selector("#%s .avatar".printf(message_id))
|
||||
WebKit.DOM.HTMLImageElement icon = Util.DOM.select(div_message, ".avatar")
|
||||
as WebKit.DOM.HTMLImageElement;
|
||||
string checksum = GLib.Checksum.compute_for_string (
|
||||
GLib.ChecksumType.MD5, email.sender.get(0).address);
|
||||
|
|
@ -610,8 +741,8 @@ public class MessageViewer : WebKit.WebView {
|
|||
|
||||
// Insert the preview text.
|
||||
try {
|
||||
WebKit.DOM.HTMLElement preview = div_message.query_selector(".header_container .preview")
|
||||
as WebKit.DOM.HTMLElement;
|
||||
WebKit.DOM.HTMLElement preview =
|
||||
Util.DOM.select(div_message, ".header_container .preview");
|
||||
string preview_str = email.get_preview_as_string();
|
||||
if (preview_str.length == Geary.Email.MAX_PREVIEW_BYTES) {
|
||||
preview_str += "…";
|
||||
|
|
@ -637,28 +768,35 @@ public class MessageViewer : WebKit.WebView {
|
|||
|
||||
// Graft header and email body into the email container.
|
||||
try {
|
||||
WebKit.DOM.HTMLElement table_header = div_email_container
|
||||
.query_selector(".header_container .header") as WebKit.DOM.HTMLElement;
|
||||
WebKit.DOM.HTMLElement table_header =
|
||||
Util.DOM.select(div_email_container, ".header_container .header");
|
||||
table_header.set_inner_html(header);
|
||||
|
||||
WebKit.DOM.HTMLElement span_body = div_email_container.query_selector(".body")
|
||||
as WebKit.DOM.HTMLElement;
|
||||
WebKit.DOM.HTMLElement span_body = Util.DOM.select(div_email_container, ".body");
|
||||
span_body.set_inner_html(body_text);
|
||||
|
||||
} catch (Error html_error) {
|
||||
warning("Error setting HTML for message: %s", html_error.message);
|
||||
}
|
||||
|
||||
// Add the attachments container if we have any attachments.
|
||||
if (email.attachments.size > 0) {
|
||||
insert_attachments(div_message, email.attachments);
|
||||
}
|
||||
|
||||
// Add classes according to the state of the email.
|
||||
update_flags(email);
|
||||
|
||||
// Attach to the click events for hiding/showing quotes, opening the menu, and so forth.
|
||||
bind_event(this, ".email", "contextmenu", (Callback) on_context_menu, this);
|
||||
bind_event(this,".quote_container > .hider", "click", (Callback) on_hide_quote_clicked);
|
||||
bind_event(this,".quote_container > .shower", "click", (Callback) on_show_quote_clicked);
|
||||
bind_event(this,".email_container .menu", "click", (Callback) on_menu_clicked, this);
|
||||
bind_event(this,".email_container .starred", "click", (Callback) on_unstar_clicked, this);
|
||||
bind_event(this,".email_container .unstarred", "click", (Callback) on_star_clicked, this);
|
||||
bind_event(this,".email .header_container", "click", (Callback) on_body_toggle_clicked, this);
|
||||
bind_event(this, ".quote_container > .hider", "click", (Callback) on_hide_quote_clicked);
|
||||
bind_event(this, ".quote_container > .shower", "click", (Callback) on_show_quote_clicked);
|
||||
bind_event(this, ".email_container .menu", "click", (Callback) on_menu_clicked, this);
|
||||
bind_event(this, ".email_container .starred", "click", (Callback) on_unstar_clicked, this);
|
||||
bind_event(this, ".email_container .unstarred", "click", (Callback) on_star_clicked, this);
|
||||
bind_event(this, ".email .header_container", "click", (Callback) on_body_toggle_clicked, this);
|
||||
bind_event(this, ".attachment_container .attachment", "click", (Callback) on_attachment_clicked, this);
|
||||
bind_event(this, ".attachment_container .attachment", "contextmenu", (Callback) on_attachment_menu, this);
|
||||
}
|
||||
|
||||
private WebKit.DOM.HTMLElement? closest_ancestor(WebKit.DOM.Element element, string selector) {
|
||||
|
|
@ -714,7 +852,7 @@ public class MessageViewer : WebKit.WebView {
|
|||
|
||||
Geary.EmailFlags flags = email.email_flags;
|
||||
|
||||
// Update the flags in out message set.
|
||||
// Update the flags in our message set.
|
||||
foreach (Geary.Email message in messages) {
|
||||
if (message.id.equals(email.id)) {
|
||||
message.set_flags(flags);
|
||||
|
|
@ -726,21 +864,14 @@ public class MessageViewer : WebKit.WebView {
|
|||
WebKit.DOM.HTMLElement container = email_to_element.get(email.id);
|
||||
try {
|
||||
WebKit.DOM.DOMTokenList class_list = container.get_class_list();
|
||||
if (flags.is_unread()) {
|
||||
class_list.remove("read");
|
||||
} else {
|
||||
class_list.add("read");
|
||||
}
|
||||
if (flags.is_flagged()) {
|
||||
class_list.add("starred");
|
||||
} else {
|
||||
class_list.remove("starred");
|
||||
}
|
||||
Util.DOM.toggle_class(class_list, "read", !flags.is_unread());
|
||||
Util.DOM.toggle_class(class_list, "starred", flags.is_flagged());
|
||||
Util.DOM.toggle_class(class_list, "attachment", email.attachments.size > 0);
|
||||
} catch (Error e) {
|
||||
warning("Failed to set classes on .email: %s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void on_context_menu(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
||||
MessageViewer message_viewer) {
|
||||
message_viewer.active_email = message_viewer.get_email_from_element(element);
|
||||
|
|
@ -768,10 +899,10 @@ public class MessageViewer : WebKit.WebView {
|
|||
private static void on_menu_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
||||
MessageViewer message_viewer) {
|
||||
event.stop_propagation();
|
||||
message_viewer.on_menu_clicked_async(element);
|
||||
message_viewer.on_menu_clicked_self(element);
|
||||
}
|
||||
|
||||
private void on_menu_clicked_async(WebKit.DOM.Element element) {
|
||||
private void on_menu_clicked_self(WebKit.DOM.Element element) {
|
||||
active_email = get_email_from_element(element);
|
||||
show_message_menu(element);
|
||||
}
|
||||
|
|
@ -779,10 +910,10 @@ public class MessageViewer : WebKit.WebView {
|
|||
private static void on_unstar_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
||||
MessageViewer message_viewer) {
|
||||
event.stop_propagation();
|
||||
message_viewer.on_unstar_clicked_async(element);
|
||||
message_viewer.on_unstar_clicked_self(element);
|
||||
}
|
||||
|
||||
private void on_unstar_clicked_async(WebKit.DOM.Element element){
|
||||
private void on_unstar_clicked_self(WebKit.DOM.Element element){
|
||||
active_email = get_email_from_element(element);
|
||||
on_unflag_message();
|
||||
}
|
||||
|
|
@ -790,10 +921,10 @@ public class MessageViewer : WebKit.WebView {
|
|||
private static void on_star_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
||||
MessageViewer message_viewer) {
|
||||
event.stop_propagation();
|
||||
message_viewer.on_star_clicked_async(element);
|
||||
message_viewer.on_star_clicked_self(element);
|
||||
}
|
||||
|
||||
private void on_star_clicked_async(WebKit.DOM.Element element){
|
||||
private void on_star_clicked_self(WebKit.DOM.Element element){
|
||||
active_email = get_email_from_element(element);
|
||||
on_flag_message();
|
||||
}
|
||||
|
|
@ -815,15 +946,53 @@ public class MessageViewer : WebKit.WebView {
|
|||
class_list.add("hide");
|
||||
}
|
||||
} catch (Error error) {
|
||||
warning("Error toggline message: %s", error.message);
|
||||
warning("Error toggling message: %s", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void on_attachment_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
||||
MessageViewer message_viewer) {
|
||||
message_viewer.on_attachment_clicked_self(element);
|
||||
}
|
||||
|
||||
private void on_attachment_clicked_self(WebKit.DOM.Element element) {
|
||||
try {
|
||||
int64 attachment_id = int64.parse(element.get_attribute("data-attachment-id"));
|
||||
open_attachment(get_email_from_element(element).get_attachment(attachment_id));
|
||||
} catch (Error error) {
|
||||
warning("Error opening attachment: %s", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void on_attachment_menu(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
||||
MessageViewer message_viewer) {
|
||||
try {
|
||||
event.stop_propagation();
|
||||
message_viewer.active_email = message_viewer.get_email_from_element(element);
|
||||
message_viewer.active_attachment = message_viewer.active_email.get_attachment(
|
||||
int64.parse(element.get_attribute("data-attachment-id")));
|
||||
message_viewer.show_message_menu(element);
|
||||
} catch (Error error) {
|
||||
warning("Error opening attachment menu: %s", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
private void on_message_menu_selection_done() {
|
||||
active_email = null;
|
||||
active_attachment = null;
|
||||
message_menu = null;
|
||||
}
|
||||
|
||||
private void on_save_attachment() {
|
||||
Gee.List<Geary.Attachment> attachments = new Gee.ArrayList<Geary.Attachment>();
|
||||
attachments.add(active_attachment != null ? active_attachment : active_email.attachments[0]);
|
||||
save_attachments(attachments);
|
||||
}
|
||||
|
||||
private void on_save_all_attachments() {
|
||||
save_attachments(active_email.attachments);
|
||||
}
|
||||
|
||||
private void on_reply_to_message() {
|
||||
reply_to_message();
|
||||
}
|
||||
|
|
@ -874,6 +1043,29 @@ public class MessageViewer : WebKit.WebView {
|
|||
message_menu = new Gtk.Menu();
|
||||
message_menu.selection_done.connect(on_message_menu_selection_done);
|
||||
|
||||
if (active_email.attachments.size > 0) {
|
||||
// Save attachment as...
|
||||
if (active_attachment != null) {
|
||||
Gtk.MenuItem save_attachment_item = new Gtk.MenuItem.with_mnemonic(_("_Save As..."));
|
||||
save_attachment_item.activate.connect(on_save_attachment);
|
||||
message_menu.append(save_attachment_item);
|
||||
}
|
||||
|
||||
// Save all attachments
|
||||
if (active_email.attachments.size > 1) {
|
||||
Gtk.MenuItem save_all_item = new Gtk.MenuItem.with_mnemonic(_("Save All A_ttachments..."));
|
||||
save_all_item.activate.connect(on_save_all_attachments);
|
||||
message_menu.append(save_all_item);
|
||||
} else if (active_attachment == null) {
|
||||
Gtk.MenuItem save_all_item = new Gtk.MenuItem.with_mnemonic(_("Save A_ttachment..."));
|
||||
save_all_item.activate.connect(on_save_attachment);
|
||||
message_menu.append(save_all_item);
|
||||
}
|
||||
|
||||
// Separator.
|
||||
message_menu.append(new Gtk.SeparatorMenuItem());
|
||||
}
|
||||
|
||||
// Reply to a message.
|
||||
Gtk.MenuItem reply_item = new Gtk.MenuItem.with_mnemonic(_("_Reply"));
|
||||
reply_item.activate.connect(on_reply_to_message);
|
||||
|
|
@ -1002,7 +1194,7 @@ public class MessageViewer : WebKit.WebView {
|
|||
|
||||
// Copy the stuff before the quote, then the wrapped quote.
|
||||
WebKit.DOM.Element quote_container = create_quote_container();
|
||||
((WebKit.DOM.HTMLElement) quote_container.query_selector(".quote")).set_inner_html(
|
||||
Util.DOM.select(quote_container, ".quote").set_inner_html(
|
||||
text.substring(quote_start, quote_end - quote_start));
|
||||
container.append_child(quote_container);
|
||||
if (quote_start > offset) {
|
||||
|
|
@ -1042,7 +1234,7 @@ public class MessageViewer : WebKit.WebView {
|
|||
|
||||
// Some HTML messages like to wrap themselves in full, proper html, head, and body tags.
|
||||
// If we have that here, lets remove it since we are sticking it in our own document.
|
||||
WebKit.DOM.HTMLElement? body = container.query_selector("body") as WebKit.DOM.HTMLElement;
|
||||
WebKit.DOM.HTMLElement? body = Util.DOM.select(container, "body");
|
||||
if (body != null) {
|
||||
container.set_inner_html(body.get_inner_html());
|
||||
}
|
||||
|
|
@ -1065,7 +1257,7 @@ public class MessageViewer : WebKit.WebView {
|
|||
// blockquote
|
||||
// sibling
|
||||
WebKit.DOM.Element quote_container = create_quote_container();
|
||||
quote_container.query_selector(".quote").append_child(blockquote_node);
|
||||
Util.DOM.select(quote_container, ".quote").append_child(blockquote_node);
|
||||
if (next_sibling == null) {
|
||||
parent.append_child(quote_container);
|
||||
} else {
|
||||
|
|
@ -1217,7 +1409,59 @@ public class MessageViewer : WebKit.WebView {
|
|||
output = r.replace_eval(output, -1, 0, 0, is_valid_url);
|
||||
return output.replace(" \01 ", "<").replace(" \02 ", ">");
|
||||
}
|
||||
|
||||
|
||||
private void insert_attachments(WebKit.DOM.HTMLElement email_container,
|
||||
Gee.List<Geary.Attachment> attachments) {
|
||||
|
||||
// <div class="attachment_container">
|
||||
// <div class="top_border"></div>
|
||||
// <table class="attachment" data-attachment-id="">
|
||||
// <tr>
|
||||
// <td class="preview">
|
||||
// <img src="" />
|
||||
// </td>
|
||||
// <td class="info">
|
||||
// <div class="filename"></div>
|
||||
// <div class="filesize"></div>
|
||||
// </td>
|
||||
// </tr>
|
||||
// </table>
|
||||
// </div>
|
||||
|
||||
try {
|
||||
// Prepare the dom for our attachments.
|
||||
WebKit.DOM.Document document = get_dom_document();
|
||||
WebKit.DOM.HTMLElement attachment_container =
|
||||
Util.DOM.clone_select(document, "#attachment_template");
|
||||
WebKit.DOM.HTMLElement attachment_template =
|
||||
Util.DOM.select(attachment_container, ".attachment");
|
||||
attachment_container.remove_attribute("id");
|
||||
attachment_container.remove_child(attachment_template);
|
||||
|
||||
// Create an attachment table for each attachment.
|
||||
foreach (Geary.Attachment attachment in attachments) {
|
||||
// Generate the attachment table.
|
||||
WebKit.DOM.HTMLElement attachment_table = Util.DOM.clone_node(attachment_template);
|
||||
Util.DOM.select(attachment_table, ".info .filename")
|
||||
.set_inner_text(attachment.filename);
|
||||
Util.DOM.select(attachment_table, ".info .filesize")
|
||||
.set_inner_text(Files.get_filesize_as_string(attachment.filesize));
|
||||
attachment_table.set_attribute("data-attachment-id", "%lld".printf(attachment.id));
|
||||
|
||||
// Set the image preview and insert it into the container.
|
||||
WebKit.DOM.HTMLImageElement img =
|
||||
Util.DOM.select(attachment_table, ".preview img") as WebKit.DOM.HTMLImageElement;
|
||||
set_image_src(img, attachment.mime_type, attachment.filepath, ATTACHMENT_PREVIEW_SIZE);
|
||||
attachment_container.append_child(attachment_table);
|
||||
}
|
||||
|
||||
// Append the attachments to the email.
|
||||
email_container.append_child(attachment_container);
|
||||
} catch (Error error) {
|
||||
debug("Failed to insert attachments: %s", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Validates a URL.
|
||||
// Ensures the URL begins with a valid protocol specifier. (If not, we don't
|
||||
// want to linkify it.)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,39 @@ public const string URL_REGEX = "(?i)\\b((?:[a-z][\\w-]+:(?:/{1,3}|[a-z0-9%])|ww
|
|||
// Regex to determine if a URL has a known protocol.
|
||||
public const string PROTOCOL_REGEX = "^(aim|apt|bitcoin|cvs|ed2k|ftp|file|finger|git|gtalk|http|https|irc|ircs|irc6|lastfm|ldap|ldaps|magnet|news|nntp|rsync|sftp|skype|smb|sms|svn|telnet|tftp|ssh|webcal|xmpp):";
|
||||
|
||||
// TODO Move these other functions and variables into this namespace.
|
||||
namespace Util.DOM {
|
||||
public WebKit.DOM.HTMLElement? select(WebKit.DOM.Node node, string selector) {
|
||||
try {
|
||||
if (node is WebKit.DOM.Document) {
|
||||
return (node as WebKit.DOM.Document).query_selector(selector) as WebKit.DOM.HTMLElement;
|
||||
} else {
|
||||
return (node as WebKit.DOM.Element).query_selector(selector) as WebKit.DOM.HTMLElement;
|
||||
}
|
||||
} catch (Error error) {
|
||||
debug("Error selecting element %s: %s", selector, error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public WebKit.DOM.HTMLElement? clone_node(WebKit.DOM.Node node, bool deep = true) {
|
||||
return node.clone_node(deep) as WebKit.DOM.HTMLElement;
|
||||
}
|
||||
|
||||
public WebKit.DOM.HTMLElement? clone_select(WebKit.DOM.Node node, string selector,
|
||||
bool deep = true) {
|
||||
return clone_node(select(node, selector), deep);
|
||||
}
|
||||
|
||||
public void toggle_class(WebKit.DOM.DOMTokenList class_list, string clas, bool add) throws Error {
|
||||
if (add) {
|
||||
class_list.add(clas);
|
||||
} else {
|
||||
class_list.remove(clas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void bind_event(WebKit.WebView view, string selector, string event, Callback callback,
|
||||
Object? extra = null) {
|
||||
try {
|
||||
|
|
|
|||
39
src/common/common-files.vala
Normal file
39
src/common/common-files.vala
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/* Copyright 2011-2012 Yorba Foundation
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
namespace Files {
|
||||
|
||||
public const int64 KILOBYTE = 1024;
|
||||
public const int64 MEGABYTE = KILOBYTE * 1024;
|
||||
public const int64 GIGABYTE = MEGABYTE * 1024;
|
||||
public const int64 TERABYTE = GIGABYTE * 1024;
|
||||
|
||||
public string get_filesize_as_string(int64 filesize) {
|
||||
int64 scale = 1;
|
||||
string units = _("bytes");
|
||||
if (filesize > TERABYTE) {
|
||||
scale = TERABYTE;
|
||||
units = C_("Abbreviation for terabyte", "TB");
|
||||
} else if (filesize > GIGABYTE) {
|
||||
scale = GIGABYTE;
|
||||
units = C_("Abbreviation for gigabyte", "GB");
|
||||
} else if (filesize > MEGABYTE) {
|
||||
scale = MEGABYTE;
|
||||
units = C_("Abbreviation for megabyte", "MB");
|
||||
} else if (filesize > KILOBYTE) {
|
||||
scale = KILOBYTE;
|
||||
units = C_("Abbreviation for kilobyte", "KB");
|
||||
}
|
||||
|
||||
if (scale == 1) {
|
||||
return "%lld %s".printf(filesize, units);
|
||||
} else {
|
||||
return "%.2f %s".printf((float) filesize / (float) scale, units);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
32
src/engine/api/geary-attachment.vala
Normal file
32
src/engine/api/geary-attachment.vala
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/* Copyright 2011-2012 Yorba Foundation
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public class Geary.Attachment {
|
||||
public const Email.Field REQUIRED_FIELDS = Email.Field.HEADER | Email.Field.BODY;
|
||||
|
||||
public string filename { get; private set; }
|
||||
public string filepath { get; private set; }
|
||||
public string mime_type { get; private set; }
|
||||
public int64 filesize { get; private set; }
|
||||
public int64 id { get; private set; }
|
||||
|
||||
internal Attachment(File data_dir, string filename, string mime_type, int64 filesize,
|
||||
int64 message_id, int64 attachment_id) {
|
||||
|
||||
this.filename = filename;
|
||||
this.mime_type = mime_type;
|
||||
this.filesize = filesize;
|
||||
this.filepath = get_path(data_dir, message_id, attachment_id, filename);
|
||||
this.id = attachment_id;
|
||||
}
|
||||
|
||||
internal static string get_path(File data_dir, int64 message_id, int64 attachment_id,
|
||||
string filename) {
|
||||
return "%s/attachments/%lld/%lld/%s".printf(data_dir.get_path(), message_id, attachment_id,
|
||||
filename);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -113,6 +113,8 @@ public class Geary.Email : Object {
|
|||
|
||||
// BODY
|
||||
public RFC822.Text? body { get; private set; default = null; }
|
||||
public Gee.List<Geary.Attachment> attachments { get; private set;
|
||||
default = new Gee.ArrayList<Geary.Attachment>(); }
|
||||
|
||||
// PROPERTIES
|
||||
public Geary.EmailProperties? properties { get; private set; default = null; }
|
||||
|
|
@ -216,13 +218,17 @@ public class Geary.Email : Object {
|
|||
|
||||
fields |= Field.PREVIEW;
|
||||
}
|
||||
|
||||
|
||||
public void set_flags(Geary.EmailFlags email_flags) {
|
||||
this.email_flags = email_flags;
|
||||
|
||||
fields |= Field.FLAGS;
|
||||
}
|
||||
|
||||
|
||||
public void add_attachment(Geary.Attachment attachment) {
|
||||
attachments.add(attachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method requires Geary.Email.Field.HEADER and Geary.Email.Field.BODY be present.
|
||||
* If not, EngineError.INCOMPLETE_MESSAGE is thrown.
|
||||
|
|
@ -238,7 +244,19 @@ public class Geary.Email : Object {
|
|||
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
public Geary.Attachment? get_attachment(int64 attachment_id) throws EngineError, RFC822Error {
|
||||
if (!fields.fulfills(Field.HEADER | Field.BODY))
|
||||
throw new EngineError.INCOMPLETE_MESSAGE("Parsed email requires HEADER and BODY");
|
||||
|
||||
foreach (Geary.Attachment attachment in attachments) {
|
||||
if (attachment.id == attachment_id) {
|
||||
return attachment;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of this email's ancestry by Message-ID. IDs are not returned in any
|
||||
* particular order. The ancestry is made up from this email's Message-ID, its References,
|
||||
|
|
|
|||
|
|
@ -265,28 +265,9 @@ public class Geary.RFC822.Message : Object {
|
|||
}
|
||||
|
||||
// convert payload to a buffer
|
||||
GMime.DataWrapper? wrapper = part.get_content_object();
|
||||
if (wrapper == null) {
|
||||
throw new RFC822Error.INVALID("Could not get the content wrapper for content-type %s",
|
||||
content_type);
|
||||
}
|
||||
|
||||
ByteArray byte_array = new ByteArray();
|
||||
GMime.StreamMem stream = new GMime.StreamMem.with_byte_array(byte_array);
|
||||
stream.set_owner(false);
|
||||
|
||||
// Convert encoding to UTF-8.
|
||||
GMime.StreamFilter stream_filter = new GMime.StreamFilter(stream);
|
||||
string? charset = part.get_content_type_parameter("charset");
|
||||
if (charset == null)
|
||||
charset = DEFAULT_ENCODING;
|
||||
stream_filter.add(new GMime.FilterCharset(charset, "UTF8"));
|
||||
|
||||
wrapper.write_to_stream(stream_filter);
|
||||
|
||||
return new Geary.Memory.Buffer(byte_array.data, byte_array.len);
|
||||
return mime_part_to_memory_buffer(part, true);
|
||||
}
|
||||
|
||||
|
||||
private GMime.Part? find_first_mime_part(GMime.Object current_root, string content_type) {
|
||||
// descend looking for the content type in a GMime.Part
|
||||
GMime.Multipart? multipart = current_root as GMime.Multipart;
|
||||
|
|
@ -298,14 +279,68 @@ public class Geary.RFC822.Message : Object {
|
|||
return child_part;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GMime.Part? part = current_root as GMime.Part;
|
||||
if (part != null && part.get_content_type().to_string() == content_type)
|
||||
if (part != null && part.get_content_type().to_string() == content_type &&
|
||||
part.get_disposition() != "attachment") {
|
||||
return part;
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
internal Gee.List<GMime.Part> get_attachments() throws RFC822Error {
|
||||
Gee.List<GMime.Part> attachments = new Gee.ArrayList<GMime.Part>();
|
||||
find_attachments( ref attachments, message.get_mime_part() );
|
||||
return attachments;
|
||||
}
|
||||
|
||||
private void find_attachments(ref Gee.List<GMime.Part> attachments, GMime.Object root)
|
||||
throws RFC822Error {
|
||||
|
||||
// If this is a multipart container, dive into each of its children.
|
||||
if (root is GMime.Multipart) {
|
||||
GMime.Multipart multipart = root as GMime.Multipart;
|
||||
int count = multipart.get_count();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
find_attachments(ref attachments, multipart.get_part(i));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise see if it has a content disposition of "attachment."
|
||||
if (root is GMime.Part && root.get_disposition() == "attachment") {
|
||||
attachments.add(root as GMime.Part);
|
||||
}
|
||||
}
|
||||
|
||||
private Geary.Memory.AbstractBuffer mime_part_to_memory_buffer(GMime.Part part,
|
||||
bool to_utf8 = false) throws RFC822Error {
|
||||
|
||||
GMime.DataWrapper? wrapper = part.get_content_object();
|
||||
if (wrapper == null) {
|
||||
throw new RFC822Error.INVALID("Could not get the content wrapper for content-type %s",
|
||||
part.get_content_type().to_string());
|
||||
}
|
||||
|
||||
ByteArray byte_array = new ByteArray();
|
||||
GMime.StreamMem stream = new GMime.StreamMem.with_byte_array(byte_array);
|
||||
stream.set_owner(false);
|
||||
|
||||
// Convert encoding to UTF-8.
|
||||
GMime.StreamFilter stream_filter = new GMime.StreamFilter(stream);
|
||||
if (to_utf8) {
|
||||
string? charset = part.get_content_type_parameter("charset");
|
||||
if (charset == null)
|
||||
charset = DEFAULT_ENCODING;
|
||||
stream_filter.add(new GMime.FilterCharset(charset, "UTF8"));
|
||||
}
|
||||
|
||||
wrapper.write_to_stream(stream_filter);
|
||||
|
||||
return new Geary.Memory.Buffer(byte_array.data, byte_array.len);
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
return message.to_string();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
public abstract class Geary.Sqlite.Database {
|
||||
internal SQLHeavy.VersionedDatabase db;
|
||||
internal File data_dir;
|
||||
internal File schema_dir;
|
||||
|
||||
private Gee.HashMap<SQLHeavy.Table, Geary.Sqlite.Table> table_map = new Gee.HashMap<
|
||||
|
|
@ -17,8 +18,9 @@ public abstract class Geary.Sqlite.Database {
|
|||
|
||||
public Database(File db_file, File schema_dir) throws Error {
|
||||
this.schema_dir = schema_dir;
|
||||
if (!db_file.get_parent().query_exists())
|
||||
db_file.get_parent().make_directory_with_parents();
|
||||
data_dir = db_file.get_parent();
|
||||
if (!data_dir.query_exists())
|
||||
data_dir.make_directory_with_parents();
|
||||
|
||||
db = new SQLHeavy.VersionedDatabase(db_file.get_path(), schema_dir.get_path());
|
||||
db.foreign_keys = true;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
|
|||
private Geary.Imap.FolderProperties? properties;
|
||||
private MessageTable message_table;
|
||||
private MessageLocationTable location_table;
|
||||
private MessageAttachmentTable attachment_table;
|
||||
private ImapMessagePropertiesTable imap_message_properties_table;
|
||||
private Geary.FolderPath path;
|
||||
|
||||
|
|
@ -47,6 +48,7 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
|
|||
|
||||
message_table = db.get_message_table();
|
||||
location_table = db.get_message_location_table();
|
||||
attachment_table = db.get_message_attachment_table();
|
||||
imap_message_properties_table = db.get_imap_message_properties_table();
|
||||
}
|
||||
|
||||
|
|
@ -232,7 +234,13 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
|
|||
MessageLocationRow location_row = new MessageLocationRow(location_table, Row.INVALID_ID,
|
||||
message_id, folder_row.id, email.id.ordering, email.position);
|
||||
yield location_table.create_async(transaction, location_row, cancellable);
|
||||
|
||||
|
||||
// Also add attachments if we have them.
|
||||
if (email.fields.fulfills(Attachment.REQUIRED_FIELDS)) {
|
||||
Gee.List<GMime.Part> attachments = email.get_message().get_attachments();
|
||||
yield save_attachments_async(transaction, attachments, message_id, cancellable);
|
||||
}
|
||||
|
||||
// only write out the IMAP email properties if they're supplied and there's something to
|
||||
// write out -- no need to create an empty row
|
||||
Geary.Imap.EmailProperties? properties = (Geary.Imap.EmailProperties?) email.properties;
|
||||
|
|
@ -251,7 +259,7 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public async Gee.List<Geary.Email>? list_email_async(int low, int count,
|
||||
Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
|
@ -325,76 +333,22 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
|
|||
|
||||
if (list == null || list.size == 0)
|
||||
return null;
|
||||
|
||||
bool partial_ok = flags.is_all_set(ListFlags.PARTIAL_OK);
|
||||
bool include_removed = flags.is_all_set(ListFlags.INCLUDE_MARKED_FOR_REMOVE);
|
||||
|
||||
|
||||
// TODO: As this loop involves multiple database operations to form an email, might make
|
||||
// sense in the future to launch each async method separately, putting the final results
|
||||
// together when all the information is fetched
|
||||
Gee.List<Geary.Email> emails = new Gee.ArrayList<Geary.Email>();
|
||||
foreach (MessageLocationRow location_row in list) {
|
||||
// PROPERTIES and FLAGS are held in separate table from messages, pull from MessageTable
|
||||
// only if something is needed from there
|
||||
Geary.Email.Field message_fields =
|
||||
required_fields.clear(Geary.Email.Field.PROPERTIES | Geary.Email.Field.FLAGS);
|
||||
|
||||
// fetch the message itself
|
||||
MessageRow? message_row = null;
|
||||
if (message_fields != Geary.Email.Field.NONE) {
|
||||
message_row = yield message_table.fetch_async(transaction, location_row.message_id,
|
||||
message_fields, cancellable);
|
||||
assert(message_row != null);
|
||||
|
||||
// only add to the list if the email contains all the required fields (because
|
||||
// properties comes out of a separate table, skip this if properties are requested)
|
||||
if (!partial_ok && !message_row.fields.fulfills(message_fields))
|
||||
continue;
|
||||
}
|
||||
|
||||
ImapMessagePropertiesRow? properties = null;
|
||||
if (required_fields.is_any_set(Geary.Email.Field.PROPERTIES | Geary.Email.Field.FLAGS)) {
|
||||
properties = yield imap_message_properties_table.fetch_async(transaction,
|
||||
location_row.message_id, cancellable);
|
||||
if (!partial_ok && properties == null)
|
||||
continue;
|
||||
}
|
||||
|
||||
Geary.Imap.UID uid = new Geary.Imap.UID(location_row.ordering);
|
||||
int position = yield location_row.get_position_async(transaction, include_removed,
|
||||
cancellable);
|
||||
if (position == -1) {
|
||||
debug("WARNING: Unable to locate position of email during list of %s, dropping",
|
||||
to_string());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Geary.Imap.EmailIdentifier email_id = new Geary.Imap.EmailIdentifier(uid);
|
||||
|
||||
Geary.Email email = (message_row != null)
|
||||
? message_row.to_email(position, email_id)
|
||||
: new Geary.Email(position, email_id);
|
||||
|
||||
if (properties != null) {
|
||||
if (required_fields.require(Geary.Email.Field.PROPERTIES)) {
|
||||
Imap.EmailProperties? email_properties = properties.get_imap_email_properties();
|
||||
if (email_properties != null)
|
||||
email.set_email_properties(email_properties);
|
||||
else if (!partial_ok)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (required_fields.require(Geary.Email.Field.FLAGS)) {
|
||||
EmailFlags? email_flags = properties.get_email_flags();
|
||||
if (email_flags != null)
|
||||
email.set_flags(email_flags);
|
||||
else if (!partial_ok)
|
||||
continue;
|
||||
try {
|
||||
emails.add(yield location_to_email_async(transaction, location_row, required_fields,
|
||||
flags, cancellable));
|
||||
} catch (EngineError error) {
|
||||
if (error is EngineError.NOT_FOUND) {
|
||||
debug("WARNING: Message not found, dropping: %s", error.message);
|
||||
} else if (!(error is EngineError.INCOMPLETE_MESSAGE)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
emails.add(email);
|
||||
}
|
||||
|
||||
return (emails.size > 0) ? emails : null;
|
||||
|
|
@ -405,8 +359,6 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
|
|||
check_open();
|
||||
|
||||
Geary.Imap.UID uid = ((Imap.EmailIdentifier) id).uid;
|
||||
bool partial_ok = flags.is_all_set(ListFlags.PARTIAL_OK);
|
||||
bool include_removed = flags.is_all_set(ListFlags.INCLUDE_MARKED_FOR_REMOVE);
|
||||
|
||||
Transaction transaction = yield db.begin_transaction_async("Folder.fetch_email_async",
|
||||
cancellable);
|
||||
|
|
@ -417,37 +369,55 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
|
|||
throw new EngineError.NOT_FOUND("No message with ID %s in folder %s", id.to_string(),
|
||||
to_string());
|
||||
}
|
||||
|
||||
|
||||
return yield location_to_email_async(transaction, location_row, required_fields, flags,
|
||||
cancellable);
|
||||
}
|
||||
|
||||
public async Geary.Email location_to_email_async(Transaction transaction,
|
||||
MessageLocationRow location_row, Geary.Email.Field required_fields, ListFlags flags,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
|
||||
// Prepare our IDs and flags.
|
||||
Geary.Imap.UID uid = new Geary.Imap.UID(location_row.ordering);
|
||||
Geary.Imap.EmailIdentifier id = new Geary.Imap.EmailIdentifier(uid);
|
||||
bool partial_ok = flags.is_all_set(ListFlags.PARTIAL_OK);
|
||||
bool include_removed = flags.is_all_set(ListFlags.INCLUDE_MARKED_FOR_REMOVE);
|
||||
|
||||
// PROPERTIES and FLAGS are held in separate table from messages, pull from MessageTable
|
||||
// only if something is needed from there
|
||||
Geary.Email.Field message_fields =
|
||||
required_fields.clear(Geary.Email.Field.PROPERTIES | Geary.Email.Field.FLAGS);
|
||||
|
||||
int position = yield location_row.get_position_async(transaction, include_removed, cancellable);
|
||||
if (position == -1) {
|
||||
throw new EngineError.NOT_FOUND("Unable to determine position of email %s in %s",
|
||||
id.to_string(), to_string());
|
||||
}
|
||||
|
||||
|
||||
// loopback on perverse case
|
||||
if (required_fields == Geary.Email.Field.NONE)
|
||||
return new Geary.Email(position, id);
|
||||
|
||||
|
||||
// Only fetch message row if we have fields other than Properties and Flags
|
||||
MessageRow? message_row = null;
|
||||
if (required_fields.clear(Geary.Email.Field.PROPERTIES | Geary.Email.Field.FLAGS) != 0) {
|
||||
message_row = yield message_table.fetch_async(transaction,
|
||||
location_row.message_id, required_fields, cancellable);
|
||||
if (message_fields != Geary.Email.Field.NONE) {
|
||||
message_row = yield message_table.fetch_async(transaction, location_row.message_id,
|
||||
message_fields, cancellable);
|
||||
if (message_row == null) {
|
||||
throw new EngineError.NOT_FOUND("No message with ID %s in folder %s", id.to_string(),
|
||||
to_string());
|
||||
}
|
||||
|
||||
|
||||
// see if the message row fulfills everything but properties, which are held in
|
||||
// separate table
|
||||
if (!partial_ok &&
|
||||
!message_row.fields.fulfills(required_fields.clear(Geary.Email.Field.PROPERTIES | Geary.Email.Field.FLAGS))) {
|
||||
if (!partial_ok && !message_row.fields.fulfills(message_fields)) {
|
||||
throw new EngineError.INCOMPLETE_MESSAGE(
|
||||
"Message %s in folder %s only fulfills %Xh fields (required: %Xh)", id.to_string(),
|
||||
to_string(), message_row.fields, required_fields);
|
||||
"Message %s in folder %s only fulfills %Xh fields (required: %Xh)",
|
||||
id.to_string(), to_string(), message_row.fields, required_fields);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ImapMessagePropertiesRow? properties = null;
|
||||
if (required_fields.is_any_set(Geary.Email.Field.PROPERTIES | Geary.Email.Field.FLAGS)) {
|
||||
properties = yield imap_message_properties_table.fetch_async(transaction,
|
||||
|
|
@ -458,19 +428,20 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
|
|||
id.to_string(), to_string());
|
||||
}
|
||||
}
|
||||
|
||||
Geary.Email email;
|
||||
email = message_row != null ? message_row.to_email(position, id) : email =
|
||||
new Geary.Email(position, id);
|
||||
|
||||
|
||||
Geary.Email email = message_row != null
|
||||
? message_row.to_email(position, id)
|
||||
: new Geary.Email(position, id);
|
||||
|
||||
if (properties != null) {
|
||||
if (required_fields.require(Geary.Email.Field.PROPERTIES)) {
|
||||
Imap.EmailProperties? email_properties = properties.get_imap_email_properties();
|
||||
if (email_properties != null) {
|
||||
email.set_email_properties(email_properties);
|
||||
} else if (!partial_ok) {
|
||||
throw new EngineError.INCOMPLETE_MESSAGE("Message %s in folder %s does not have PROPERTIES fields",
|
||||
id.to_string(), to_string());
|
||||
throw new EngineError.INCOMPLETE_MESSAGE(
|
||||
"Message %s in folder %s does not have PROPERTIES fields", id.to_string(),
|
||||
to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -479,15 +450,26 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
|
|||
if (email_flags != null) {
|
||||
email.set_flags(email_flags);
|
||||
} else if (!partial_ok) {
|
||||
throw new EngineError.INCOMPLETE_MESSAGE("Message %s in folder %s does not have FLAGS fields",
|
||||
id.to_string(), to_string());
|
||||
throw new EngineError.INCOMPLETE_MESSAGE(
|
||||
"Message %s in folder %s does not have FLAGS fields", id.to_string(),
|
||||
to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Load the attachments as well if we have the full message.
|
||||
if (required_fields.fulfills(Geary.Attachment.REQUIRED_FIELDS)) {
|
||||
Gee.List<MessageAttachmentRow> attachments = yield attachment_table.list_async(
|
||||
transaction, location_row.message_id, cancellable);
|
||||
foreach (MessageAttachmentRow row in attachments) {
|
||||
email.add_attachment(row.to_attachment());
|
||||
}
|
||||
}
|
||||
|
||||
return email;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public async Geary.Imap.UID? get_earliest_uid_async(Cancellable? cancellable = null) throws Error {
|
||||
return yield get_uid_extremes_async(true, cancellable);
|
||||
}
|
||||
|
|
@ -624,20 +606,34 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
|
|||
// if nothing to merge, nothing to do
|
||||
if (email.fields == Geary.Email.Field.NONE)
|
||||
return;
|
||||
|
||||
|
||||
// Only merge with MessageTable if has fields applicable to it
|
||||
if (email.fields.clear(Geary.Email.Field.PROPERTIES | Geary.Email.Field.FLAGS) != 0) {
|
||||
MessageRow? message_row = yield message_table.fetch_async(transaction, message_id, email.fields,
|
||||
cancellable);
|
||||
MessageRow? message_row = yield message_table.fetch_async(transaction, message_id,
|
||||
(email.fields | Attachment.REQUIRED_FIELDS), cancellable);
|
||||
|
||||
assert(message_row != null);
|
||||
|
||||
Geary.Email.Field db_fields = message_row.fields;
|
||||
message_row.merge_from_remote(email);
|
||||
|
||||
// possible nothing has changed or been added
|
||||
if (message_row.fields != Geary.Email.Field.NONE)
|
||||
|
||||
// Get the combined email from the merge which will be used below to save the attachments.
|
||||
Geary.Email combined_email = message_row.to_email(email.position, email.id);
|
||||
|
||||
// Next see if all the fields we've received are already in the DB. If they are then
|
||||
// there is nothing for us to do.
|
||||
if ((db_fields & email.fields) != email.fields) {
|
||||
yield message_table.merge_async(transaction, message_row, cancellable);
|
||||
|
||||
// Also update the saved attachments if we don't already have them in the database
|
||||
// and between the database and the new fields we have what is required.
|
||||
if (!db_fields.fulfills(Attachment.REQUIRED_FIELDS) &&
|
||||
combined_email.fields.fulfills(Attachment.REQUIRED_FIELDS)) {
|
||||
yield save_attachments_async(transaction,
|
||||
combined_email.get_message().get_attachments(), message_id, cancellable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// update IMAP properties
|
||||
if (email.fields.fulfills(Geary.Email.Field.PROPERTIES)) {
|
||||
Geary.Imap.EmailProperties properties = (Geary.Imap.EmailProperties) email.properties;
|
||||
|
|
@ -658,7 +654,63 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
|
|||
flags, cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async void save_attachments_async(Transaction transaction,
|
||||
Gee.List<GMime.Part> attachments, int64 message_id, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
|
||||
// Nothing to do if no attachments.
|
||||
if (attachments.size == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (GMime.Part attachment in attachments) {
|
||||
// Get the info about the attachment.
|
||||
string? filename = attachment.get_filename();
|
||||
string mime_type = attachment.get_content_type().to_string();
|
||||
if (filename == null || filename.length == 0) {
|
||||
filename = _("none");
|
||||
}
|
||||
|
||||
// Convert the attachment content into a usable ByteArray.
|
||||
GMime.DataWrapper attachment_data = attachment.get_content_object();
|
||||
ByteArray byte_array = new ByteArray();
|
||||
GMime.StreamMem stream = new GMime.StreamMem.with_byte_array(byte_array);
|
||||
stream.set_owner(false);
|
||||
attachment_data.write_to_stream(stream);
|
||||
uint filesize = byte_array.len;
|
||||
|
||||
// Insert it into the database.
|
||||
MessageAttachmentRow attachment_row = new MessageAttachmentRow(attachment_table, 0,
|
||||
message_id, filename, mime_type, filesize);
|
||||
int64 attachment_id = yield attachment_table.create_async(transaction, attachment_row,
|
||||
cancellable);
|
||||
|
||||
try {
|
||||
// Create the file where the attachment will be saved and get the output stream.
|
||||
string saved_name = Attachment.get_path(db.data_dir, message_id, attachment_id,
|
||||
filename);
|
||||
debug("Saving attachment to %s", saved_name);
|
||||
File saved_file = File.new_for_path(saved_name);
|
||||
saved_file.get_parent().make_directory_with_parents();
|
||||
FileOutputStream saved_stream = yield saved_file.create_async(
|
||||
FileCreateFlags.REPLACE_DESTINATION, Priority.DEFAULT, cancellable);
|
||||
|
||||
// Save the data to disk and flush it.
|
||||
yield saved_stream.write_async(byte_array.data[0:filesize], Priority.DEFAULT,
|
||||
cancellable);
|
||||
yield saved_stream.flush_async();
|
||||
} catch (Error error) {
|
||||
// An error occurred while saving the attachment, so lets remove the attachment from
|
||||
// the database.
|
||||
// TODO Use SQLite transactions here and do a rollback.
|
||||
debug("Failed to save attachment: %s", error.message);
|
||||
yield attachment_table.remove_async(transaction, attachment_id, cancellable);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void remove_marked_email_async(Geary.EmailIdentifier id, out bool marked,
|
||||
Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
|
|
|||
|
|
@ -6,10 +6,9 @@
|
|||
|
||||
public class Geary.Sqlite.MailDatabase : Geary.Sqlite.Database {
|
||||
public const string FILENAME = "geary.db";
|
||||
|
||||
|
||||
public MailDatabase(string user, File user_data_dir, File resource_dir) throws Error {
|
||||
base (user_data_dir.get_child(user).get_child(FILENAME),
|
||||
resource_dir.get_child("sql"));
|
||||
base (user_data_dir.get_child(user).get_child(FILENAME), resource_dir.get_child("sql"));
|
||||
}
|
||||
|
||||
public Geary.Sqlite.FolderTable get_folder_table() {
|
||||
|
|
@ -39,5 +38,15 @@ public class Geary.Sqlite.MailDatabase : Geary.Sqlite.Database {
|
|||
? location_table
|
||||
: (MessageLocationTable) add_table(new MessageLocationTable(this, heavy_table));
|
||||
}
|
||||
|
||||
public Geary.Sqlite.MessageAttachmentTable get_message_attachment_table() {
|
||||
SQLHeavy.Table heavy_table;
|
||||
MessageAttachmentTable? attachment_table = get_table("MessageAttachmentTable", out heavy_table)
|
||||
as MessageAttachmentTable;
|
||||
|
||||
return (attachment_table != null)
|
||||
? attachment_table
|
||||
: (MessageAttachmentTable) add_table(new MessageAttachmentTable(this, heavy_table));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
39
src/engine/sqlite/email/sqlite-message-attachment-row.vala
Normal file
39
src/engine/sqlite/email/sqlite-message-attachment-row.vala
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/* Copyright 2011-2012 Yorba Foundation
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public class Geary.Sqlite.MessageAttachmentRow : Geary.Sqlite.Row {
|
||||
public int64 id { get; private set; }
|
||||
public int64 message_id { get; private set; }
|
||||
public int64 filesize { get; private set; }
|
||||
public string filename { get; private set; }
|
||||
public string mime_type { get; private set; }
|
||||
|
||||
public MessageAttachmentRow(MessageAttachmentTable table, int64 id, int64 message_id,
|
||||
string filename, string mime_type, int64 filesize) {
|
||||
base (table);
|
||||
|
||||
this.id = id;
|
||||
this.message_id = message_id;
|
||||
this.filename = filename;
|
||||
this.mime_type = mime_type;
|
||||
this.filesize = filesize;
|
||||
}
|
||||
|
||||
public MessageAttachmentRow.from_query_result(MessageAttachmentTable table,
|
||||
SQLHeavy.QueryResult result) throws Error {
|
||||
base (table);
|
||||
|
||||
id = fetch_int64_for(result, MessageAttachmentTable.Column.ID);
|
||||
message_id = fetch_int64_for(result, MessageAttachmentTable.Column.MESSAGE_ID);
|
||||
filename = fetch_string_for(result, MessageAttachmentTable.Column.FILENAME);
|
||||
mime_type = fetch_string_for(result, MessageAttachmentTable.Column.MIME_TYPE);
|
||||
}
|
||||
|
||||
public Geary.Attachment to_attachment() {
|
||||
return new Attachment(table.gdb.data_dir, filename, mime_type, filesize, message_id, id);
|
||||
}
|
||||
}
|
||||
|
||||
90
src/engine/sqlite/email/sqlite-message-attachment-table.vala
Normal file
90
src/engine/sqlite/email/sqlite-message-attachment-table.vala
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/* Copyright 2011-2012 Yorba Foundation
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public class Geary.Sqlite.MessageAttachmentTable : Geary.Sqlite.Table {
|
||||
// This row *must* match the order in the schema
|
||||
public enum Column {
|
||||
ID,
|
||||
MESSAGE_ID,
|
||||
FILENAME,
|
||||
MIME_TYPE,
|
||||
FILESIZE
|
||||
}
|
||||
|
||||
public MessageAttachmentTable(Geary.Sqlite.Database db, SQLHeavy.Table table) {
|
||||
base (db, table);
|
||||
}
|
||||
|
||||
public async int64 create_async(Transaction? transaction, MessageAttachmentRow row,
|
||||
Cancellable? cancellable) throws Error {
|
||||
Transaction locked = yield obtain_lock_async(transaction, "MessageAttachmentTable.create_async",
|
||||
cancellable);
|
||||
|
||||
SQLHeavy.Query query = locked.prepare(
|
||||
"INSERT INTO MessageAttachmentTable (message_id, filename, mime_type, filesize) " +
|
||||
"VALUES (?, ?, ?, ?)");
|
||||
query.bind_int64(0, row.message_id);
|
||||
query.bind_string(1, row.filename);
|
||||
query.bind_string(2, row.mime_type);
|
||||
query.bind_int64(3, row.filesize);
|
||||
|
||||
int64 id = yield query.execute_insert_async(cancellable);
|
||||
locked.set_commit_required();
|
||||
|
||||
yield release_lock_async(transaction, locked, cancellable);
|
||||
|
||||
check_cancel(cancellable, "create_async");
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public async Gee.List<MessageAttachmentRow>? list_async(Transaction? transaction,
|
||||
int64 message_id, Cancellable? cancellable) throws Error {
|
||||
|
||||
Transaction locked = yield obtain_lock_async(transaction, "MessageAttachmentTable.list_async",
|
||||
cancellable);
|
||||
|
||||
SQLHeavy.Query query = locked.prepare(
|
||||
"SELECT id, filename, mime_type, filesize FROM MessageAttachmentTable " +
|
||||
"WHERE message_id = ? ORDER BY id");
|
||||
query.bind_int64(0, message_id);
|
||||
|
||||
SQLHeavy.QueryResult results = yield query.execute_async();
|
||||
check_cancel(cancellable, "list_async");
|
||||
|
||||
Gee.List<MessageAttachmentRow> list = new Gee.ArrayList<MessageAttachmentRow>();
|
||||
if (results.finished)
|
||||
return list;
|
||||
|
||||
do {
|
||||
list.add(new MessageAttachmentRow(this, results.fetch_int64(0), message_id,
|
||||
results.fetch_string(1), results.fetch_string(2), results.fetch_int64(3)));
|
||||
|
||||
yield results.next_async();
|
||||
|
||||
check_cancel(cancellable, "list_async");
|
||||
} while (!results.finished);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public async void remove_async(Transaction? transaction, int64 attachment_id,
|
||||
Cancellable? cancellable) throws Error {
|
||||
|
||||
Transaction locked = yield obtain_lock_async(transaction,
|
||||
"MessageAttachmentTable.remove_async", cancellable);
|
||||
|
||||
SQLHeavy.Query query = locked.prepare(
|
||||
"DELETE FROM MessageAttachmentTable WHERE attachment_id = ?");
|
||||
query.bind_int64(0, attachment_id);
|
||||
|
||||
yield query.execute_async();
|
||||
locked.set_commit_required();
|
||||
yield release_lock_async(transaction, locked, cancellable);
|
||||
check_cancel(cancellable, "remove_async");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,8 +132,6 @@ public class Geary.Sqlite.MessageRow : Geary.Sqlite.Row {
|
|||
foreach (Geary.Email.Field field in Geary.Email.Field.all()) {
|
||||
if ((email.fields & field) != 0)
|
||||
set_from_email(field, email);
|
||||
else
|
||||
unset_fields(field);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -224,62 +222,5 @@ public class Geary.Sqlite.MessageRow : Geary.Sqlite.Row {
|
|||
this.fields = this.fields.set(Geary.Email.Field.PREVIEW);
|
||||
}
|
||||
}
|
||||
|
||||
private void unset_fields(Geary.Email.Field fields) {
|
||||
if ((fields & Geary.Email.Field.DATE) != 0) {
|
||||
date = null;
|
||||
date_time_t = -1;
|
||||
|
||||
this.fields = this.fields.clear(Geary.Email.Field.DATE);
|
||||
}
|
||||
|
||||
if ((fields & Geary.Email.Field.ORIGINATORS) != 0) {
|
||||
from = null;
|
||||
sender = null;
|
||||
reply_to = null;
|
||||
|
||||
this.fields = this.fields.clear(Geary.Email.Field.ORIGINATORS);
|
||||
}
|
||||
|
||||
if ((fields & Geary.Email.Field.RECEIVERS) != 0) {
|
||||
to = null;
|
||||
cc = null;
|
||||
bcc = null;
|
||||
|
||||
this.fields = this.fields.clear(Geary.Email.Field.RECEIVERS);
|
||||
}
|
||||
|
||||
if ((fields & Geary.Email.Field.REFERENCES) != 0) {
|
||||
message_id = null;
|
||||
in_reply_to = null;
|
||||
references = null;
|
||||
|
||||
this.fields = this.fields.clear(Geary.Email.Field.REFERENCES);
|
||||
}
|
||||
|
||||
if ((fields & Geary.Email.Field.SUBJECT) != 0) {
|
||||
subject = null;
|
||||
|
||||
this.fields = this.fields.clear(Geary.Email.Field.SUBJECT);
|
||||
}
|
||||
|
||||
if ((fields & Geary.Email.Field.HEADER) != 0) {
|
||||
header = null;
|
||||
|
||||
this.fields = this.fields.clear(Geary.Email.Field.HEADER);
|
||||
}
|
||||
|
||||
if ((fields & Geary.Email.Field.BODY) != 0) {
|
||||
body = null;
|
||||
|
||||
this.fields = this.fields.clear(Geary.Email.Field.BODY);
|
||||
}
|
||||
|
||||
if ((fields & Geary.Email.Field.PREVIEW) != 0) {
|
||||
preview = null;
|
||||
|
||||
this.fields = this.fields.clear(Geary.Email.Field.PREVIEW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue