diff --git a/src/engine/api/geary-account.vala b/src/engine/api/geary-account.vala index 3dbd5d1f..ae0fc056 100644 --- a/src/engine/api/geary-account.vala +++ b/src/engine/api/geary-account.vala @@ -1,4 +1,6 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018-2019 Michael Gratton . * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -304,6 +306,20 @@ public abstract class Geary.Account : BaseObject { */ public abstract async void rebuild_async(Cancellable? cancellable = null) throws Error; + /** + * Returns an email identifier from its serialised form. + * + * This is useful for converting a string representation of a + * email id back into an actual instance of an id. This does not + * guarantee that the email represented by the id will exist. + * + * @see EmailIdentifier.to_variant + * @throws EngineError.BAD_PARAMETERS when the variant is not the + * have the correct type. + */ + public abstract EmailIdentifier to_email_identifier(GLib.Variant serialised) + throws EngineError.BAD_PARAMETERS; + /** * Lists all the currently-available folders found under the parent path * unless it's null, in which case it lists all the root folders. If the diff --git a/src/engine/api/geary-email-identifier.vala b/src/engine/api/geary-email-identifier.vala index 017faef6..28d514f2 100644 --- a/src/engine/api/geary-email-identifier.vala +++ b/src/engine/api/geary-email-identifier.vala @@ -1,7 +1,9 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2019 Michael Gratton . * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. + * (version 2.1 or later). See the COPYING file in this distribution. */ /** @@ -28,6 +30,25 @@ public abstract class Geary.EmailIdentifier : BaseObject, Gee.Hashable. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -12,11 +14,14 @@ * * @see FolderRoot */ - public class Geary.FolderPath : BaseObject, Gee.Hashable, Gee.Comparable { + /** Type of the GLib.Variant used to represent folder paths */ + public const string VARIANT_TYPE = "as"; + + // Workaround for Vala issue #659. See children below. private class FolderPathWeakRef { @@ -218,7 +223,21 @@ public class Geary.FolderPath : } /** - * Returns a string version of the path using a default separator. + * Returns a representation useful for serialisation. + * + * This can be used to transmit folder paths as D-Bus method and + * GLib Action parameters, and so on. + * + * @returns a serialised form of this path, that will match the + * GVariantType specified by {@link VARIANT_TYPE}. + * @see FolderRoot.from_folder_path + */ + public GLib.Variant to_variant() { + return new GLib.Variant.strv(as_array()); + } + + /** + * Returns a representation useful for debugging. * * Do not use this for obtaining an IMAP mailbox name to send to a * server, use {@link @@ -287,6 +306,7 @@ public class Geary.FolderPath : } + /** * The root of a folder hierarchy. * @@ -314,4 +334,24 @@ public class Geary.FolderRoot : FolderPath { this.default_case_sensitivity = default_case_sensitivity; } + /** + * Reconstructs a path under this root from a GLib variant. + * + * @see FolderPath.to_variant + */ + public FolderPath from_variant(GLib.Variant serialised) + throws EngineError { + if (serialised.get_type_string() != VARIANT_TYPE) { + throw new EngineError.BAD_PARAMETERS( + "Invalid serialised id type: %s", serialised.get_type_string() + ); + } + + FolderPath path = this; + foreach (string step in serialised.get_strv()) { + path = path.get_child(step); + } + return path; + } + } diff --git a/src/engine/imap-db/imap-db-email-identifier.vala b/src/engine/imap-db/imap-db-email-identifier.vala index 734e5ea7..9829d52d 100644 --- a/src/engine/imap-db/imap-db-email-identifier.vala +++ b/src/engine/imap-db/imap-db-email-identifier.vala @@ -1,10 +1,17 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018-2019 Michael Gratton . * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. + * (version 2.1 or later). See the COPYING file in this distribution. */ private class Geary.ImapDB.EmailIdentifier : Geary.EmailIdentifier { + + + private const string VARIANT_TYPE = "(yxx)"; + + public int64 message_id { get; private set; } public Imap.UID? uid { get; private set; } @@ -26,6 +33,22 @@ private class Geary.ImapDB.EmailIdentifier : Geary.EmailIdentifier { this.uid = uid; } + /** Reconstructs an identifier from its variant representation. */ + public EmailIdentifier.from_variant(GLib.Variant serialised) + throws EngineError.BAD_PARAMETERS { + if (serialised.get_type_string() != VARIANT_TYPE) { + throw new EngineError.BAD_PARAMETERS( + "Invalid serialised id type: %s", serialised.get_type_string() + ); + } + Imap.UID? uid = null; + int64 uid_value = serialised.get_child_value(2).get_int64(); + if (uid_value >= 0) { + uid = new Imap.UID(uid_value); + } + this(serialised.get_child_value(1).get_int64(), uid); + } + // Used to promote an id created with no_message_id to one that has a // message id. Warning: this causes the hash value to change, so if you // have any EmailIdentifiers in a hashed data structure, this will cause @@ -55,6 +78,17 @@ private class Geary.ImapDB.EmailIdentifier : Geary.EmailIdentifier { return uid.compare_to(other.uid); } + public override GLib.Variant to_variant() { + // Return a tuple to satisfy the API contract, add an 'i' to + // inform GenericAccount that it's an IMAP id. + int64 uid_value = this.uid != null ? this.uid.value : -1; + return new GLib.Variant.tuple(new Variant[] { + new GLib.Variant.byte('i'), + new GLib.Variant.int64(this.message_id), + new GLib.Variant.int64(uid_value) + }); + } + public override string to_string() { return "[%s/%s]".printf(message_id.to_string(), (uid == null ? "null" : uid.to_string())); } @@ -68,4 +102,5 @@ private class Geary.ImapDB.EmailIdentifier : Geary.EmailIdentifier { return uids; } + } diff --git a/src/engine/imap-engine/imap-engine-generic-account.vala b/src/engine/imap-engine/imap-engine-generic-account.vala index 1c997089..908d8d94 100644 --- a/src/engine/imap-engine/imap-engine-generic-account.vala +++ b/src/engine/imap-engine/imap-engine-generic-account.vala @@ -25,6 +25,9 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { Geary.SpecialFolderType.ARCHIVE, }; + private static GLib.VariantType email_id_type = new GLib.VariantType("(y*)"); + + /** Service for incoming IMAP connections. */ public Imap.ClientService imap { get; private set; } @@ -412,6 +415,23 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { } } + /** {@inheritDoc} */ + public override EmailIdentifier to_email_identifier(GLib.Variant serialised) + throws EngineError.BAD_PARAMETERS { + if (serialised.is_of_type(GenericAccount.email_id_type)) { + throw new EngineError.BAD_PARAMETERS( + "Invalid outer serialised type: (y*)" + ); + } + char type = (char) serialised.get_child_value(0).get_byte(); + if (type == 'i') + return new ImapDB.EmailIdentifier.from_variant(serialised); + if (type == 's') + return new Outbox.EmailIdentifier.from_variant(serialised); + + throw new EngineError.BAD_PARAMETERS("Unknown serialised type: %c", type); + } + public override Gee.Collection list_matching_folders(Geary.FolderPath? parent) throws Error { check_open(); diff --git a/src/engine/imap/api/imap-folder-root.vala b/src/engine/imap/api/imap-folder-root.vala index 0f8a39ee..bbe525b6 100644 --- a/src/engine/imap/api/imap-folder-root.vala +++ b/src/engine/imap/api/imap-folder-root.vala @@ -1,5 +1,6 @@ /* * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2019 Michael Gratton . * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -17,7 +18,7 @@ public class Geary.Imap.FolderRoot : Geary.FolderRoot { - /** + /** * The canonical path for the IMAP inbox. * * This specific path object will always be returned when a child diff --git a/src/engine/outbox/outbox-email-identifier.vala b/src/engine/outbox/outbox-email-identifier.vala index 2828eeb9..f3b93a62 100644 --- a/src/engine/outbox/outbox-email-identifier.vala +++ b/src/engine/outbox/outbox-email-identifier.vala @@ -1,5 +1,6 @@ /* * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018-2019 Michael Gratton . * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -8,14 +9,30 @@ private class Geary.Outbox.EmailIdentifier : Geary.EmailIdentifier { + private const string VARIANT_TYPE = "(yxx)"; + + public int64 message_id { get; private set; } public int64 ordering { get; private set; } public EmailIdentifier(int64 message_id, int64 ordering) { base("Outbox.EmailIdentifier:%s".printf(message_id.to_string())); + this.message_id = message_id; this.ordering = ordering; } + internal EmailIdentifier.from_variant(GLib.Variant serialised) + throws EngineError.BAD_PARAMETERS { + if (serialised.get_type_string() != VARIANT_TYPE) { + throw new EngineError.BAD_PARAMETERS( + "Invalid serialised id type: %s", serialised.get_type_string() + ); + } + GLib.Variant mid = serialised.get_child_value(1); + GLib.Variant uid = serialised.get_child_value(2); + this(mid.get_int64(), uid.get_int64()); + } + public override int natural_sort_comparator(Geary.EmailIdentifier o) { EmailIdentifier? other = o as EmailIdentifier; if (other == null) { @@ -24,4 +41,14 @@ private class Geary.Outbox.EmailIdentifier : Geary.EmailIdentifier { return (int) (ordering - other.ordering).clamp(-1, 1); } + public override GLib.Variant to_variant() { + // Return a tuple to satisfy the API contract, add an 's' to + // inform GenericAccount that it's an SMTP id. + return new GLib.Variant.tuple(new Variant[] { + new GLib.Variant.byte('s'), + new GLib.Variant.int64(this.message_id), + new GLib.Variant.int64(this.ordering) + }); + } + } diff --git a/test/engine/api/geary-account-mock.vala b/test/engine/api/geary-account-mock.vala index b605d9d1..fb1189a6 100644 --- a/test/engine/api/geary-account-mock.vala +++ b/test/engine/api/geary-account-mock.vala @@ -120,6 +120,21 @@ public class Geary.MockAccount : Account, MockObject { } } + public override EmailIdentifier to_email_identifier(GLib.Variant serialised) + throws EngineError.BAD_PARAMETERS { + try { + return object_or_throw_call( + "to_email_identifier", + { box_arg(serialised) }, + new EngineError.BAD_PARAMETERS("Mock error") + ); + } catch (EngineError.BAD_PARAMETERS err) { + throw err; + } catch (GLib.Error err) { + return new MockEmailIdentifer(0); + } + } + public override Gee.Collection list_folders() throws Error { return object_call>( "list_folders", {}, Gee.List.empty() diff --git a/test/engine/api/geary-email-identifier-mock.vala b/test/engine/api/geary-email-identifier-mock.vala index 83367ead..2928ca16 100644 --- a/test/engine/api/geary-email-identifier-mock.vala +++ b/test/engine/api/geary-email-identifier-mock.vala @@ -21,4 +21,8 @@ public class Geary.MockEmailIdentifer : EmailIdentifier { return (other_mock == null) ? 1 : this.id - other_mock.id; } + public override GLib.Variant to_variant() { + return new GLib.Variant.int32(id); + } + } diff --git a/test/engine/api/geary-folder-path-test.vala b/test/engine/api/geary-folder-path-test.vala index 9f9a5f6f..952ca44c 100644 --- a/test/engine/api/geary-folder-path-test.vala +++ b/test/engine/api/geary-folder-path-test.vala @@ -26,6 +26,7 @@ public class Geary.FolderPathTest : TestCase { add_test("path_compare", path_compare); add_test("path_compare_normalised", path_compare_normalised); add_test("distinct_roots_compare", distinct_roots_compare); + add_test("variant_representation", variant_representation); } public override void set_up() { @@ -305,4 +306,12 @@ public class Geary.FolderPathTest : TestCase { } + public void variant_representation() throws GLib.Error { + FolderPath orig = this.root.get_child("test"); + GLib.Variant variant = orig.to_variant(); + FolderPath copy = this.root.from_variant(variant); + + assert_true(orig.equal_to(copy)); + } + } diff --git a/test/engine/imap-db/imap-db-email-identifier-test.vala b/test/engine/imap-db/imap-db-email-identifier-test.vala new file mode 100644 index 00000000..a8d04311 --- /dev/null +++ b/test/engine/imap-db/imap-db-email-identifier-test.vala @@ -0,0 +1,26 @@ +/* + * Copyright 2019 Michael Gratton + * + * 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.ImapDB.EmailIdentifierTest : TestCase { + + + public EmailIdentifierTest() { + base("Geary.ImapDB.EmailIdentifierTest"); + add_test("variant_representation", variant_representation); + } + + public void variant_representation() throws GLib.Error { + EmailIdentifier orig = new EmailIdentifier( + 123, new Imap.UID(321) + ); + GLib.Variant variant = orig.to_variant(); + EmailIdentifier copy = new EmailIdentifier.from_variant(variant); + + assert_true(orig.equal_to(copy)); + } + +} diff --git a/test/engine/outbox/outbox-email-identifier-test.vala b/test/engine/outbox/outbox-email-identifier-test.vala new file mode 100644 index 00000000..ad14ae9b --- /dev/null +++ b/test/engine/outbox/outbox-email-identifier-test.vala @@ -0,0 +1,24 @@ +/* + * Copyright 2019 Michael Gratton + * + * 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.Outbox.EmailIdentifierTest : TestCase { + + + public EmailIdentifierTest() { + base("Geary.Outbox.EmailIdentifierTest"); + add_test("variant_representation", variant_representation); + } + + public void variant_representation() throws GLib.Error { + EmailIdentifier orig = new EmailIdentifier(123, 321); + GLib.Variant variant = orig.to_variant(); + EmailIdentifier copy = new EmailIdentifier.from_variant(variant); + + assert_true(orig.equal_to(copy)); + } + +} diff --git a/test/meson.build b/test/meson.build index d93cd883..143d40fd 100644 --- a/test/meson.build +++ b/test/meson.build @@ -40,9 +40,11 @@ geary_test_engine_sources = [ 'engine/imap-db/imap-db-account-test.vala', 'engine/imap-db/imap-db-attachment-test.vala', 'engine/imap-db/imap-db-database-test.vala', + 'engine/imap-db/imap-db-email-identifier-test.vala', 'engine/imap-db/imap-db-folder-test.vala', 'engine/imap-engine/account-processor-test.vala', 'engine/mime-content-type-test.vala', + 'engine/outbox/outbox-email-identifier-test.vala', 'engine/rfc822-mailbox-address-test.vala', 'engine/rfc822-mailbox-addresses-test.vala', 'engine/rfc822-message-test.vala', diff --git a/test/test-engine.vala b/test/test-engine.vala index 6a9a17a2..8a575708 100644 --- a/test/test-engine.vala +++ b/test/test-engine.vala @@ -50,11 +50,13 @@ int main(string[] args) { engine.add_suite(new Geary.ImapDB.AttachmentTest().get_suite()); engine.add_suite(new Geary.ImapDB.AttachmentIoTest().get_suite()); engine.add_suite(new Geary.ImapDB.DatabaseTest().get_suite()); + engine.add_suite(new Geary.ImapDB.EmailIdentifierTest().get_suite()); engine.add_suite(new Geary.ImapDB.FolderTest().get_suite()); engine.add_suite(new Geary.ImapEngine.AccountProcessorTest().get_suite()); engine.add_suite(new Geary.Inet.Test().get_suite()); engine.add_suite(new Geary.JS.Test().get_suite()); engine.add_suite(new Geary.Mime.ContentTypeTest().get_suite()); + engine.add_suite(new Geary.Outbox.EmailIdentifierTest().get_suite()); engine.add_suite(new Geary.RFC822.MailboxAddressTest().get_suite()); engine.add_suite(new Geary.RFC822.MailboxAddressesTest().get_suite()); engine.add_suite(new Geary.RFC822.MessageTest().get_suite());