Closes #7236 Replace previous draft
This commit is contained in:
parent
4e5349d13d
commit
0e185be7be
13 changed files with 140 additions and 114 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue