diff options
author | Niels De Graef <nielsdegraef@gmail.com> | 2021-01-11 19:22:17 +0100 |
---|---|---|
committer | Niels De Graef <nielsdegraef@gmail.com> | 2022-08-06 12:45:49 +0200 |
commit | fcbc87c40406b322513c209844d3430bc4108b13 (patch) | |
tree | 74efc305e00d893a72b3c8fb22017435b6792111 /src/contacts-main-window.vala | |
parent | f37176d5e04b3dc7cc2ad8cb9843d7d7fc35485e (diff) | |
download | gnome-contacts-fcbc87c40406b322513c209844d3430bc4108b13.tar.gz |
Enable importing & exporting VCards
This commit adds the experimental functionality in Contacts to import
VCard (*.vcf) files.
Since importing a contact means we have to take in untrusted/unvalidated
input, let's give a high-level view of what happens:
* Contacts starts a native file chooser dialog so the user can choose
which file to import
* According to the chosen file, Contacts will launch a subprocess to do
the actual parsing using a `Contacts.Io.Parser`. At this point, we
only have a single subclass, which allows importing VCards.
* The helper process serializes the result to a `GLib.Variant`, and
sends it to the main process, which will receive the result and
parses it again.
* After the parsing operation is done, we can then start up a
`ImportOperation`, which will import the contacts using libfolks' API.
Exporting contacts is quite a bit easier, since we don't have to deal
with untrusted input: we serialize the list of selected contacts and
asynchronously write each to the given output stream. In the app, that's
a user chosen file; in tests, that can be a string.
Fixes: https://gitlab.gnome.org/GNOME/gnome-contacts/-/issues/1
Fixes: https://gitlab.gnome.org/GNOME/gnome-contacts/-/issues/38
Diffstat (limited to 'src/contacts-main-window.vala')
-rw-r--r-- | src/contacts-main-window.vala | 56 |
1 files changed, 56 insertions, 0 deletions
diff --git a/src/contacts-main-window.vala b/src/contacts-main-window.vala index e395d7e..42d2073 100644 --- a/src/contacts-main-window.vala +++ b/src/contacts-main-window.vala @@ -27,6 +27,7 @@ public class Contacts.MainWindow : Adw.ApplicationWindow { { "stop-editing-contact", stop_editing_contact, "b" }, { "link-marked-contacts", link_marked_contacts }, { "delete-marked-contacts", delete_marked_contacts }, + { "export-marked-contacts", export_marked_contacts }, // { "share-contact", share_contact }, { "unlink-contact", unlink_contact }, { "delete-contact", delete_contact }, @@ -179,6 +180,9 @@ public class Contacts.MainWindow : Adw.ApplicationWindow { unowned var action = lookup_action ("delete-marked-contacts"); ((SimpleAction) action).set_enabled (n_selected > 0); + action = lookup_action ("export-marked-contacts"); + ((SimpleAction) action).set_enabled (n_selected > 0); + action = lookup_action ("link-marked-contacts"); ((SimpleAction) action).set_enabled (n_selected > 1); @@ -543,6 +547,58 @@ public class Contacts.MainWindow : Adw.ApplicationWindow { return toast; } + private void export_marked_contacts (GLib.SimpleAction action, GLib.Variant? parameter) { + // Take a copy, since we'll unselect everything later + var selection = this.marked_contacts.get_selection ().copy (); + + // Go back to normal state as much as possible + this.store.selection.unselect_all (); + this.marked_contacts.unselect_all (); + this.state = UiState.NORMAL; + + // Open up a file chooser + var chooser = new Gtk.FileChooserNative (_("Export to file"), + this, + Gtk.FileChooserAction.SAVE, + _("_Export"), + _("_Cancel")); + chooser.set_current_name ("contacts.vcf"); + chooser.modal = true; + chooser.response.connect ((response) => { + if (response != Gtk.ResponseType.ACCEPT) { + chooser.destroy (); + return; + } + + // Do the actual export + var individuals = bitset_to_individuals (this.store.filter_model, + selection); + + OutputStream filestream = null; + try { + filestream = chooser.get_file ().replace (null, false, FileCreateFlags.NONE); + } catch (Error err) { + warning ("Couldn't create file: %s", err.message); + return; + } + + var op = new Io.VCardExportOperation (individuals, filestream); + this.operations.execute.begin (op, null, (obj, res) => { + try { + this.operations.execute.end (res); + filestream.close (); + } catch (Error e) { + warning ("ERROR: %s", e.message); + } + }); + + chooser.destroy (); + add_toast_for_operation (op); + }); + + chooser.show (); + } + // Little helper private Gee.LinkedList<Individual> bitset_to_individuals (GLib.ListModel model, Gtk.Bitset bitset) { |