Commit 0e185be7 authored by Eric Gregory's avatar Eric Gregory

Closes #7236 Replace previous draft

parent 4e5349d1
......@@ -167,6 +167,11 @@ public class ComposerWindow : Gtk.Window {
private bool is_attachment_overlay_visible = false;
private Gee.List<Geary.Attachment>? pending_attachments = null;
private Geary.FolderSupport.Create? drafts_folder = null;
private Geary.EmailIdentifier? draft_id = null;
private Cancellable cancellable_drafts = new Cancellable();
private string default_save_label = "";
private WebKit.WebView editor;
// We need to keep a reference to the edit-fixer in composer-window, so it doesn't get
// garbage-collected.
......@@ -431,6 +436,10 @@ public class ComposerWindow : Gtk.Window {
chain.append(attachments_box);
chain.append(button_area);
box.set_focus_chain(chain);
actions.get_action(ACTION_SAVE).sensitive = false;
default_save_label = actions.get_action(ACTION_SAVE).label;
open_drafts_folder.begin(cancellable_drafts); // Open drafts folder for initial account.
}
public ComposerWindow.from_mailto(Geary.Account account, string mailto) {
......@@ -741,30 +750,57 @@ public class ComposerWindow : Gtk.Window {
}
// Returns the drafts folder for the current From account.
private Geary.Folder? get_drafts_folder() {
Geary.Folder? drafts_folder = null;
try {
drafts_folder = account.get_special_folder(Geary.SpecialFolderType.DRAFTS);
} catch (Error e) {
debug("Error getting drafts folder: %s", e.message);
private async void open_drafts_folder(Cancellable cancellable) throws Error {
if (drafts_folder != null) {
// Close existing folder.
yield drafts_folder.close_async(cancellable);
drafts_folder = null;
draft_id = null;
}
return drafts_folder;
actions.get_action(ACTION_SAVE).sensitive = false;
Geary.FolderSupport.Create? folder = account.get_special_folder(Geary.SpecialFolderType.DRAFTS)
as Geary.FolderSupport.Create;
if (folder == null)
return; // No drafts folder.
yield folder.open_async(Geary.Folder.OpenFlags.FAST_OPEN, cancellable);
// Only show Save button if we have a drafts folder to write to.
actions.get_action(ACTION_SAVE).sensitive = true;
drafts_folder = folder;
}
// Save to the draft folder, if available.
// Note that drafts are NOT "linkified."
private void on_save() {
Geary.Folder? drafts_folder = get_drafts_folder();
save_async.begin(on_save_completed);
}
private async void save_async() {
if (drafts_folder == null) {
stdout.printf("No drafts folder available for this account.\n");
warning("No drafts folder available for this account.");
return;
}
account.create_email_async.begin(drafts_folder.path,
new Geary.RFC822.Message.from_composed_email(get_composed_email()),
new Geary.EmailFlags(), null, null);
actions.get_action(ACTION_SAVE).sensitive = false;
actions.get_action(ACTION_SAVE).set_label(_("Saving..."));
try {
draft_id = yield drafts_folder.create_email_async(new Geary.RFC822.Message.from_composed_email(
get_composed_email()), new Geary.EmailFlags(), null, draft_id, null);
} catch (Error e) {
warning("Error saving draft: %s", e.message);
}
}
private void on_save_completed() {
actions.get_action(ACTION_SAVE).sensitive = true;
actions.get_action(ACTION_SAVE).set_label(default_save_label);
}
private void on_add_attachment_button_clicked() {
......@@ -1500,6 +1536,8 @@ public class ComposerWindow : Gtk.Window {
if (compose_type != ComposeType.NEW_MESSAGE)
return;
actions.get_action(ACTION_SAVE).sensitive = false;
// Since we've set the combo box ID to the email addresses, we can
// fetch that and use it to grab the account from the engine.
string? id = from_multiple.get_active_id();
......@@ -1512,14 +1550,13 @@ public class ComposerWindow : Gtk.Window {
account = Geary.Engine.instance.get_account_instance(new_account_info);
from = new_account_info.get_from().to_rfc822_string();
set_entry_completions();
open_drafts_folder.begin(cancellable_drafts);
}
} catch (Error e) {
debug("Error updating account in Composer: %s", e.message);
}
}
// Only show Save button if we have a drafts folder to write to.
actions.get_action(ACTION_SAVE).visible = get_drafts_folder() != null;
}
private void set_entry_completions() {
......
......@@ -109,30 +109,6 @@ public abstract class Geary.AbstractAccount : BaseObject, Geary.Account {
public abstract async Gee.Collection<string>? get_search_matches_async(
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable = null) throws Error;
public virtual async void create_email_async(Geary.FolderPath path, Geary.RFC822.Message rfc822,
Geary.EmailFlags? flags, DateTime? date_received, Cancellable? cancellable = null) throws Error {
Folder folder = yield fetch_folder_async(path, cancellable);
FolderSupport.Create? supports_create = folder as FolderSupport.Create;
if (supports_create == null)
throw new EngineError.UNSUPPORTED("Folder %s does not support create", path.to_string());
yield supports_create.open_async(Folder.OpenFlags.NONE, cancellable);
// don't leave folder open if create fails
Error? create_err = null;
try {
yield supports_create.create_email_async(rfc822, flags, date_received, cancellable);
} catch (Error err) {
create_err = err;
}
yield supports_create.close_async(cancellable);
if (create_err != null)
throw create_err;
}
public virtual string to_string() {
return name;
}
......
......@@ -275,30 +275,6 @@ public interface Geary.Account : BaseObject {
public abstract async Gee.Collection<string>? get_search_matches_async(
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable = null) throws Error;
/**
* Creates (appends) the message to the specified {@link Folder}.
*
* Some implementations may locate the Folder for the caller, check that it supports
* {@link FolderSupport.Create}, then call its create method. In that case, the usual
* {@link EngineError} exceptions apply, except for the requirement that the Folder be open.
* (@link Account} will open the Folder for the caller.)
*
* For other Folders, the Account may be able to bypass opening the folder and save it directly.
*
* The optional {@link EmailFlags} allows for those flags to be set when saved. Some Folders
* may ignore those flags (i.e. Outbox) if not applicable.
*
* The optional DateTime allows for the message's "date received" time to be set when saved.
* Like EmailFlags, this is optional if not applicable.
*
* Both values can be retrieved later via {@link EmailProperties}.
*
* @throws EngineError.NOT_FOUND If {@link FolderPath} could not be resolved.
* @throws EngineError.UNSUPPORTED If the Folder does not support FolderSupport.Create.
*/
public abstract async void create_email_async(Geary.FolderPath path, Geary.RFC822.Message rfc822,
Geary.EmailFlags? flags, DateTime? date_received, Cancellable? cancellable = null) throws Error;
/**
* Used only for debugging. Should not be used for user-visible strings.
*/
......
......@@ -15,11 +15,20 @@
*/
public interface Geary.FolderSupport.Create : Geary.Folder {
/**
* Creates a message in the folder. If the message already exists in the {@link Geary.Folder},
* it will be merged (that is, fields in the message not already present will be added).
* Creates (appends) the message to this folder.
*
* The Folder must be opened prior to attempting this operation.
*
* The optional {@link EmailFlags} allows for those flags to be set when saved. Some Folders
* may ignore those flags (i.e. Outbox) if not applicable.
*
* The optional DateTime allows for the message's "date received" time to be set when saved.
* Like EmailFlags, this is optional if not applicable.
*
* If an id is passed, this will replace the existing message by deleting it after the new
* message is created. The new message's ID is returned.
*/
public abstract async void create_email_async(Geary.RFC822.Message rfc822, EmailFlags? flags,
DateTime? date_received, Cancellable? cancellable = null) throws Error;
public abstract async Geary.EmailIdentifier? create_email_async(Geary.RFC822.Message rfc822, EmailFlags? flags,
DateTime? date_received, Geary.EmailIdentifier? id = null, Cancellable? cancellable = null) throws Error;
}
......@@ -274,11 +274,11 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractLocalFolder, Geary.FolderSu
return row.outbox_id;
}
public virtual async void create_email_async(Geary.RFC822.Message rfc822, EmailFlags? flags,
DateTime? date_received, Cancellable? cancellable = null) throws Error {
public virtual async Geary.EmailIdentifier? create_email_async(Geary.RFC822.Message rfc822, EmailFlags? flags,
DateTime? date_received, Geary.EmailIdentifier? id = null, Cancellable? cancellable = null) throws Error {
check_open();
yield enqueue_email_async(rfc822, cancellable);
return yield enqueue_email_async(rfc822, cancellable);
}
public override async Gee.List<Geary.Email>? list_email_by_id_async(
......
......@@ -536,20 +536,6 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
return yield local.get_search_matches_async(previous_prepared_search_query, ids, cancellable);
}
public override async void create_email_async(FolderPath path, RFC822.Message rfc822, Geary.EmailFlags? flags,
DateTime? date_received, Cancellable? cancellable = null) throws Error {
// local folders go through normal paths
Folder? folder = local_only.get(path);
if (folder != null) {
yield base.create_email_async(path, rfc822, flags, date_received, cancellable);
return;
}
// use IMAP APPEND command on remote folders, which doesn't require opening a folder
yield remote.create_email_async(path, rfc822, flags, date_received, cancellable);
}
private void on_login_failed(Geary.Credentials? credentials) {
do_login_failed_async.begin(credentials);
}
......
......@@ -8,7 +8,8 @@
//
// Service-specific accounts can use this or subclass it for further customization
private class Geary.ImapEngine.GenericDraftsFolder : GenericFolder, Geary.FolderSupport.Remove {
private class Geary.ImapEngine.GenericDraftsFolder : GenericFolder, Geary.FolderSupport.Remove,
Geary.FolderSupport.Create {
public GenericDraftsFolder(GenericAccount account, Imap.Account remote, ImapDB.Account local,
ImapDB.Folder local_folder, SpecialFolderType special_folder_type) {
base (account, remote, local, local_folder, special_folder_type);
......@@ -18,5 +19,10 @@ private class Geary.ImapEngine.GenericDraftsFolder : GenericFolder, Geary.Folder
Cancellable? cancellable = null) throws Error {
yield expunge_email_async(email_ids, cancellable);
}
public new async Geary.EmailIdentifier? create_email_async(RFC822.Message message, Geary.EmailFlags? flags,
DateTime? date_received, Geary.EmailIdentifier? id, Cancellable? cancellable) throws Error {
return yield base.create_email_async(message, flags, date_received, id, cancellable);
}
}
......@@ -1107,5 +1107,27 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
// now the EmailIdentifier should be valid, but use the one generated by the list operation
return list[0].id;
}
internal async Geary.EmailIdentifier create_email_async(RFC822.Message rfc822, Geary.EmailFlags? flags,
DateTime? date_received, Geary.EmailIdentifier? id, Cancellable? cancellable = null) throws Error {
yield wait_for_open_async(cancellable);
check_open("create_email_async");
if (id != null)
check_id("create_email_async", id);
// use IMAP APPEND command on remote folders, which doesn't require opening a folder
Geary.EmailIdentifier ret = yield remote_folder.create_email_async(rfc822, flags,
date_received, cancellable);
// Remove old message.
if (id != null && this is Geary.FolderSupport.Remove) {
Gee.List<EmailIdentifier> id_list = new Gee.ArrayList<EmailIdentifier>();
id_list.add(id);
yield expunge_email_async(id_list, cancellable);
}
return ret;
}
}
......@@ -341,30 +341,6 @@ private class Geary.Imap.Account : BaseObject {
return (list_results.size > 0) ? list_results : null;
}
public async void create_email_async(Geary.FolderPath path, RFC822.Message message,
Geary.EmailFlags? flags, DateTime? date_received, Cancellable? cancellable) throws Error {
check_open();
Geary.FolderPath? processed = normalize_inbox(path);
if (processed == null)
throw new ImapError.INVALID("Invalid path %s", path.to_string());
MessageFlags? msg_flags = null;
if (flags != null) {
Imap.EmailFlags imap_flags = Imap.EmailFlags.from_api_email_flags(flags);
msg_flags = imap_flags.message_flags;
}
InternalDate? internaldate = null;
if (date_received != null)
internaldate = new InternalDate.from_date_time(date_received);
AppendCommand cmd = new AppendCommand(new MailboxSpecifier.from_folder_path(processed, null),
msg_flags, internaldate, message.get_network_buffer(false));
yield send_command_async(cmd, null, null, cancellable);
}
private async StatusResponse send_command_async(Command cmd,
Gee.List<MailboxInformation>? list_results, Gee.List<StatusData>? status_results,
Cancellable? cancellable) throws Error {
......
......@@ -261,13 +261,12 @@ private class Geary.Imap.Folder : BaseObject {
}
// All commands must executed inside the cmd_mutex; returns FETCH or STORE results
private async void exec_commands_async(Gee.Collection<Command> cmds,
private async Gee.Map<Command, StatusResponse>? exec_commands_async(Gee.Collection<Command> cmds,
out Gee.HashMap<SequenceNumber, FetchedData>? fetched,
out Gee.TreeSet<Imap.UID>? search_results, Cancellable? cancellable) throws Error {
int token = yield cmd_mutex.claim_async(cancellable);
// execute commands with mutex locked
Gee.Map<Command, StatusResponse>? responses = null;
// execute commands with mutex locked
Error? err = null;
try {
responses = yield session.send_multiple_commands_async(cmds, cancellable);
......@@ -300,6 +299,8 @@ private class Geary.Imap.Folder : BaseObject {
assert(responses != null);
foreach (Command cmd in responses.keys)
throw_on_failed_status(responses.get(cmd), cmd);
return responses;
}
private void throw_on_failed_status(StatusResponse response, Command cmd) throws Error {
......@@ -522,7 +523,7 @@ private class Geary.Imap.Folder : BaseObject {
new MailboxSpecifier.from_folder_path(destination, null));
Gee.Collection<Command> cmds = new Collection.SingleItem<Command>(cmd);
yield exec_commands_async(cmds, null, null, cancellable);
yield exec_commands_async(cmds, null, null, cancellable);
}
// TODO: Support MOVE extension
......@@ -835,6 +836,39 @@ private class Geary.Imap.Folder : BaseObject {
return email;
}
internal async Geary.EmailIdentifier? create_email_async(RFC822.Message message, Geary.EmailFlags? flags,
DateTime? date_received, Cancellable? cancellable) throws Error {
check_open();
MessageFlags? msg_flags = null;
if (flags != null) {
Imap.EmailFlags imap_flags = Imap.EmailFlags.from_api_email_flags(flags);
msg_flags = imap_flags.message_flags;
}
InternalDate? internaldate = null;
if (date_received != null)
internaldate = new InternalDate.from_date_time(date_received);
AppendCommand cmd = new AppendCommand(new MailboxSpecifier.from_folder_path(path, null),
msg_flags, internaldate, message.get_network_buffer(false));
Gee.Map<Command, StatusResponse> responses = yield exec_commands_async(
new Collection.SingleItem<AppendCommand>(cmd), null, null, cancellable);
// Grab the response and parse out the UID, if available.
StatusResponse response = responses.get(cmd);
if (response.status == Status.OK && response.response_code != null &&
response.response_code.get_response_code_type().is_value("appenduid")) {
UID new_id = new UID(response.response_code.get_as_string(2).as_int());
return new Geary.Imap.EmailIdentifier(new_id, path);
}
// We didn't get a UID back from the server.
return null;
}
private bool required_but_not_set(Geary.Email.Field check, Geary.Email.Field users_fields, Geary.Email email) {
return users_fields.require(check) ? !email.fields.is_all_set(check) : false;
}
......
......@@ -17,6 +17,7 @@
public class Geary.Imap.ResponseCodeType : BaseObject, Gee.Hashable<ResponseCodeType> {
public const string ALERT = "alert";
public const string ALREADYEXISTS = "alreadyexists";
public const string APPENDUID = "appenduid";
public const string AUTHENTICATIONFAILED = "authenticationfailed";
public const string AUTHORIZATIONFAILED = "authorizationfailed";
public const string BADCHARSET = "badcharset";
......
......@@ -145,6 +145,7 @@
<child>
<object class="GtkAction" id="save">
<property name="label" translatable="yes">Sa_ve Draft</property>
<property name="visible">False</property>
</object>
</child>
</object>
......@@ -682,7 +683,7 @@
</packing>
</child>
<child>
<object class="GtkButton" id="save_draft">
<object class="GtkButton" id="save_draft_button">
<property name="related_action">save</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
......
......@@ -28,4 +28,6 @@
<accelerator action="insertlink" />
<accelerator action="close" />
<accelerator action="save" />
</ui>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment