summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCorentin Noël <tintou@noel.tf>2023-04-29 14:59:56 +0200
committerCorentin Noël <tintou@noel.tf>2023-05-15 23:41:01 +0200
commit0b3c48b6d328789e4d08f74d9b2eecf5cfd967ce (patch)
tree12ceed2e8198b1aba8d3e6ae401d4c92d15e48b4
parent8f8010e4da2fc1d88c71a13330f7aaad7e2ff817 (diff)
downloadfolks-tintou/newlib.tar.gz
-rw-r--r--.gitlab-ci.yml2
-rwxr-xr-x.gitlab/ci/style-check.sh4
-rw-r--r--backends/bluez/bluez-backend-factory.vala50
-rw-r--r--backends/bluez/bluez-backend.vala850
-rw-r--r--backends/bluez/bluez-persona-store.vala1149
-rw-r--r--backends/bluez/bluez-persona.vala469
-rw-r--r--backends/bluez/meson.build44
-rw-r--r--backends/bluez/org-bluez-obex-client.vala99
-rw-r--r--backends/bluez/org-bluez.vala121
-rw-r--r--backends/dummy/dummy-backend-factory.vala49
-rw-r--r--backends/dummy/lib/dummy-backend.vala395
-rw-r--r--backends/dummy/lib/dummy-full-persona.vala1152
-rw-r--r--backends/dummy/lib/dummy-persona-store.vala1094
-rw-r--r--backends/dummy/lib/dummy-persona.vala338
-rw-r--r--backends/dummy/lib/folks-dummy.deps4
-rw-r--r--backends/dummy/lib/folks-dummy.map7
-rw-r--r--backends/dummy/lib/meson.build82
-rw-r--r--backends/dummy/meson.build35
-rw-r--r--backends/eds/eds-backend-factory.vala49
-rw-r--r--backends/eds/eds-backend.vala389
-rw-r--r--backends/eds/lib/edsf-persona-store.vala2920
-rw-r--r--backends/eds/lib/edsf-persona.vala2314
-rw-r--r--backends/eds/lib/folks-eds.deps6
-rw-r--r--backends/eds/lib/folks-eds.map7
-rw-r--r--backends/eds/lib/meson.build99
-rw-r--r--backends/eds/meson.build38
-rw-r--r--backends/key-file/folks-key-file.deps2
-rw-r--r--backends/key-file/kf-backend-factory.vala46
-rw-r--r--backends/key-file/kf-backend.vala302
-rw-r--r--backends/key-file/kf-persona-store.vala491
-rw-r--r--backends/key-file/kf-persona.vala547
-rw-r--r--backends/key-file/meson.build33
-rw-r--r--backends/meson.build40
-rw-r--r--backends/namespace.vala.in6
-rw-r--r--backends/ofono/meson.build36
-rw-r--r--backends/ofono/ofono-backend-factory.vala52
-rw-r--r--backends/ofono/ofono-backend.vala378
-rw-r--r--backends/ofono/ofono-persona-store.vala295
-rw-r--r--backends/ofono/ofono-persona.vala233
-rw-r--r--backends/ofono/org-ofono.vala59
-rw-r--r--backends/telepathy/lib/folks-telepathy.deps4
-rw-r--r--backends/telepathy/lib/folks-telepathy.map6
-rw-r--r--backends/telepathy/lib/meson.build176
-rw-r--r--backends/telepathy/lib/tp-lowlevel.c110
-rw-r--r--backends/telepathy/lib/tp-zeitgeist-dummy.vala51
-rw-r--r--backends/telepathy/lib/tp-zeitgeist.vala236
-rw-r--r--backends/telepathy/lib/tpf-logger.vala200
-rw-r--r--backends/telepathy/lib/tpf-persona-store-cache.vala368
-rw-r--r--backends/telepathy/lib/tpf-persona-store.vala1701
-rw-r--r--backends/telepathy/lib/tpf-persona.vala1426
-rw-r--r--backends/telepathy/meson.build38
-rw-r--r--backends/telepathy/tp-backend-factory.vala45
-rw-r--r--backends/telepathy/tp-backend.vala282
-rw-r--r--folks/abstract-field-details.vala452
-rw-r--r--folks/alias-details.vala63
-rw-r--r--folks/anti-linkable.vala229
-rw-r--r--folks/avatar-cache.vala319
-rw-r--r--folks/avatar-details.vala65
-rw-r--r--folks/backend-store.vala985
-rw-r--r--folks/backend.vala210
-rw-r--r--folks/birthday-details.vala95
-rw-r--r--folks/build-conf.vapi80
-rw-r--r--folks/debug.vala483
-rw-r--r--folks/email-details.vala122
-rw-r--r--folks/extended-info.vala151
-rw-r--r--folks/favourite-details.vala60
-rw-r--r--folks/folks-generics.vapi51
-rw-r--r--folks/folks-manager.c244
-rw-r--r--folks/folks-manager.h43
-rw-r--r--folks/folks-persona-priv.h (renamed from folks/types.vala)44
-rw-r--r--folks/folks-persona-store-priv.h (renamed from backends/telepathy/lib/tp-lowlevel.h)35
-rw-r--r--folks/folks-persona-store.c275
-rw-r--r--folks/folks-persona-store.h43
-rw-r--r--folks/folks-persona.c182
-rw-r--r--folks/folks-persona.h46
-rw-r--r--folks/folks.convert2
-rw-r--r--folks/folks.deps4
-rw-r--r--folks/folks.h (renamed from folks/folks-namespace.vala)24
-rw-r--r--folks/gender-details.vala81
-rw-r--r--folks/group-details.vala176
-rw-r--r--folks/im-details.vala253
-rw-r--r--folks/individual-aggregator.vala2543
-rw-r--r--folks/individual.vala3140
-rw-r--r--folks/interaction-details.vala73
-rw-r--r--folks/internal.vala113
-rw-r--r--folks/local-id-details.vala66
-rw-r--r--folks/location-details.vala137
-rw-r--r--folks/meson.build183
-rw-r--r--folks/name-details.vala494
-rw-r--r--folks/note-details.vala141
-rw-r--r--folks/object-cache.vala476
-rw-r--r--folks/org.freedesktop.folks.gschema.xml.in12
-rw-r--r--folks/persona-store.vala807
-rw-r--r--folks/persona.vala392
-rw-r--r--folks/phone-details.vala257
-rw-r--r--folks/postal-address-details.vala364
-rw-r--r--folks/potential-match.vala686
-rw-r--r--folks/presence-details.vala222
-rw-r--r--folks/query.vala140
-rw-r--r--folks/redeclare-internal-api.h46
-rw-r--r--folks/role-details.vala282
-rw-r--r--folks/search-view.vala538
-rw-r--r--folks/simple-query.vala522
-rw-r--r--folks/small-set-internal.h98
-rw-r--r--folks/small-set.c973
-rw-r--r--folks/small-set.h84
-rw-r--r--folks/url-details.vala154
-rw-r--r--folks/utils.vala276
-rw-r--r--folks/warnings.h85
-rw-r--r--folks/web-service-details.vala125
-rw-r--r--meson.build61
-rw-r--r--meson_options.txt1
-rw-r--r--subprojects/folks-daemon/meson.build23
-rw-r--r--subprojects/folks-daemon/meson_options.txt3
-rw-r--r--subprojects/folks-daemon/src/bluez/folksd-bluez-device-handler.c309
-rw-r--r--subprojects/folks-daemon/src/bluez/folksd-bluez-device-handler.h31
-rw-r--r--subprojects/folks-daemon/src/bluez/folksd-bluez-miner.c325
-rw-r--r--subprojects/folks-daemon/src/bluez/folksd-bluez-miner.h14
-rw-r--r--subprojects/folks-daemon/src/bluez/org.bluez.Device1.xml31
-rw-r--r--subprojects/folks-daemon/src/bluez/org.bluez.obex.Client1.xml61
-rw-r--r--subprojects/folks-daemon/src/bluez/org.bluez.obex.PhonebookAccess1.xml45
-rw-r--r--subprojects/folks-daemon/src/bluez/org.bluez.obex.Transfer1.xml17
-rw-r--r--subprojects/folks-daemon/src/eds/folksd-eds-miner.c316
-rw-r--r--subprojects/folks-daemon/src/eds/folksd-eds-miner.h14
-rw-r--r--subprojects/folks-daemon/src/eds/missing-autocleanups/e-book-autocleanups.h31
-rw-r--r--subprojects/folks-daemon/src/eds/missing-autocleanups/e-book-contacts-autocleanups.h42
-rw-r--r--subprojects/folks-daemon/src/eds/missing-autocleanups/missing-autocleanups.h7
-rw-r--r--subprojects/folks-daemon/src/folksd-contacts-miner.c200
-rw-r--r--subprojects/folks-daemon/src/folksd-contacts-miner.h18
-rw-r--r--subprojects/folks-daemon/src/folksd-utils.c32
-rw-r--r--subprojects/folks-daemon/src/folksd-utils.h27
-rw-r--r--subprojects/folks-daemon/src/main.c45
-rw-r--r--subprojects/folks-daemon/src/meson.build67
-rw-r--r--tools/import-pidgin.vala288
-rw-r--r--tools/import.vala215
-rw-r--r--tools/inspect.vala42
-rw-r--r--tools/inspect/command-backends.vala103
-rw-r--r--tools/inspect/command-debug.vala55
-rw-r--r--tools/inspect/command-help.vala96
-rw-r--r--tools/inspect/command-individuals.vala92
-rw-r--r--tools/inspect/command-linking.vala347
-rw-r--r--tools/inspect/command-persona-stores.vala109
-rw-r--r--tools/inspect/command-personas.vala92
-rw-r--r--tools/inspect/command-quit.vala57
-rw-r--r--tools/inspect/command-search.vala85
-rw-r--r--tools/inspect/command-set.vala199
-rw-r--r--tools/inspect/command-signals.vala262
-rw-r--r--tools/inspect/inspect.vala620
-rw-r--r--tools/inspect/meson.build35
-rw-r--r--tools/inspect/signal-manager.vala509
-rw-r--r--tools/inspect/utils.vala647
-rw-r--r--tools/meson.build44
152 files changed, 2648 insertions, 40144 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e4f56b1e..94474d04 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -28,7 +28,7 @@ build-folks:
dbus-glib-devel evolution-data-server-devel glib2-devel
gobject-introspection-devel libgee-devel libxml2-devel meson ninja-build
python3-dbusmock readline-devel redhat-rpm-config telepathy-glib-devel
- telepathy-glib-vala vala valadoc gtk-doc
+ telepathy-glib-vala tracker-devel vala valadoc gtk-doc
dbus-daemon # FIXME: dbus-broker breaks the CI, see https://github.com/bus1/dbus-broker/issues/145
script:
- meson _build -Ddocs=true
diff --git a/.gitlab/ci/style-check.sh b/.gitlab/ci/style-check.sh
index 2fa70095..f694e7eb 100755
--- a/.gitlab/ci/style-check.sh
+++ b/.gitlab/ci/style-check.sh
@@ -8,7 +8,7 @@ source "$scriptdir/junit-report.sh"
TESTNAME="No tabs"
-tabs_occurrences="$(fgrep -nRI $'\t' folks backends tests tools)"
+tabs_occurrences="$(fgrep -nRI $'\t' folks subprojects tests tools)"
if [[ -z "$tabs_occurrences" ]]; then
append_passed_test_case "$TESTNAME"
else
@@ -18,7 +18,7 @@ fi
TESTNAME="No trailing whitespace"
-trailing_ws_occurrences="$(grep -nRI '[[:blank:]]$' folks backends tests tools)"
+trailing_ws_occurrences="$(grep -nRI '[[:blank:]]$' folks subprojects tests tools)"
if [[ -z "$trailing_ws_occurrences" ]]; then
append_passed_test_case "$TESTNAME"
else
diff --git a/backends/bluez/bluez-backend-factory.vala b/backends/bluez/bluez-backend-factory.vala
deleted file mode 100644
index 22ef9a51..00000000
--- a/backends/bluez/bluez-backend-factory.vala
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2009 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>.
- * Copyright (C) 2009 Nokia Corporation.
- * Copyright (C) 2012-2013 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Arun Raghavan <arun.raghavan@collabora.co.uk>
- *
- * Based on kf-backend-factory.vala by:
- * Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using Folks;
-
-/**
- * The backend module entry point.
- *
- * @backend_store a store to add the BlueZ backends to
- * @since 0.9.6
- */
-public void module_init (BackendStore backend_store)
-{
- backend_store.add_backend (new Folks.Backends.BlueZ.Backend ());
-}
-
-/**
- * The backend module exit point.
- *
- * @param backend_store the store to remove the backends from
- * @since 0.9.6
- */
-public void module_finalize (BackendStore backend_store)
-{
- /* FIXME: No way to remove backends from the store. */
-}
diff --git a/backends/bluez/bluez-backend.vala b/backends/bluez/bluez-backend.vala
deleted file mode 100644
index 7c9744aa..00000000
--- a/backends/bluez/bluez-backend.vala
+++ /dev/null
@@ -1,850 +0,0 @@
-/*
- * Copyright (C) 2012-2013 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Arun Raghavan <arun.raghavan@collabora.co.uk>
- * Jeremy Whiting <jeremy.whiting@collabora.com>
- * Simon McVittie <simon.mcvittie@collabora.co.uk>
- * Gustavo Padovan <gustavo.padovan@collabora.co.uk>
- * Matthieu Bouron <matthieu.bouron@collabora.com>
- * Philip Withnall <philip.withnall@collabora.co.uk>
- *
- * Based on kf-backend.vala by:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-using Folks;
-using Folks.Backends.BlueZ;
-using org.bluez;
-
-extern const string BACKEND_NAME;
-
-/**
- * A backend which loads {@link Persona}s from paired Bluetooth
- * devices using the Phonebook Access Protocol (PBAP) and presents them
- * using one {@link PersonaStore} per device.
- *
- * Each device can be in four states:
- * - Unpaired and unconnected
- * - Unpaired but connected
- * - Paired but unconnected
- * - Paired and connected
- *
- * The default state for a device is unpaired. The user must explicitly pair
- * their device and enable it using {@link Backend.enable_persona_store} or
- * {@link Backend.set_persona_stores} before folks will begin to use it — folks
- * ignores unpaired devices. Once a device is paired and enabled, folks will
- * attempt to do an OBEX PBAP transfer to copy the device’s address book; this
- * will automatically connect the device. After the transfer is complete, the
- * device will go back to being paired and unconnected.
- *
- * Note that by requiring that devices are explicitly enabled by calling
- * {@link Backend.set_persona_stores}, the BlueZ backend is empty by default
- * every time libfolks is loaded, until the client explicitly enables a device.
- * This may change in future, and the state of enabled devices may be retained
- * between runs.
- *
- * Every time the user explicitly connects to the device, folks will re-download
- * its address book. Currently, folks will not otherwise re-download it (i.e.
- * there are no change notifications and no polling).
- *
- * If a transfer is started from an unpaired device, the device will move to the
- * unpaired but connected state, and will pop up a notification asking the user
- * whether they want to pair to the computer. This should be avoided, and is why
- * folks ignores all unpaired devices.
- *
- * If a connection timeout occurs (e.g. because the user took too long to
- * approve a pairing request, or explicitly denied it), the device will become
- * disconnected again.
- *
- * If the phone user explicitly denies the phone’s request to share address book
- * data with the laptop (which happens after pairing is successful), creating an
- * OBEX transfer session will fail with an explicit error, which is handled in
- * the {@link PersonaStore}.
- *
- * No caching is implemented by libfolks at the moment, so the address book
- * will be downloaded every time folks starts up.
- *
- * Each device can be advertised by BlueZ as trusted or untrusted, a property
- * which is explicitly set by the user on the laptop (not on the device). Folks
- * will set the PersonaStore’s trust level appropriately, fully trusting devices
- * marked as trusted, and only partially trusting others.
- *
- * Each device can also be advertised by BlueZ as being blocked or non-blocked.
- * Blocked devices are not made available as persona stores, even if they are
- * paired with the laptop.
- *
- * @since 0.9.6
- */
-public class Folks.Backends.BlueZ.Backend : Folks.Backend
-{
- private bool _is_prepared = false;
- private bool _prepare_pending = false; /* used for unprepare() too */
- private bool _is_quiescent = false;
- /* Map from PersonaStore.id to PersonaStore. */
- private HashMap<string, PersonaStore> _persona_stores;
- private Map<string, PersonaStore> _persona_stores_ro;
- private DBusObjectManagerClient? _manager; /* null before prepare() */
- private ulong _object_added_handler;
- private ulong _object_removed_handler;
- private ulong _properties_changed_handler;
- /* Map from device D-Bus object path to PersonaStore. */
- private HashMap<string, PersonaStore> _watched_devices;
- private org.bluez.obex.Client? _obex_client = null;
- /* Set of Bluetooth device addresses for the enabled devices. If a device
- * isn’t listed here, a PersonaStore will not be created for it. */
- private SmallSet<string> _enabled_devices;
-
- /**
- * Whether this Backend has been prepared.
- *
- * See {@link Folks.Backend.is_prepared}.
- *
- * @since 0.9.6
- */
- public override bool is_prepared
- {
- get { return this._is_prepared; }
- }
-
- /**
- * Whether this Backend has reached a quiescent state.
- *
- * See {@link Folks.Backend.is_quiescent}.
- *
- * @since 0.9.6
- */
- public override bool is_quiescent
- {
- get { return this._is_quiescent; }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public override string name { get { return BACKEND_NAME; } }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public override Map<string, Folks.PersonaStore> persona_stores
- {
- get { return this._persona_stores_ro; }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public override void disable_persona_store (Folks.PersonaStore store)
- {
- var _store = store as BlueZ.PersonaStore;
- if (_store == null)
- return;
-
- debug ("Disabling persona store ‘%s’.", store.id);
-
- var device_address = store.id; /* implicit assumption */
- this._enabled_devices.remove (device_address);
- this._save_enabled_devices.begin ((o, r) =>
- {
- this._save_enabled_devices.end (r);
- });
-
- if (!this._persona_stores.has_key (store.id))
- return;
-
- this._remove_persona_store ((!) _store, true, true);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public override void enable_persona_store (Folks.PersonaStore store)
- {
- var _store = store as BlueZ.PersonaStore;
- if (_store == null)
- return;
-
- debug ("Enabling persona store ‘%s’.", store.id);
-
- var device_address = store.id; /* implicit assumption */
- this._enabled_devices.add (device_address);
- this._save_enabled_devices.begin ((o, r) =>
- {
- this._save_enabled_devices.end (r);
- });
-
- /* See if any existing device objects correspond to the store. */
- this._refresh_devices.begin ((o, r) =>
- {
- this._refresh_devices.end (r);
- });
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public override void set_persona_stores (Set<string>? storeids)
- {
- if (storeids == null)
- {
- /* Use all available devices. */
- var objs = this._manager.get_objects ();
-
- foreach (var obj in objs)
- {
- var device = obj.get_interface ("org.bluez.Device1") as Device;
- if (device == null)
- continue;
-
- this._enabled_devices.add (device.address);
- this._add_device.begin (obj, (o, r) =>
- {
- this._add_device.end (r);
- });
- }
-
- this._save_enabled_devices.begin ((o, r) =>
- {
- this._save_enabled_devices.end (r);
- });
-
- return;
- }
-
- /* Clear the set of enabled persona stores and re-populate it with only
- * the addresses from storeids. */
- this._enabled_devices.clear ();
- foreach (var store_id in storeids)
- {
- var device_address = store_id; /* implicit assumption */
- this._enabled_devices.add (device_address);
- }
-
- this._save_enabled_devices.begin ((o, r) =>
- {
- this._save_enabled_devices.end (r);
- });
-
- this._refresh_devices.begin ((o, r) =>
- {
- this._refresh_devices.end (r);
- });
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public Backend ()
- {
- Object ();
- }
-
- construct
- {
- this._persona_stores = new HashMap<string, PersonaStore> ();
- this._persona_stores_ro = this._persona_stores.read_only_view;
- this._watched_devices = new HashMap<string, PersonaStore> ();
- this._enabled_devices = new SmallSet<string> ();
- }
-
- /**
- * Callback executed when a device property has changed.
- *
- * The callback is executed when a PropertiesChanged signal is received
- * on device. If the device is seen as connected it tries to update the
- * Persona store associated with it. If the device is seen as disconnected,
- * the OBEX session used by the {@link PersonaStore} is removed.
- *
- * @param obj_proxy D-Bus proxy for the object
- * @param iface_proxy D-Bus proxy for the interface on which the property
- * changed
- * @param changed the list of properties that have changed
- * @param invalidated the list of properties that have been invalidated
- *
- * @since 0.9.6
- */
- private void _device_properties_changed_cb (DBusObjectProxy obj_proxy,
- DBusProxy iface_proxy, Variant changed, string[] invalidated)
- {
- debug ("Properties changed on interface ‘%s’ of object ‘%s’:",
- iface_proxy.g_interface_name, obj_proxy.g_object_path);
- var iter = changed.iterator ();
- string key;
- Variant variant;
- while (iter.next ("{sv}", out key, out variant) == true)
- debug (" %s", key);
-
- if (iface_proxy.g_interface_name != "org.bluez.Device1")
- return;
-
- var device = (Device) iface_proxy;
-
- /* UUIDs, Paired and Blocked properties. All affect whether we add or
- * remove a device/persona store. */
- var uuids = changed.lookup_value ("UUIDs", null);
- var paired = changed.lookup_value ("Paired", VariantType.BOOLEAN);
- var blocked = changed.lookup_value ("Blocked", VariantType.BOOLEAN);
-
- if (uuids != null || paired != null || blocked != null)
- {
- /* Sometimes the UUIDs property only changes a second or two after
- * the device first appears, so try adding the device again. */
- if (device.paired == true && device.blocked == false &&
- this._device_supports_pbap_pse (device))
- {
- this._add_device.begin (obj_proxy, (o, r) =>
- {
- this._add_device.end (r);
- });
- }
- else
- {
- this._remove_device.begin (obj_proxy, (o, r) =>
- {
- this._remove_device.end (r);
- });
- }
- }
-
- var store = this._persona_stores.get (device.address);
-
- if (store == null)
- return;
-
- /* Connected property. */
- var connected = changed.lookup_value ("Connected", VariantType.BOOLEAN);
- if (connected != null)
- {
- store.set_connection_state.begin (connected.get_boolean (), (o, r) =>
- {
- try
- {
- store.set_connection_state.end (r);
- }
- catch (IOError e1)
- {
- debug ("Changing connection state for device ‘%s’ (%s) " +
- "was cancelled.", device.alias, device.address);
- }
- catch (PersonaStoreError e2)
- {
- warning ("Error changing connection state for device " +
- "‘%s’ (%s): %s", device.alias, device.address,
- e2.message);
- }
- });
- }
-
- /* Trust level. */
- var trusted = changed.lookup_value ("Trusted", VariantType.BOOLEAN);
- if (trusted != null)
- {
- store.set_is_trusted (trusted.get_boolean ());
- }
-
- /* Alias. */
- var alias = changed.lookup_value ("Alias", VariantType.STRING);
- if (alias != null)
- {
- store.set_alias (alias.get_string ());
- }
- }
-
- /**
- * Add a new Persona store to this backend.
- *
- * Add a new Persona store associated with a device identified by
- * its address and alias. The function takes care of creating all
- * the D-Bus object and path required by the Personna store.
- *
- * @param device the D-Bus object for the Bluetooth device
- * @param path the path of the D-Bus device object.
- *
- * @since 0.9.6
- */
- private async void _add_persona_store (Device device, string path)
- {
- PersonaStore store =
- new BlueZ.PersonaStore (device, path, this._obex_client);
-
- /* Set the initial properties. */
- store.set_is_trusted (device.trusted);
- store.set_alias (device.alias);
-
- this._watched_devices[path] = store;
- this._persona_stores.set (store.id, store);
-
- store.removed.connect (this._persona_store_removed_cb);
- this.persona_store_added (store);
- this.notify_property ("persona-stores");
- }
-
- private void _remove_persona_store (PersonaStore store,
- bool remove_from_persona_stores, bool remove_from_watched_devices)
- {
- store.removed.disconnect (this._persona_store_removed_cb);
-
- /* Cancel all ongoing activity in the store and, critically, eliminate
- * the reflexive reference it holds. */
- store.cancel_updates ();
-
- this.persona_store_removed (store);
-
- if (remove_from_persona_stores == true)
- this._persona_stores.unset (store.id);
- if (remove_from_watched_devices == true)
- this._watched_devices.unset (store.object_path);
-
- this.notify_property ("persona-stores");
- }
-
- /**
- * Check if a device supports PSE (Phone Book Server Equipment.
- *
- * We assume that UUIDs won’t change after we initially see the device, so
- * don’t listen for changes to it.
- *
- * @param device the D-Bus device object
- * @return ``true`` if the device supports PSE, ``false`` otherwise.
- *
- * @since 0.9.6
- */
- private bool _device_supports_pbap_pse (Device device)
- {
- string[]? uuids = device.uuids;
-
- /* The UUIDs property is optional; if unset, it’s null. */
- if (uuids == null)
- return false;
-
- foreach (var uuid in (!) uuids)
- {
- /* Phonebook Access - PSE (Phone Book Server Equipment).
- * 0x112F is the pse part. */
- if (uuid == "0000112f-0000-1000-8000-00805f9b34fb")
- return true;
- }
-
- return false;
- }
-
- /**
- * Refresh the list of devices from D-Bus.
- *
- * Add any new devices which are on D-Bus but aren’t in the BlueZ backend.
- * This does not currently remove devices which have disappeared from D-Bus.
- *
- * @since 0.9.7
- */
- private async void _refresh_devices ()
- {
- var objs = this._manager.get_objects ();
-
- foreach (var obj in objs)
- {
- yield this._add_device (obj);
- }
- }
-
- /**
- * Add a device to the backend.
- *
- * @param _obj the device's D-Bus object
- *
- * @since 0.9.6
- */
- private async void _add_device (DBusObject obj)
- {
- debug ("Adding device at path ‘%s’.", obj.get_object_path ());
-
- var device = obj.get_interface ("org.bluez.Device1") as Device;
- if (device == null)
- {
- debug (" Device doesn’t implement org.bluez.Device1 " +
- "interface. Ignoring.");
- return;
- }
-
- var path = obj.get_object_path ();
-
- if (this._watched_devices.has_key (path))
- {
- debug (" Device already watched. Ignoring.");
- return;
- }
-
- if (device.paired == false)
- {
- debug (" Device isn’t paired. Ignoring. Manually pair the device" +
- " to start downloading contacts.");
- return;
- }
-
- if (device.blocked == true)
- {
- debug (" Device is blocked. Ignoring.");
- return;
- }
-
- if (!this._device_supports_pbap_pse (device))
- {
- debug (" Doesn’t support PBAP PSE. Ignoring.");
- return;
- }
-
- if (!this._enabled_devices.contains (device.address))
- {
- debug (" Device not in enabled devices list.");
- return;
- }
-
- yield this._add_persona_store (device, path);
- }
-
- /**
- * Remove a device from the backend.
- *
- * @param obj the device's D-Bus object
- *
- * @since 0.9.6
- */
- private async void _remove_device (DBusObject obj)
- {
- var path = obj.get_object_path ();
- PersonaStore? store = null;
-
- debug ("Removing device at ‘%s’.", path);
-
- if (this._watched_devices.unset (path, out store) == true)
- {
- debug ("Device ‘%s’ removed", path);
- this._remove_persona_store (store, true, false);
- }
- }
-
- private string _get_enabled_devices_key_file_path ()
- {
- var file = File.new_for_path (Environment.get_user_data_dir ());
- file = file.get_child ("folks");
- file = file.get_child ("bluez-persona-stores.ini");
-
- return file.get_path ();
- }
-
- /**
- * Save the list of enabled devices to a configuration file.
- *
- * @since 0.11.4
- */
- private async void _save_enabled_devices ()
- {
- var kf = new GLib.KeyFile ();
- var kf_path = this._get_enabled_devices_key_file_path ();
-
- foreach (var address in this._enabled_devices)
- kf.set_boolean (address, "enabled", true);
-
- debug ("Saving BlueZ enabled devices key file ‘%s’.", kf_path);
-
- try
- {
- var file = File.new_for_path (kf_path);
- var data = kf.to_data ();
- file.replace_contents (data.data,
- null, false, FileCreateFlags.PRIVATE,
- null, null);
- }
- catch (GLib.Error e)
- {
- warning ("Could not write updated BlueZ enabled devices key file " +
- "‘%s’: %s", kf_path, e.message);
- }
- }
-
- /**
- * Load the list of enabled devices from a configuration file.
- *
- * @since 0.11.4
- */
- private async void _load_enabled_devices ()
- {
- var kf = new GLib.KeyFile ();
- var kf_path = this._get_enabled_devices_key_file_path ();
-
- debug ("Loading BlueZ enabled devices key file ‘%s’.", kf_path);
-
- try
- {
- uint8[] contents;
-
- var file = File.new_for_path (kf_path);
- yield file.load_contents_async (null, out contents, null);
- unowned string contents_s = (string) contents;
-
- if (contents_s.length > 0)
- {
- kf.load_from_data (contents_s,
- contents_s.length, KeyFileFlags.KEEP_COMMENTS);
- }
- }
- catch (GLib.Error e1)
- {
- if (!(e1 is IOError.NOT_FOUND))
- {
- warning ("The BlueZ enabled devices key file ‘%s’ could not be " +
- "loaded: %s", kf_path, e1.message);
- return;
- }
- else
- {
- debug (" Not found.");
- }
- }
-
- /* Update the set of enabled devices in memory. */
- this._enabled_devices.clear ();
- var groups = kf.get_groups ();
- foreach (var address in groups)
- {
- try
- {
- if (kf.get_boolean (address, "enabled"))
- {
- debug (" Enabling device ‘%s’", address);
- this._enabled_devices.add (address);
- }
- }
- catch (GLib.KeyFileError e)
- {
- /* Ignore. */
- }
- }
- }
-
- private delegate Type TypeFunc ();
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public override async void prepare () throws DBusError
- {
- var profiling = Internal.profiling_start ("preparing BlueZ.Backend");
-
- if (this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- /* In brief, this function:
- * 0. Loads the list of enabled devices — these are the Bluetooth devices
- * which will be turned into PersonaStores (if they are connected,
- * paired, support PBAP, etc.).
- * 1. Connects to org.bluez. If that’s not available, we assume BlueZ
- * is not installed (or is not version 5), and throw an error, leaving
- * the store unprepared.
- * 2. Connects to org.bluez.obex. Similarly, if that’s not available,
- * we throw an error and leave the store unprepared.
- * 3. Connects to loads of signals and enumerates all the existing
- * devices known to BlueZ. This cannot fail.
- */
- try
- {
- this._prepare_pending = true;
- this.freeze_notify ();
-
- /* Load the enabled devices. */
- yield this._load_enabled_devices ();
-
- try
- {
- this._manager =
-#if VALA_0_36
- yield new DBusObjectManagerClient.for_bus (BusType.SYSTEM,
-#else
- yield DBusObjectManagerClient.new_for_bus (BusType.SYSTEM,
-#endif
- DBusObjectManagerClientFlags.NONE, "org.bluez", "/",
- /* DBusProxyTypeFunc: */
- (manager, path, iface_name) =>
- {
- debug ("DBusProxyTypeFunc for path ‘%s’ and " +
- "interface ‘%s’.", path, iface_name);
-
- Type retval;
-
- /* FIXME: Horrible hack to grab the proxy object for
- * org.bluez.Device (rather than the interface itself)
- * from Vala. Vala generates C code for both, but we
- * can’t normally access the proxy object.
- *
- * See:
- * https://bugzilla.gnome.org/show_bug.cgi?id=710817
- */
- if (iface_name == "org.bluez.Device1")
- {
- var q =
- Quark.from_string ("vala-dbus-proxy-type");
- var dev_type = typeof (org.bluez.Device);
- retval = ((TypeFunc) (dev_type.get_qdata (q))) ();
- }
- /* Fallback. */
- else if (iface_name == null)
- retval = typeof (DBusObjectProxy);
- else
- retval = typeof (DBusProxy);
-
- debug (" Returning: %s", retval.name ());
-
- return retval;
- });
- }
- catch (GLib.Error e1)
- {
- throw new DBusError.SERVICE_UNKNOWN (
- _("No BlueZ 5 object manager running, so the BlueZ backend will be inactive. Either your BlueZ installation is too old (only version 5 is supported) or the service can’t be started."));
- }
-
- /* Set up the OBEX client which will be used for all transfers. */
- try
- {
- this._obex_client =
- yield Bus.get_proxy (BusType.SESSION, "org.bluez.obex",
- "/org/bluez/obex");
- }
- catch (GLib.Error e1)
- {
- throw new DBusError.SERVICE_UNKNOWN (
- _("Error connecting to OBEX transfer daemon over D-Bus. Ensure BlueZ and obexd are installed."));
- }
-
- /* Successfully connected to both D-Bus services. Now connect up some
- * signal handlers. */
- this._object_added_handler =
- this._manager.object_added.connect ((obj) =>
- {
- this._add_device.begin (obj, (o, r) =>
- {
- this._add_device.end (r);
- });
- });
-
- this._object_removed_handler =
- this._manager.object_removed.connect ((obj) =>
- {
- this._remove_device.begin (obj, (o, r) =>
- {
- this._remove_device.end (r);
- });
- });
-
- this._properties_changed_handler =
- this._manager.interface_proxy_properties_changed.connect (
- this._device_properties_changed_cb);
-
- /* Add all the existing device objects. */
- yield this._refresh_devices ();
-
- this._is_prepared = true;
- this.notify_property ("is-prepared");
-
- this._is_quiescent = true;
- this.notify_property ("is-quiescent");
- }
- finally
- {
- this.thaw_notify ();
- this._prepare_pending = false;
- }
-
- Internal.profiling_end ((owned) profiling);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public override async void unprepare () throws GLib.Error
- {
- if (!this._is_prepared || this._prepare_pending == true)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
-
- if (this._manager != null)
- {
- this._manager.disconnect (this._object_added_handler);
- this._manager.disconnect (this._object_removed_handler);
- this._manager.disconnect (this._properties_changed_handler);
- this._manager = null;
- this._object_added_handler = 0;
- this._object_removed_handler = 0;
- this._properties_changed_handler = 0;
- }
-
- this._obex_client = null;
-
- this.freeze_notify ();
-
- var iter = this._persona_stores.map_iterator ();
- while (iter.next () == true)
- {
- this._remove_persona_store (iter.get_value (), false, true);
- iter.unset ();
- }
-
- this.notify_property ("persona-stores");
-
- this._is_quiescent = false;
- this.notify_property ("is-quiescent");
-
- this._is_prepared = false;
- this.notify_property ("is-prepared");
-
- this.thaw_notify ();
- }
- finally
- {
- this._prepare_pending = false;
- }
- }
-
- private void _persona_store_removed_cb (Folks.PersonaStore store)
- {
- this._remove_persona_store ((BlueZ.PersonaStore) store, true, true);
- }
-}
diff --git a/backends/bluez/bluez-persona-store.vala b/backends/bluez/bluez-persona-store.vala
deleted file mode 100644
index 5e0a3d13..00000000
--- a/backends/bluez/bluez-persona-store.vala
+++ /dev/null
@@ -1,1149 +0,0 @@
-/*
- * Copyright (C) 2010-2013 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Arun Raghavan <arun.raghavan@collabora.co.uk>
- * Jeremy Whiting <jeremy.whiting@collabora.com>
- * Simon McVittie <simon.mcvittie@collabora.co.uk>
- * Gustavo Padovan <gustavo.padovan@collabora.co.uk>
- * Matthieu Bouron <matthieu.bouron@collabora.com>
- * Philip Withnall <philip.withnall@collabora.co.uk>
- *
- * Based on kf-persona-store.vala by:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-using Folks;
-using Folks.Backends.BlueZ;
-using org.bluez;
-
-/* FIXME: Once we depend on gettext 0.18.3, translatable strings can once more
- * be split over multiple lines without breaking the .po file. */
-
-/**
- * A persona store which is associated with a single BlueZ PBAP server (i.e.
- * one {@link PersonaStore} per device). It will create a {@link Persona} for
- * each contact on the device.
- *
- * Since large contact lists can take a long time to download in full (on the
- * order of 1s per 10 contacts), contacts are downloaded in two phases:
- * # Phase 1 downloads all non-PHOTO data. This is very fast (on the order of
- * 1s per 400 contacts)
- * # Phase 2 downloads all PHOTO data for those contacts. This is slow, but
- * happens later, in the background.
- *
- * Subsequent download attempts happen on an exponentially increasing interval,
- * up to a limit (once this limit is reached, updates occur on a regular
- * interval; the linear region). Download attempts repeat indefinitely unless a
- * certain number of consecutive attempts end in failure. See the documentation
- * for {@link _schedule_update_contacts} for details.
- *
- * @since 0.9.6
- */
-public class Folks.Backends.BlueZ.PersonaStore : Folks.PersonaStore
-{
- private HashMap<string, Persona> _personas;
- private Map<string, Persona> _personas_ro;
- private bool _is_prepared = false;
- private bool _prepare_pending = false;
- private bool _is_quiescent = false;
-
- private static string[] _always_writeable_properties = {};
-
- private org.bluez.obex.Client _obex_client;
- private string _object_path;
- private Device _device;
- private string _display_name;
-
- /* Non-null iff an _update_contacts() call is in progress. */
- private Cancellable? _update_contacts_cancellable = null;
- /* Non-0 iff an _update_contacts() call is scheduled. */
- private uint _update_contacts_id = 0;
- private bool _photos_up_to_date = true;
- /* Counter of the number of _update_contacts() calls which have been
- * scheduled. */
- private uint _update_contacts_n = 0;
- /* Number of consecutive failures in _update_contacts(). */
- private uint _update_contacts_failures = 0;
-
- /* Parameters for calculating the timeout for repeated _update_contacts()
- * calls. See the documentation for _schedule_update_contacts() for more. */
- private const uint _TIMEOUT_MIN = 4 /* seconds */;
- private const uint _TIMEOUT_BASE = 2 /* seconds */;
- private const uint _TIMEOUT_MAX = 5 * 60 /* minutes */;
-
- /* Number of consecutive failures in _update_contacts() before we give up
- * completely and stop trying to update from the phone. */
- private const uint _MAX_CONSECUTIVE_FAILURES = 3;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public override string type_id { get { return BACKEND_NAME; } }
-
- /**
- * Whether this PersonaStore can add {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_add_personas}.
- *
- * @since 0.9.6
- */
- public override MaybeBool can_add_personas
- {
- get { return MaybeBool.FALSE; }
- }
-
- /**
- * Whether this PersonaStore can set the alias of {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_alias_personas}.
- *
- * @since 0.9.6
- */
- public override MaybeBool can_alias_personas
- {
- get { return MaybeBool.FALSE; }
- }
-
- /**
- * Whether this PersonaStore can set the groups of {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_group_personas}.
- *
- * @since 0.9.6
- */
- public override MaybeBool can_group_personas
- {
- get { return MaybeBool.FALSE; }
- }
-
- /**
- * Whether this PersonaStore can remove {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_remove_personas}.
- *
- * @since 0.9.6
- */
- public override MaybeBool can_remove_personas
- {
- get { return MaybeBool.FALSE; }
- }
-
- /**
- * Whether this PersonaStore has been prepared.
- *
- * See {@link Folks.PersonaStore.is_prepared}.
- *
- * @since 0.9.6
- */
- public override bool is_prepared
- {
- get { return this._is_prepared; }
- }
-
- /**
- * Whether this PersonaStore has reached a quiescent state.
- *
- * See {@link Folks.PersonaStore.is_quiescent}.
- *
- * @since 0.9.6
- */
- public override bool is_quiescent
- {
- get { return this._is_quiescent; }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public override string[] always_writeable_properties
- {
- get { return BlueZ.PersonaStore._always_writeable_properties; }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public override Map<string, Folks.Persona> personas
- {
- get { return this._personas_ro; }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public new string display_name
- {
- /* FIXME: Folks.display_name should be abstract, and this should be
- * override. */
- get { return this._display_name; }
- construct { this._display_name = value; }
- }
-
- /**
- * Path of the D-Bus object backing this {@link PersonaStore}.
- *
- * This is the path of the BlueZ device object on D-Bus which provides the
- * contacts in this store.
- *
- * @since 0.9.6
- */
- public string object_path
- {
- get { return this._object_path; }
- construct { this._object_path = value; }
- }
-
- /**
- * Create a new PersonaStore.
- *
- * Create a new persona store to expose the {@link Persona}s provided by the
- * device with the given Bluetooth address.
- *
- * @param device the D-Bus object for the Bluetooth device.
- * @param object_path the D-Bus path of the object for the Bluetooth device
- * @param obex_client the D-Bus obex client object.
- *
- * @since 0.9.6
- */
- public PersonaStore (Device device, string object_path,
- org.bluez.obex.Client obex_client)
- {
- Object (id: device.address,
- object_path: object_path,
- display_name: device.alias);
-
- this._device = device;
- this._obex_client = obex_client;
-
- this.set_is_trusted (this._device.trusted);
- }
-
- construct
- {
- this._personas = new HashMap<string, Persona> ();
- this._personas_ro = this._personas.read_only_view;
- }
-
- /**
- * Load contacts from a file and update the persona store.
- *
- * Load contacts from a file identified by its {@link File} and update
- * the persona store accordingly. Contacts are stored in the file as a
- * sequence of vCards, separated by blank lines.
- *
- * If a contact already exists in the store, its properties will be updated
- * from the vCard; otherwise it will be added as a new contact to the store.
- * Contacts which are in the store and not in the vCard will be removed from
- * the store.
- *
- * If this throws an error, it guarantees to leave the store’s internal state
- * unchanged, but may change the state of {@link Persona}s in the store.
- *
- * @param file the file where the contacts are stored
- * @throws IOError if there was an error communicating with D-Bus
- * @throws Error if the given file couldn’t be read
- *
- * @since 0.9.6
- */
- private async void _update_contacts_from_file (File file) throws IOError
- {
- var added_personas = new HashSet<Persona> ();
- var removed_personas = new HashSet<Persona> ();
- var photos_up_to_date = true;
-
- debug ("Parsing contacts from file ‘%s’.", file.get_path ());
-
- /* Start with all personas being marked as removed, and then eliminate the
- * ones which are found in the vCard. */
- removed_personas.add_all (this._personas.values);
-
- try
- {
- var dis = new DataInputStream (file.read ());
- uint i = 0;
- string? line = null;
- StringBuilder vcard = new StringBuilder ();
- var vcard_without_photo = new StringBuilder ();
-
- /* For each vCard in the file create or update a Persona. */
- while ((line = yield dis.read_line_async ()) != null)
- {
- /* Ignore blank lines between vCards. */
- if (vcard.len == 0 && line.strip () == "")
- continue;
-
- vcard.append (line);
- vcard.append_c ('\n');
-
- if (!line.has_prefix ("PHOTO:") && !line.has_prefix ("PHOTO;"))
- {
- vcard_without_photo.append (line);
- vcard_without_photo.append_c ('\n');
- }
-
- if (line.strip () == "END:VCARD")
- {
- var card = new E.VCard.from_string (vcard.str);
-
- /* The first vCard is always the user themselves. */
- var is_user = (i == 0);
-
- /* Construct the card’s IID. */
- var iid_is_checksum = false;
- string iid;
-
- /* This prefers the ‘UID’ attribute from the vCard, if it’s
- * available. However, it is not a required attribute, so many
- * phones do not implement it; in those cases, fall back to a
- * checksum of the vCard data itself. This means that whenever
- * a contact’s properties change in the vCard its IID will
- * change and hence the persona will be removed and re-added,
- * but without stable UIDs this is unavoidable.
- *
- * Note that the checksum is always calculated from the vCard
- * data *without* the photo. This hopefully ensures that IIDs
- * from queries which do and do not include photos will
- * match. */
- var attribute = card.get_attribute ("UID");
- if (attribute != null)
- {
- /* Try the UID attribute. */
- iid = attribute.get_value_decoded ().str;
- }
- else
- {
- /* Fallback. */
- iid =
- Checksum.compute_for_string (ChecksumType.SHA1,
- vcard_without_photo.str);
- iid_is_checksum = true;
- }
-
- /* Create or update the persona. */
- var persona = this._personas.get (iid);
- if (persona == null)
- {
- persona =
- new Persona (vcard.str, card, this, is_user, iid);
- photos_up_to_date = false;
- }
- else
- {
- /* If the IID is a checksum and we found the persona in
- * the store, that means their properties haven’t
- * changed, so as an optimisation, don’t bother updating
- * the Persona from the vCard in that case. */
- if (iid_is_checksum == false ||
- vcard_without_photo.len != vcard.len)
- {
- /* Note: This updates persona’s state, which could be
- * left updated if we later throw an error. */
- if (persona.update_from_vcard (card) == true)
- photos_up_to_date = false;
- }
- }
-
- if (removed_personas.remove (persona) == false)
- added_personas.add (persona);
-
- i++;
- vcard.erase ();
- vcard_without_photo.erase ();
- }
- }
- }
- catch (GLib.Error e1)
- {
- /* I/O error reading the file. */
- throw new IOError.FAILED (
- /* Translators: the parameter is an error message. */
- _("Error reading the transferred address book file: %s"),
- e1.message);
- }
-
- /* Now that all the I/O is done and no more errors can be thrown, update
- * the store’s internal state. */
- debug ("Finished parsing personas; now updating store state with %u " +
- "added personas and %u removed personas.", added_personas.size,
- removed_personas.size);
-
- foreach (var p in added_personas)
- this._personas.set (p.iid, p);
- foreach (var p in removed_personas)
- this._personas.unset (p.iid);
-
- this._photos_up_to_date = photos_up_to_date;
-
- if (added_personas.is_empty == false ||
- removed_personas.is_empty == false)
- {
- this._emit_personas_changed (added_personas, removed_personas);
- }
- }
-
- /**
- * Set the persona store's alias.
- *
- * This will be called in response to a property change sent to the Backend.
- *
- * @param alias the device’s new alias
- *
- * @since 0.9.6
- */
- internal void set_alias (string alias)
- {
- debug ("Device ‘%s’ (%s) changed alias to ‘%s’.", this._display_name,
- this._device.address, alias);
-
- this._display_name = alias;
- this.notify_property ("display-name");
- }
-
- /**
- * Set the persona store's trust level.
- *
- * This will be called in response to a property change sent to the Backend.
- *
- * Default to partial trust. BlueZ persona UIDs are built from a SHA1
- * of the contact’s vCard, which we believe can’t be maliciously edited
- * to corrupt linking.
- *
- * The trust for each device is manually set by the user in the BlueZ
- * interface on the computer.
- *
- * @param trusted ``true`` if the user trusts the device, ``false`` otherwise
- *
- * @since 0.9.6
- */
- internal void set_is_trusted (bool trusted)
- {
- debug ("Device ‘%s’ (%s) marked as %s.", this._device.alias,
- this._device.address, trusted ? "trusted" : "untrusted");
-
- this.trust_level =
- trusted ? PersonaStoreTrust.FULL : PersonaStoreTrust.PARTIAL;
- }
-
- /**
- * Set the persona store's connection state.
- *
- * This will be called in response to a property change sent to the Backend.
- *
- * If this throws an error, it guarantees to leave the store’s internal state
- * unchanged.
- *
- * @param connected ``true`` if the device is now connected, ``false``
- * otherwise
- *
- * @throws IOError if the operation was cancelled
- * (see {@link _update_contacts})
- * @throws PersonaStoreError if the contacts couldn’t be updated
- * (see {@link _update_contacts})
- *
- * @since 0.9.6
- */
- internal async void set_connection_state (bool connected)
- throws IOError, PersonaStoreError
- {
- if (connected == true)
- {
- debug ("Device ‘%s’ (%s) is connected.", this._device.alias,
- this._device.address);
-
- yield this._update_contacts (false);
- }
- else
- {
- debug ("Device ‘%s’ (%s) is disconnected.", this._device.alias,
- this._device.address);
-
- /* Cancel any ongoing or scheduled transfers. */
- this.cancel_updates ();
- }
- }
-
- /**
- * Create a new obex session for this Persona store.
- *
- * Create a new obex session for this Persona store if no previous session
- * already exists.
- *
- * @param obex_pbap return location for an OBEX PBAP proxy object
- * @returns the path of the OBEX session D-Bus object
- * @throws IOError if it can't connect to D-Bus
- * @throws DBusError if it can't create a new OBEX session
- *
- * @since 0.9.6
- */
- private async dynamic ObjectPath _new_obex_session (
- out org.bluez.obex.PhonebookAccess obex_pbap)
- throws DBusError, IOError
- {
- debug ("Creating a new OBEX session.");
-
- var args = new HashTable<string, Variant> (null, null);
- args["Target"] = "PBAP";
-
- var session_path = yield this._obex_client.create_session (this.id, args);
-
- debug (" Got OBEX session path: %s", session_path);
-
- obex_pbap =
- yield Bus.get_proxy (BusType.SESSION, "org.bluez.obex", session_path);
-
- debug (" Got OBEX PBAP proxy: %p", obex_pbap);
-
- return session_path;
- }
-
- /**
- * Remove the specified OBEX session from this persona store.
- *
- * Remove the specified OBEX session for this persona store and discard its
- * transfer.
- *
- * @param session_path the path of the OBEX session D-Bus object to remove
- *
- * @since 0.9.6
- */
- private async void _remove_obex_session (dynamic ObjectPath session_path)
- {
- try
- {
- yield this._obex_client.remove_session (session_path);
- }
- catch (IOError ie)
- {
- /* Ignore errors from closing or cancelling, or if the session has
- * disappeared already. */
- if (ie is IOError.CLOSED || ie is IOError.CANCELLED)
- return;
- if (ie is IOError.DBUS_ERROR &&
- ie.message.has_prefix ("GDBus.Error:org.freedesktop.DBus." +
- "Python.dbus.exceptions.DBusException: " +
- "('org.freedesktop.DBus.Mock.NameError'"))
- {
- /* Only used in unit tests. */
- return;
- }
-
- warning ("Couldn’t remove OBEX session ‘%s’: %s",
- session_path, ie.message);
- }
- catch (DBusError de)
- {
- warning ("Couldn’t remove OBEX session ‘%s’: %s",
- session_path, de.message);
- }
- }
-
- /**
- * Watch an OBEX transfer identified by its D-Bus path.
- *
- * This only returns once the transfer is complete (or has failed) and the
- * transfer object has been destroyed.
- *
- * If this throws an error, it guarantees to leave the store’s internal state
- * unchanged.
- *
- * @param path the D-Bus transfer object path to watch.
- * @param cancellable an optional {@link Cancellable} object to cancel the
- * transfer
- *
- * @throws IOError if the operation was cancelled, or if another failure
- * occurred (unavoidable; valac generates invalid C if we try to handle
- * IOError internally here)
- * @throws PersonaStoreError if the transfer failed
- *
- * @since 0.9.6
- */
- private async void _perform_obex_transfer (string path,
- Cancellable? cancellable = null)
- throws IOError, PersonaStoreError
- {
- org.bluez.obex.Transfer? transfer = null;
-
- try
- {
- /* Bail early if the transfer's already been cancelled. */
- if (cancellable != null)
- cancellable.set_error_if_cancelled ();
-
- /* Get an OBEX proxy for the transfer object. */
- transfer =
- yield Bus.get_proxy (BusType.SESSION, "org.bluez.obex", path);
- var transfer_proxy = (DBusProxy) transfer;
-
- var has_yielded = false;
- string? transfer_status = null;
- ulong signal_id;
- ulong cancellable_id = 0;
-
- /* Find the initial status, if it’s already been set. Otherwise it’ll
- * be null. */
- transfer_status = transfer.status;
-
- /* Set up the cancellable. */
- if (cancellable != null)
- {
- cancellable_id = cancellable.connect (() =>
- {
- transfer_status = "error";
- if (has_yielded == true)
- this._perform_obex_transfer.callback ();
- });
- }
-
- /* There is no need to add a timeout here, as BlueZ already has one
- * implemented for if transactions take too long. */
- signal_id = transfer_proxy.g_properties_changed.connect (
- (changed, invalidated) =>
- {
- var property =
- changed.lookup_value ("Status", VariantType.STRING);
- if (property == null)
- return;
-
- var status = property.get_string ();
- transfer_status = status;
-
- if (status == "complete" || status == "error")
- {
- /* Finished. Return to the yield. */
- if (has_yielded == true)
- this._perform_obex_transfer.callback ();
- }
- else if (status == "queued" || status == "active")
- {
- /* Do nothing. */
- }
- else
- {
- warning ("Unknown OBEX transfer status ‘%s’.", status);
- }
- });
-
- /* Yield until the above signal handler is called with a ‘success’ or
- * ‘error’ status. */
- if (transfer_status != "complete" && transfer_status != "error")
- {
- has_yielded = true;
- yield;
- }
-
- transfer_proxy.disconnect (signal_id);
-
- if (cancellable_id != 0)
- cancellable.disconnect (cancellable_id);
-
- /* Process the results: either success or error. */
- if (transfer_status == "complete")
- {
- string? filename = transfer.filename;
- if (filename == null)
- {
- /* The Filename property is optional, so bail if it’s not
- * available for whatever reason. */
- throw new PersonaStoreError.STORE_OFFLINE (
- /* Translators: the first parameter is the name of the
- * failed transfer, and the second is a Bluetooth device
- * alias. */
- _("Error during transfer of the address book ‘%s’ from " +
- "Bluetooth device ‘%s’."),
- transfer.name, this._display_name);
- }
-
- var file = File.new_for_path ((!) filename);
-
- debug ("vCard’s filename for device ‘%s’ (%s): %s",
- this._display_name, this.id, (!) filename);
-
- yield this._update_contacts_from_file (file);
- }
- else if (transfer_status == "error")
- {
- /* On cancellation, throw an IOError instead of a
- * PersonaStoreError. */
- if (cancellable != null)
- cancellable.set_error_if_cancelled ();
-
- throw new PersonaStoreError.STORE_OFFLINE (
- /* Translators: the first parameter is the name of the failed
- * transfer, and the second is a Bluetooth device alias. */
- _("Error during transfer of the address book ‘%s’ from Bluetooth device ‘%s’."),
- transfer.name, this._display_name);
- }
- else
- {
- assert_not_reached ();
- }
- }
- finally
- {
- /* Reset the OBEX transfer and clear out the temporary file. Do this
- * without yielding because BlueZ should choose a different filename
- * next time (using mkstemp() or similar). */
- if (transfer != null && transfer.filename != null)
- {
- var file = File.new_for_path (transfer.filename);
- file.delete_async.begin (GLib.Priority.DEFAULT, null,
- (o, r) =>
- {
- try
- {
- file.delete_async.end (r);
- }
- catch (GLib.Error e1)
- {
- /* Ignore. */
- }
- });
- }
- }
- }
-
- /**
- * Update contacts from this persona store.
- *
- * Update contacts from this persona store by initiating a new OBEX
- * transfer, unless one is already in progress. If a transfer is already in
- * progress, leave it running and return immediately.
- *
- * If this throws an error, it guarantees to leave the store’s internal state
- * unchanged, apart from scheduling a new update operation to happen in the
- * future. This will always happen, regardless of success or failure.
- *
- * @param download_photos whether to download photos
- * @throws IOError if the operation was cancelled
- * @throws PersonaStoreError if the contacts couldn’t be downloaded from the
- * device
- *
- * @since 0.9.6
- */
- private async void _update_contacts (bool download_photos)
- throws IOError, PersonaStoreError
- {
- dynamic ObjectPath? session_path = null;
- org.bluez.obex.PhonebookAccess? obex_pbap = null;
- var success = true;
-
- if (this._update_contacts_cancellable != null)
- {
- /* There’s an ongoing _update_contacts() call. Since downloading
- * the address book takes a long time (tens of seconds), we don’t
- * want to cancel the ongoing operation. Just return
- * immediately. */
- debug ("Not updating contacts due to ongoing update operation.");
- return;
- }
-
- var profiling = Internal.profiling_start ("updating BlueZ.PersonaStore (ID: %s) " +
- "contacts", this.id);
-
- debug ("Updating contacts.");
-
- try
- {
-
- string path;
- HashTable<string, Variant> props;
-
- this._update_contacts_cancellable = new Cancellable ();
-
- /* Set up an OBEX session. */
- try
- {
- session_path = yield this._new_obex_session (out obex_pbap);
- }
- catch (GLib.Error e1)
- {
- if (e1 is IOError.DBUS_ERROR &&
- e1.message.has_suffix ("OBEX Connect failed with 0x43"))
- {
- /* This error is sent when the user denies the computer access
- * to the phone’s address book over Bluetooth, after accepting
- * the pairing request. */
- throw new PersonaStoreError.PERMISSION_DENIED (
- _("Permission to access the address book on Bluetooth device ‘%s’ was denied by the user."),
- this._device.alias);
- }
-
- throw new PersonaStoreError.STORE_OFFLINE (
- /* Translators: the first parameter is a Bluetooth device
- * alias, and the second is an error message. */
- _("An OBEX address book transfer from device ‘%s’ could not be started: %s"),
- this._device.alias, e1.message);
- }
-
- try
- {
- /* Select the phonebook object we want to download ie:
- * PB: phonebook for the saved contacts */
- obex_pbap.select ("int", "PB");
-
- /* Initiate a phone book transfer from the PSE server using a
- * plain string vCard format, transferring to a temporary file. */
- var phonebook_filter =
- new HashTable<string, Variant> (null , null);
- phonebook_filter.insert ("Format", "Vcard30");
- if (download_photos == true)
- {
- /* Download everything including the photo. */
- phonebook_filter.insert ("Fields",
- new Variant.strv ({
- "UID", "N", "FN", "NICKNAME", "TEL", "URL", "EMAIL",
- "PHOTO"
- }));
- }
- else
- {
- /* Download everything except the photo. */
- phonebook_filter.insert ("Fields",
- new Variant.strv ({
- "UID", "N", "FN", "NICKNAME", "TEL", "URL", "EMAIL"
- }));
- }
-
- obex_pbap.pull_all ("", phonebook_filter, out path, out props);
- }
- catch (GLib.Error e2)
- {
- throw new PersonaStoreError.STORE_OFFLINE (
- /* Translators: the first parameter is a Bluetooth device
- * alias, and the second is an error message. */
- _("The OBEX address book transfer from device ‘%s’ failed: %s"),
- this._device.alias, e2.message);
- }
-
- try
- {
- yield this._perform_obex_transfer (path,
- this._update_contacts_cancellable);
- }
- catch (IOError e3)
- {
- if (e3 is IOError.CANCELLED)
- throw e3;
-
- throw new PersonaStoreError.STORE_OFFLINE (
- /* Translators: the first parameter is a Bluetooth device
- * alias, and the second is an error message. */
- _("Error during transfer of the address book from Bluetooth device ‘%s’: %s"),
- this._display_name, e3.message);
- }
- }
- catch (IOError e4)
- {
- /* Used below. */
- success = false;
- throw e4;
- }
- catch (PersonaStoreError e5)
- {
- /* Used below. */
- success = false;
- throw e5;
- }
- finally
- {
- /* Tear down again. */
- if (session_path != null)
- this._remove_obex_session.begin (session_path);
- obex_pbap = null;
-
- this._update_contacts_cancellable = null;
-
- /* Track the number of consecutive failures. */
- if (success == true)
- this._update_contacts_failures = 0;
- else
- this._update_contacts_failures++;
-
- /* Schedule the next update. See the documentation for
- * _schedule_update_contacts() for details. */
- var new_download_photos =
- success == true && this._photos_up_to_date == false;
- this._schedule_update_contacts (new_download_photos);
-
- Internal.profiling_end ((owned) profiling);
- }
- }
-
- /**
- * Schedule the next call to {@link _update_contacts}.
- *
- * This calculates a suitable timeout value and schedules the next timeout
- * for updating the contacts.
- *
- * The update scheme is as follows:
- * 1. Download the contacts (without photos) as soon as connected to the
- * phone.
- * 2. Schedule a second download attempt for a few seconds after the first
- * one completes. If the first one completes successfully, this second
- * download will include photos; otherwise, it won’t.
- * 3. Schedule subsequent download attempts for exponentially increasing
- * timeouts, up to a maximum timeout (at which point the timeouts enter a
- * linear region and repeat indefinitely). Subsequent download attempts
- * will include photos only if they have not been successfully downloaded
- * already, or if the previous download attempt caused other property
- * changes in a persona (indicating that the address book has been edited
- * on the phone).
- * 4. If updates fail a certain number of consecutive times, give up
- * completely and leave the persona store in a prepared but empty
- * quiescent state. Update attempts will only restart if the phone is then
- * disconnected and reconnected.
- *
- * The rationale for this design is to:
- * A. Allow for the user accidentally denying the first connection request on
- * the phone, or not noticing it and it timing out. Attempting a second
- * download after a timeout gives them an opportunity to fix the problem.
- * B. If the user explicitly denies the connection request on the phone, the
- * phone should remember this and automatically deny all future connection
- * attempts until the consecutive failure limit is reached. The user
- * shouldn’t be pestered to accept again.
- * C. Watch for changes in the user’s address book and update the persona
- * store accordingly. Unfortunately this has to be done by polling, since
- * neither PBAP not OBEX itself support push notifications.
- *
- * @param download_photos whether to download photos
- *
- * @since 0.9.7
- */
- private void _schedule_update_contacts (bool download_photos)
- {
- /* Bail if a call is already scheduled. */
- if (this._update_contacts_id != 0)
- return;
-
- /* If there have been too many consecutive failures in _update_contacts(),
- * give up. */
- if (this._update_contacts_failures >=
- PersonaStore._MAX_CONSECUTIVE_FAILURES)
- return;
-
- /* Calculate the timeout (in milliseconds). If no divisor is applied, the
- * timeout should always be a whole number of seconds. */
- var timeout =
- uint.min (PersonaStore._TIMEOUT_MIN +
- (uint) Math.pow (PersonaStore._TIMEOUT_BASE,
- this._update_contacts_n),
- PersonaStore._TIMEOUT_MAX);
- this._update_contacts_n++;
-
- timeout *= 1000; /* convert from seconds to milliseconds */
-
- /* Allow the timeout to be tweaked for testing. */
- var divisor_str =
- Environment.get_variable ("FOLKS_BLUEZ_TIMEOUT_DIVISOR");
- if (divisor_str != null)
- {
- uint64 divisor;
- if (uint64.try_parse (divisor_str, out divisor) == true)
- timeout /= (uint) divisor;
- }
-
- /* Schedule the update. */
- SourceFunc fn = () =>
- {
- debug ("Scheduled update firing for BlueZ store ‘%s’.", this.id);
-
- /* Acknowledge the source has fired. */
- this._update_contacts_id = 0;
-
- this._update_contacts.begin (download_photos, (o, r) =>
- {
- try
- {
- this._update_contacts.end (r);
- }
- catch (GLib.Error e4)
- {
- /* Ignore cancellation. */
- if (e4 is IOError.CANCELLED)
- return;
-
- /* Don't warn about offline stores. */
- if (e4 is PersonaStoreError.STORE_OFFLINE)
- {
- debug ("Not updating persona store from BlueZ due to " +
- "store being offline: %s", e4.message);
- }
- else
- {
- warning ("Error updating persona store from BlueZ: %s",
- e4.message);
- }
- }
- });
-
- return false;
- };
-
- if (timeout % 1000 == 0)
- {
- this._update_contacts_id =
- Timeout.add_seconds (timeout / 1000, (owned) fn);
- }
- else
- {
- this._update_contacts_id =
- Timeout.add (timeout, (owned) fn);
- }
- }
-
- /**
- * Cancel ongoing and scheduled updates from the device.
- *
- * This doesn't remove the store, but does cancel all ongoing updates and
- * future scheduled updates, in preparation for removing the store. This is
- * necessary to avoid the store maintaining a reference to itself (through the
- * closure for the next scheduled update) and thus never being finalised.
- *
- * @since 0.9.7
- */
- internal void cancel_updates ()
- {
- if (this._update_contacts_cancellable != null)
- this._update_contacts_cancellable.cancel ();
- if (this._update_contacts_id != 0)
- {
- Source.remove (this._update_contacts_id);
- this._update_contacts_id = 0;
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public override async void prepare () throws PersonaStoreError
- {
- var profiling = Internal.profiling_start ("preparing BlueZ.PersonaStore (ID: %s)",
- this.id);
-
- if (this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
-
- /* Start downloading the contacts, regardless of the phone’s
- * connection state. If the phone is disconnected, the download should
- * force it to be connected. */
- try
- {
- yield this._update_contacts (false);
- }
- catch (IOError e1)
- {
- /* If this happens, the update operation was cancelled, which
- * means the phone spontaneously disconnected during the transfer.
- * Act as if the store has gone offline and mark preparation as
- * complete. */
- throw new PersonaStoreError.STORE_OFFLINE (
- _("Bluetooth device ‘%s’ disappeared during address book transfer."),
- this._device.alias);
- }
- finally
- {
- /* Done or failed. We always mark the persona store as prepared
- * and quiescent because of the limited data available to us from
- * BlueZ: we only have the Paired and Connected properties.
- * So a phone can be paired with the laptop, but its Bluetooth
- * can be turned off; or a phone can be paired with the laptop and
- * its Bluetooth turned on but no connection is active. In the
- * former case, we don't want to connect to the device (because
- * that will just fail). In the latter case, we do, because we
- * want to download the address book. However, BlueZ exposes no
- * information allowing differentiation of the two cases, so we
- * must always create a persona store for a paired device, and
- * must always try and connect. In order to prevent paired but
- * disconnected phones from causing quiescence to never be reached
- * (which may be a common occurrence), we always mark the stores
- * as prepared and quiescent.
- *
- * FIXME: Note that this will fit in well with caching, if that is
- * ever implemented in the BlueZ backend. Paired but disconnected
- * phones (with their Bluetooth off) can still have persona stores
- * on the laptop, and those persona stores can be populated by
- * cached personas until the phone is reconnected. */
- this._is_prepared = true;
- this.notify_property ("is-prepared");
-
- this._is_quiescent = true;
- this.notify_property ("is-quiescent");
- }
- }
- finally
- {
- this._prepare_pending = false;
- }
-
- Internal.profiling_end ((owned) profiling);
- }
-
- /**
- * Remove a {@link Persona} from the PersonaStore.
- *
- * See {@link Folks.PersonaStore.remove_persona}.
- *
- * @param persona the {@link Persona} to remove
- * @throws Folks.PersonaStoreError.READ_ONLY every time since the
- * BlueZ backend is read-only.
- *
- * @since 0.9.6
- */
- public override async void remove_persona (Folks.Persona persona)
- throws Folks.PersonaStoreError
- {
- throw new PersonaStoreError.READ_ONLY (
- "Personas cannot be removed from this store.");
- }
-
- /**
- * Add a new {@link Persona} to the PersonaStore.
- *
- * See {@link Folks.PersonaStore.add_persona_from_details}.
- *
- * @param details a map of keys to values giving the persona’s initial details
- * @throws Folks.PersonaStoreError.READ_ONLY every time since the
- * BlueZ backend is read-only.
- *
- * @since 0.9.6
- */
- public override async Folks.Persona? add_persona_from_details (
- HashTable<string, Value?> details) throws Folks.PersonaStoreError
- {
- throw new PersonaStoreError.READ_ONLY (
- "Personas cannot be added to this store.");
- }
-}
diff --git a/backends/bluez/bluez-persona.vala b/backends/bluez/bluez-persona.vala
deleted file mode 100644
index 7d0fdc4d..00000000
--- a/backends/bluez/bluez-persona.vala
+++ /dev/null
@@ -1,469 +0,0 @@
-/*
- * Copyright (C) 2010-2013 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Arun Raghavan <arun.raghavan@collabora.co.uk>
- * Jeremy Whiting <jeremy.whiting@collabora.com>
- * Simon McVittie <simon.mcvittie@collabora.co.uk>
- * Matthieu Bouron <matthieu.bouron@collabora.com>
- *
- * Based on kf-persona.vala by:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-using Folks;
-using Folks.Backends.BlueZ;
-
-/**
- * A persona subclass which represents a single persona from a simple key file.
- *
- * @since 0.9.6
- */
-public class Folks.Backends.BlueZ.Persona : Folks.Persona,
- AvatarDetails,
- EmailDetails,
- NameDetails,
- PhoneDetails,
- UrlDetails
-{
- private const string[] _linkable_properties =
- {
- "phone-numbers",
- "email-addresses"
- };
- private static string[] _writeable_properties = { };
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public override string[] linkable_properties
- {
- get { return BlueZ.Persona._linkable_properties; }
- }
-
- private SmallSet<UrlFieldDetails>? _urls = null;
- private Set<UrlFieldDetails> _urls_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- [CCode (notify = false)]
- public Set<UrlFieldDetails> urls
- {
- get { return this._urls_ro; }
- set { this.change_urls.begin (value); } /* not writeable */
- }
-
- private LoadableIcon? _avatar = null;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- [CCode (notify = false)]
- public LoadableIcon? avatar
- {
- get { return this._avatar; }
- set { this.change_avatar.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public override string[] writeable_properties
- {
- get { return BlueZ.Persona._writeable_properties; }
- }
-
- private SmallSet<PhoneFieldDetails>? _phone_numbers = null;
- private Set<PhoneFieldDetails> _phone_numbers_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- [CCode (notify = false)]
- public Set<PhoneFieldDetails> phone_numbers
- {
- get { return this._phone_numbers_ro; }
- set { this.change_phone_numbers.begin (value); } /* not writeable */
- }
-
- private StructuredName? _structured_name = null;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- [CCode (notify = false)]
- public StructuredName? structured_name
- {
- get { return this._structured_name; }
- set { this.change_structured_name.begin (value); } /* not writeable */
- }
-
- private string _full_name = "";
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- [CCode (notify = false)]
- public string full_name
- {
- get { return this._full_name; }
- set { this.change_full_name.begin (value); } /* not writeable */
- }
-
- private string _nickname = "";
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- [CCode (notify = false)]
- public string nickname
- {
- get { return this._nickname; }
- set { this.change_nickname.begin (value); } /* not writeable */
- }
-
- private SmallSet<EmailFieldDetails>? _email_addresses = null;
- private Set<EmailFieldDetails> _email_addresses_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- [CCode (notify = false)]
- public Set<EmailFieldDetails> email_addresses
- {
- get { return this._email_addresses_ro; }
- set { this.change_email_addresses.begin (value); } /* not writeable */
- }
-
- /**
- * Create a new persona.
- *
- * Create a new persona for the {@link PersonaStore} ``store``, representing
- * the Persona in the given ``vcard``.
- *
- * @param vcard the vCard stored as a string
- * @param card a parsed version of the vCard
- * @param store the store to which the Persona belongs.
- * @param is_user whether the Persona is the user itself or not.
- * @param iid pre-calculated IID for the persona
- *
- * @since 0.9.6
- */
- public Persona (string vcard, E.VCard card, Folks.PersonaStore store,
- bool is_user, string iid)
- {
- var uid = Folks.Persona.build_uid ("bluez", store.id, iid);
-
- /* Have to use the IID as the display ID, since PBAP vCards provide no
- * other useful human-readable and unique IDs. */
- Object (display_id: iid,
- iid: iid,
- uid: uid,
- store: store,
- is_user: is_user);
-
- this.update_from_vcard (card);
- }
-
- construct
- {
- debug ("Adding BlueZ Persona '%s' (IID '%s')", this.uid, this.iid);
-
- this._phone_numbers = new SmallSet<PhoneFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._phone_numbers_ro = this._phone_numbers.read_only_view;
- this._email_addresses = new SmallSet<EmailFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._email_addresses_ro = this._email_addresses.read_only_view;
- this._urls = new SmallSet<UrlFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._urls_ro = this._urls.read_only_view;
- }
-
- private void _update_params (AbstractFieldDetails details,
- E.VCardAttribute attr)
- {
- foreach (unowned E.VCardAttributeParam param in attr.get_params ())
- {
- /* EVCard handles parameter names and values entirely
- * case-insensitively, so we’ll do the same. */
- foreach (unowned string param_value in param.get_values ())
- {
- details.add_parameter (param.get_name ().down (),
- param_value.down ());
- }
- }
- }
-
- /**
- * Update the Persona’s properties from a vCard.
- *
- * Parse the given ``vcard`` and set the persona’s properties from it. This
- * emits property change notifications as appropriate.
- *
- * @param vcard pre-parsed vCard
- * @return ``true`` if any properties were changed, ``false`` otherwise
- *
- * @since 0.9.7
- */
- internal bool update_from_vcard (E.VCard card)
- {
- var properties_changed = false;
-
- /* Somewhere to store the new property values. */
- var new_phone_numbers = new SmallSet<PhoneFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- var new_uris = new SmallSet<UrlFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- var new_email_addresses = new SmallSet<EmailFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- BytesIcon? new_avatar = null;
- var new_full_name = "";
- var new_nickname = "";
- StructuredName? new_structured_name = null;
-
- /* Parse the attributes by iterating over the vCard’s attribute list once
- * only. Convenience functions like E.VCard.get_attribute() cause multiple
- * iterations over the list. */
- unowned GLib.List<E.VCardAttribute> attrs =
- card.get_attributes ();
-
- foreach (var attr in attrs)
- {
- unowned string attr_name = attr.get_name ();
-
- if (attr_name == "TEL")
- {
- var val = attr.get_value ();
- if (val == null || (!) val == "")
- continue;
-
- var new_field_details = new PhoneFieldDetails ((!) val);
- this._update_params (new_field_details, attr);
- new_phone_numbers.add (new_field_details);
- }
- else if (attr_name == "URL")
- {
- var val = attr.get_value ();
- if (val == null || (!) val == "")
- continue;
-
- var new_field_details = new UrlFieldDetails ((!) val);
- this._update_params (new_field_details, attr);
- new_uris.add (new_field_details);
- }
- else if (attr_name == "EMAIL")
- {
- var val = attr.get_value ();
- if (val == null || (!) val == "")
- continue;
-
- var new_field_details = new EmailFieldDetails ((!) val);
- this._update_params (new_field_details, attr);
- new_email_addresses.add (new_field_details);
- }
- else if (attr_name == "PHOTO")
- {
- var encoded_data = (string) attr.get_value ().data;
- var bytes = new Bytes (Base64.decode (encoded_data));
- new_avatar = new BytesIcon (bytes);
- }
- else if (attr_name == "FN")
- new_full_name = attr.get_value ();
- else if (attr_name == "NICKNAME")
- new_nickname = attr.get_value ();
- else if (attr_name == "N")
- {
- unowned GLib.List<string> values = attr.get_values ();
- unowned string? family_name = null, given_name = null,
- additional_names = null, prefixes = null, suffixes = null;
-
- if (values != null)
- {
- family_name = values.data;
- values = values.next;
- }
- if (values != null)
- {
- given_name = values.data;
- values = values.next;
- }
- if (values != null)
- {
- additional_names = values.data;
- values = values.next;
- }
- if (values != null)
- {
- prefixes = values.data;
- values = values.next;
- }
- if (values != null)
- {
- suffixes = values.data;
- values = values.next;
- }
-
- if (suffixes == null || values != null)
- {
- debug ("Expected 5 components in N attribute of vCard, " +
- "but got %s.", (suffixes == null) ? "fewer" : "more");
- }
-
- new_structured_name =
- new StructuredName (family_name, given_name, additional_names,
- prefixes, suffixes);
- }
- else if (attr_name != "VERSION" && attr_name != "UID")
- {
- /* Unknown attribute. */
- warning ("Unknown attribute ‘%s’ in vCard for persona %s.",
- attr_name, this.uid);
- }
- }
-
- /* Now test the new property values to see if they’ve changed; if so, emit
- * property change notifications. */
- this.freeze_notify ();
-
- /* Phone numbers. */
- if (!Utils.set_string_afd_equal (this._phone_numbers,
- new_phone_numbers))
- {
- this._phone_numbers = new_phone_numbers;
- this._phone_numbers_ro = new_phone_numbers.read_only_view;
- this.notify_property ("phone-numbers");
- properties_changed = true;
- }
-
- /* URIs. */
- if (!Folks.Internal.equal_sets<UrlFieldDetails> (this._urls, new_uris))
- {
- this._urls = new_uris;
- this._urls_ro = new_uris.read_only_view;
- this.notify_property ("urls");
- properties_changed = true;
- }
-
- /* E-mail addresses. */
- if (!Folks.Internal.equal_sets<EmailFieldDetails> (this._email_addresses,
- new_email_addresses))
- {
- this._email_addresses = new_email_addresses;
- this._email_addresses_ro = new_email_addresses.read_only_view;
- this.notify_property ("email-addresses");
- properties_changed = true;
- }
-
- /* Photo. */
- if ((new_avatar == null) != (this._avatar == null) ||
- (new_avatar != null && this._avatar != null &&
- !new_avatar.equal (this._avatar)))
- {
- this._avatar = new_avatar;
- this.notify_property ("avatar");
- properties_changed = true;
- }
-
- /* Full name. */
- if (this._full_name != new_full_name)
- {
- this._full_name = new_full_name;
- this.notify_property ("full-name");
- properties_changed = true;
- }
-
- /* Nickname. */
- if (this._nickname != new_nickname)
- {
- this._nickname = new_nickname;
- this.notify_property ("nickname");
- properties_changed = true;
- }
-
- /* Structured name. */
- if ((new_structured_name == null) != (this._structured_name == null) ||
- (new_structured_name != null && this._structured_name != null &&
- !new_structured_name.equal (this._structured_name)))
- {
- this._structured_name = new_structured_name;
- this.notify_property ("structured-name");
- properties_changed = true;
- }
-
- this.thaw_notify ();
-
- return properties_changed;
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.6
- */
- public override void linkable_property_to_links (string prop_name,
- Folks.Persona.LinkablePropertyCallback callback)
- {
- if (prop_name == "phone-numbers")
- {
- foreach (var phone_number in this._phone_numbers)
- {
- if (phone_number.value != null)
- callback (phone_number.value);
- }
- }
- else if (prop_name == "email-addresses")
- {
- foreach (var email_address in this._email_addresses)
- {
- if (email_address.value != null)
- callback (email_address.value);
- }
- }
- else
- {
- /* Chain up */
- base.linkable_property_to_links (prop_name, callback);
- }
- }
-}
diff --git a/backends/bluez/meson.build b/backends/bluez/meson.build
deleted file mode 100644
index afeae63f..00000000
--- a/backends/bluez/meson.build
+++ /dev/null
@@ -1,44 +0,0 @@
-bluez_backend_name = 'bluez'
-
-# NOTE: we don't export a backend library here.
-
-bluez_backend_sources = [
- 'bluez-backend-factory.vala',
- 'bluez-backend.vala',
- 'bluez-persona-store.vala',
- 'bluez-persona.vala',
- 'org-bluez-obex-client.vala',
- 'org-bluez.vala',
-]
-
-bluez_backend_deps = [
- backend_deps,
- libebook_dep,
- libm_dep,
-]
-
-bluez_backend_vala_flags = [
-]
-
-bluez_backend_c_flags = [
- '-include', 'config.h',
- '-DBACKEND_NAME="@0@"'.format(bluez_backend_name),
- '-DG_LOG_DOMAIN="@0@"'.format(bluez_backend_name),
-]
-
-bluez_backend = shared_module(bluez_backend_name,
- bluez_backend_sources,
- dependencies: bluez_backend_deps,
- vala_args: bluez_backend_vala_flags,
- c_args: bluez_backend_c_flags,
- name_prefix: '',
- install_dir: folks_backend_dir / bluez_backend_name,
- install: true,
-)
-
-bluez_backend_dep = declare_dependency(
- link_with: bluez_backend,
- include_directories: include_directories('.'),
-)
-
-folks_backends += bluez_backend
diff --git a/backends/bluez/org-bluez-obex-client.vala b/backends/bluez/org-bluez-obex-client.vala
deleted file mode 100644
index 81ce9e07..00000000
--- a/backends/bluez/org-bluez-obex-client.vala
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2012-2013 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Arun Raghavan <arun.raghavan@collabora.co.uk>
- * Gustavo Padovan <gustavo.padovan@collabora.co.uk>
- * Matthieu Bouron <matthieu.bouron@collabora.com>
- */
-
-using GLib;
-
-namespace org
- {
- namespace bluez
- {
- namespace obex
- {
- [DBus (name = "org.bluez.obex.Client1")]
- public interface Client : Object
- {
- [DBus (name = "CreateSession")]
- public async abstract ObjectPath create_session (string address,
- HashTable<string, Variant> args) throws DBusError, IOError;
- [DBus (name = "RemoveSession")]
- public async abstract void remove_session (ObjectPath session)
- throws DBusError, IOError;
- }
-
- [DBus (name = "org.bluez.obex.PhonebookAccess1")]
- public interface PhonebookAccess : Object
- {
- /* Returned by List () */
- public struct PhonebookEntry
- {
- public string vcard;
- public string name;
- }
-
- public struct PhonebookPull
- {
- public ObjectPath path;
- public HashTable<string, Variant> props;
- }
-
- [DBus (name = "Select")]
- public abstract void select (string location, string phonebook)
- throws DBusError, IOError;
- [DBus (name = "List")]
- public abstract PhonebookEntry[] list (
- HashTable<string, Variant> filters)
- throws DBusError, IOError;
- [DBus (name = "ListFilterFields")]
- public abstract string[] list_filter_fields ()
- throws DBusError, IOError;
- [DBus (name = "PullAll")]
- public abstract void pull_all (string target,
- HashTable<string, Variant> filters, out string path,
- out HashTable<string, Variant> props)
- throws DBusError, IOError;
- }
-
- [DBus (name = "org.bluez.obex.Transfer1")]
- public interface Transfer : Object
- {
- [DBus (name = "Cancel")]
- public abstract void cancel () throws DBusError, IOError;
- [DBus (name = "Status")]
- public abstract string status { owned get; }
- [DBus (name = "Session")]
- public abstract ObjectPath session { owned get; }
- [DBus (name = "Name")]
- public abstract string name { owned get; }
- [DBus (name = "Type")]
- public abstract string transfer_type { owned get; }
- [DBus (name = "Time")]
- public abstract int64 time { get; }
- [DBus (name = "Size")]
- public abstract uint64 size { get; }
- [DBus (name = "Transferred")]
- public abstract uint64 transferred { get; }
- [DBus (name = "Filename")]
- public abstract string filename { owned get; }
- }
- }
- }
- }
diff --git a/backends/bluez/org-bluez.vala b/backends/bluez/org-bluez.vala
deleted file mode 100644
index c1e0cae8..00000000
--- a/backends/bluez/org-bluez.vala
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2012-2013 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Arun Raghavan <arun.raghavan@collabora.co.uk>
- * Gustavo Padovan <gustavo.padovan@collabora.co.uk>
- * Matthieu Bouron <matthieu.bouron@collabora.com>
- */
-
-using GLib;
-
-/* Reference:
- * http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/device-api.txt */
-namespace org
- {
- namespace bluez
- {
- [DBus (name = "org.bluez.Error")]
- public errordomain Error
- {
- NOT_READY,
- FAILED,
- IN_PROGRESS,
- ALREADY_CONNECTED,
- NOT_CONNECTED,
- DOES_NOT_EXIST,
- CONNECT_FAILED,
- NOT_SUPPORTED,
- INVALID_ARGUMENTS,
- AUTHENTICATION_CANCELED,
- AUTHENTICATION_FAILED,
- AUTHENTICATION_REJECTED,
- AUTHENTICATION_TIMEOUT,
- CONNECTION_ATTEMPT_FAILED
- }
-
- [DBus (name = "org.bluez.Device1")]
- public interface Device : Object
- {
- /* Methods. */
- [DBus (name = "Connect")]
- public abstract void connect ()
- throws org.bluez.Error, DBusError, IOError;
-
- [DBus (name = "Disconnect")]
- public abstract void disconnect ()
- throws org.bluez.Error, DBusError, IOError;
-
- [DBus (name = "DisconnectProfile")]
- public abstract void disconnect_profile (string uuid)
- throws org.bluez.Error, DBusError, IOError;
-
- [DBus (name = "Pair")]
- public abstract void pair ()
- throws org.bluez.Error, DBusError, IOError;
-
- [DBus (name = "CancelPairing")]
- public abstract void cancel_pairing ()
- throws org.bluez.Error, DBusError, IOError;
-
- /* Properties. */
- [DBus (name = "Address")]
- public abstract string address { owned get; }
-
- [DBus (name = "Name")]
- public abstract string name { owned get; }
-
- [DBus (name = "Icon")]
- public abstract string icon { owned get; }
-
- [DBus (name = "Class")]
- public abstract uint32 bluetooth_class { get; }
-
- [DBus (name = "Appearance")]
- public abstract uint16 appearance { get; }
-
- [DBus (name = "UUIDs")]
- public abstract string[] uuids { owned get; }
-
- [DBus (name = "Paired")]
- public abstract bool paired { get; }
-
- [DBus (name = "Connected")]
- public abstract bool connected { get; }
-
- [DBus (name = "Trusted")]
- public abstract bool trusted { get; set; }
-
- [DBus (name = "Blocked")]
- public abstract bool blocked { get; set; }
-
- [DBus (name = "Alias")]
- public abstract string alias { owned get; set; }
-
- [DBus (name = "Adapter")]
- public abstract ObjectPath adapter { owned get; }
-
- [DBus (name = "LegacyPairing")]
- public abstract bool legacy_pairing { get; }
-
- [DBus (name = "Modalias")]
- public abstract string mod_alias { owned get; }
-
- [DBus (name = "RSSI")]
- public abstract int16 rssi { get; }
- }
- }
- }
diff --git a/backends/dummy/dummy-backend-factory.vala b/backends/dummy/dummy-backend-factory.vala
deleted file mode 100644
index f507fc30..00000000
--- a/backends/dummy/dummy-backend-factory.vala
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2009 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>.
- * Copyright (C) 2009 Nokia Corporation.
- * Copyright (C) 2011, 2013 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Marco Barisione <marco.barisione@collabora.co.uk>
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- *
- * This file was originally part of Rygel.
- */
-
-using Folks;
-
-/**
- * The dummy backend module entry point.
- *
- * @backend_store a store to add the dummy backends to
- * @since 0.9.7
- */
-public void module_init (BackendStore backend_store)
-{
- backend_store.add_backend (new FolksDummy.Backend ());
-}
-
-/**
- * The dummy backend module exit point.
- *
- * @param backend_store the store to remove the backends from
- * @since 0.9.7
- */
-public void module_finalize (BackendStore backend_store)
-{
- /* FIXME: No backend_store.remove_backend() API exists. */
-}
diff --git a/backends/dummy/lib/dummy-backend.vala b/backends/dummy/lib/dummy-backend.vala
deleted file mode 100644
index bbd5fa89..00000000
--- a/backends/dummy/lib/dummy-backend.vala
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * Copyright (C) 2013 Philip Withnall
- * Copyright (C) 2013 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using Gee;
-using GLib;
-using Folks;
-
-extern const string BACKEND_NAME;
-
-/**
- * A backend which allows {@link FolksDummy.PersonaStore}s and
- * {@link FolksDummy.Persona}s to be programmatically created and manipulated,
- * for the purposes of testing the core of libfolks itself.
- *
- * This backend is not meant to be enabled in production use. The methods on
- * {@link FolksDummy.Backend} (and other classes) for programmatically
- * manipulating the backend's state are considered internal to libfolks and are
- * not stable.
- *
- * This backend maintains two sets of persona stores: the set of all persona
- * stores, and the set of enabled persona stores (which must be a subset of the
- * former). {@link FolksDummy.Backend.register_persona_stores} adds persona
- * stores to the set of all stores. Optionally it also enables them, adding them
- * to the set of enabled stores. The set of persona stores advertised by the
- * backend as {@link Folks.Backend.persona_stores} is the set of enabled stores.
- * libfolks may internally enable or disable stores using
- * {@link Folks.Backend.enable_persona_store},
- * {@link Folks.Backend.disable_persona_store}
- * and {@link Folks.Backend.set_persona_stores}. The ``register_`` and
- * ``unregister_`` prefixes are commonly used for backend methods.
- *
- * The API in {@link FolksDummy} is unstable and may change wildly. It is
- * designed mostly for use by libfolks unit tests.
- *
- * @since 0.9.7
- */
-public class FolksDummy.Backend : Folks.Backend
-{
- private bool _is_prepared = false;
- private bool _prepare_pending = false; /* used for unprepare() too */
- private bool _is_quiescent = false;
-
- private HashMap<string, PersonaStore> _all_persona_stores;
- private HashMap<string, PersonaStore> _enabled_persona_stores;
- private Map<string, PersonaStore> _enabled_persona_stores_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public Backend ()
- {
- Object ();
- }
-
- construct
- {
- this._all_persona_stores = new HashMap<string, PersonaStore> ();
- this._enabled_persona_stores = new HashMap<string, PersonaStore> ();
- this._enabled_persona_stores_ro =
- this._enabled_persona_stores.read_only_view;
- }
-
- /**
- * Whether this Backend has been prepared.
- *
- * See {@link Folks.Backend.is_prepared}.
- *
- * @since 0.9.7
- */
- public override bool is_prepared
- {
- get { return this._is_prepared; }
- }
-
- /**
- * Whether this Backend has reached a quiescent state.
- *
- * See {@link Folks.Backend.is_quiescent}.
- *
- * @since 0.9.7
- */
- public override bool is_quiescent
- {
- get { return this._is_quiescent; }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public override string name { get { return BACKEND_NAME; } }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public override Map<string, Folks.PersonaStore> persona_stores
- {
- get { return this._enabled_persona_stores_ro; }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public override void disable_persona_store (Folks.PersonaStore store)
- {
- this._disable_persona_store (store);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public override void enable_persona_store (Folks.PersonaStore store)
- {
- this._enable_persona_store ((FolksDummy.PersonaStore) store);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public override void set_persona_stores (Set<string>? store_ids)
- {
- /* If the set is empty, load all unloaded stores then return. */
- if (store_ids == null)
- {
- this.freeze_notify ();
- foreach (var store in this._all_persona_stores.values)
- {
- this._enable_persona_store (store);
- }
- this.thaw_notify ();
-
- return;
- }
-
- /* First handle adding any missing persona stores. */
- this.freeze_notify ();
-
- foreach (var id in store_ids)
- {
- if (!this._enabled_persona_stores.has_key (id))
- {
- var store = this._all_persona_stores.get (id);
- if (store != null)
- {
- this._enable_persona_store (store);
- }
- }
- }
-
- /* Keep persona stores to remove in a separate array so we don't
- * invalidate the list we are iterating over. */
- PersonaStore[] stores_to_remove = {};
-
- foreach (var store in this._enabled_persona_stores.values)
- {
- if (!store_ids.contains (store.id))
- {
- stores_to_remove += store;
- }
- }
-
- foreach (var store in stores_to_remove)
- {
- this._disable_persona_store (store);
- }
-
- this.thaw_notify ();
- }
-
- private void _enable_persona_store (PersonaStore store)
- {
- if (this._enabled_persona_stores.has_key (store.id))
- {
- return;
- }
- assert (this._all_persona_stores.has_key (store.id));
-
- store.removed.connect (this._store_removed_cb);
-
- this._enabled_persona_stores.set (store.id, store);
-
- this.persona_store_added (store);
- this.notify_property ("persona-stores");
- }
-
- private void _disable_persona_store (Folks.PersonaStore store)
- {
- if (!this._enabled_persona_stores.unset (store.id))
- {
- return;
- }
- assert (this._all_persona_stores.has_key (store.id));
-
- this.persona_store_removed (store);
- this.notify_property ("persona-stores");
-
- store.removed.disconnect (this._store_removed_cb);
- }
-
- private void _store_removed_cb (Folks.PersonaStore store)
- {
- this._disable_persona_store (store);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public override async void prepare () throws GLib.Error
- {
- var profiling = Internal.profiling_start ("preparing Dummy.Backend");
-
- if (this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
- this.freeze_notify ();
-
- this._is_prepared = true;
- this.notify_property ("is-prepared");
-
- this._is_quiescent = true;
- this.notify_property ("is-quiescent");
- }
- finally
- {
- this.thaw_notify ();
- this._prepare_pending = false;
- }
-
- Internal.profiling_end ((owned) profiling);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public override async void unprepare () throws GLib.Error
- {
- if (!this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
- this.freeze_notify ();
-
- var enabled_stores = this._enabled_persona_stores.values.to_array ();
- foreach (var persona_store in enabled_stores)
- {
- this._disable_persona_store (persona_store);
- }
-
- this._is_quiescent = false;
- this.notify_property ("is-quiescent");
-
- this._is_prepared = false;
- this.notify_property ("is-prepared");
- }
- finally
- {
- this.thaw_notify ();
- this._prepare_pending = false;
- }
- }
-
-
- /*
- * All the functions below here are to be used by testing code rather than by
- * libfolks clients. They form the interface which would normally be between
- * the Backend and a web service or backing store of some kind.
- */
-
-
- /**
- * Register and enable some {@link FolksDummy.PersonaStore}s.
- *
- * For each of the persona stores in ``stores``, register it with this
- * backend. If ``enable_stores`` is ``true``, added stores will also be
- * enabled, emitting {@link Folks.Backend.persona_store_added} for each
- * newly-enabled store. After all addition signals are emitted, a change
- * notification for {@link Folks.Backend.persona_stores} will be emitted (but
- * only if at least one addition signal is emitted).
- *
- * Persona stores are identified by their {@link Folks.PersonaStore.id}; if a
- * store in ``stores`` has the same ID as a store previously registered
- * through this method, the duplicate will be ignored (so
- * {@link Folks.Backend.persona_store_added} won't be emitted for that store).
- *
- * Persona stores must be instances of {@link FolksDummy.PersonaStore} or
- * subclasses of it, allowing for different persona store implementations to
- * be tested.
- *
- * @param stores set of persona stores to register
- * @param enable_stores whether to automatically enable the stores
- * @since 0.9.7
- */
- public void register_persona_stores (Set<PersonaStore> stores,
- bool enable_stores = true)
- {
- this.freeze_notify ();
-
- foreach (var store in stores)
- {
- assert (store is FolksDummy.PersonaStore);
-
- if (this._all_persona_stores.has_key (store.id))
- {
- continue;
- }
-
- this._all_persona_stores.set (store.id, store);
-
- if (enable_stores == true)
- {
- this._enable_persona_store (store);
- }
- }
-
- this.thaw_notify ();
- }
-
- /**
- * Disable and unregister some {@link FolksDummy.PersonaStore}s.
- *
- * For each of the persona stores in ``stores``, disable it (if it was
- * enabled) and unregister it from the backend so that it cannot be re-enabled
- * using {@link Folks.Backend.enable_persona_store} or
- * {@link Folks.Backend.set_persona_stores}.
- *
- * {@link Folks.Backend.persona_store_removed} will be emitted for all persona
- * stores in ``stores`` which were previously enabled. After all removal
- * signals are emitted, a change notification for
- * {@link Folks.Backend.persona_stores} will be emitted (but only if at least
- * one removal signal is emitted).
- *
- * @since 0.9.7
- */
- public void unregister_persona_stores (Set<PersonaStore> stores)
- {
- this.freeze_notify ();
-
- foreach (var store in stores)
- {
- assert (store is FolksDummy.PersonaStore);
-
- if (!this._all_persona_stores.has_key (store.id))
- {
- continue;
- }
-
- this._disable_persona_store (store);
- this._all_persona_stores.unset (store.id);
- }
-
- this.thaw_notify ();
- }
-}
diff --git a/backends/dummy/lib/dummy-full-persona.vala b/backends/dummy/lib/dummy-full-persona.vala
deleted file mode 100644
index c1ab43d3..00000000
--- a/backends/dummy/lib/dummy-full-persona.vala
+++ /dev/null
@@ -1,1152 +0,0 @@
-/*
- * Copyright (C) 2013 Philip Withnall
- * Copyright (C) 2013 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip@tecnocode.co.uk>
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Marco Barisione <marco.barisione@collabora.co.uk>
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- */
-
-using Folks;
-using Gee;
-using GLib;
-
-/**
- * A persona subclass representing a single ‘full’ contact.
- *
- * This mocks up a ‘full’ persona which implements all the available property
- * interfaces provided by libfolks. This is in contrast with
- * {@link FolksDummy.Persona}, which provides a base class implementing none of
- * libfolks’ interfaces.
- *
- * The full dummy persona can be used to simulate a persona from most libfolks
- * backends, if writing a custom {@link FolksDummy.Persona} subclass is not an
- * option.
- *
- * There are two sides to this class’ interface: the normal methods required by
- * the libfolks ‘details’ interfaces, such as
- * {@link Folks.GenderDetails.change_gender},
- * and the backend methods which should be called by test driver code to
- * simulate changes in the backing store providing this persona, such as
- * {@link FullPersona.update_gender}. For example, test driver code should call
- * {@link FullPersona.update_nickname} to simulate the user editing a contact’s
- * nickname in an online address book which is being exposed to libfolks. The
- * ``update_``, ``register_`` and ``unregister_`` prefixes are commonly used for
- * backend methods.
- *
- * The API in {@link FolksDummy} is unstable and may change wildly. It is
- * designed mostly for use by libfolks unit tests.
- *
- * @since 0.9.7
- */
-public class FolksDummy.FullPersona : FolksDummy.Persona,
- AntiLinkable,
- AvatarDetails,
- BirthdayDetails,
- EmailDetails,
- FavouriteDetails,
- GenderDetails,
- GroupDetails,
- ImDetails,
- LocalIdDetails,
- NameDetails,
- NoteDetails,
- PhoneDetails,
- RoleDetails,
- UrlDetails,
- PostalAddressDetails,
- WebServiceDetails
-{
- private const string[] _default_linkable_properties =
- {
- "im-addresses",
- "email-addresses",
- "local-ids",
- "web-service-addresses",
- null /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=682698 */
- };
-
-
- /**
- * Create a new ‘full’ persona.
- *
- * Create a new persona for the {@link FolksDummy.PersonaStore} ``store``,
- * with the given construct-only properties.
- *
- * @param store the store which will contain the persona
- * @param contact_id a unique free-form string identifier for the persona
- * @param is_user ``true`` if the persona represents the user, ``false``
- * otherwise
- * @param linkable_properties an array of names of the properties which should
- * be used for linking this persona to others
- *
- * @since 0.9.7
- */
- public FullPersona (PersonaStore store, string contact_id,
- bool is_user = false,
- string[] linkable_properties = {})
- {
- base (store, contact_id, is_user, linkable_properties);
- }
-
- construct
- {
- this._local_ids_ro = this._local_ids.read_only_view;
- this._postal_addresses_ro = this._postal_addresses.read_only_view;
- this._email_addresses_ro = this._email_addresses.read_only_view;
- this._phone_numbers_ro = this._phone_numbers.read_only_view;
- this._notes_ro = this._notes.read_only_view;
- this._urls_ro = this._urls.read_only_view;
- this._groups_ro = this._groups.read_only_view;
- this._roles_ro = this._roles.read_only_view;
- this._anti_links_ro = this._anti_links.read_only_view;
- this.update_linkable_properties (
- FullPersona._default_linkable_properties);
- }
-
- private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses =
- new HashMultiMap<string, WebServiceFieldDetails> (
- null, null,
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public MultiMap<string, WebServiceFieldDetails> web_service_addresses
- {
- get { return this._web_service_addresses; }
- set { this.change_web_service_addresses.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_web_service_addresses (
- MultiMap<string, WebServiceFieldDetails> web_service_addresses)
- throws PropertyError
- {
- yield this.change_property ("web-service-addresses", () =>
- {
- this.update_web_service_addresses (web_service_addresses);
- });
- }
-
- private HashSet<string> _local_ids = new HashSet<string> ();
- private Set<string> _local_ids_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public Set<string> local_ids
- {
- get
- {
- if (this._local_ids.contains (this.iid) == false)
- {
- this._local_ids.add (this.iid);
- }
- return this._local_ids_ro;
- }
- set { this.change_local_ids.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_local_ids (Set<string> local_ids)
- throws PropertyError
- {
- yield this.change_property ("local-ids", () =>
- {
- this.update_local_ids (local_ids);
- });
- }
-
- private HashSet<PostalAddressFieldDetails> _postal_addresses =
- new HashSet<PostalAddressFieldDetails> ();
- private Set<PostalAddressFieldDetails> _postal_addresses_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public Set<PostalAddressFieldDetails> postal_addresses
- {
- get { return this._postal_addresses_ro; }
- set { this.change_postal_addresses.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_postal_addresses (
- Set<PostalAddressFieldDetails> postal_addresses) throws PropertyError
- {
- yield this.change_property ("postal-addresses", () =>
- {
- this.update_postal_addresses (postal_addresses);
- });
- }
-
- private HashSet<PhoneFieldDetails> _phone_numbers =
- new HashSet<PhoneFieldDetails> ();
- private Set<PhoneFieldDetails> _phone_numbers_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public Set<PhoneFieldDetails> phone_numbers
- {
- get { return this._phone_numbers_ro; }
- set { this.change_phone_numbers.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_phone_numbers (
- Set<PhoneFieldDetails> phone_numbers) throws PropertyError
- {
- yield this.change_property ("phone-numbers", () =>
- {
- this.update_phone_numbers (phone_numbers);
- });
- }
-
- private HashSet<EmailFieldDetails>? _email_addresses =
- new HashSet<EmailFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- private Set<EmailFieldDetails> _email_addresses_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public Set<EmailFieldDetails> email_addresses
- {
- get { return this._email_addresses_ro; }
- set { this.change_email_addresses.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_email_addresses (
- Set<EmailFieldDetails> email_addresses) throws PropertyError
- {
- yield this.change_property ("email-addresses", () =>
- {
- this.update_email_addresses (email_addresses);
- });
- }
-
- private HashSet<NoteFieldDetails> _notes = new HashSet<NoteFieldDetails> ();
- private Set<NoteFieldDetails> _notes_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public Set<NoteFieldDetails> notes
- {
- get { return this._notes_ro; }
- set { this.change_notes.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_notes (Set<NoteFieldDetails> notes)
- throws PropertyError
- {
- yield this.change_property ("notes", () =>
- {
- this.update_notes (notes);
- });
- }
-
- private LoadableIcon? _avatar = null;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public LoadableIcon? avatar
- {
- get { return this._avatar; }
- set { this.change_avatar.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_avatar (LoadableIcon? avatar) throws PropertyError
- {
- yield this.change_property ("avatar", () =>
- {
- this.update_avatar (avatar);
- });
- }
-
- private StructuredName? _structured_name = null;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public StructuredName? structured_name
- {
- get { return this._structured_name; }
- set { this.change_structured_name.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_structured_name (StructuredName? structured_name)
- throws PropertyError
- {
- yield this.change_property ("structured-name", () =>
- {
- this.update_structured_name (structured_name);
- });
- }
-
- private string _full_name = "";
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public string full_name
- {
- get { return this._full_name; }
- set { this.change_full_name.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_full_name (string full_name) throws PropertyError
- {
- yield this.change_property ("full-name", () =>
- {
- this.update_full_name (full_name);
- });
- }
-
- private string _nickname = "";
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public string nickname
- {
- get { return this._nickname; }
- set { this.change_nickname.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_nickname (string nickname) throws PropertyError
- {
- yield this.change_property ("nickname", () =>
- {
- this.update_nickname (nickname);
- });
- }
-
- private Gender _gender = Gender.UNSPECIFIED;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public Gender gender
- {
- get { return this._gender; }
- set { this.change_gender.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_gender (Gender gender) throws PropertyError
- {
- yield this.change_property ("gender", () =>
- {
- this.update_gender (gender);
- });
- }
-
- private HashSet<UrlFieldDetails> _urls = new HashSet<UrlFieldDetails> ();
- private Set<UrlFieldDetails> _urls_ro;
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public Set<UrlFieldDetails> urls
- {
- get { return this._urls_ro; }
- set { this.change_urls.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_urls (Set<UrlFieldDetails> urls) throws PropertyError
- {
- yield this.change_property ("urls", () =>
- {
- this.update_urls (urls);
- });
- }
-
- private HashMultiMap<string, ImFieldDetails> _im_addresses =
- new HashMultiMap<string, ImFieldDetails> (null, null,
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public MultiMap<string, ImFieldDetails> im_addresses
- {
- get { return this._im_addresses; }
- set { this.change_im_addresses.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_im_addresses (
- MultiMap<string, ImFieldDetails> im_addresses) throws PropertyError
- {
- yield this.change_property ("im-addresses", () =>
- {
- this.update_im_addresses (im_addresses);
- });
- }
-
- private HashSet<string> _groups = new HashSet<string> ();
- private Set<string> _groups_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public Set<string> groups
- {
- get { return this._groups_ro; }
- set { this.change_groups.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_group (string group, bool is_member)
- throws GLib.Error
- {
- /* Nothing to do? */
- if ((is_member == true && this._groups.contains (group) == true) ||
- (is_member == false && this._groups.contains (group) == false))
- {
- return;
- }
-
- /* Replace the current set of groups with a modified one. */
- var new_groups = new HashSet<string> ();
- foreach (var category_name in this._groups)
- {
- new_groups.add (category_name);
- }
-
- if (is_member == false)
- {
- new_groups.remove (group);
- }
- else
- {
- new_groups.add (group);
- }
-
- yield this.change_groups (new_groups);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_groups (Set<string> groups) throws PropertyError
- {
- yield this.change_property ("groups", () =>
- {
- this.update_groups (groups);
- });
- }
-
- private string? _calendar_event_id = null;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public string? calendar_event_id
- {
- get { return this._calendar_event_id; }
- set { this.change_calendar_event_id.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_calendar_event_id (string? calendar_event_id)
- throws PropertyError
- {
- yield this.change_property ("calendar-event-id", () =>
- {
- this.update_calendar_event_id (calendar_event_id);
- });
- }
-
- private DateTime? _birthday = null;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public DateTime? birthday
- {
- get { return this._birthday; }
- set { this.change_birthday.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_birthday (DateTime? bday)
- throws PropertyError
- {
- yield this.change_property ("birthday", () =>
- {
- this.update_birthday (bday);
- });
- }
-
- private HashSet<RoleFieldDetails> _roles = new HashSet<RoleFieldDetails> ();
- private Set<RoleFieldDetails> _roles_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public Set<RoleFieldDetails> roles
- {
- get { return this._roles_ro; }
- set { this.change_roles.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_roles (Set<RoleFieldDetails> roles)
- throws PropertyError
- {
- yield this.change_property ("roles", () =>
- {
- this.update_roles (roles);
- });
- }
-
- private bool _is_favourite = false;
-
- /**
- * Whether this contact is a user-defined favourite.
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public bool is_favourite
- {
- get { return this._is_favourite; }
- set { this.change_is_favourite.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_is_favourite (bool is_favourite) throws PropertyError
- {
- yield this.change_property ("is-favourite", () =>
- {
- this.update_is_favourite (is_favourite);
- });
- }
-
- private HashSet<string> _anti_links = new HashSet<string> ();
- private Set<string> _anti_links_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public Set<string> anti_links
- {
- get { return this._anti_links_ro; }
- set { this.change_anti_links.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public async void change_anti_links (Set<string> anti_links)
- throws PropertyError
- {
- yield this.change_property ("anti-links", () =>
- {
- this.update_anti_links (anti_links);
- });
- }
-
-
- /*
- * All the functions below here are to be used by testing code rather than by
- * libfolks clients. They form the interface which would normally be between
- * the Persona and a web service or backing store of some kind.
- */
-
-
- private HashSet<T> _dup_to_hash_set<T> (Set<T> input_set)
- {
- var output_set = new HashSet<T> ();
- output_set.add_all (input_set);
- return output_set;
- }
-
- private HashMultiMap<S, T> _dup_to_hash_multi_map<S, T> (
- MultiMap<S, T> input_multi_map)
- {
- var output_multi_map = new HashMultiMap<S, T> ();
-
- var iter = input_multi_map.map_iterator ();
- while (iter.next () == true)
- output_multi_map.set (iter.get_key (), iter.get_value ());
-
- return output_multi_map;
- }
-
- /**
- * Update persona's gender.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.GenderDetails.gender} property. It is intended to be used for
- * testing code which consumes this property. If the property value changes,
- * this results in a property change notification on the persona.
- *
- * @param gender persona's new gender
- * @since 0.9.7
- */
- public void update_gender (Gender gender)
- {
- if (this._gender != gender)
- {
- this._gender = gender;
- this.notify_property ("gender");
- }
- }
-
- /**
- * Update persona's birthday calendar event ID.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.BirthdayDetails.calendar_event_id} property. It is intended to
- * be used for testing code which consumes this property. If the property
- * value changes, this results in a property change notification on the
- * persona.
- *
- * @param calendar_event_id persona's new birthday calendar event ID
- * @since 0.9.7
- */
- public void update_calendar_event_id (string? calendar_event_id)
- {
- if (calendar_event_id != this._calendar_event_id)
- {
- this._calendar_event_id = calendar_event_id;
- this.notify_property ("calendar-event-id");
- }
- }
-
- /**
- * Update persona's birthday.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.BirthdayDetails.birthday} property. It is intended to be used
- * for testing code which consumes this property. If the property value
- * changes, this results in a property change notification on the persona.
- *
- * @param birthday persona's new birthday
- * @since 0.9.7
- */
- public void update_birthday (DateTime? birthday)
- {
- if ((this._birthday == null) != (birthday == null) ||
- (this._birthday != null && birthday != null &&
- !((!) this._birthday).equal ((!) birthday)))
- {
- this._birthday = birthday;
- this.notify_property ("birthday");
- }
- }
-
- /**
- * Update persona's roles.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.RoleDetails.roles} property. It is intended to be used for
- * testing code which consumes this property. If the property value changes,
- * this results in a property change notification on the persona.
- *
- * @param roles persona's new roles
- * @since 0.9.7
- */
- public void update_roles (Set<RoleFieldDetails> roles)
- {
- if (!Folks.Internal.equal_sets<RoleFieldDetails> (roles, this._roles))
- {
- this._roles = this._dup_to_hash_set<RoleFieldDetails> (roles);
- this._roles_ro = this._roles.read_only_view;
- this.notify_property ("roles");
- }
- }
-
- /**
- * Update persona's groups.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.GroupDetails.groups} property. It is intended to be used for
- * testing code which consumes this property. If the property value changes,
- * this results in a property change notification on the persona.
- *
- * @param groups persona's new groups
- * @since 0.9.7
- */
- public void update_groups (Set<string> groups)
- {
- if (!Folks.Internal.equal_sets<string> (groups, this._groups))
- {
- this._groups = this._dup_to_hash_set<string> (groups);
- this._groups_ro = this._groups.read_only_view;
- this.notify_property ("groups");
- }
- }
-
- /**
- * Update persona's web service addresses.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.WebServiceDetails.web_service_addresses} property. It is
- * intended to be used for testing code which consumes this property. If the
- * property value changes, this results in a property change notification on
- * the persona.
- *
- * @param web_service_addresses persona's new web service addresses
- * @since 0.9.7
- */
- public void update_web_service_addresses (
- MultiMap<string, WebServiceFieldDetails> web_service_addresses)
- {
- if (!Utils.multi_map_str_afd_equal (web_service_addresses,
- this._web_service_addresses))
- {
- this._web_service_addresses =
- this._dup_to_hash_multi_map<string, WebServiceFieldDetails> (
- web_service_addresses);
- this.notify_property ("web-service-addresses");
- }
- }
-
- /**
- * Update persona's e-mail addresses.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.EmailDetails.email_addresses} property. It is intended to be
- * used for testing code which consumes this property. If the property value
- * changes, this results in a property change notification on the persona.
- *
- * @param email_addresses persona's new e-mail addresses
- * @since 0.9.7
- */
- public void update_email_addresses (Set<EmailFieldDetails> email_addresses)
- {
- if (!Folks.Internal.equal_sets<EmailFieldDetails> (email_addresses,
- this._email_addresses))
- {
- this._email_addresses =
- this._dup_to_hash_set<EmailFieldDetails> (email_addresses);
- this._email_addresses_ro = this._email_addresses.read_only_view;
- this.notify_property ("email-addresses");
- }
- }
-
- /**
- * Update persona's notes.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.NoteDetails.notes} property. It is intended to be used for
- * testing code which consumes this property. If the property value changes,
- * this results in a property change notification on the persona.
- *
- * @param notes persona's new notes
- * @since 0.9.7
- */
- public void update_notes (Set<NoteFieldDetails> notes)
- {
- if (!Folks.Internal.equal_sets<NoteFieldDetails> (notes, this._notes))
- {
- this._notes = this._dup_to_hash_set<NoteFieldDetails> (notes);
- this._notes_ro = this._notes.read_only_view;
- this.notify_property ("notes");
- }
- }
-
- /**
- * Update persona's full name.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.NameDetails.full_name} property. It is intended to be used for
- * testing code which consumes this property. If the property value changes,
- * this results in a property change notification on the persona.
- *
- * @param full_name persona's new full name
- * @since 0.9.7
- */
- public void update_full_name (string full_name)
- {
- if (this._full_name != full_name)
- {
- this._full_name = full_name;
- this.notify_property ("full-name");
- }
- }
-
- /**
- * Update persona's nickname.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.NameDetails.nickname} property. It is intended to be used for
- * testing code which consumes this property. If the property value changes,
- * this results in a property change notification on the persona.
- *
- * @param nickname persona's new nickname
- * @since 0.9.7
- */
- public void update_nickname (string nickname)
- {
- if (this._nickname != nickname)
- {
- this._nickname = nickname;
- this.notify_property ("nickname");
- }
- }
-
- /**
- * Update persona's structured name.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.NameDetails.structured_name} property. It is intended to be
- * used for testing code which consumes this property. If the property value
- * changes, this results in a property change notification on the persona.
- *
- * @param structured_name persona's new structured name
- * @since 0.9.7
- */
- public void update_structured_name (StructuredName? structured_name)
- {
- if (structured_name != null && !((!) structured_name).is_empty ())
- {
- this._structured_name = (!) structured_name;
- this.notify_property ("structured-name");
- }
- else if (this._structured_name != null)
- {
- this._structured_name = null;
- this.notify_property ("structured-name");
- }
- }
-
- /**
- * Update persona's avatar.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.AvatarDetails.avatar} property. It is intended to be used for
- * testing code which consumes this property. If the property value changes,
- * this results in a property change notification on the persona.
- *
- * @param avatar persona's new avatar
- * @since 0.9.7
- */
- public void update_avatar (LoadableIcon? avatar)
- {
- if ((this._avatar == null) != (avatar == null) ||
- (this._avatar != null && avatar != null &&
- !((!) this._avatar).equal ((!) avatar)))
- {
- this._avatar = avatar;
- this.notify_property ("avatar");
- }
- }
-
- /**
- * Update persona's URIs.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.UrlDetails.urls} property. It is intended to be used for
- * testing code which consumes this property. If the property value changes,
- * this results in a property change notification on the persona.
- *
- * @param urls persona's new URIs
- * @since 0.9.7
- */
- public void update_urls (Set<UrlFieldDetails> urls)
- {
- if (!Utils.set_afd_equal (urls, this._urls))
- {
- this._urls = this._dup_to_hash_set<UrlFieldDetails> (urls);
- this._urls_ro = this._urls.read_only_view;
- this.notify_property ("urls");
- }
- }
-
- /**
- * Update persona's IM addresses.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.ImDetails.im_addresses} property. It is intended to be used
- * for testing code which consumes this property. If the property value
- * changes, this results in a property change notification on the persona.
- *
- * @param im_addresses persona's new IM addresses
- * @since 0.9.7
- */
- public void update_im_addresses (
- MultiMap<string, ImFieldDetails> im_addresses)
- {
- if (!Utils.multi_map_str_afd_equal (im_addresses,
- this._im_addresses))
- {
- this._im_addresses =
- this._dup_to_hash_multi_map<string, ImFieldDetails> (
- im_addresses);
- this.notify_property ("im-addresses");
- }
- }
-
- /**
- * Update persona's phone numbers.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.PhoneDetails.phone_numbers} property. It is intended to be
- * used for testing code which consumes this property. If the property value
- * changes, this results in a property change notification on the persona.
- *
- * @param phone_numbers persona's new phone numbers
- * @since 0.9.7
- */
- public void update_phone_numbers (Set<PhoneFieldDetails> phone_numbers)
- {
- if (!Utils.set_string_afd_equal (phone_numbers,
- this._phone_numbers))
- {
- this._phone_numbers =
- this._dup_to_hash_set<PhoneFieldDetails> (phone_numbers);
- this._phone_numbers_ro = this._phone_numbers.read_only_view;
- this.notify_property ("phone-numbers");
- }
- }
-
- /**
- * Update persona's postal addresses.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.PostalAddressDetails.postal_addresses} property. It is
- * intended to be used for testing code which consumes this property. If the
- * property value changes, this results in a property change notification on
- * the persona.
- *
- * @param postal_addresses persona's new postal addresses
- * @since 0.9.7
- */
- public void update_postal_addresses (
- Set<PostalAddressFieldDetails> postal_addresses)
- {
- if (!Folks.Internal.equal_sets<PostalAddressFieldDetails> (
- postal_addresses, this._postal_addresses))
- {
- this._postal_addresses =
- this._dup_to_hash_set<PostalAddressFieldDetails> (
- postal_addresses);
- this._postal_addresses_ro = this._postal_addresses.read_only_view;
- this.notify_property ("postal-addresses");
- }
- }
-
- /**
- * Update persona's local IDs.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.LocalIdDetails.local_ids} property. It is intended to be used
- * for testing code which consumes this property. If the property value
- * changes, this results in a property change notification on the persona.
- *
- * @param local_ids persona's new local IDs
- * @since 0.9.7
- */
- public void update_local_ids (Set<string> local_ids)
- {
- if (!Folks.Internal.equal_sets<string> (local_ids, this.local_ids))
- {
- this._local_ids = this._dup_to_hash_set<string> (local_ids);
- this._local_ids_ro = this._local_ids.read_only_view;
- this.notify_property ("local-ids");
- }
- }
-
- /**
- * Update persona's status as a favourite.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.FavouriteDetails.is_favourite} property. It is intended to be
- * used for testing code which consumes this property. If the property value
- * changes, this results in a property change notification on the persona.
- *
- * @param is_favourite persona's new status as a favourite
- * @since 0.9.7
- */
- public void update_is_favourite (bool is_favourite)
- {
- if (is_favourite != this._is_favourite)
- {
- this._is_favourite = is_favourite;
- this.notify_property ("is-favourite");
- }
- }
-
- /**
- * Update persona's anti-links.
- *
- * This simulates a backing-store-side update of the persona's
- * {@link Folks.AntiLinkable.anti_links} property. It is intended to be used
- * for testing code which consumes this property. If the property value
- * changes, this results in a property change notification on the persona.
- *
- * @param anti_links persona's new anti-links
- * @since 0.9.7
- */
- public void update_anti_links (Set<string> anti_links)
- {
- if (!Folks.Internal.equal_sets<string> (anti_links, this._anti_links))
- {
- this._anti_links = this._dup_to_hash_set<string> (anti_links);
- this._anti_links_ro = this._anti_links.read_only_view;
- this.notify_property ("anti-links");
- }
- }
-}
diff --git a/backends/dummy/lib/dummy-persona-store.vala b/backends/dummy/lib/dummy-persona-store.vala
deleted file mode 100644
index 7c63430d..00000000
--- a/backends/dummy/lib/dummy-persona-store.vala
+++ /dev/null
@@ -1,1094 +0,0 @@
-/*
- * Copyright (C) 2013 Philip Withnall
- * Copyright (C) 2013 Canonical Ltd
- * Copyright (C) 2013 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip@tecnocode.co.uk>
- * Renato Araujo Oliveira Filho <renato@canonical.com>
- */
-
-using Folks;
-using Gee;
-using GLib;
-
-/**
- * A persona store which allows {@link FolksDummy.Persona}s to be
- * programmatically created and manipulated, for the purposes of testing the
- * core of libfolks itself. This should not be used in user-visible
- * applications.
- *
- * There are two sides to this class’ interface: the methods and properties
- * declared by {@link Folks.PersonaStore}, which form the normal libfolks
- * persona store API; and the mock methods and properties (see for example
- * {@link FolksDummy.PersonaStore.set_add_persona_from_details_mock}) which are
- * intended to be used by test driver code to simulate the behaviour of a real
- * backing store. Calls to these mock methods effect state changes in the store
- * which are visible in the normal libfolks API. The ``update_``, ``register_``
- * and ``unregister_`` prefixes and the ``mock`` suffix are commonly used for
- * backing store methods.
- *
- * The main action performed with a dummy persona store is to change its set of
- * personas, adding and removing them dynamically to test client-side behaviour.
- * The client-side APIs ({@link Folks.PersonaStore.add_persona_from_details} and
- * {@link Folks.PersonaStore.remove_persona}) should //not// be used for this.
- * Instead, the mock APIs should be used:
- * {@link FolksDummy.PersonaStore.freeze_personas_changed},
- * {@link FolksDummy.PersonaStore.register_personas},
- * {@link FolksDummy.PersonaStore.unregister_personas} and
- * {@link FolksDummy.PersonaStore.thaw_personas_changed}. These can be used to
- * build up complex {@link Folks.PersonaStore.personas_changed} signal
- * emissions, which are only emitted after the final call to
- * {@link FolksDummy.PersonaStore.thaw_personas_changed}.
- *
- * The API in {@link FolksDummy} is unstable and may change wildly. It is
- * designed mostly for use by libfolks unit tests.
- *
- * @since 0.9.7
- */
-public class FolksDummy.PersonaStore : Folks.PersonaStore
-{
- private bool _is_prepared = false;
- private bool _prepare_pending = false;
- private bool _is_quiescent = false;
- private bool _quiescent_on_prepare = false;
- private int _contact_id = 0;
-
- /**
- * The type of persona store this is.
- *
- * See {@link Folks.PersonaStore.type_id}.
- *
- * @since 0.9.7
- */
- public override string type_id { get { return BACKEND_NAME; } }
-
- private MaybeBool _can_add_personas = MaybeBool.FALSE;
-
- /**
- * Whether this PersonaStore can add {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_add_personas}.
- *
- * @since 0.9.7
- */
- public override MaybeBool can_add_personas
- {
- get
- {
- if (!this._is_prepared)
- {
- return MaybeBool.FALSE;
- }
-
- return this._can_add_personas;
- }
- }
-
- private MaybeBool _can_alias_personas = MaybeBool.FALSE;
-
- /**
- * Whether this PersonaStore can set the alias of {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_alias_personas}.
- *
- * @since 0.9.7
- */
- public override MaybeBool can_alias_personas
- {
- get
- {
- if (!this._is_prepared)
- {
- return MaybeBool.FALSE;
- }
-
- return this._can_alias_personas;
- }
- }
-
- /**
- * Whether this PersonaStore can set the groups of {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_group_personas}.
- *
- * @since 0.9.7
- */
- public override MaybeBool can_group_personas
- {
- get
- {
- return ("groups" in this._always_writeable_properties)
- ? MaybeBool.TRUE : MaybeBool.FALSE;
- }
- }
-
- private MaybeBool _can_remove_personas = MaybeBool.FALSE;
-
- /**
- * Whether this PersonaStore can remove {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_remove_personas}.
- *
- * @since 0.9.7
- */
- public override MaybeBool can_remove_personas
- {
- get
- {
- if (!this._is_prepared)
- {
- return MaybeBool.FALSE;
- }
-
- return this._can_remove_personas;
- }
- }
-
- /**
- * Whether this PersonaStore has been prepared.
- *
- * See {@link Folks.PersonaStore.is_prepared}.
- *
- * @since 0.9.7
- */
- public override bool is_prepared
- {
- get { return this._is_prepared; }
- }
-
- private string[] _always_writeable_properties = {};
- private static string[] _always_writeable_properties_empty = {}; /* oh Vala */
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public override string[] always_writeable_properties
- {
- get
- {
- if (!this._is_prepared)
- {
- return PersonaStore._always_writeable_properties_empty;
- }
-
- return this._always_writeable_properties;
- }
- }
-
- /*
- * Whether this PersonaStore has reached a quiescent state.
- *
- * See {@link Folks.PersonaStore.is_quiescent}.
- *
- * @since 0.9.7
- */
- public override bool is_quiescent
- {
- get { return this._is_quiescent; }
- }
-
- private HashMap<string, Persona> _personas;
- private Map<string, Persona> _personas_ro;
-
- /* Personas which have been registered but not yet emitted in a
- * personas-changed signal. */
- private HashSet<Persona> _pending_persona_registrations;
-
- /* Personas which have been unregistered but not yet emitted in a
- * personas-changed signal. */
- private HashSet<Persona> _pending_persona_unregistrations;
-
- /* Freeze counter for persona changes: personas-changed is only emitted when
- * this is 0. */
- private uint _personas_changed_frozen = 0;
-
- /**
- * The {@link Persona}s exposed by this PersonaStore.
- *
- * See {@link Folks.PersonaStore.personas}.
- *
- * @since 0.9.7
- */
- public override Map<string, Folks.Persona> personas
- {
- get { return this._personas_ro; }
- }
-
- /**
- * Create a new persona store.
- *
- * This store will have no personas to begin with; use
- * {@link FolksDummy.PersonaStore.register_personas} to add some, then call
- * {@link FolksDummy.PersonaStore.reach_quiescence} to signal the store
- * reaching quiescence.
- *
- * @param id The new store's ID.
- * @param display_name The new store's display name.
- * @param always_writeable_properties The set of always writeable properties.
- *
- * @since 0.9.7
- */
- public PersonaStore (string id, string display_name,
- string[] always_writeable_properties)
- {
- Object (
- id: id,
- display_name: display_name);
-
- this._always_writeable_properties = always_writeable_properties;
- }
-
- construct
- {
- this._personas = new HashMap<string, Persona> ();
- this._personas_ro = this._personas.read_only_view;
- this._pending_persona_registrations = new HashSet<Persona> ();
- this._pending_persona_unregistrations = new HashSet<Persona> ();
- }
-
- /**
- * Add a new {@link Persona} to the PersonaStore.
- *
- * Accepted keys for ``details`` are:
- * - PersonaStore.detail_key (PersonaDetail.AVATAR)
- * - PersonaStore.detail_key (PersonaDetail.BIRTHDAY)
- * - PersonaStore.detail_key (PersonaDetail.EMAIL_ADDRESSES)
- * - PersonaStore.detail_key (PersonaDetail.FULL_NAME)
- * - PersonaStore.detail_key (PersonaDetail.GENDER)
- * - PersonaStore.detail_key (PersonaDetail.IM_ADDRESSES)
- * - PersonaStore.detail_key (PersonaDetail.IS_FAVOURITE)
- * - PersonaStore.detail_key (PersonaDetail.PHONE_NUMBERS)
- * - PersonaStore.detail_key (PersonaDetail.POSTAL_ADDRESSES)
- * - PersonaStore.detail_key (PersonaDetail.ROLES)
- * - PersonaStore.detail_key (PersonaDetail.STRUCTURED_NAME)
- * - PersonaStore.detail_key (PersonaDetail.LOCAL_IDS)
- * - PersonaStore.detail_key (PersonaDetail.WEB_SERVICE_ADDRESSES)
- * - PersonaStore.detail_key (PersonaDetail.NOTES)
- * - PersonaStore.detail_key (PersonaDetail.URLS)
- *
- * See {@link Folks.PersonaStore.add_persona_from_details}.
- *
- * @param details key–value pairs giving the new persona’s details
- * @throws Folks.PersonaStoreError.STORE_OFFLINE if the store hasn’t been
- * prepared
- * @throws Folks.PersonaStoreError.CREATE_FAILED if creating the persona in
- * the dummy store failed
- *
- * @since 0.9.7
- */
- public override async Folks.Persona? add_persona_from_details (
- HashTable<string, Value?> details) throws PersonaStoreError
- {
- /* We have to have called prepare() beforehand. */
- if (!this._is_prepared)
- {
- throw new PersonaStoreError.STORE_OFFLINE (
- "Persona store has not yet been prepared.");
- }
-
- /* Allow overriding the class used. */
- var contact_id = this._contact_id.to_string();
- this._contact_id++;
- var uid = Folks.Persona.build_uid (BACKEND_NAME, this.id, contact_id);
- var iid = this.id + ":" + contact_id;
-
- var persona = Object.new (this._persona_type,
- "display-id", contact_id,
- "uid", uid,
- "iid", iid,
- "store", this,
- "is-user", false,
- null) as FolksDummy.Persona;
- assert (persona != null);
- persona.update_writeable_properties (this.always_writeable_properties);
-
- unowned Value? v;
-
- try
- {
- v = details.lookup (
- Folks.PersonaStore.detail_key (PersonaDetail.FULL_NAME));
- var p_name = persona as NameDetails;
- if (p_name != null && v != null)
- {
- string full_name = ((!) v).get_string () ?? "";
- yield p_name.change_full_name (full_name);
- }
-
- v = details.lookup (
- Folks.PersonaStore.detail_key (PersonaDetail.STRUCTURED_NAME));
- if (p_name != null && v != null)
- {
- var sname = (StructuredName) ((!) v).get_object ();
- if (sname != null)
- yield p_name.change_structured_name (sname);
- }
-
- v = details.lookup (
- Folks.PersonaStore.detail_key (PersonaDetail.NICKNAME));
- if (p_name != null && v != null)
- {
- string nickname = ((!) v).get_string () ?? "";
- yield p_name.change_nickname (nickname);
- }
-
- v = details.lookup (
- Folks.PersonaStore.detail_key (PersonaDetail.EMAIL_ADDRESSES));
- var p_email = persona as EmailDetails;
- if (p_email != null && v != null)
- {
- var email_addresses = (Set<EmailFieldDetails>) ((!) v).get_object ();
- if (email_addresses != null)
- yield p_email.change_email_addresses (email_addresses);
- }
-
- v = details.lookup (
- Folks.PersonaStore.detail_key (PersonaDetail.AVATAR));
- var p_avatar = persona as AvatarDetails;
- if (p_avatar != null && v != null)
- {
- var avatar = (LoadableIcon?) ((!) v).get_object ();
- if (avatar != null)
- yield p_avatar.change_avatar (avatar);
- }
-
- v = details.lookup (
- Folks.PersonaStore.detail_key (PersonaDetail.IM_ADDRESSES));
- var p_im = persona as ImDetails;
- if (p_im != null && v != null)
- {
- var im_addresses =
- (MultiMap<string,ImFieldDetails>) ((!) v).get_object ();
- if (im_addresses != null)
- yield p_im.change_im_addresses (im_addresses);
- }
-
- v = details.lookup (
- Folks.PersonaStore.detail_key (PersonaDetail.PHONE_NUMBERS));
- var p_phone = persona as PhoneDetails;
- if (p_phone != null && v != null)
- {
- var phone_numbers = (Set<PhoneFieldDetails>) ((!) v).get_object ();
- if (phone_numbers != null)
- yield p_phone.change_phone_numbers (phone_numbers);
- }
-
- v = details.lookup (
- Folks.PersonaStore.detail_key (PersonaDetail.POSTAL_ADDRESSES));
- var p_postal = persona as PostalAddressDetails;
- if (p_postal != null && v != null)
- {
- var postal_fds =
- (Set<PostalAddressFieldDetails>) ((!) v).get_object ();
- if (postal_fds != null)
- yield p_postal.change_postal_addresses (postal_fds);
- }
-
- v = details.lookup (
- Folks.PersonaStore.detail_key (PersonaDetail.LOCAL_IDS));
- var p_local = persona as LocalIdDetails;
- if (p_local != null && v != null)
- {
- var local_ids = (Set<string>) ((!) v).get_object ();
- if (local_ids != null)
- yield p_local.change_local_ids (local_ids);
- }
-
- v = details.lookup (
- Folks.PersonaStore.detail_key (
- PersonaDetail.WEB_SERVICE_ADDRESSES));
- var p_web = persona as WebServiceDetails;
- if (p_web != null && v != null)
- {
- var addrs =
- (HashMultiMap<string, WebServiceFieldDetails>)
- ((!) v).get_object ();
- if (addrs != null)
- yield p_web.change_web_service_addresses (addrs);
- }
-
- v = details.lookup (
- Folks.PersonaStore.detail_key (PersonaDetail.NOTES));
- var p_note = persona as NoteDetails;
- if (p_note != null && v != null)
- {
- var notes = (Gee.HashSet<NoteFieldDetails>) ((!) v).get_object ();
- if (notes != null)
- yield p_note.change_notes (notes);
- }
-
- v = details.lookup (
- Folks.PersonaStore.detail_key (PersonaDetail.GENDER));
- var p_gender = persona as GenderDetails;
- if (p_gender != null && v != null)
- {
- var gender = (Gender) ((!) v).get_enum ();
- yield p_gender.change_gender (gender);
- }
-
- v = details.lookup (
- Folks.PersonaStore.detail_key (PersonaDetail.URLS));
- var p_url = persona as UrlDetails;
- if (p_url != null && v != null)
- {
- var urls = (Set<UrlFieldDetails>) ((!) v).get_object ();
- if (urls != null)
- yield p_url.change_urls (urls);
- }
-
- v = details.lookup (
- Folks.PersonaStore.detail_key (PersonaDetail.BIRTHDAY));
- var p_birthday = persona as BirthdayDetails;
- if (p_birthday != null && v != null)
- {
- var birthday = (DateTime?) ((!) v).get_boxed ();
- if (birthday != null)
- yield p_birthday.change_birthday (birthday);
- }
-
- v = details.lookup (
- Folks.PersonaStore.detail_key (PersonaDetail.ROLES));
- var p_role = persona as RoleDetails;
- if (p_role != null && v != null)
- {
- var roles = (Set<RoleFieldDetails>) ((!) v).get_object ();
- if (roles != null)
- yield p_role.change_roles (roles);
- }
-
- v = details.lookup (
- Folks.PersonaStore.detail_key (PersonaDetail.IS_FAVOURITE));
- var p_favourite = persona as FavouriteDetails;
- if (p_favourite != null && v != null)
- {
- bool is_fav = ((!) v).get_boolean ();
- yield p_favourite.change_is_favourite (is_fav);
- }
- }
- catch (PropertyError e1)
- {
- throw new PersonaStoreError.CREATE_FAILED (
- "Setting a property on the new persona failed: %s", e1.message);
- }
-
- /* Allow the caller to inject failures and delays into
- * add_persona_from_details() by providing a mock function. */
- if (this._add_persona_from_details_mock != null)
- {
- var delay = this._add_persona_from_details_mock (persona);
- yield this._implement_mock_delay (delay);
- }
-
- /* No simulated failure: continue adding the persona. */
- this._personas.set (persona.iid, persona);
-
- /* Notify of the new persona. */
- var added_personas = new HashSet<Persona> ();
- added_personas.add (persona);
- this._emit_personas_changed (added_personas, null);
-
- return persona;
- }
-
- /**
- * Remove a {@link Persona} from the PersonaStore.
- *
- * See {@link Folks.PersonaStore.remove_persona}.
- *
- * @param persona the persona that should be removed
- * @throws Folks.PersonaStoreError.STORE_OFFLINE if the store hasn’t been
- * prepared or has gone offline
- * @throws Folks.PersonaStoreError.PERMISSION_DENIED if the store denied
- * permission to delete the contact
- * @throws Folks.PersonaStoreError.READ_ONLY if the store is read only
- * @throws Folks.PersonaStoreError.REMOVE_FAILED if any other errors happened
- * in the store
- *
- * @since 0.9.7
- */
- public override async void remove_persona (Folks.Persona persona)
- throws PersonaStoreError
- requires (persona is FolksDummy.Persona)
- {
- /* We have to have called prepare() beforehand. */
- if (!this._is_prepared)
- {
- throw new PersonaStoreError.STORE_OFFLINE (
- "Persona store has not yet been prepared.");
- }
-
- /* Allow the caller to inject failures and delays. */
- if (this._remove_persona_mock != null)
- {
- var delay = this._remove_persona_mock ((FolksDummy.Persona) persona);
- yield this._implement_mock_delay (delay);
- }
-
- Persona? _persona = this._personas.get (persona.iid);
- if (_persona != null)
- {
- this._personas.unset (persona.iid);
-
- /* Handle the case where a contact is removed while persona changes
- * are frozen. */
- this._pending_persona_registrations.remove ((!) _persona);
- this._pending_persona_unregistrations.remove ((!) _persona);
-
- /* Notify of the removal. */
- var removed_personas = new HashSet<Folks.Persona> ();
- removed_personas.add ((!) persona);
- this._emit_personas_changed (null, removed_personas);
- }
- }
-
- /**
- * Prepare the PersonaStore for use.
- *
- * See {@link Folks.PersonaStore.prepare}.
- *
- * @throws Folks.PersonaStoreError.STORE_OFFLINE if the store is offline
- * @throws Folks.PersonaStoreError.PERMISSION_DENIED if permission was denied
- * to open the store
- * @throws Folks.PersonaStoreError.INVALID_ARGUMENT if any other error
- * occurred in the store
- *
- * @since 0.9.7
- */
- public override async void prepare () throws PersonaStoreError
- {
- var profiling = Internal.profiling_start ("preparing Dummy.PersonaStore (ID: %s)",
- this.id);
-
- if (this._is_prepared == true || this._prepare_pending == true)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
-
- /* Allow the caller to inject failures and delays. */
- if (this._prepare_mock != null)
- {
- var delay = this._prepare_mock ();
- yield this._implement_mock_delay (delay);
- }
-
- this._is_prepared = true;
- this.notify_property ("is-prepared");
-
- /* If reach_quiescence() has been called already, signal
- * quiescence. */
- if (this._quiescent_on_prepare == true)
- {
- this.reach_quiescence ();
- }
- }
- finally
- {
- this._prepare_pending = false;
- }
-
- Internal.profiling_end ((owned) profiling);
- }
-
-
- /*
- * All the functions below here are to be used by testing code rather than by
- * libfolks clients. They form the interface which would normally be between
- * the PersonaStore and a web service or backing store of some kind.
- */
-
-
- /**
- * Delay for the given number of milliseconds.
- *
- * This implements an asynchronous delay (which should be yielded on) until
- * the given number of milliseconds has elapsed.
- *
- * If ``delay`` is negative, this function returns immediately. If it is
- * zero, this function returns in an idle callback.
- *
- * @param delay number of milliseconds to delay for
- *
- * @since 0.9.7
- */
- private async void _implement_mock_delay (int delay)
- {
- if (delay < 0)
- {
- /* No delay. */
- return;
- }
- else if (delay == 0)
- {
- /* Idle delay. */
- Idle.add (() =>
- {
- this._implement_mock_delay.callback ();
- return false;
- });
-
- yield;
- }
- else
- {
- /* Timed delay. */
- Timeout.add (delay, () =>
- {
- this._implement_mock_delay.callback ();
- return false;
- });
-
- yield;
- }
- }
-
- /**
- * Type of a mock function for
- * {@link Folks.PersonaStore.add_persona_from_details}.
- *
- * See {@link FolksDummy.PersonaStore.set_add_persona_from_details_mock}.
- *
- * @param persona the persona being added to the store, as constructed from
- * the details passed to {@link Folks.PersonaStore.add_persona_from_details}.
- * @throws PersonaStoreError to be thrown from
- * {@link Folks.PersonaStore.add_persona_from_details}
- * @return delay to apply to the add persona operation (negative delays
- * complete synchronously; zero delays complete in an idle callback; positive
- * delays complete after that many milliseconds)
- *
- * @since 0.9.7
- */
- public delegate int AddPersonaFromDetailsMock (Persona persona)
- throws PersonaStoreError;
-
- /**
- * Mock function for {@link Folks.PersonaStore.add_persona_from_details}.
- */
- private unowned AddPersonaFromDetailsMock? _add_persona_from_details_mock = null;
-
- /**
- * Type of a mock function for {@link Folks.PersonaStore.remove_persona}.
- *
- * See {@link FolksDummy.PersonaStore.set_remove_persona_mock}.
- *
- * @param persona the persona being removed from the store
- * @throws PersonaStoreError to be thrown from
- * {@link Folks.PersonaStore.remove_persona}
- * @return delay to apply to the remove persona operation (negative and zero
- * delays complete in an idle callback; positive
- * delays complete after that many milliseconds)
- *
- * @since 0.9.7
- */
- public delegate int RemovePersonaMock (Persona persona)
- throws PersonaStoreError;
-
- /**
- * Mock function for {@link Folks.PersonaStore.remove_persona}.
- */
- private unowned RemovePersonaMock? _remove_persona_mock = null;
-
- /**
- * Type of a mock function for {@link Folks.PersonaStore.prepare}.
- *
- * See {@link FolksDummy.PersonaStore.set_prepare_mock}.
- *
- * @throws PersonaStoreError to be thrown from
- * {@link Folks.PersonaStore.prepare}
- * @return delay to apply to the prepare operation (negative and zero delays
- * complete in an idle callback; positive
- * delays complete after that many milliseconds)
- *
- * @since 0.9.7
- */
- public delegate int PrepareMock () throws PersonaStoreError;
-
- /**
- * Mock function for {@link Folks.PersonaStore.prepare}.
- */
- private unowned PrepareMock? _prepare_mock = null;
-
- private Type _persona_type = typeof (FolksDummy.Persona);
-
- /**
- * Type of programmatically created personas.
- *
- * This is the type used to create new personas when
- * {@link Folks.PersonaStore.add_persona_from_details} is called. It must be a
- * subtype of {@link FolksDummy.Persona}.
- *
- * This may be modified at any time, with modifications taking effect for the
- * next call to {@link Folks.PersonaStore.add_persona_from_details} or
- * {@link FolksDummy.PersonaStore.register_personas}.
- *
- * @since 0.9.7
- */
- public Type persona_type
- {
- get { return this._persona_type; }
- set
- {
- assert (value.is_a (typeof (FolksDummy.Persona)));
- if (this._persona_type != value)
- {
- this._persona_type = value;
- this.notify_property ("persona-type");
- }
- }
- }
-
- /**
- * Set capabilities of the persona store.
- *
- * This sets the capabilities of the store, as if they were changed on a
- * backing store somewhere. This is intended to be used for testing code which
- * depends on the values of {@link Folks.PersonaStore.can_add_personas},
- * {@link Folks.PersonaStore.can_alias_personas} and
- * {@link Folks.PersonaStore.can_remove_personas}.
- *
- * @param can_add_personas whether the store can handle adding personas
- * @param can_alias_personas whether the store can handle and update
- * user-specified persona aliases
- * @param can_remove_personas whether the store can handle removing personas
- *
- * @since 0.9.7
- */
- public void update_capabilities (MaybeBool can_add_personas,
- MaybeBool can_alias_personas, MaybeBool can_remove_personas)
- {
- this.freeze_notify ();
-
- if (can_add_personas != this._can_add_personas)
- {
- this._can_add_personas = can_add_personas;
- this.notify_property ("can-add-personas");
- }
-
- if (can_alias_personas != this._can_alias_personas)
- {
- this._can_alias_personas = can_alias_personas;
- this.notify_property ("can-alias-personas");
- }
-
- if (can_remove_personas != this._can_remove_personas)
- {
- this._can_remove_personas = can_remove_personas;
- this.notify_property ("can-remove-personas");
- }
-
- this.thaw_notify ();
- }
-
- /**
- * Freeze persona changes in the store.
- *
- * This freezes externally-visible changes to the set of personas in the store
- * until {@link FolksDummy.PersonaStore.thaw_personas_changed} is called, at
- * which point all pending changes are made visible in the
- * {@link Folks.PersonaStore.personas} property and by emitting
- * {@link Folks.PersonaStore.personas_changed}.
- *
- * Calls to {@link FolksDummy.PersonaStore.freeze_personas_changed} and
- * {@link FolksDummy.PersonaStore.thaw_personas_changed} must be well-nested.
- * Pending changes will only be committed after the final call to
- * {@link FolksDummy.PersonaStore.thaw_personas_changed}.
- *
- * @see PersonaStore.thaw_personas_changed
- * @since 0.9.7
- */
- public void freeze_personas_changed ()
- {
- this._personas_changed_frozen++;
- }
-
- /**
- * Thaw persona changes in the store.
- *
- * This thaws externally-visible changes to the set of personas in the store.
- * If the number of calls to
- * {@link FolksDummy.PersonaStore.thaw_personas_changed} matches the number of
- * calls to {@link FolksDummy.PersonaStore.freeze_personas_changed}, all
- * pending changes are committed and made externally-visible.
- *
- * @see PersonaStore.freeze_personas_changed
- * @since 0.9.7
- */
- public void thaw_personas_changed ()
- {
- assert (this._personas_changed_frozen > 0);
- this._personas_changed_frozen--;
-
- if (this._personas_changed_frozen == 0)
- {
- /* Emit the queued changes. */
- this._emit_personas_changed (this._pending_persona_registrations,
- this._pending_persona_unregistrations);
-
- this._pending_persona_registrations.clear ();
- this._pending_persona_unregistrations.clear ();
- }
- }
-
- /**
- * Register new personas with the persona store.
- *
- * This registers a set of personas as if they had just appeared in the
- * backing store. If the persona store is not frozen (see
- * {@link FolksDummy.PersonaStore.freeze_personas_changed}) the changes are
- * made externally visible on the store immediately (e.g. in the
- * {@link Folks.PersonaStore.personas} property and through a
- * {@link Folks.PersonaStore.personas_changed} signal). If the store is
- * frozen, the changes will be pending until the store is next unfrozen.
- *
- * All elements in the @personas set be of type
- * {@link FolksDummy.PersonaStore.persona_type}.
- *
- * @param personas set of personas to register
- *
- * @since 0.9.7
- */
- public void register_personas (Set<Persona> personas)
- {
- Set<Persona> added_personas;
- var emit_notifications = (this._personas_changed_frozen == 0);
-
- /* If the persona store has persona changes frozen, queue up the
- * personas and emit a notification about them later. */
- if (emit_notifications == false)
- added_personas = this._pending_persona_registrations;
- else
- added_personas = new HashSet<Persona> ();
-
- foreach (var persona in personas)
- {
- assert (persona.get_type ().is_a (this._persona_type));
-
- /* Handle the case where a persona is unregistered while the store is
- * frozen, then registered again before it's unfrozen. */
- if (this._pending_persona_unregistrations.remove (persona))
- this._personas.unset (persona.iid);
-
- if (this._personas.has_key (persona.iid))
- continue;
-
- added_personas.add (persona);
- if (emit_notifications == true)
- this._personas.set (persona.iid, persona);
- }
-
- if (added_personas.size > 0 && emit_notifications == true)
- this._emit_personas_changed (added_personas, null);
- }
-
- /**
- * Unregister existing personas with the persona store.
- *
- * This unregisters a set of personas as if they had just disappeared from the
- * backing store. If the persona store is not frozen (see
- * {@link FolksDummy.PersonaStore.freeze_personas_changed}) the changes are
- * made externally visible on the store immediately (e.g. in the
- * {@link Folks.PersonaStore.personas} property and through a
- * {@link Folks.PersonaStore.personas_changed} signal). If the store is
- * frozen, the changes will be pending until the store is next unfrozen.
- *
- * @param personas set of personas to unregister
- *
- * @since 0.9.7
- */
- public void unregister_personas (Set<Persona> personas)
- {
- Set<Persona> removed_personas;
- var emit_notifications = (this._personas_changed_frozen == 0);
-
- /* If the persona store has persona changes frozen, queue up the
- * personas and emit a notification about them later. */
- if (emit_notifications == false)
- removed_personas = this._pending_persona_unregistrations;
- else
- removed_personas = new HashSet<Persona> ();
-
- foreach (var _persona in personas)
- {
- /* Handle the case where a persona is registered while the store is
- * frozen, then unregistered before it's unfrozen. */
- this._pending_persona_registrations.remove (_persona);
-
- Persona? persona = this._personas.get (_persona.iid);
- if (persona == null)
- continue;
-
- removed_personas.add ((!) persona);
- }
-
- /* Modify this._personas afterwards, just in case
- * personas == this._personas. */
- if (removed_personas.size > 0 && emit_notifications == true)
- {
- foreach (var _persona in removed_personas)
- this._personas.unset (_persona.iid);
-
- this._emit_personas_changed (null, removed_personas);
- }
- }
-
- /**
- * Reach quiescence on the store.
- *
- * If the {@link Folks.PersonaStore.prepare} method has already been called on
- * the store, this causes the store to signal that it has reached quiescence
- * immediately. If the store has not yet been prepared, this will set a flag
- * to ensure that quiescence is reached as soon as
- * {@link Folks.PersonaStore.prepare} is called.
- *
- * This must be called before the store will reach quiescence.
- *
- * @since 0.9.7
- */
- public void reach_quiescence ()
- {
- /* Can't reach quiescence until prepare() has been called. */
- if (this._is_prepared == false)
- {
- this._quiescent_on_prepare = true;
- return;
- }
-
- /* The initial query is complete, so signal that we've reached
- * quiescence (even if there was an error). */
- if (this._is_quiescent == false)
- {
- this._is_quiescent = true;
- this.notify_property ("is-quiescent");
- }
- }
-
- /**
- * Update the {@link Folks.PersonaStore.is_user_set_default} property.
- *
- * Backend method for use by test code to simulate a backing-store-driven
- * change in the {@link Folks.PersonaStore.is_user_set_default} property.
- *
- * @param is_user_set_default new value for the property
- *
- * @since 0.9.7
- */
- public void update_is_user_set_default (bool is_user_set_default)
- {
- /* Implemented as an ‘update_*()’ method to make it more explicit that
- * this is for test driver use only. */
- this.is_user_set_default = is_user_set_default;
- }
-
- /**
- * Update the {@link Folks.PersonaStore.trust_level} property.
- *
- * Backend method for use by test code to simulate a backing-store-driven
- * change in the {@link Folks.PersonaStore.trust_level} property.
- *
- * @param trust_level new value for the property
- *
- * @since 0.9.7
- */
- public void update_trust_level (PersonaStoreTrust trust_level)
- {
- /* Implemented as an ‘update_*()’ method to make it more explicit that
- * this is for test driver use only. */
- this.trust_level = trust_level;
- }
-
- /**
- * Mock function for {@link Folks.PersonaStore.add_persona_from_details}.
- *
- * This function is called whenever this store's
- * {@link Folks.PersonaStore.add_persona_from_details} method is called. It
- * allows the caller to determine whether adding the given persona should
- * fail, by throwing an error from this mock function. If no error is thrown
- * from this function, adding the given persona will succeed. This is useful
- * for testing error handling of calls to
- * {@link Folks.PersonaStore.add_persona_from_details}.
- *
- * The value returned by this function gives a delay which is imposed for
- * completion of the {@link Folks.PersonaStore.add_persona_from_details} call.
- * Negative or zero delays
- * result in completion in an idle callback, and positive delays result in
- * completion after that many milliseconds.
- *
- * If this is ``null``, all calls to
- * {@link Folks.PersonaStore.add_persona_from_details} will succeed.
- *
- * This mock function may be changed at any time; changes will take effect for
- * the next call to {@link Folks.PersonaStore.add_persona_from_details}.
- *
- * @since 0.9.7
- */
- public void set_add_persona_from_details_mock (AddPersonaFromDetailsMock? mock)
- {
- this._add_persona_from_details_mock = mock;
- }
-
- /**
- * Mock function for {@link Folks.PersonaStore.remove_persona}.
- *
- * This function is called whenever this store's
- * {@link Folks.PersonaStore.remove_persona} method is called. It allows
- * the caller to determine whether removing the given persona should fail, by
- * throwing an error from this mock function. If no error is thrown from this
- * function, removing the given persona will succeed. This is useful for
- * testing error handling of calls to
- * {@link Folks.PersonaStore.remove_persona}.
- *
- * See {@link FolksDummy.PersonaStore.set_add_persona_from_details_mock}.
- *
- * This mock function may be changed at any time; changes will take effect for
- * the next call to {@link Folks.PersonaStore.remove_persona}.
- *
- * @since 0.9.7
- */
- public void set_remove_persona_mock (RemovePersonaMock? mock)
- {
- this._remove_persona_mock = mock;
- }
-
- /**
- * Mock function for {@link Folks.PersonaStore.prepare}.
- *
- * This function is called whenever this store's
- * {@link Folks.PersonaStore.prepare} method is called on an unprepared store.
- * It allows the caller to determine whether preparing the store should fail,
- * by throwing an error from this mock function. If no error is thrown from
- * this function, preparing the store will succeed (and all future calls to
- * {@link Folks.PersonaStore.prepare} will return immediately without calling
- * this mock function). This is useful for testing error handling of calls to
- * {@link Folks.PersonaStore.prepare}.
- *
- * See {@link FolksDummy.PersonaStore.set_add_persona_from_details_mock}.
- *
- * This mock function may be changed at any time; changes will take effect for
- * the next call to {@link Folks.PersonaStore.prepare}.
- *
- * @since 0.9.7
- */
- public void set_prepare_mock (PrepareMock? mock)
- {
- this._prepare_mock = mock;
- }
-}
diff --git a/backends/dummy/lib/dummy-persona.vala b/backends/dummy/lib/dummy-persona.vala
deleted file mode 100644
index a92fb2c5..00000000
--- a/backends/dummy/lib/dummy-persona.vala
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (C) 2013 Philip Withnall
- * Copyright (C) 2013 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using Folks;
-using Gee;
-using GLib;
-
-/**
- * A persona subclass representing a single contact.
- *
- * This mocks up a ‘thin’ persona which implements none of the available
- * property interfaces provided by libfolks, and is designed as a base class to
- * be subclassed by personas which will implement one or more of these
- * interfaces. For example, {@link FolksDummy.FullPersona} is one such subclass
- * which implements all available interfaces.
- *
- * There are two sides to this class’ interface: the normal methods required by
- * {@link Folks.Persona}, such as
- * {@link Folks.Persona.linkable_property_to_links},
- * and the backend methods which should be called by test driver code to
- * simulate changes in the backing store providing this persona, such as
- * {@link FolksDummy.Persona.update_writeable_properties}. The ``update_``,
- * ``register_`` and ``unregister_`` prefixes are commonly used for backend
- * methods.
- *
- * All property changes for contact details of subclasses of
- * {@link FolksDummy.Persona} have a configurable delay before taking effect,
- * which can be controlled by {@link FolksDummy.Persona.property_change_delay}.
- *
- * The API in {@link FolksDummy} is unstable and may change wildly. It is
- * designed mostly for use by libfolks unit tests.
- *
- * @since 0.9.7
- */
-public class FolksDummy.Persona : Folks.Persona
-{
- private string[] _linkable_properties = new string[0];
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public override string[] linkable_properties
- {
- get { return this._linkable_properties; }
- }
-
- private string[] _writeable_properties = new string[0];
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public override string[] writeable_properties
- {
- get { return this._writeable_properties; }
- }
-
- /**
- * Create a new persona.
- *
- * Create a new persona for the {@link FolksDummy.PersonaStore} ``store``,
- * with the given construct-only properties.
- *
- * The persona’s {@link Folks.Persona.writeable_properties} are initialised to
- * the given ``store``’s
- * {@link Folks.PersonaStore.always_writeable_properties}. They may be updated
- * afterwards using {@link FolksDummy.Persona.update_writeable_properties}.
- *
- * @param store the store which will contain the persona
- * @param contact_id a unique free-form string identifier for the persona
- * @param is_user ``true`` if the persona represents the user, ``false``
- * otherwise
- * @param linkable_properties an array of names of the properties which should
- * be used for linking this persona to others
- *
- * @since 0.9.7
- */
- public Persona (PersonaStore store, string contact_id,
- bool is_user = false, string[] linkable_properties = {})
- {
- var uid = Folks.Persona.build_uid (BACKEND_NAME, store.id, contact_id);
- var iid = store.id + ":" + contact_id;
-
- Object (display_id: contact_id,
- uid: uid,
- iid: iid,
- store: store,
- is_user: is_user);
-
- this._linkable_properties = linkable_properties;
- this._writeable_properties = this.store.always_writeable_properties;
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.7
- */
- public override void linkable_property_to_links (string prop_name,
- Folks.Persona.LinkablePropertyCallback callback)
- {
- if (prop_name == "im-addresses")
- {
- var persona = this as ImDetails;
- assert (persona != null);
-
- foreach (var protocol in persona.im_addresses.get_keys ())
- {
- var im_fds = persona.im_addresses.get (protocol);
-
- foreach (var im_fd in im_fds)
- {
- callback (protocol + ":" + im_fd.value);
- }
- }
- }
- else if (prop_name == "local-ids")
- {
- var persona = this as LocalIdDetails;
- assert (persona != null);
-
- foreach (var id in persona.local_ids)
- {
- callback (id);
- }
- }
- else if (prop_name == "web-service-addresses")
- {
- var persona = this as WebServiceDetails;
- assert (persona != null);
-
- foreach (var web_service in persona.web_service_addresses.get_keys ())
- {
- var web_service_addresses =
- persona.web_service_addresses.get (web_service);
-
- foreach (var ws_fd in web_service_addresses)
- {
- callback (web_service + ":" + ws_fd.value);
- }
- }
- }
- else if (prop_name == "email-addresses")
- {
- var persona = this as EmailDetails;
- assert (persona != null);
-
- foreach (var email in persona.email_addresses)
- {
- callback (email.value);
- }
- }
- else
- {
- /* Chain up */
- base.linkable_property_to_links (prop_name, callback);
- }
- }
-
-
- /*
- * All the functions below here are to be used by testing code rather than by
- * libfolks clients. They form the interface which would normally be between
- * the Persona and a web service or backing store of some kind.
- */
-
-
- /**
- * Update the persona’s set of writeable properties.
- *
- * Update the {@link Folks.Persona.writeable_properties} property to contain
- * the union of {@link Folks.PersonaStore.always_writeable_properties} from
- * the persona’s store, and the given ``writeable_properties``.
- *
- * This should be used to simulate a change in the backing store for the
- * persona which affects the writeability of one or more of its properties.
- *
- * @since 0.9.7
- */
- public void update_writeable_properties (string[] writeable_properties)
- {
- var new_writeable_properties = new HashSet<string> ();
-
- foreach (var p in this.store.always_writeable_properties)
- new_writeable_properties.add (p);
- foreach (var p in writeable_properties)
- new_writeable_properties.add (p);
-
- /* Check for changes. */
- var changed = false;
-
- if (this._writeable_properties.length != new_writeable_properties.size)
- {
- changed = true;
- }
- else
- {
- foreach (var p in this._writeable_properties)
- {
- if (new_writeable_properties.contains (p) == false)
- {
- changed = true;
- break;
- }
- }
- }
-
- if (changed == true)
- {
- this._writeable_properties = new_writeable_properties.to_array ();
- this.notify_property ("writeable-properties");
- }
- }
-
- /**
- * Update the persona’s set of linkable properties.
- *
- * Update the {@link Folks.Persona.linkable_properties} property to contain
- * the given ``linkable_properties``.
- *
- * @param linkable_properties new set of linkable property names, in lower
- * case, hyphenated form
- * @since 0.9.7
- */
- public void update_linkable_properties (string[] linkable_properties)
- {
- var new_linkable_properties = new SmallSet<string> ();
- new_linkable_properties.add_all_array (linkable_properties);
-
- var old_linkable_properties = new SmallSet<string> ();
- old_linkable_properties.add_all_array (this._linkable_properties);
-
- if (!Folks.Internal.equal_sets<string> (old_linkable_properties,
- new_linkable_properties))
- {
- this._linkable_properties = linkable_properties;
- this.notify_property ("linkable-properties");
- }
- }
-
- /**
- * Delay between property changes and notifications.
- *
- * This sets an optional delay between client code requesting a property
- * change (e.g. by calling {@link Folks.NameDetails.change_nickname}) and the
- * property change taking place and a {@link Object.notify} signal being
- * emitted for it.
- *
- * Delays are in milliseconds. Negative delays mean that property change
- * notifications happen synchronously in the change method. A delay of 0
- * means that property change notifications happen in an idle callback
- * immediately after the change method. A positive delay means that property
- * change notifications happen that many milliseconds after the change method
- * is called.
- *
- * @since 0.9.7
- */
- protected int property_change_delay { get; set; default = 0; }
-
- /**
- * Callback to effect a property change in a backing store.
- *
- * This is called by {@link FolksDummy.Persona.change_property} after the
- * {@link FolksDummy.Persona.property_change_delay} has expired. It must
- * effect the property change in the simulated backing store, for example by
- * calling an ‘update’ method such as
- * {@link FolksDummy.FullPersona.update_nickname}.
- *
- * @since 0.9.7
- */
- protected delegate void ChangePropertyCallback ();
-
- /**
- * Change a property in the simulated backing store.
- *
- * This triggers a property change in the simulated backing store, applying
- * the current {@link FolksDummy.Persona.property_change_delay} before calling
- * the given ``callback`` which should actually effect the property change.
- *
- * @param property_name name of the property being changed
- * @param callback callback to call once the change delay has passed
- * @since 0.9.7
- */
- protected async void change_property (string property_name,
- ChangePropertyCallback callback)
- {
- if (this.property_change_delay < 0)
- {
- /* No delay. */
- callback ();
- }
- else if (this.property_change_delay == 0)
- {
- /* Idle delay. */
- Idle.add (() =>
- {
- callback ();
- this.change_property.callback ();
- return false;
- });
-
- yield;
- }
- else
- {
- /* Timed delay. */
- Timeout.add (this.property_change_delay, () =>
- {
- callback ();
- this.change_property.callback ();
- return false;
- });
-
- yield;
- }
- }
-}
diff --git a/backends/dummy/lib/folks-dummy.deps b/backends/dummy/lib/folks-dummy.deps
deleted file mode 100644
index 45c26b81..00000000
--- a/backends/dummy/lib/folks-dummy.deps
+++ /dev/null
@@ -1,4 +0,0 @@
-glib-2.0
-gobject-2.0
-folks
-gee-0.8
diff --git a/backends/dummy/lib/folks-dummy.map b/backends/dummy/lib/folks-dummy.map
deleted file mode 100644
index a3d859a4..00000000
--- a/backends/dummy/lib/folks-dummy.map
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-global:
- FOLKS_DUMMY_*;
- folks_dummy_*;
-local:
- *;
-};
diff --git a/backends/dummy/lib/meson.build b/backends/dummy/lib/meson.build
deleted file mode 100644
index 32448f35..00000000
--- a/backends/dummy/lib/meson.build
+++ /dev/null
@@ -1,82 +0,0 @@
-dummy_backendlib_gir_name = 'FolksDummy-@0@'.format(folks_api_version)
-
-dummy_backendlib_sources = files(
- 'dummy-backend.vala',
- 'dummy-full-persona.vala',
- 'dummy-persona-store.vala',
- 'dummy-persona.vala',
-)
-
-dummy_backendlib_deps = [
- backend_deps,
-]
-
-# FIXME: we need to set these manually for the valadoc target as long as meson
-# doesn't have native support (https://github.com/mesonbuild/meson/issues/894)
-dummy_backendlib_doc_deps = [
- '--pkg', 'folks',
-]
-
-dummy_backendlib_vala_flags = [
- common_backendlib_vala_flags,
-]
-
-dummy_backendlib_c_flags = [
- '-include', 'config.h',
- '-DBACKEND_NAME="@0@"'.format(dummy_backend_name),
- '-DG_LOG_DOMAIN="@0@"'.format(dummy_backend_name),
-]
-
-dummy_backendlib_symbolmap = meson.current_source_dir() / 'folks-@0@.map'.format(dummy_backend_name)
-dummy_backendlib_link_flags = cc.get_supported_link_arguments(
- '-Wl,--version-script,@0@'.format(dummy_backendlib_symbolmap),
-)
-
-dummy_backendlib = shared_library('folks-@0@'.format(dummy_backend_name),
- dummy_backendlib_sources,
- dependencies: dummy_backendlib_deps,
- vala_args: dummy_backendlib_vala_flags,
- c_args: dummy_backendlib_c_flags,
- link_args: dummy_backendlib_link_flags,
- link_depends: dummy_backendlib_symbolmap,
- version: folks_dummy_lib_version,
- vala_header: 'folks/folks-@0@.h'.format(dummy_backend_name),
- vala_gir: dummy_backendlib_gir_name + '.gir',
- install: true,
- install_dir: [ true, folks_headers_install_dir, true, true ],
-)
-
-# Also make sure to install the VAPI's .deps file
-install_data('folks-dummy.deps',
- install_dir: get_option('datadir') / 'vala' / 'vapi',
-)
-
-# FIXME: This comes straight from the Meson docs on how to create/install a
-# typelib file for your Vala shared library. However, as mentioned in
-# https://github.com/mesonbuild/meson/issues/4481, this is not ideal.
-custom_target(dummy_backendlib_gir_name + '.typelib',
- command: [ g_ir_compiler,
- '--includedir', libfolks_gir_include_dir,
- '--output', '@OUTPUT@',
- '--shared-library', 'lib' + dummy_backendlib.name(),
- meson.current_build_dir() / (dummy_backendlib_gir_name + '.gir')
- ],
- output: dummy_backendlib_gir_name + '.typelib',
- depends: dummy_backendlib,
- install: true,
- install_dir: folks_typelibdir,
-)
-
-dummy_backendlib_dep = declare_dependency(
- link_with: dummy_backendlib,
- include_directories: include_directories('.'),
-)
-
-# Pkg-config file
-pkgconfig.generate(dummy_backendlib,
- name: 'Folks dummy support library',
- description: 'Dummy support library for the Folks meta-contacts library',
- filebase: 'folks-@0@'.format(dummy_backend_name),
- requires: [ 'folks', glib_dep, gobject_dep, gee_dep, ],
- variables: common_pkgconf_variables,
-)
diff --git a/backends/dummy/meson.build b/backends/dummy/meson.build
deleted file mode 100644
index c3518896..00000000
--- a/backends/dummy/meson.build
+++ /dev/null
@@ -1,35 +0,0 @@
-# The dummy backend
-dummy_backend_name = 'dummy'
-
-# Backend library
-subdir('lib')
-
-dummy_backend_sources = [
- 'dummy-backend-factory.vala',
-]
-
-dummy_backend_deps = [
- backend_deps,
- dummy_backendlib_dep,
-]
-
-dummy_backend_vala_flags = [
-]
-
-dummy_backend_c_flags = [
- '-include', 'config.h',
- '-DBACKEND_NAME="@0@"'.format(dummy_backend_name),
- '-DG_LOG_DOMAIN="@0@"'.format(dummy_backend_name),
-]
-
-dummy_backend = shared_module(dummy_backend_name,
- dummy_backend_sources,
- dependencies: dummy_backend_deps,
- vala_args: dummy_backend_vala_flags,
- c_args: dummy_backend_c_flags,
- name_prefix: '',
- install_dir: folks_backend_dir / dummy_backend_name,
- install: true,
-)
-
-folks_backends += dummy_backend
diff --git a/backends/eds/eds-backend-factory.vala b/backends/eds/eds-backend-factory.vala
deleted file mode 100644
index 51dcf62e..00000000
--- a/backends/eds/eds-backend-factory.vala
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2009 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>.
- * Copyright (C) 2009 Nokia Corporation.
- * Copyright (C) 2011 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Marco Barisione <marco.barisione@collabora.co.uk>
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- *
- * This file was originally part of Rygel.
- */
-
-using Folks;
-
-/**
- * The EDS backend module entry point.
- *
- * @backend_store a store to add the EDS backends to
- * @since 0.6.0
- */
-public void module_init (BackendStore backend_store)
-{
- backend_store.add_backend (new Folks.Backends.Eds.Backend ());
-}
-
-/**
- * The EDS backend module exit point.
- *
- * @param backend_store the store to remove the backends from
- * @since 0.6.0
- */
-public void module_finalize (BackendStore backend_store)
-{
- /* FIXME: No way to remove backends from the store. */
-}
diff --git a/backends/eds/eds-backend.vala b/backends/eds/eds-backend.vala
deleted file mode 100644
index fd008148..00000000
--- a/backends/eds/eds-backend.vala
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Marco Barisione <marco.barisione@collabora.co.uk>
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- */
-
-using E;
-using Gee;
-using GLib;
-using Folks;
-using Folks.Backends.Eds;
-
-extern const string BACKEND_NAME;
-
-/* The following function is needed in order to use the async SourceRegistry
- * constructor. FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=659886 */
-[CCode (cname = "e_source_registry_new", cheader_filename = "libedataserver/libedataserver.h", finish_name = "e_source_registry_new_finish")]
-internal extern static async E.SourceRegistry create_source_registry (GLib.Cancellable? cancellable = null) throws GLib.Error;
-
-/**
- * A backend which connects to EDS and creates a {@link PersonaStore}
- * for each service.
- */
-public class Folks.Backends.Eds.Backend : Folks.Backend
-{
- private const string _use_address_books =
- "FOLKS_BACKEND_EDS_USE_ADDRESS_BOOKS";
- private bool _is_prepared = false;
- private bool _prepare_pending = false; /* used for unprepare() too */
- private bool _is_quiescent = false;
- private HashMap<string, PersonaStore> _persona_stores;
- private Map<string, PersonaStore> _persona_stores_ro;
- private E.SourceRegistry _ab_sources;
- private Set<string>? _storeids;
-
- /**
- * {@inheritDoc}
- */
- public override string name { get { return BACKEND_NAME; } }
-
- /**
- * {@inheritDoc}
- */
- public override Map<string, PersonaStore> persona_stores
- {
- get { return this._persona_stores_ro; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override void disable_persona_store (PersonaStore store)
- {
- if (this._persona_stores.has_key (store.id))
- {
- this._remove_address_book (store);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public override void enable_persona_store (PersonaStore store)
- {
- if (this._persona_stores.has_key (store.id) == false)
- {
- this._add_persona_store (store);
- }
- }
-
- private void _add_persona_store (PersonaStore store, bool notify = true)
- {
- store.removed.connect (this._store_removed_cb);
-
- this._persona_stores.set (store.id, store);
-
- this.persona_store_added (store);
- if (notify)
- {
- this.notify_property ("persona-stores");
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public override void set_persona_stores (Set<string>? storeids)
- {
- this._storeids = storeids;
-
- /* If the set is empty, load all unloaded stores then return. */
- if (storeids == null)
- {
- this._ab_source_list_changed_cb ();
- return;
- }
-
- bool stores_changed = false;
- /* First handle adding any missing persona stores. */
- foreach (string id in storeids)
- {
- if (this._persona_stores.has_key (id) == false)
- {
- E.Source? s = this._ab_sources.ref_source (id);
-
- if (s == null)
- {
- warning ("Unable to reference EDS source with ID %s", id);
- continue;
- }
-
- var store =
- new Edsf.PersonaStore.with_source_registry (this._ab_sources, s);
- this._add_persona_store (store, false);
-
- stores_changed = true;
- }
- }
-
- var iter = this._persona_stores.map_iterator ();
-
- while (iter.next ())
- {
- var store = iter.get_value ();
-
- if (!storeids.contains (store.id))
- {
- this._remove_address_book (store, false, iter);
- stores_changed = true;
- }
- }
-
- if (stores_changed)
- {
- this.notify_property ("persona-stores");
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public Backend ()
- {
- Object ();
- }
-
- construct
- {
- this._persona_stores = new HashMap<string, PersonaStore> ();
- this._persona_stores_ro = this._persona_stores.read_only_view;
- this._storeids = null;
- }
-
- /**
- * Whether this Backend has been prepared.
- *
- * See {@link Folks.Backend.is_prepared}.
- *
- * @since 0.6.0
- */
- public override bool is_prepared
- {
- get { return this._is_prepared; }
- }
-
- /**
- * Whether this Backend has reached a quiescent state.
- *
- * See {@link Folks.Backend.is_quiescent}.
- *
- * @since 0.6.2
- */
- public override bool is_quiescent
- {
- get { return this._is_quiescent; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override async void prepare () throws GLib.Error
- {
- var profiling = Internal.profiling_start ("preparing Eds.Backend");
-
- if (this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
- this.freeze_notify ();
-
- this._create_avatars_cache_dir ();
-
- this._ab_sources = yield create_source_registry ();
- /* Our callback only looks for added sources, so we only
- need to connect to source-added and source-enabled signals */
- this._ab_sources.source_added.connect (
- this._ab_source_list_changed_cb);
- this._ab_sources.source_enabled.connect (
- this._ab_source_list_changed_cb);
- this._ab_source_list_changed_cb ();
-
- this._is_prepared = true;
- this.notify_property ("is-prepared");
-
- this._is_quiescent = true;
- this.notify_property ("is-quiescent");
- }
- finally
- {
- this.thaw_notify ();
- this._prepare_pending = false;
- }
-
- Internal.profiling_end ((owned) profiling);
- }
-
- /**
- * {@inheritDoc}
- */
- public override async void unprepare () throws GLib.Error
- {
- if (!this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
- this.freeze_notify ();
-
- var iter = this._persona_stores.map_iterator ();
-
- while (iter.next ())
- this._remove_address_book (iter.get_value (), true, iter);
-
- this._ab_sources.source_added.disconnect (this._ab_source_list_changed_cb);
- this._ab_sources.source_enabled.disconnect (this._ab_source_list_changed_cb);
- this._ab_sources = null;
-
- this._is_quiescent = false;
- this.notify_property ("is-quiescent");
-
- this._is_prepared = false;
- this.notify_property ("is-prepared");
- }
- finally
- {
- this.thaw_notify ();
- this._prepare_pending = false;
- }
- }
-
- private void _create_avatars_cache_dir ()
- {
- string avatars_dir = GLib.Path.build_filename
- (GLib.Environment.get_user_cache_dir (), "folks", "avatars");
- DirUtils.create_with_parents (avatars_dir, 0700);
- }
-
- /* Called every time the list of E.Sources changes. Note that there may be
- * cases where it's called but we don't have to do anything. For example, if
- * an address book is renamed, we don't have to add or remove any persona
- * stores since we don't use the address book names. */
- private void _ab_source_list_changed_cb ()
- {
- string[] use_addressbooks = this._get_addressbooks_from_env ();
- GLib.List<E.Source> books =
- this._ab_sources.list_enabled (SOURCE_EXTENSION_ADDRESS_BOOK);
-
- debug ("Address book source list changed.");
-
- /* Add address books which didn't previously exist in the backend.
- * We don't deal with removals here: see _source_list_changed_cb() in
- * Edsf.PersonaStore for that. */
- var added_sources = new LinkedList<E.Source> ();
-
- foreach (E.Source s in books)
- {
- /* If we've been told to use just a specific set of address
- * books, we must ignore all others. */
- var uid = s.get_uid ();
- if (use_addressbooks.length > 0 &&
- !(uid in use_addressbooks))
- {
- continue;
- }
-
- if (this._storeids != null &&
- !(uid in this._storeids))
- {
- continue;
- }
-
- if (!this._persona_stores.has_key (uid))
- {
- added_sources.add (s);
- }
- }
-
- /* Actually apply the changes to our state. We can't do this any earlier
- * or we'll mess up the calculation of what's been added. */
- foreach (var s in added_sources)
- {
- this._add_address_book (s);
- }
- }
-
- /**
- * Add a new addressbook connected to a Persona Store.
- */
- private void _add_address_book (E.Source s)
- {
- string uid = s.get_uid ();
- if (this._persona_stores.has_key (uid))
- return;
-
- debug ("Adding address book '%s'.", uid);
-
- var store =
- new Edsf.PersonaStore.with_source_registry (this._ab_sources, s);
-
- this.enable_persona_store (store);
- }
-
- private void _remove_address_book (Folks.PersonaStore store,
- bool notify = true,
- MapIterator<string, Folks.PersonaStore>? iter = null)
- {
- debug ("Removing address book '%s'.", store.id);
-
- if (iter != null)
- {
- assert (store == iter.get_value ());
- iter.unset ();
- }
- else
- {
- this._persona_stores.unset (store.id);
- }
-
- this.persona_store_removed (store);
-
- if (notify)
- {
- this.notify_property ("persona-stores");
- }
-
- store.removed.disconnect (this._store_removed_cb);
- }
-
- private void _store_removed_cb (Folks.PersonaStore store)
- {
- this._remove_address_book (store);
- }
-
- private string[] _get_addressbooks_from_env ()
- {
- string[] addressbooks = {};
- string ab_list = Environment.get_variable (Backend._use_address_books);
-
- if (ab_list != null && ab_list != "")
- {
- addressbooks = ab_list.split (":");
- }
-
- return addressbooks;
- }
-}
diff --git a/backends/eds/lib/edsf-persona-store.vala b/backends/eds/lib/edsf-persona-store.vala
deleted file mode 100644
index 0f4a2086..00000000
--- a/backends/eds/lib/edsf-persona-store.vala
+++ /dev/null
@@ -1,2920 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- * Copyright (C) 2013, 2016 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip.withnall@collabora.co.uk>
- * Marco Barisione <marco.barisione@collabora.co.uk>
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- */
-
-using E;
-using Folks;
-using Gee;
-using GLib;
-
-extern const string BACKEND_NAME;
-
-/* The following function is needed in order to use the async SourceRegistry
- * constructor. FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=659886 */
-[CCode (cname = "e_source_registry_new", cheader_filename = "libedataserver/libedataserver.h", finish_name = "e_source_registry_new_finish")]
-internal extern static async E.SourceRegistry create_source_registry (GLib.Cancellable? cancellable = null) throws GLib.Error;
-
-/**
- * A persona store representing a single EDS address book.
- *
- * The persona store will contain {@link Edsf.Persona}s for each contact in the
- * address book it represents.
- */
-public class Edsf.PersonaStore : Folks.PersonaStore
-{
- private HashMap<string, Persona> _personas;
- private Map<string, Persona> _personas_ro;
- private bool _is_prepared = false;
- private bool _prepare_pending = false;
- private bool _is_quiescent = false;
- private HashSet<Persona>? _pending_personas = null; /* null before prepare()*/
- private E.BookClient? _addressbook = null; /* null before prepare() */
- private E.BookClientView? _ebookview = null; /* null before prepare() */
- private E.SourceRegistry? _source_registry = null; /* null before prepare() */
- private string _query_str;
-
- /* The timeout after which we consider a property change to have failed if we
- * haven't received a property change notification for it. */
- private const uint _property_change_timeout = 30; /* seconds */
-
- /* The timeout after which we consider a contact addition to have failed if we
- * haven't received an object addition signal for it. */
- private const uint _new_contact_timeout = 30; /* seconds */
-
- /* Translators: This should be translated to the name of the “Starred in
- * Android” group in Google Contacts for your language. If Google have not
- * localised the group for your language, or Google Contacts isn't available
- * in your language, please *do not* translate this string (i.e. just copy
- * the msgid to the msgstr unchanged). */
- internal const string android_favourite_group_name = N_("Starred in Android");
-
- internal const string anti_links_attribute_name = "X-FOLKS-ANTI-LINKS";
-
- /**
- * The type of persona store this is.
- *
- * See {@link Folks.PersonaStore.type_id}.
- *
- * @since 0.6.0
- */
- public override string type_id { get { return BACKEND_NAME; } }
-
- /**
- * Create a new address book with the given ID.
- *
- * A new Address Book will be created with the given ID and the EDS
- * SourceRegistry will notice the new Address Book source and will emit
- * source_added with the new {@link E.Source} object which
- * {@link Folks.Backends.Eds.Backend} will then create a new
- * {@link Edsf.PersonaStore} from.
- *
- * @param id the name and id for the new address book
- * @throws GLib.Error if an error occurred while creating or committing to
- * the {@link E.SourceRegistry}
- *
- * @since 0.9.0
- */
- public static async void create_address_book (string id) throws GLib.Error
- {
- debug ("Creating addressbook %s", id);
- /* In order to create a new Address Book with the given id we follow
- * the guidelines explained here:
- * https://live.gnome.org/Evolution/ESourceMigrationGuide#How_do_I_create_a_new_calendar_or_address_book.3F
- * for setting the backend name and parent UID to "local" and
- * "local-stub" respectively.
- */
- E.Source new_source = new E.Source.with_uid (id, null);
-
- new_source.set_parent ("local-stub");
- new_source.set_display_name (id);
-
- E.SourceAddressBook ab_extension =
- (E.SourceAddressBook) new_source.get_extension ("Address Book");
- ab_extension.set_backend_name ("local");
-
- E.SourceRegistry registry = yield create_source_registry ();
- yield registry.commit_source (new_source, null);
- }
-
- /**
- * Remove a persona store's address book permamently.
- *
- * This is a utility function to remove an {@link Edsf.PersonaStore}'s address
- * book from the disk permanently. This simply wraps the EDS API to do
- * the same.
- *
- * @param store the PersonaStore to delete the address book for.
- * @throws GLib.Error if an error occurred in {@link E.Source.remove}
- *
- * @since 0.9.0
- */
- public static async void remove_address_book (Edsf.PersonaStore store) throws GLib.Error
- {
- yield store.source.remove (null);
- }
-
- private void _address_book_notify_read_only_cb (Object address_book,
- ParamSpec pspec)
- {
- this._update_trust_level ();
- this.notify_property ("can-add-personas");
- this.notify_property ("can-remove-personas");
- }
-
- /**
- * Whether this PersonaStore can add {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_add_personas}.
- *
- * @since 0.6.0
- */
- public override MaybeBool can_add_personas
- {
- get
- {
- if (this._addressbook == null)
- {
- return MaybeBool.FALSE;
- }
-
- return ((!) this._addressbook).readonly
- ? MaybeBool.FALSE : MaybeBool.TRUE;
- }
- }
-
- /**
- * Whether this PersonaStore can set the alias of {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_alias_personas}.
- *
- * @since 0.6.0
- */
- public override MaybeBool can_alias_personas
- {
- get { return MaybeBool.FALSE; }
- }
-
- /**
- * Whether this PersonaStore can set the groups of {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_group_personas}.
- *
- * @since 0.6.0
- */
- public override MaybeBool can_group_personas
- {
- get
- {
- return ("groups" in this._always_writeable_properties)
- ? MaybeBool.TRUE : MaybeBool.FALSE;
- }
- }
-
- /**
- * Whether this PersonaStore can remove {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_remove_personas}.
- *
- * @since 0.6.0
- */
- public override MaybeBool can_remove_personas
- {
- get
- {
- if (this._addressbook == null)
- {
- return MaybeBool.FALSE;
- }
-
- return ((!) this._addressbook).readonly
- ? MaybeBool.FALSE : MaybeBool.TRUE;
- }
- }
-
- /**
- * Whether this PersonaStore has been prepared.
- *
- * See {@link Folks.PersonaStore.is_prepared}.
- *
- * @since 0.6.0
- */
- public override bool is_prepared
- {
- get { return this._is_prepared; }
- }
-
- private string[] _always_writeable_properties = {};
- private static string[] _always_writeable_properties_empty = {}; /* oh Vala */
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public override string[] always_writeable_properties
- {
- get
- {
- if (this._addressbook == null ||
- ((!) this._addressbook).readonly == true)
- {
- return PersonaStore._always_writeable_properties_empty;
- }
-
- return this._always_writeable_properties;
- }
- }
-
- /*
- * Whether this PersonaStore has reached a quiescent state.
- *
- * See {@link Folks.PersonaStore.is_quiescent}.
- *
- * @since 0.6.2
- */
- public override bool is_quiescent
- {
- get { return this._is_quiescent; }
- }
-
- /**
- * The {@link Persona}s exposed by this PersonaStore.
- *
- * See {@link Folks.PersonaStore.personas}.
- *
- * @since 0.6.0
- */
- public override Map<string, Folks.Persona> personas
- {
- get { return this._personas_ro; }
- }
-
- /**
- * The EDS {@link E.Source} associated with this persona store.
- *
- * @since 0.6.6
- */
- public E.Source source
- {
- get; construct;
- }
-
- /**
- * Create a new PersonaStore.
- *
- * Create a new persona store to store the {@link Persona}s for the contacts
- *
- * @param s the e-d-s source being represented by the persona store
- *
- * @since 0.6.0
- */
- [Version (deprecated = true, deprecated_since = "0.7.2",
- replacement = "Edsf.PersonaStore.with_source_registry")]
- public PersonaStore (E.Source s)
- {
- string eds_uid = s.get_uid ();
- string eds_name = s.get_display_name ();
- Object (id: eds_uid,
- display_name: eds_name,
- source: s);
-
- this._source_registry = null; /* created in prepare() */
- }
-
- /**
- * Create a new PersonaStore.
- *
- * Create a new persona store to store the {@link Persona}s for the contacts
- * in ``s``. Passing a re-used source registry to the constructor (compared to
- * the old {@link Edsf.PersonaStore} constructor) saves a lot of time and
- * D-Bus round trips.
- *
- * @param r the EDS source registry giving access to all EDS sources
- * @param s the EDS source being represented by the persona store
- *
- * @since 0.7.2
- */
- public PersonaStore.with_source_registry (E.SourceRegistry r, E.Source s)
- {
- string eds_uid = s.get_uid ();
- string eds_name = s.get_display_name ();
- Object (id: eds_uid,
- display_name: eds_name,
- source: s);
-
- this._source_registry = r;
- }
-
- construct
- {
- this._personas = new HashMap<string, Persona> ();
- this._personas_ro = this._personas.read_only_view;
- this._query_str = "(contains \"x-evolution-any-field\" \"\")";
- this.source.changed.connect (this._source_changed_cb);
- }
-
- ~PersonaStore ()
- {
- try
- {
- if (this._ebookview != null)
- {
- ((!) this._ebookview).objects_added.disconnect (
- this._contacts_added_cb);
- ((!) this._ebookview).objects_removed.disconnect (
- this._contacts_removed_cb);
- ((!) this._ebookview).objects_modified.disconnect (
- this._contacts_changed_cb);
- ((!) this._ebookview).complete.disconnect (
- this._contacts_complete_cb);
- ((!) this._ebookview).stop ();
-
- this._ebookview = null;
- }
-
- if (this._addressbook != null)
- {
- ((!) this._addressbook).notify["readonly"].disconnect (
- this._address_book_notify_read_only_cb);
-
- this._addressbook = null;
- }
-
- if (this._source_registry != null)
- {
- ((!) this._source_registry).source_removed.disconnect (
- this._source_registry_changed_cb);
- ((!) this._source_registry).source_disabled.disconnect (
- this._source_registry_changed_cb);
- this._source_registry = null;
- }
- }
- catch (GLib.Error e)
- {
- if (!(e is IOError.CLOSED) && !(e is DBusError.NOT_SUPPORTED))
- GLib.warning ("~PersonaStore: %s\n", e.message);
- }
- }
-
- /**
- * Add a new {@link Persona} to the PersonaStore.
- *
- * Accepted keys for ``details`` are:
- * - PersonaStore.detail_key (PersonaDetail.AVATAR)
- * - PersonaStore.detail_key (PersonaDetail.BIRTHDAY)
- * - PersonaStore.detail_key (PersonaDetail.EMAIL_ADDRESSES)
- * - PersonaStore.detail_key (PersonaDetail.FULL_NAME)
- * - PersonaStore.detail_key (PersonaDetail.GENDER)
- * - PersonaStore.detail_key (PersonaDetail.IM_ADDRESSES)
- * - PersonaStore.detail_key (PersonaDetail.IS_FAVOURITE)
- * - PersonaStore.detail_key (PersonaDetail.NICKNAME)
- * - PersonaStore.detail_key (PersonaDetail.PHONE_NUMBERS)
- * - PersonaStore.detail_key (PersonaDetail.POSTAL_ADDRESSES)
- * - PersonaStore.detail_key (PersonaDetail.ROLES)
- * - PersonaStore.detail_key (PersonaDetail.STRUCTURED_NAME)
- * - PersonaStore.detail_key (PersonaDetail.LOCAL_IDS)
- * - PersonaStore.detail_key (PersonaDetail.LOCATION)
- * - PersonaStore.detail_key (PersonaDetail.WEB_SERVICE_ADDRESSES)
- * - PersonaStore.detail_key (PersonaDetail.NOTES)
- * - PersonaStore.detail_key (PersonaDetail.URLS)
- * - PersonaStore.detail_key (PersonaDetail.GROUPS)
- *
- * See {@link Folks.PersonaStore.add_persona_from_details}.
- *
- * @throws Folks.PersonaStoreError.STORE_OFFLINE if the store hasn’t been
- * prepared
- * @throws Folks.PersonaStoreError.CREATE_FAILED if creating the persona in
- * the EDS store failed
- *
- * @since 0.6.0
- */
- public override async Folks.Persona? add_persona_from_details (
- HashTable<string, Value?> details) throws Folks.PersonaStoreError
- {
- // We have to have called prepare() beforehand.
- if (!this._is_prepared)
- {
- throw new PersonaStoreError.STORE_OFFLINE (
- "Persona store has not yet been prepared.");
- }
-
- E.Contact contact = new E.Contact ();
-
- var iter = HashTableIter<string, Value?> (details);
- unowned string k;
- unowned Value? _v;
- bool is_fav = false; // Remember this for _set_contact_groups.
- Set<string> groups = new SmallSet<string> (); // For _set_is_favourite
-
- while (iter.next (out k, out _v) == true)
- {
- if (_v == null)
- {
- continue;
- }
- unowned Value v = (!) _v;
-
- if (k == Folks.PersonaStore.detail_key (
- PersonaDetail.FULL_NAME))
- {
- unowned string? full_name = v.get_string ();
- if (full_name != null && (!) full_name == "")
- {
- full_name = null;
- }
-
- contact.set (E.Contact.field_id ("full_name"), full_name);
- }
- else if (k == Folks.PersonaStore.detail_key (
- PersonaDetail.NICKNAME))
- {
- unowned string? nickname = v.get_string ();
- if (nickname != null && (!) nickname == "")
- {
- nickname = null;
- }
-
- contact.set (E.Contact.field_id ("nickname"), nickname);
- }
- else if (k == Folks.PersonaStore.detail_key (
- PersonaDetail.EMAIL_ADDRESSES))
- {
- unowned var email_addresses =
- (Set<EmailFieldDetails>) v.get_object ();
- this._set_contact_attributes_string (contact,
- email_addresses,
- "EMAIL", E.ContactField.EMAIL);
- }
- else if (k == Folks.PersonaStore.detail_key (PersonaDetail.AVATAR))
- {
- try
- {
- unowned var avatar = (LoadableIcon?) v.get_object ();
- yield this._set_contact_avatar (contact, avatar);
- }
- catch (PropertyError e1)
- {
- warning ("Couldn't set avatar on the EContact: %s",
- e1.message);
- }
- }
- else if (k == Folks.PersonaStore.detail_key (
- PersonaDetail.IM_ADDRESSES))
- {
- unowned var im_fds = (MultiMap<string, ImFieldDetails>) v.get_object ();
- this._set_contact_im_fds (contact, im_fds);
- }
- else if (k == Folks.PersonaStore.detail_key (
- PersonaDetail.PHONE_NUMBERS))
- {
- unowned var phone_numbers =
- (Set<PhoneFieldDetails>) v.get_object ();
- this._set_contact_attributes_string (contact,
- phone_numbers, "TEL",
- E.ContactField.TEL);
- }
- else if (k == Folks.PersonaStore.detail_key (
- PersonaDetail.POSTAL_ADDRESSES))
- {
- unowned var postal_fds =
- (Set<PostalAddressFieldDetails>) v.get_object ();
- this._set_contact_postal_addresses (contact, postal_fds);
- }
- else if (k == Folks.PersonaStore.detail_key (
- PersonaDetail.STRUCTURED_NAME))
- {
- unowned var sname = (StructuredName) v.get_object ();
- this._set_contact_name (contact, sname);
- }
- else if (k == Folks.PersonaStore.detail_key (PersonaDetail.LOCAL_IDS))
- {
- unowned var local_ids = (Set<string>) v.get_object ();
- this._set_contact_local_ids (contact, local_ids);
- }
- else if (k == Folks.PersonaStore.detail_key (PersonaDetail.LOCATION))
- {
- unowned var location = (Location?) v.get_object ();
- this._set_contact_location (contact, location);
- }
- else if (k == Folks.PersonaStore.detail_key
- (PersonaDetail.WEB_SERVICE_ADDRESSES))
- {
- unowned var web_service_addresses =
- (HashMultiMap<string, WebServiceFieldDetails>) v.get_object ();
- this._set_contact_web_service_addresses (contact,
- web_service_addresses);
- }
- else if (k == Folks.PersonaStore.detail_key (PersonaDetail.NOTES))
- {
- unowned var notes = (Gee.Set<NoteFieldDetails>) v.get_object ();
- this._set_contact_notes (contact, notes);
- }
- else if (k == Folks.PersonaStore.detail_key (PersonaDetail.GENDER))
- {
- var gender = (Gender) v.get_enum ();
- this._set_contact_gender (contact, gender);
- }
- else if (k == Folks.PersonaStore.detail_key (PersonaDetail.URLS))
- {
- unowned var urls = (Set<UrlFieldDetails>) v.get_object ();
- this._set_contact_urls (contact, urls);
- }
- else if (k == Folks.PersonaStore.detail_key (PersonaDetail.BIRTHDAY))
- {
- unowned var birthday = (DateTime?) v.get_boxed ();
- this._set_contact_birthday (contact, birthday);
- }
- else if (k == Folks.PersonaStore.detail_key (PersonaDetail.ROLES))
- {
- unowned var roles =
- (Set<RoleFieldDetails>) v.get_object ();
- this._set_contact_roles (contact, roles);
- }
- else if (k == Folks.PersonaStore.detail_key (PersonaDetail.GROUPS))
- {
- groups = (Set<string>) v.get_object ();
- this._set_contact_groups (contact, groups, is_fav);
- }
- else if (k == Folks.PersonaStore.detail_key (
- PersonaDetail.IS_FAVOURITE))
- {
- is_fav = v.get_boolean ();
- this._set_contact_is_favourite (contact, is_fav);
- /* Ensure the contact is added to the “Starred in Android” group
- * if appropriate. */
- this._set_contact_groups (contact, groups, is_fav);
- }
- }
-
- /* _addressbook is guaranteed to be non-null before we ensure that
- * prepare() has already been called. */
- var added_uid = yield this._add_contact (contact);
-
- debug ("Created contact with UID: %s", added_uid);
- var iid = Edsf.Persona.build_iid (this.id, added_uid);
- var _persona = this._personas.get (iid);
- assert (_persona != null);
-
- return _persona;
- }
-
- /**
- * Remove a {@link Persona} from the PersonaStore.
- *
- * See {@link Folks.PersonaStore.remove_persona}.
- *
- * @param persona the persona that should be removed
- * @throws Folks.PersonaStoreError.STORE_OFFLINE if the store hasn’t been
- * prepared or has gone offline
- * @throws Folks.PersonaStoreError.PERMISSION_DENIED if the store denied
- * permission to delete the contact
- * @throws Folks.PersonaStoreError.READ_ONLY if the store is read only
- * @throws Folks.PersonaStoreError.REMOVE_FAILED if any other errors happened
- * in the store
- *
- * @since 0.6.0
- */
- public override async void remove_persona (Folks.Persona persona)
- throws Folks.PersonaStoreError
- {
- // We have to have called prepare() beforehand.
- if (!this._is_prepared)
- {
- throw new PersonaStoreError.STORE_OFFLINE (
- "Persona store has not yet been prepared.");
- }
-
- try
- {
- /* _addressbook is guaranteed to be non-null before we ensure that
- * prepare() has already been called. */
- yield ((!) this._addressbook).remove_contact (
- ((Edsf.Persona) persona).contact, E.BookOperationFlags.NONE, null);
- }
- catch (GLib.Error e)
- {
- if (e is BookClientError)
- {
- if (e is BookClientError.CONTACT_NOT_FOUND)
- {
- /* Not an error, since we've got nothing to do! */
- return;
- }
-
- /* We don't expect to receive any of the error codes below: */
- if (e is BookClientError.NO_SUCH_BOOK ||
- e is BookClientError.CONTACT_ID_ALREADY_EXISTS ||
- e is BookClientError.NO_SUCH_SOURCE ||
- e is BookClientError.NO_SPACE)
- {
- /* Fall out */
- }
- }
- else if (e.domain == Client.error_quark ())
- {
- switch ((ClientError) e.code)
- {
- case ClientError.REPOSITORY_OFFLINE:
- throw new PersonaStoreError.STORE_OFFLINE (
- /* Translators: the first parameter is an address book
- * URI and the second is a persona UID. */
- _("Address book ‘%s’ is offline, so contact ‘%s’ cannot be removed."),
- this.id, persona.uid);
- case ClientError.PERMISSION_DENIED:
- throw new PersonaStoreError.PERMISSION_DENIED (
- /* Translators: the first parameter is an address book
- * URI and the second is an error message. */
- _("Permission denied to remove contact ‘%s’: %s"),
- persona.uid, e.message);
- case ClientError.NOT_SUPPORTED:
- throw new PersonaStoreError.READ_ONLY (
- /* Translators: the parameter is an error message. */
- _("Removing contacts isn’t supported by this persona store: %s"),
- e.message);
- case ClientError.AUTHENTICATION_REQUIRED:
- /* TODO: Support authentication. bgo#653339 */
- /* We expect to receive these, but they don't need special
- * error codes: */
- case ClientError.INVALID_ARG:
- case ClientError.BUSY:
- case ClientError.DBUS_ERROR:
- case ClientError.OTHER_ERROR:
- /* Fall through. */
- /* We don't expect to receive any of the error codes below: */
- case ClientError.COULD_NOT_CANCEL:
- case ClientError.AUTHENTICATION_FAILED:
- case ClientError.TLS_NOT_AVAILABLE:
- case ClientError.OFFLINE_UNAVAILABLE:
- case ClientError.UNSUPPORTED_AUTHENTICATION_METHOD:
- case ClientError.SEARCH_SIZE_LIMIT_EXCEEDED:
- case ClientError.SEARCH_TIME_LIMIT_EXCEEDED:
- case ClientError.INVALID_QUERY:
- case ClientError.QUERY_REFUSED:
- default:
- /* Fall out */
- break;
- }
- }
-
- /* Fallback error. */
- throw new PersonaStoreError.REMOVE_FAILED (
- _("Can’t remove contact ‘%s’: %s"), persona.uid, e.message);
- }
- }
-
- /**
- * Prepare the PersonaStore for use.
- *
- * See {@link Folks.PersonaStore.prepare}.
- *
- * @throws Folks.PersonaStoreError.STORE_OFFLINE if the EDS store is offline
- * @throws Folks.PersonaStoreError.PERMISSION_DENIED if permission was denied
- * to open the EDS store
- * @throws Folks.PersonaStoreError.INVALID_ARGUMENT if any other error
- * occurred in the EDS store
- *
- * @since 0.6.0
- */
- public override async void prepare () throws PersonaStoreError
- {
- var profiling = Internal.profiling_start ("preparing Edsf.PersonaStore (ID: %s)",
- this.id);
-
- if (this._is_prepared == true || this._prepare_pending == true)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
-
- try
- {
- /* Listen for removal signals for the address book. There's no
- * need to check if we still exist in the list, as
- * addressbook.open() will fail if we don't. */
- if (this._source_registry == null)
- {
- this._source_registry = yield create_source_registry ();
- }
-
- /* We know _source_registry != null because otherwise
- * create_source_registry() would've thrown an error. */
- ((!) this._source_registry).source_removed.connect (
- this._source_registry_changed_cb);
- ((!) this._source_registry).source_disabled.connect (
- this._source_registry_changed_cb);
-
- /* Connect and open the address book */
- this._addressbook = yield E.BookClient.connect (this.source, 1, null);
-
- ((!) this._addressbook).notify["readonly"].connect (
- this._address_book_notify_read_only_cb);
-
- debug ("Successfully finished opening address book %p for " +
- "persona store ‘%s’ (%p).", this._addressbook, this.id, this);
-
- Internal.profiling_point ("opened address book in " +
- "Edsf.PersonaStore (ID: %s)", this.id);
-
- this._notify_if_default ();
- this._update_trust_level ();
- }
- catch (GLib.Error e1)
- {
- /* Remove the persona store on error */
- this.removed ();
-
- if (e1 is BookClientError)
- {
- /* We don't expect to receive any of the error codes
- * below: */
- if (e1 is BookClientError.NO_SUCH_BOOK ||
- e1 is BookClientError.NO_SUCH_SOURCE ||
- e1 is BookClientError.CONTACT_NOT_FOUND ||
- e1 is BookClientError.CONTACT_ID_ALREADY_EXISTS ||
- e1 is BookClientError.NO_SPACE)
- {
- /* Fall out */
- }
- }
- else if (e1.domain == Client.error_quark ())
- {
- switch ((ClientError) e1.code)
- {
- case ClientError.REPOSITORY_OFFLINE:
- throw new PersonaStoreError.STORE_OFFLINE (
- /* Translators: the parameter is an address book
- * URI. */
- _("Address book ‘%s’ is offline."), this.id);
- case ClientError.PERMISSION_DENIED:
- throw new PersonaStoreError.PERMISSION_DENIED (
- /* Translators: the first parameter is an address
- * book URI and the second is an error message. */
- _("Permission denied to open address book ‘%s’: %s"),
- this.id, e1.message);
- case ClientError.AUTHENTICATION_REQUIRED:
- /* TODO: Support authentication. bgo#653339 */
- /* We expect to receive these, but they don't need special
- * error codes: */
- case ClientError.NOT_SUPPORTED:
- case ClientError.INVALID_ARG:
- case ClientError.BUSY:
- case ClientError.DBUS_ERROR:
- case ClientError.OTHER_ERROR:
- /* Fall through. */
- /* We don't expect to receive any of the error codes
- * below: */
- case ClientError.COULD_NOT_CANCEL:
- case ClientError.AUTHENTICATION_FAILED:
- case ClientError.TLS_NOT_AVAILABLE:
- case ClientError.OFFLINE_UNAVAILABLE:
- case ClientError.UNSUPPORTED_AUTHENTICATION_METHOD:
- case ClientError.SEARCH_SIZE_LIMIT_EXCEEDED:
- case ClientError.SEARCH_TIME_LIMIT_EXCEEDED:
- case ClientError.INVALID_QUERY:
- case ClientError.QUERY_REFUSED:
- default:
- /* Fall out */
- break;
- }
- }
-
- /* Fallback error */
- throw new PersonaStoreError.INVALID_ARGUMENT (
- /* Translators: the first parameter is an address book URI
- * and the second is an error message. */
- _("Couldn’t open address book ‘%s’: %s"), this.id, e1.message);
- }
-
- /* Determine which fields the address book supports. This is necessary
- * to work out which writeable properties we can support.
- *
- * Note: We assume this is constant over the lifetime of the address
- * book. This seems reasonable. */
- try
- {
- string? supported_fields = null;
- yield ((!) this._addressbook).get_backend_property (
- "supported-fields", null, out supported_fields);
-
- Internal.profiling_point ("got supported fields in " +
- "Edsf.PersonaStore (ID: %s)", this.id);
-
- var prop_set = new SmallSet<string> ();
-
- /* We get a comma-separated list of fields back. */
- if (supported_fields != null)
- {
- string[] fields = ((!) supported_fields).split (",");
-
- /* We always support local-ids, web-service-addresses, gender,
- * anti-links and favourite because we use custom vCard
- * attributes for them. */
- prop_set.add ((!) Folks.PersonaStore.detail_key (
- PersonaDetail.LOCAL_IDS));
- prop_set.add ((!) Folks.PersonaStore.detail_key (
- PersonaDetail.WEB_SERVICE_ADDRESSES));
- prop_set.add ((!) Folks.PersonaStore.detail_key (
- PersonaDetail.GENDER));
- prop_set.add ((!) Folks.PersonaStore.detail_key (
- PersonaDetail.IS_FAVOURITE));
- prop_set.add ((!) Folks.PersonaStore.detail_key (
- PersonaDetail.ANTI_LINKS));
- prop_set.add ((!) Folks.PersonaStore.detail_key (
- PersonaDetail.EXTENDED_INFO));
-
- foreach (unowned string field in fields)
- {
- var prop = Folks.PersonaStore.detail_key (
- this._eds_field_name_to_folks_persona_detail (field));
-
- if (prop != null)
- {
- prop_set.add ((!) (owned) prop);
- }
- }
- }
-
- /* Convert the property set to an array. We can't use .to_array()
- * here because it fails to null-terminate the array. Sigh. */
- this._always_writeable_properties = new string[prop_set.size];
- uint i = 0;
- foreach (var final_prop in prop_set)
- {
- this._always_writeable_properties[i++] = final_prop;
- }
- }
- catch (GLib.Error e2)
- {
- /* Remove the persona store on error */
- this.removed ();
-
- throw new PersonaStoreError.INVALID_ARGUMENT (
- /* Translators: the parameteter is an error message. */
- _("Couldn’t get address book capabilities: %s"), e2.message);
- }
-
- /* Get the set of capabilities supported by the address book.
- * Specifically, we're looking for do-initial-query, which signifies
- * that we should expect an initial _contacts_added_cb() callback. */
- var do_initial_query = false;
- try
- {
- string? capabilities = null;
- yield ((!) this._addressbook).get_backend_property (
- "capabilities", null, out capabilities);
-
- Internal.profiling_point ("got capabilities in " +
- "Edsf.PersonaStore (ID: %s)", this.id);
-
- if (capabilities != null)
- {
- string[] caps = ((!) capabilities).split (",");
-
- do_initial_query = ("do-initial-query" in caps);
- }
- }
- catch (GLib.Error e4)
- {
- /* Remove the persona store on error */
- this.removed ();
-
- throw new PersonaStoreError.INVALID_ARGUMENT (
- /* Translators: the parameteter is an error message. */
- _("Couldn’t get address book capabilities: %s"), e4.message);
- }
-
- bool got_view = false;
- try
- {
- got_view = yield ((!) this._addressbook).get_view (
- this._query_str, null, out this._ebookview);
-
- Internal.profiling_point ("opened book view in " +
- "Edsf.PersonaStore (ID: %s)", this.id);
-
- if (got_view == false)
- {
- throw new PersonaStoreError.INVALID_ARGUMENT (
- /* Translators: the parameter is an address book URI. */
- _("Couldn’t get view for address book ‘%s’."),
- this.id);
- }
-
- ((!) this._ebookview).objects_added.connect (
- this._contacts_added_cb);
- ((!) this._ebookview).objects_removed.connect (
- this._contacts_removed_cb);
- ((!) this._ebookview).objects_modified.connect (
- this._contacts_changed_cb);
- ((!) this._ebookview).complete.connect (
- this._contacts_complete_cb);
-
- ((!) this._ebookview).start ();
- }
- catch (GLib.Error e3)
- {
- /* Remove the persona store on error */
- this.removed ();
-
- if (e3 is BookClientError)
- {
- /* We don't expect to receive any of the error codes
- * below: */
- if (e3 is BookClientError.NO_SUCH_BOOK ||
- e3 is BookClientError.NO_SUCH_SOURCE ||
- e3 is BookClientError.CONTACT_NOT_FOUND ||
- e3 is BookClientError.CONTACT_ID_ALREADY_EXISTS ||
- e3 is BookClientError.NO_SPACE)
- {
- /* Fall out */
- }
- }
- else if (e3.domain == Client.error_quark ())
- {
- switch ((ClientError) e3.code)
- {
- case ClientError.REPOSITORY_OFFLINE:
- throw new PersonaStoreError.STORE_OFFLINE (
- /* Translators: the parameter is an address book
- * URI. */
- _("Address book ‘%s’ is offline."), this.id);
- case ClientError.PERMISSION_DENIED:
- throw new PersonaStoreError.PERMISSION_DENIED (
- /* Translators: the first parameter is an address
- * book URI and the second is an error message. */
- _("Permission denied to open address book ‘%s’: %s"),
- this.id, e3.message);
- case ClientError.AUTHENTICATION_REQUIRED:
- /* TODO: Support authentication. bgo#653339 */
- /* We expect to receive these, but they don't need special
- * error codes: */
- case ClientError.NOT_SUPPORTED:
- case ClientError.INVALID_ARG:
- case ClientError.BUSY:
- case ClientError.DBUS_ERROR:
- case ClientError.OTHER_ERROR:
- case ClientError.SEARCH_SIZE_LIMIT_EXCEEDED:
- case ClientError.SEARCH_TIME_LIMIT_EXCEEDED:
- case ClientError.QUERY_REFUSED:
- /* Fall through. */
- /* We don't expect to receive any of the error codes
- * below: */
- case ClientError.COULD_NOT_CANCEL:
- case ClientError.AUTHENTICATION_FAILED:
- case ClientError.TLS_NOT_AVAILABLE:
- case ClientError.OFFLINE_UNAVAILABLE:
- case ClientError.UNSUPPORTED_AUTHENTICATION_METHOD:
- case ClientError.INVALID_QUERY:
- default:
- /* Fall out */
- break;
- }
- }
-
- /* Fallback error */
- throw new PersonaStoreError.INVALID_ARGUMENT (
- /* Translators: the first parameter is an address book URI
- * and the second is an error message. */
- _("Couldn’t get view for address book ‘%s’: %s"),
- this.id, e3.message);
- }
-
- this._is_prepared = true;
- this.notify_property ("is-prepared");
-
- /* If the address book isn't going to do an initial query (i.e.
- * because it's a search-only address book, such as LDAP), we reach
- * a quiescent state immediately. */
- if (do_initial_query == false && this._is_quiescent == false)
- {
- assert (this._pending_personas == null); /* no initial query */
- this._is_quiescent = true;
- this.notify_property ("is-quiescent");
- }
- }
- finally
- {
- this._prepare_pending = false;
- }
-
- Internal.profiling_end ((owned) profiling);
- }
-
- private PersonaDetail _eds_field_name_to_folks_persona_detail (
- string eds_field_name)
- {
- var eds_field_id = Contact.field_id (eds_field_name);
-
- switch (eds_field_id)
- {
- case ContactField.FULL_NAME:
- return PersonaDetail.FULL_NAME;
- case ContactField.GIVEN_NAME:
- case ContactField.FAMILY_NAME:
- case ContactField.NAME:
- return PersonaDetail.STRUCTURED_NAME;
- case ContactField.GEO:
- return PersonaDetail.LOCATION;
- case ContactField.NICKNAME:
- return PersonaDetail.NICKNAME;
- case ContactField.EMAIL_1:
- case ContactField.EMAIL_2:
- case ContactField.EMAIL_3:
- case ContactField.EMAIL_4:
- case ContactField.EMAIL:
- return PersonaDetail.EMAIL_ADDRESSES;
- case ContactField.ADDRESS_LABEL_HOME:
- case ContactField.ADDRESS_LABEL_WORK:
- case ContactField.ADDRESS_LABEL_OTHER:
- case ContactField.ADDRESS:
- case ContactField.ADDRESS_HOME:
- case ContactField.ADDRESS_WORK:
- case ContactField.ADDRESS_OTHER:
- return PersonaDetail.POSTAL_ADDRESSES;
- case ContactField.PHONE_ASSISTANT:
- case ContactField.PHONE_BUSINESS:
- case ContactField.PHONE_BUSINESS_2:
- case ContactField.PHONE_BUSINESS_FAX:
- case ContactField.PHONE_CALLBACK:
- case ContactField.PHONE_CAR:
- case ContactField.PHONE_COMPANY:
- case ContactField.PHONE_HOME:
- case ContactField.PHONE_HOME_2:
- case ContactField.PHONE_HOME_FAX:
- case ContactField.PHONE_ISDN:
- case ContactField.PHONE_MOBILE:
- case ContactField.PHONE_OTHER:
- case ContactField.PHONE_OTHER_FAX:
- case ContactField.PHONE_PAGER:
- case ContactField.PHONE_PRIMARY:
- case ContactField.PHONE_RADIO:
- case ContactField.PHONE_TELEX:
- case ContactField.PHONE_TTYTDD:
- case ContactField.TEL:
- case ContactField.SIP:
- return PersonaDetail.PHONE_NUMBERS;
- case ContactField.ORG:
- case ContactField.ORG_UNIT:
- case ContactField.OFFICE:
- case ContactField.TITLE:
- case ContactField.ROLE:
- case ContactField.MANAGER:
- case ContactField.ASSISTANT:
- return PersonaDetail.ROLES;
- case ContactField.HOMEPAGE_URL:
- case ContactField.BLOG_URL:
- case ContactField.FREEBUSY_URL:
- case ContactField.VIDEO_URL:
- return PersonaDetail.URLS;
- case ContactField.CATEGORIES:
- case ContactField.CATEGORY_LIST:
- return PersonaDetail.GROUPS;
- case ContactField.NOTE:
- return PersonaDetail.NOTES;
- case ContactField.IM_AIM_HOME_1:
- case ContactField.IM_AIM_HOME_2:
- case ContactField.IM_AIM_HOME_3:
- case ContactField.IM_AIM_WORK_1:
- case ContactField.IM_AIM_WORK_2:
- case ContactField.IM_AIM_WORK_3:
- case ContactField.IM_GROUPWISE_HOME_1:
- case ContactField.IM_GROUPWISE_HOME_2:
- case ContactField.IM_GROUPWISE_HOME_3:
- case ContactField.IM_GROUPWISE_WORK_1:
- case ContactField.IM_GROUPWISE_WORK_2:
- case ContactField.IM_GROUPWISE_WORK_3:
- case ContactField.IM_JABBER_HOME_1:
- case ContactField.IM_JABBER_HOME_2:
- case ContactField.IM_JABBER_HOME_3:
- case ContactField.IM_JABBER_WORK_1:
- case ContactField.IM_JABBER_WORK_2:
- case ContactField.IM_JABBER_WORK_3:
- case ContactField.IM_YAHOO_HOME_1:
- case ContactField.IM_YAHOO_HOME_2:
- case ContactField.IM_YAHOO_HOME_3:
- case ContactField.IM_YAHOO_WORK_1:
- case ContactField.IM_YAHOO_WORK_2:
- case ContactField.IM_YAHOO_WORK_3:
- case ContactField.IM_MSN_HOME_1:
- case ContactField.IM_MSN_HOME_2:
- case ContactField.IM_MSN_HOME_3:
- case ContactField.IM_MSN_WORK_1:
- case ContactField.IM_MSN_WORK_2:
- case ContactField.IM_MSN_WORK_3:
- case ContactField.IM_ICQ_HOME_1:
- case ContactField.IM_ICQ_HOME_2:
- case ContactField.IM_ICQ_HOME_3:
- case ContactField.IM_ICQ_WORK_1:
- case ContactField.IM_ICQ_WORK_2:
- case ContactField.IM_ICQ_WORK_3:
- case ContactField.IM_AIM:
- case ContactField.IM_GROUPWISE:
- case ContactField.IM_JABBER:
- case ContactField.IM_YAHOO:
- case ContactField.IM_MSN:
- case ContactField.IM_ICQ:
- case ContactField.IM_GADUGADU_HOME_1:
- case ContactField.IM_GADUGADU_HOME_2:
- case ContactField.IM_GADUGADU_HOME_3:
- case ContactField.IM_GADUGADU_WORK_1:
- case ContactField.IM_GADUGADU_WORK_2:
- case ContactField.IM_GADUGADU_WORK_3:
- case ContactField.IM_GADUGADU:
- case ContactField.IM_SKYPE_HOME_1:
- case ContactField.IM_SKYPE_HOME_2:
- case ContactField.IM_SKYPE_HOME_3:
- case ContactField.IM_SKYPE_WORK_1:
- case ContactField.IM_SKYPE_WORK_2:
- case ContactField.IM_SKYPE_WORK_3:
- case ContactField.IM_SKYPE:
- case ContactField.IM_GOOGLE_TALK_HOME_1:
- case ContactField.IM_GOOGLE_TALK_HOME_2:
- case ContactField.IM_GOOGLE_TALK_HOME_3:
- case ContactField.IM_GOOGLE_TALK_WORK_1:
- case ContactField.IM_GOOGLE_TALK_WORK_2:
- case ContactField.IM_GOOGLE_TALK_WORK_3:
- case ContactField.IM_GOOGLE_TALK:
- return PersonaDetail.IM_ADDRESSES;
- case ContactField.PHOTO:
- return PersonaDetail.AVATAR;
- case ContactField.BIRTH_DATE:
- return PersonaDetail.BIRTHDAY;
- /* Irrelevant */
- case ContactField.UID: /* identifier */
- case ContactField.REV: /* revision date */
- case ContactField.BOOK_UID: /* parent identifier */
- case ContactField.NAME_OR_ORG: /* FULL_NAME or ORG; both handled */
- return PersonaDetail.INVALID;
- /* Unsupported */
- case ContactField.FILE_AS:
- case ContactField.MAILER:
- case ContactField.CALENDAR_URI:
- case ContactField.ICS_CALENDAR:
- case ContactField.SPOUSE:
- case ContactField.LOGO:
- case ContactField.WANTS_HTML:
- case ContactField.IS_LIST:
- case ContactField.LIST_SHOW_ADDRESSES:
- case ContactField.ANNIVERSARY:
- case ContactField.X509_CERT:
- default:
- debug ("Unsupported/Unknown EDS field name '%s'.", eds_field_name);
- return PersonaDetail.INVALID;
- }
- }
-
- /* Add a contact to the address book. It guarantees to only return once the
- * contact addition has been notified. It returns the new contact's UID on
- * success. */
- private async string _add_contact (E.Contact contact) throws PersonaStoreError
- {
- /* We require _addressbook to be non-null. This should be the case
- * because we're only called after _prepare(). */
- assert (this._addressbook != null);
-
- var debug_obj = Debug.dup ();
- if (debug_obj.debug_output_enabled == true)
- {
- debug ("Adding new contact (UID: %s) to address book.", contact.id);
- debug ("New vCard: %s", contact.to_string (E.VCardFormat.@30));
- }
-
- ulong signal_id = 0;
- uint timeout_id = 0;
-
- /* Track the @added_uid, which is returned by the add_contact() call, and
- * the @added_uids, which are those emitted in the objects-added signal.
- * The signal emission and add_contact() return for this @contact could
- * occur in any order (signal emission before add_contact() returns, or
- * after), so the overall contact addition operation is only complete once
- * @added_uid is present in @added_uids. At this point, we are guaranteed
- * to have an entry keyed with @added_uid in this._personas, as
- * implemented by contacts_added_idle(). */
- string added_uid = "";
- var added_uids = new SmallSet<string> ();
-
- try
- {
- var received_notification = false;
- var has_yielded = false;
-
- signal_id = ((!) this._ebookview).objects_added.connect (
- (_contacts) =>
- {
-#if HAS_EDS_3_41
- GLib.SList<E.Contact> contacts = _contacts.copy_deep ((GLib.CopyFunc<E.Contact>) GLib.Object.ref);
-#else
- GLib.SList<E.Contact> contacts = ((GLib.SList<E.Contact>) _contacts).copy_deep ((GLib.CopyFunc<E.Contact>) GLib.Object.ref);
-#endif
-
- /* All handlers for objects-added have to be pushed through the
- * idle queue so they remain in order with respect to each other
- * and implement in-order modifications to this._personas. */
- foreach (unowned E.Contact c in contacts)
- {
- this._idle_queue (() =>
- {
- debug ("Received new contact %s from EDS.", c.id);
- added_uids.add (c.id);
- received_notification = (added_uid in added_uids);
-
- /* Success! Return to _add_contact(). */
- if (received_notification)
- {
- if (has_yielded == true)
- {
- has_yielded = false;
- this._add_contact.callback ();
- }
- }
-
- return false;
- });
-
- return;
- }
- });
-
- /* Commit the new contact. _addressbook is asserted as being non-null
- * above. */
- yield ((!) this._addressbook).add_contact (contact, E.BookOperationFlags.NONE, null,
- out added_uid);
-
- debug ("Finished sending new contact to EDS; received UID %s.",
- added_uid);
-
- timeout_id = Timeout.add_seconds (PersonaStore._new_contact_timeout,
- () =>
- {
- /* Failure! Return to _add_contact() without setting
- * received_notification. */
- if (has_yielded == true)
- {
- has_yielded = false;
- this._add_contact.callback ();
- }
-
- return false;
- }, Priority.LOW);
-
- /* Wait until we get a notification that the contact's been added. We
- * basically hold off on completing the GAsyncResult until the
- * objects-added signal handler (above). We only do this if we haven't
- * already received an objects-added signal. We don't need locking
- * around these variables because they can only be modified from the
- * main loop. */
- received_notification = (added_uid in added_uids);
-
- if (received_notification == false)
- {
- debug ("Yielding.");
- has_yielded = true;
- yield;
- }
-
- debug ("Finished: received_notification = %s, has_yielded = %s",
- received_notification ? "yes" : "no",
- has_yielded ? "yes" : "no");
-
- /* If we hit the timeout instead of the property notification, throw
- * an error. */
- if (received_notification == false)
- {
- throw new PropertyError.UNKNOWN_ERROR (
- _("Creating a new contact failed due to reaching the timeout."));
- }
-
- assert (added_uid != null && added_uid != "");
- return added_uid;
- }
- catch (GLib.Error e)
- {
- throw this.e_client_error_to_persona_store_error (e);
- }
- finally
- {
- /* Remove the callbacks. */
- if (signal_id != 0)
- {
- ((!) this._ebookview).disconnect (signal_id);
- }
-
- if (timeout_id != 0)
- {
- GLib.Source.remove (timeout_id);
- }
- }
- }
-
- /* Commit modified properties to the address book. This assumes you've already
- * modified the persona's contact appropriately. It guarantees to only return
- * once the modified property has been notified.
- *
- * If @property_name is null, this yields until the persona as a whole is
- * updated by EDS. This is intended _only_ for changes which are not tied to
- * a specific Edsf.Persona property, such as change_extended_field(). */
- private async void _commit_modified_property (Edsf.Persona persona,
- string? property_name) throws PropertyError
- {
- /* We require _addressbook to be non-null. This should be the case
- * because we're only called from property setters, and they check whether
- * the properties are writeable first. Properties shouldn't be writeable
- * if _addressbook is null. */
- assert (this._addressbook != null);
-
- var debug_obj = Debug.dup ();
- if (debug_obj.debug_output_enabled == true)
- {
- debug ("Committing modified property ‘%s’ to persona %p (UID: %s).",
- property_name, persona, persona.uid);
-
- debug ("Modified vCard: %s",
- persona.contact.to_string (E.VCardFormat.@30));
- }
-
- var contact = persona.contact;
-
- ulong signal_id = 0;
- uint timeout_id = 0;
-
- try
- {
- var received_notification = false;
- var has_yielded = false;
- unowned var signal_name = property_name ?? "contact";
-
- signal_id = persona.notify[signal_name].connect ((obj, pspec) =>
- {
- /* Success! Return to _commit_modified_property(). */
- received_notification = true;
-
- if (has_yielded == true)
- {
- this._commit_modified_property.callback ();
- }
- });
-
- /* Commit the modification. _addressbook is asserted as being non-null
- * above. */
- yield ((!) this._addressbook).modify_contact (contact, E.BookOperationFlags.NONE, null);
-
- timeout_id = Timeout.add_seconds (PersonaStore._property_change_timeout, () =>
- {
- /* Failure! Return to _commit_modified_property() without setting
- * received_notification. */
- if (has_yielded == true)
- {
- this._commit_modified_property.callback ();
- }
-
- return false;
- }, Priority.LOW);
-
- /* Wait until we get a notification that the property's changed. We
- * basically hold off on completing the GAsyncResult until the
- * signal handler for notification of the property change (above).
- * We only do this if we haven't already received a property change
- * notification. We don't need locking around these variables because
- * they can only be modified from the main loop. */
- if (received_notification == false)
- {
- debug ("Yielding.");
- has_yielded = true;
- yield;
- }
-
- debug ("Finished: received_notification = %s, has_yielded = %s",
- received_notification ? "yes" : "no",
- has_yielded ? "yes" : "no");
-
- /* If we hit the timeout instead of the property notification, throw
- * an error. */
- if (received_notification == false)
- {
- throw new PropertyError.UNKNOWN_ERROR (
- /* Translators: the parameter is the name of a property on a
- * contact, formatted in the normal GObject style (e.g.
- * lowercase with hyphens to separate words). */
- _("Changing the ‘%s’ property failed due to reaching the timeout."),
- property_name);
- }
- }
- catch (GLib.Error e)
- {
- throw this.e_client_error_to_property_error (property_name, e);
- }
- finally
- {
- /* Remove the callbacks. */
- if (signal_id != 0)
- {
- persona.disconnect (signal_id);
- }
-
- if (timeout_id != 0)
- {
- GLib.Source.remove (timeout_id);
- }
- }
- }
-
- private void _remove_attribute (E.Contact contact, string attr_name)
- {
- contact.remove_attributes (null, attr_name);
- }
-
- internal async void _set_avatar (Edsf.Persona persona, LoadableIcon? avatar)
- throws PropertyError
- {
- if (!("avatar" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("Avatar is not writeable on this contact."));
- }
-
- /* Return early if there will be no change */
- if ((persona.avatar == null && avatar == null) ||
- (persona.avatar != null && ((!) persona.avatar).equal (avatar)))
- {
- return;
- }
-
- yield this._set_contact_avatar (persona.contact, avatar);
- yield this._commit_modified_property (persona, "avatar");
- }
-
- internal async void _set_web_service_addresses (Edsf.Persona persona,
- MultiMap<string, WebServiceFieldDetails> web_service_addresses)
- throws PropertyError
- {
- if (!("web-service-addresses" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("Web service addresses are not writeable on this contact."));
- }
-
- if (Utils.multi_map_str_afd_equal (persona.web_service_addresses,
- web_service_addresses))
- return;
-
- this._set_contact_web_service_addresses (persona.contact,
- web_service_addresses);
- yield this._commit_modified_property (persona, "web-service-addresses");
- }
-
- private void _set_contact_web_service_addresses (E.Contact contact,
- MultiMap<string, WebServiceFieldDetails> web_service_addresses)
- {
- this._remove_attribute (contact, "X-FOLKS-WEB-SERVICES-IDS");
-
- var attr_n = new VCardAttribute (null, "X-FOLKS-WEB-SERVICES-IDS");
- foreach (var service in web_service_addresses.get_keys ())
- {
- var param = new E.VCardAttributeParam (service);
- foreach (var ws_fd in web_service_addresses.get (service))
- {
- param.add_value (ws_fd.value);
- }
- attr_n.add_param (param);
- }
- contact.add_attribute ((owned) attr_n);
- }
-
- internal async void _set_urls (Edsf.Persona persona,
- Set<UrlFieldDetails> urls) throws PropertyError
- {
- if (!("urls" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("URLs are not writeable on this contact."));
- }
-
- if (Utils.set_afd_equal (persona.urls, urls))
- return;
-
- this._set_contact_urls (persona.contact, urls);
- yield this._commit_modified_property (persona, "urls");
- }
-
- private void _set_contact_urls (E.Contact contact, Set<UrlFieldDetails> urls)
- {
- var vcard = (E.VCard) contact;
- vcard.remove_attributes (null, "X-URIS");
- contact.set (ContactField.HOMEPAGE_URL, null);
- contact.set (ContactField.VIDEO_URL, null);
- contact.set (ContactField.BLOG_URL, null);
- contact.set (ContactField.FREEBUSY_URL, null);
-
- foreach (var u in urls)
- {
- /* A way to escape from the inner loop, since Vala doesn't have
- * "continue 3". */
- var set_attr_already = false;
-
- var attr = new E.VCardAttribute (null, "X-URIS");
- attr.add_value (u.value);
- foreach (var param_name in u.parameters.get_keys ())
- {
- var param = new E.VCardAttributeParam (param_name.up ());
- foreach (var param_val in u.parameters.get (param_name))
- {
- if (param_name == AbstractFieldDetails.PARAM_TYPE)
- {
- /* Handle TYPEs which need mapping to custom vCard attrs
- * for EDS. */
- foreach (unowned Edsf.Persona.UrlTypeMapping mapping
- in Edsf.Persona._url_properties)
- {
- if (param_val.down () == mapping.folks_type)
- {
- contact.set (
- E.Contact.field_id (mapping.vcard_field_name),
- u.value);
-
- set_attr_already = true;
- break;
- }
- }
- }
-
- if (set_attr_already == true)
- {
- break;
- }
-
- param.add_value (param_val);
- }
-
- if (set_attr_already == true)
- {
- break;
- }
-
- attr.add_param (param);
- }
-
- if (set_attr_already == true)
- {
- continue;
- }
-
- contact.add_attribute ((owned) attr);
- }
- }
-
- internal async void _set_local_ids (Edsf.Persona persona,
- Set<string> local_ids) throws PropertyError
- {
- if (!("local-ids" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("Local IDs are not writeable on this contact."));
- }
-
- if (Folks.Internal.equal_sets<string> (local_ids, persona.local_ids))
- return;
-
- this._set_contact_local_ids (persona.contact, local_ids);
- yield this._commit_modified_property (persona, "local-ids");
- }
-
- private void _set_contact_local_ids (E.Contact contact, Set<string> local_ids)
- {
- this._remove_attribute (contact, "X-FOLKS-CONTACTS-IDS");
-
- var new_attr = new VCardAttribute (null, "X-FOLKS-CONTACTS-IDS");
- foreach (var local_id in local_ids)
- {
- new_attr.add_value (local_id);
- }
-
- contact.add_attribute ((owned) new_attr);
- }
-
- internal async void _set_is_favourite (Edsf.Persona persona,
- bool is_favourite) throws PropertyError
- {
- if (!("is-favourite" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("The contact cannot be marked as favourite."));
- }
-
- if (is_favourite == persona.is_favourite)
- return;
-
- this._set_contact_is_favourite (persona.contact, is_favourite);
- /* If this is a Google Contacts address book, change the user's membership
- * of the “Starred in Android” group accordingly. See: bgo#661490. */
- this._set_contact_groups (persona.contact, persona.groups, is_favourite);
- yield this._commit_modified_property (persona, "is-favourite");
- }
-
- private void _set_contact_is_favourite (E.Contact contact, bool is_favourite)
- {
- this._remove_attribute (contact, "X-FOLKS-FAVOURITE");
-
- if (is_favourite)
- {
- var new_attr = new VCardAttribute (null, "X-FOLKS-FAVOURITE");
- new_attr.add_value ("true");
- contact.add_attribute ((owned) new_attr);
- }
- }
-
- private async void _set_contact_avatar (E.Contact contact,
- LoadableIcon? avatar) throws PropertyError
- {
- if (avatar == null)
- {
- this._remove_attribute (contact, "PHOTO");
- }
- else
- {
- try
- {
- /* Set the avatar on the contact */
- var cp = new ContactPhoto ();
- cp.type = ContactPhotoType.INLINED;
- var input_s = yield ((!) avatar).load_async (-1, null, null);
-
- uint8[] image_data = new uint8[0];
- uint8[] buffer = new uint8[4096];
- while (true)
- {
- var size_read = yield input_s.read_async (buffer);
- if (size_read <= 0)
- {
- break;
- }
- var read_cur = image_data.length;
- image_data.resize (read_cur + (int)size_read);
- Memory.copy (&image_data[read_cur], buffer, size_read);
- }
-
- cp.set_inlined (image_data);
-
- bool uncertain = false;
- var mime_type = ContentType.guess (null, image_data,
- out uncertain);
- if (!uncertain)
- {
- cp.set_mime_type (mime_type);
- }
-
- contact.set (ContactField.PHOTO, cp);
- }
- catch (GLib.Error e1)
- {
- /* Loading/Reading the avatar failed. */
- throw new PropertyError.INVALID_VALUE (
- /* Translators: the parameter is an error message. */
- _("Can’t update avatar: %s"), e1.message);
- }
- }
- }
-
- internal async void _set_emails (Edsf.Persona persona,
- Set<EmailFieldDetails> emails) throws PropertyError
- {
- if (!("email-addresses" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("E-mail addresses are not writeable on this contact."));
- }
-
- if (Folks.Internal.equal_sets<EmailFieldDetails> (emails,
- persona.email_addresses))
- return;
-
- this._set_contact_attributes_string (persona.contact, emails,
- "EMAIL", E.ContactField.EMAIL);
- yield this._commit_modified_property (persona, "email-addresses");
- }
-
- internal ExtendedFieldDetails? _get_extended_field (Edsf.Persona persona,
- string name)
- {
- unowned VCardAttribute? attr = persona.contact.get_attribute (name);
- if (attr == null)
- {
- return null;
- }
-
- unowned var vals = attr.get_values ();
- unowned string? val = (vals != null)? vals.data : null;
- var details = new ExtendedFieldDetails (val, null);
-
- foreach (unowned E.VCardAttributeParam param in attr.get_params ())
- {
- unowned string param_name = param.get_name ();
- foreach (unowned string param_value in param.get_values ())
- {
- details.add_parameter (param_name, param_value);
- }
- }
-
- return details;
- }
-
- internal async void _change_extended_field (Edsf.Persona persona,
- string name, ExtendedFieldDetails details) throws PropertyError
- {
- var vcard = (E.VCard) persona.contact;
- unowned E.VCardAttribute? prev_attr = vcard.get_attribute (name);
-
- if (prev_attr != null)
- vcard.remove_attribute (prev_attr);
-
- E.VCardAttribute new_attr = new E.VCardAttribute (null, name);
- new_attr.add_value (details.value);
-
- vcard.add_attribute ((owned) new_attr);
-
- yield this._commit_modified_property (persona, null);
- }
-
- internal async void _remove_extended_field (Edsf.Persona persona,
- string name) throws PropertyError
- {
- persona.contact.remove_attributes ("", name);
- yield this._commit_modified_property (persona, null);
- }
-
- internal async void _set_phones (Edsf.Persona persona,
- Set<PhoneFieldDetails> phones) throws PropertyError
- {
- if (!("phone-numbers" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("Phone numbers are not writeable on this contact."));
- }
-
- if (Utils.set_string_afd_equal (phones,
- persona.phone_numbers))
- return;
-
- this._set_contact_attributes_string (persona.contact, phones, "TEL",
- E.ContactField.TEL);
- yield this._commit_modified_property (persona, "phone-numbers");
- }
-
- internal async void _set_postal_addresses (Edsf.Persona persona,
- Set<PostalAddressFieldDetails> postal_fds) throws PropertyError
- {
- if (!("postal-addresses" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("Postal addresses are not writeable on this contact."));
- }
-
- if (Folks.Internal.equal_sets<PostalAddressFieldDetails> (postal_fds,
- persona.postal_addresses))
- return;
-
- this._set_contact_postal_addresses (persona.contact, postal_fds);
- yield this._commit_modified_property (persona, "postal-addresses");
- }
-
- private void _set_contact_postal_addresses (E.Contact contact,
- Set<PostalAddressFieldDetails> postal_fds)
- {
- this._set_contact_attributes<PostalAddress> (contact,
- postal_fds,
- (attr, address) => {
- attr.add_value (address.po_box);
- attr.add_value (address.extension);
- attr.add_value (address.street);
- attr.add_value (address.locality);
- attr.add_value (address.region);
- attr.add_value (address.postal_code);
- attr.add_value (address.country);
- },
- "ADR", E.ContactField.ADDRESS);
- }
-
- delegate void FieldToAttribute<T> (E.VCardAttribute attr, T value);
-
- private void _set_contact_attributes<T> (E.Contact contact,
- Set<AbstractFieldDetails<T>> new_attributes,
- FieldToAttribute<T> fill_attribute,
- string attrib_name, E.ContactField field_id)
- {
- var attributes = new GLib.List <E.VCardAttribute>();
-
- foreach (var e in new_attributes)
- {
- var attr = new E.VCardAttribute (null, attrib_name);
- fill_attribute (attr, e.value);
- foreach (var param_name in e.parameters.get_keys ())
- {
- var param = new E.VCardAttributeParam (param_name.up ());
- foreach (var param_val in e.parameters.get (param_name))
- {
- param.add_value (param_val);
- }
- attr.add_param (param);
- }
- attributes.prepend ((owned) attr);
- }
-
- contact.set_attributes (field_id, attributes);
- }
-
- private void _set_contact_attributes_string (E.Contact contact,
- Set<AbstractFieldDetails<string>> new_attributes,
- string attrib_name, E.ContactField field_id)
- {
- this._set_contact_attributes<string> (contact, new_attributes,
- (attr, value) => { attr.add_value (value); },
- attrib_name, field_id);
- }
-
- internal async void _set_full_name (Edsf.Persona persona,
- string full_name) throws PropertyError
- {
- if (!("full-name" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("Full name is not writeable on this contact."));
- }
-
- unowned string? _full_name = full_name;
- if (full_name == "")
- {
- _full_name = null;
- }
-
- if (persona.full_name == _full_name)
- return;
-
- persona.contact.set (E.Contact.field_id ("full_name"), _full_name);
- yield this._commit_modified_property (persona, "full-name");
- }
-
- internal async void _set_nickname (Edsf.Persona persona, string nickname)
- throws PropertyError
- {
- if (!("nickname" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("Nickname is not writeable on this contact."));
- }
-
- unowned string? _nickname = nickname;
- if (nickname == "")
- {
- _nickname = null;
- }
-
- if (persona.nickname == _nickname)
- return;
-
- persona.contact.set (E.Contact.field_id ("nickname"), _nickname);
- yield this._commit_modified_property (persona, "nickname");
- }
-
- internal async void _set_notes (Edsf.Persona persona,
- Set<NoteFieldDetails> notes) throws PropertyError
- {
- if (!("notes" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("Notes are not writeable on this contact."));
- }
-
- if (Folks.Internal.equal_sets<NoteFieldDetails> (notes, persona.notes))
- return;
-
- this._set_contact_notes (persona.contact, notes);
- yield this._commit_modified_property (persona, "notes");
- }
-
- private void _set_contact_notes (E.Contact contact,
- Set<NoteFieldDetails> notes)
- {
- string note_str = "";
- foreach (var note in notes)
- {
- if (note_str != "")
- {
- note_str += ". ";
- }
- note_str += note.value;
- }
-
- contact.set (E.Contact.field_id ("note"), note_str);
- }
-
- internal async void _set_birthday (Edsf.Persona persona,
- DateTime? bday) throws PropertyError
- {
- if (!("birthday" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("Birthday is not writeable on this contact."));
- }
-
- if (persona.birthday != null &&
- bday != null &&
- ((!) persona.birthday).equal ((!) bday))
- return;
-
- /* Maybe the current and new b-day are unset */
- if (persona.birthday == null &&
- bday == null)
- return;
-
- this._set_contact_birthday (persona.contact, bday);
- yield this._commit_modified_property (persona, "birthday");
- }
-
- private void _set_contact_birthday (E.Contact contact, DateTime? _bday)
- {
- E.ContactDate? _contact_bday = null;
-
- if (_bday != null)
- {
- unowned var bday = (!) _bday;
- var bdaylocal = bday.to_local();
- E.ContactDate contact_bday;
-
- contact_bday = new E.ContactDate ();
- contact_bday.year = (uint) bdaylocal.get_year ();
- contact_bday.month = (uint) bdaylocal.get_month ();
- contact_bday.day = (uint) bdaylocal.get_day_of_month ();
-
- _contact_bday = contact_bday;
- }
-
- contact.set (E.Contact.field_id ("birth_date"), _contact_bday);
- }
-
- internal async void _set_roles (Edsf.Persona persona,
- Set<RoleFieldDetails> roles) throws PropertyError
- {
- if (!("roles" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("Roles are not writeable on this contact."));
- }
-
- if (Folks.Internal.equal_sets<RoleFieldDetails> (roles, persona.roles))
- return;
-
- this._set_contact_roles (persona.contact, roles);
- yield this._commit_modified_property (persona, "roles");
- }
-
- private void _set_contact_roles (E.Contact contact,
- Set<RoleFieldDetails> roles)
- {
- unowned var vcard = (E.VCard) contact;
- vcard.remove_attributes (null, "X-ROLES");
-
- unowned string? org = null;
- unowned string? org_unit = null;
- unowned string? office = null;
- unowned string? title = null;
- unowned string? role = null;
- unowned string? manager = null;
- unowned string? assistant = null;
-
- /* Because e-d-s supports only fields for one Role we save the
- * first in the Set to the fields available and the rest goes
- * to X-ROLES */
- int count = 0;
- foreach (var role_fd in roles)
- {
- if (count == 0)
- {
- org = role_fd.value.organisation_name;
- title = role_fd.value.title;
- role = role_fd.value.role;
-
- /* FIXME: we are swallowing the extra parameter values */
- var org_unit_values = role_fd.get_parameter_values ("org_unit");
- if (org_unit_values != null &&
- ((!) org_unit_values).size > 0)
- org_unit = ((!) org_unit_values).to_array ()[0];
-
- var office_values = role_fd.get_parameter_values ("office");
- if (office_values != null &&
- ((!) office_values).size > 0)
- office = ((!) office_values).to_array ()[0];
-
- var manager_values = role_fd.get_parameter_values ("manager");
- if (manager_values != null &&
- ((!) manager_values).size > 0)
- manager = ((!) manager_values).to_array ()[0];
-
- var assistant_values = role_fd.get_parameter_values ("assistant");
- if (assistant_values != null &&
- ((!) assistant_values).size > 0)
- assistant = ((!) assistant_values).to_array ()[0];
- }
- else
- {
- var attr = new E.VCardAttribute (null, "X-ROLES");
- attr.add_value (role_fd.value.role);
-
- var param1 = new E.VCardAttributeParam ("organisation_name");
- param1.add_value (role_fd.value.organisation_name);
- attr.add_param (param1);
-
- var param2 = new E.VCardAttributeParam ("title");
- param2.add_value (role_fd.value.title);
- attr.add_param (param2);
-
- foreach (var param_name in role_fd.parameters.get_keys ())
- {
- var param3 = new E.VCardAttributeParam (param_name.up ());
- foreach (var param_val in role_fd.parameters.get (param_name))
- {
- param3.add_value (param_val);
- }
- attr.add_param (param3);
- }
-
- contact.add_attribute ((owned) attr);
- }
-
- count++;
- }
-
- contact.set (E.Contact.field_id ("org"), org);
- contact.set (E.Contact.field_id ("org_unit"), org_unit);
- contact.set (E.Contact.field_id ("office"), office);
- contact.set (E.Contact.field_id ("title"), title);
- contact.set (E.Contact.field_id ("role"), role);
- contact.set (E.Contact.field_id ("manager"), manager);
- contact.set (E.Contact.field_id ("assistant"), assistant);
- }
-
- internal async void _set_structured_name (Edsf.Persona persona,
- StructuredName? sname) throws PropertyError
- {
- if (!("structured-name" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("Structured name is not writeable on this contact."));
- }
-
- if (persona.structured_name != null && sname != null &&
- ((!) persona.structured_name).equal ((!) sname))
- return;
-
- /* Maybe the current and new name are unset */
- if (persona.structured_name == null && sname == null)
- return;
-
- this._set_contact_name (persona.contact, sname);
- yield this._commit_modified_property (persona, "structured-name");
- }
-
- private void _set_contact_name (E.Contact contact, StructuredName? _sname)
- {
- E.ContactName contact_name = new E.ContactName ();
-
- if (_sname != null)
- {
- unowned var sname = (!) _sname;
-
- contact_name.family = sname.family_name;
- contact_name.given = sname.given_name;
- contact_name.additional = sname.additional_names;
- contact_name.suffixes = sname.suffixes;
- contact_name.prefixes = sname.prefixes;
- }
-
- contact.set (E.Contact.field_id ("name"), contact_name);
- }
-
- internal async void _set_im_fds (Edsf.Persona persona,
- MultiMap<string, ImFieldDetails> im_fds) throws PropertyError
- {
- if (!("im-addresses" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("IM addresses are not writeable on this contact."));
- }
-
- if (Utils.multi_map_str_afd_equal (persona.im_addresses, im_fds))
- return;
-
- this._set_contact_im_fds (persona.contact, im_fds);
- yield this._commit_modified_property (persona, "im-addresses");
- }
-
- /* TODO: this could be smarter & more efficient. */
- private void _set_contact_im_fds (E.Contact contact,
- MultiMap<string, ImFieldDetails> im_fds)
- {
- unowned var im_eds_map = Edsf.Persona._get_im_eds_map ();
-
- /* First let's remove everything */
- foreach (var field_id in im_eds_map.get_values ())
- {
- contact.remove_attributes (null, E.Contact.vcard_attribute (field_id));
- }
-
- foreach (var proto in im_fds.get_keys ())
- {
- var attributes = new GLib.List <E.VCardAttribute>();
- var attrib_name = ("X-" + proto).up ();
- bool added = false;
-
- foreach (var im_fd in im_fds.get (proto))
- {
- var attr_n = new E.VCardAttribute (null, attrib_name);
- attr_n.add_value (im_fd.value);
- attributes.prepend ((owned) attr_n);
- added = true;
- }
-
- if (added)
- {
- var field_id_t = im_eds_map.lookup (proto);
- contact.set_attributes (field_id_t, attributes);
- }
- }
- }
-
- internal async void _set_groups (Edsf.Persona persona,
- Set<string> groups) throws PropertyError
- {
- if (!("groups" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("Groups are not writeable on this contact."));
- }
-
- if (Folks.Internal.equal_sets<string> (groups, persona.groups))
- return;
-
- this._set_contact_groups (persona.contact, groups, persona.is_favourite);
- yield this._commit_modified_property (persona, "groups");
- }
-
- internal async void _set_system_groups (Edsf.Persona persona,
- Set<string> system_groups) throws PropertyError
- {
- if (!this._is_google_contacts_address_book ())
- {
- throw new PropertyError.NOT_WRITEABLE (_("My Contacts is only available for Google Contacts"));
- }
-
- if (Folks.Internal.equal_sets<string> (system_groups,
- persona.system_groups))
- return;
-
- this._set_contact_system_groups (persona.contact, system_groups);
- yield this._commit_modified_property (persona, "system-groups");
- }
-
- private void _set_contact_groups (E.Contact contact, Set<string> groups,
- bool is_favourite)
- {
- var categories = new GLib.List<string> ();
-
- foreach (var group in groups)
- {
- if (group == "")
- {
- continue;
- }
- else if (this._is_google_contacts_address_book () &&
- group == Edsf.PersonaStore.android_favourite_group_name)
- {
- continue;
- }
-
- categories.prepend (group);
- }
-
- /* If this is a Google address book, we must transparently add/remove the
- * “Starred in Android” group to/from the group list, depending on our
- * favourite status. */
- if (is_favourite && this._is_google_contacts_address_book ())
- {
- categories.prepend (Edsf.PersonaStore.android_favourite_group_name);
- }
-
- contact.set (ContactField.CATEGORY_LIST, categories);
- }
-
- private void _set_contact_system_groups (E.Contact contact, Set<string> system_groups)
- {
- unowned var group_ids_str = "X-GOOGLE-SYSTEM-GROUP-IDS";
- unowned var vcard = (E.VCard) contact;
- unowned E.VCardAttribute? prev_attr = vcard.get_attribute (group_ids_str);
-
- if (prev_attr != null)
- contact.remove_attributes (null, group_ids_str);
-
- E.VCardAttribute new_attr = new E.VCardAttribute (null, group_ids_str);
- foreach (var group in system_groups)
- {
- if (group == null || group == "")
- {
- continue;
- }
-
- new_attr.add_value (group);
- }
-
- vcard.add_attribute ((owned) new_attr);
- }
-
- internal async void _set_gender (Edsf.Persona persona,
- Gender gender) throws PropertyError
- {
- if (!("gender" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("Gender is not writeable on this contact."));
- }
-
- if (gender == persona.gender)
- return;
-
- this._set_contact_gender (persona.contact, gender);
- yield this._commit_modified_property (persona, "gender");
- }
-
- private void _set_contact_gender (E.Contact contact, Gender gender)
- {
- this._remove_attribute (contact, Edsf.Persona.gender_attribute_name);
-
- var new_attr =
- new VCardAttribute (null, Edsf.Persona.gender_attribute_name);
-
- switch (gender)
- {
- case Gender.UNSPECIFIED:
- break;
- case Gender.MALE:
- new_attr.add_value (Edsf.Persona.gender_male);
- contact.add_attribute ((owned) new_attr);
- break;
- case Gender.FEMALE:
- new_attr.add_value (Edsf.Persona.gender_female);
- contact.add_attribute ((owned) new_attr);
- break;
- }
- }
-
- internal async void _set_anti_links (Edsf.Persona persona,
- Set<string> anti_links) throws PropertyError
- {
- if (!("anti-links" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("Anti-links are not writeable on this contact."));
- }
-
- if (Folks.Internal.equal_sets<string> (anti_links, persona.anti_links))
- {
- return;
- }
-
- this._set_contact_anti_links (persona.contact, anti_links);
- yield this._commit_modified_property (persona, "anti-links");
- }
-
- private void _set_contact_anti_links (E.Contact contact,
- Set<string> anti_links)
- {
- var vcard = (E.VCard) contact;
- vcard.remove_attributes (null, PersonaStore.anti_links_attribute_name);
-
- var persona_uid =
- Folks.Persona.build_uid (BACKEND_NAME, this.id, contact.id);
-
- foreach (var anti_link_uid in anti_links)
- {
- /* Skip the persona's UID; don't allow reflexive anti-links. */
- if (anti_link_uid == persona_uid)
- {
- continue;
- }
-
- var attr = new E.VCardAttribute (null,
- PersonaStore.anti_links_attribute_name);
- attr.add_value (anti_link_uid);
-
- contact.add_attribute ((owned) attr);
- }
- }
-
- internal async void _set_location (Edsf.Persona persona,
- Location? location) throws PropertyError
- {
- if (!("location" in this._always_writeable_properties))
- {
- throw new PropertyError.NOT_WRITEABLE (
- _("Location is not writeable on this contact."));
- }
-
- this._set_contact_location (persona.contact, location);
- yield this._commit_modified_property (persona, "location");
- }
-
- private void _set_contact_location (E.Contact contact, Location? location)
- {
- if (location == null)
- {
- this._remove_attribute (contact, "GEO");
- }
- else
- {
- E.ContactGeo geo = new E.ContactGeo ();
- geo.latitude = location.latitude;
- geo.longitude = location.longitude;
- contact.set (ContactField.GEO, geo);
- }
- }
-
- /*
- * The EDS store receives change notifications as quickly as it
- * can and then stores them for later processing in a glib idle
- * callback.
- *
- * This avoids problems where many incoming D-Bus change notifications
- * block processing of other incoming D-Bus messages. Delays of over a minute
- * have been observed in worst-case (but not entirely unrealistic) scenarios
- * (1000 contacts added one-by-one while folks is active). See
- * https://bugzilla.gnome.org/show_bug.cgi?id=694385 (folks issue) and
- * http://bugs.freedesktop.org/show_bug.cgi?id=60851 (SyncEvolution issue).
- *
- * Cannot store a GLib.SourceFunc directly
- * in a LinkedList ("Delegates with target are not supported as generic type arguments",
- * https://mail.gnome.org/archives/vala-list/2011-June/msg00002.html)
- * and thus have to wrap it in a class. When dealing with delegates, we must be
- * careful to transfer ownership, because they are not reference counted.
- */
- class IdleTask
- {
- public GLib.SourceFunc callback;
- }
-
-
- private Gee.Queue<IdleTask> _idle_tasks = new Gee.LinkedList<IdleTask> ();
- private uint _idle_handle = 0;
-
- /*
- * Deal with some chunk of work encapsulated in the delegate later.
- * As in any other SourceFunc, the callback may request to be called
- * again by returning true. In contrast to Idle.add, _idle_queue
- * ensures that only one task is processed per idle cycle.
- */
- private void _idle_queue (owned GLib.SourceFunc callback)
- {
- IdleTask task = new IdleTask ();
- task.callback = (owned) callback;
- this._idle_tasks.add (task);
- /*
- * Ensure that there is an active idle callback to process
- * the task, otherwise register it. We cannot just
- * queue each task separately, because then we might
- * end up with multiple tasks being done at once
- * when the process gets idle, instead of one
- * task at a time.
- */
- if (this._idle_handle == 0)
- {
- this._idle_handle = Idle.add (this._idle_process);
- }
- }
-
- private bool _idle_process ()
- {
- IdleTask? task = this._idle_tasks.peek ();
- if (task != null)
- {
- if (task.callback ())
- {
- /* Task is not done yet, run it again later. */
- return true;
- }
- this._idle_tasks.poll ();
- }
-
- /* Check for future work. */
- task = this._idle_tasks.peek ();
- if (task == null)
- {
- /*
- * Remember that we need to re-register idle
- * processing when _idle_queue is called again.
- */
- this._idle_handle = 0;
- /* Done, will remove idle handler. */
- return false;
- }
- else
- {
- /* Continue processing. */
- return true;
- }
- }
-
-#if HAS_EDS_3_41
- private void _contacts_added_cb (GLib.SList<E.Contact> contacts)
- {
-#else
- // The binding was using the wrong list type
- private void _contacts_added_cb (GLib.List<E.Contact> _contacts)
- {
- unowned GLib.SList<E.Contact> contacts = (GLib.SList<E.Contact>)_contacts;
-#endif
- GLib.SList<E.Contact> copy = contacts.copy_deep ((GLib.CopyFunc<E.Contact>) GLib.Object.ref);
- this._idle_queue (() => { return this._contacts_added_idle (copy); });
- }
-
- private bool _contacts_added_idle (GLib.SList<E.Contact> contacts)
- {
- HashSet<Persona> added_personas, removed_personas;
-
- /* If the persona store hasn't yet reached quiescence, queue up the
- * personas and emit a notification about them later; see
- * _contacts_complete_cb() for details. */
- if (this._is_quiescent == false)
- {
- /* Lazily create pending_personas. */
- if (this._pending_personas == null)
- {
- this._pending_personas = new HashSet<Persona> ();
- }
-
- added_personas = this._pending_personas;
- }
- else
- {
- added_personas = new HashSet<Persona> ();
- }
-
- removed_personas = new HashSet<Persona> ();
-
- foreach (unowned E.Contact c in contacts)
- {
- string? _iid = Edsf.Persona.build_iid_from_contact (this.id, c);
-
- if (_iid == null)
- {
- debug ("Ignoring contact %p as UID is not set", c);
- continue;
- }
-
- unowned string iid = (!) _iid;
- var old_persona = this._personas.get (iid);
- var new_persona = new Persona (this, c);
-
- if (old_persona != null)
- {
- debug ("Removing old persona %p from contact %s.",
- old_persona, iid);
- removed_personas.add (old_persona);
-
- /* Handle the case where a contact is removed before the persona
- * store has reached quiescence. */
- if (this._pending_personas != null)
- {
- this._pending_personas.remove (old_persona);
- }
- }
-
- debug ("Adding persona %p from contact %s.", new_persona, iid);
-
- this._personas.set (new_persona.iid, new_persona);
- added_personas.add (new_persona);
- }
-
- if (added_personas.size > 0 && this._is_quiescent == true)
- {
- this._emit_personas_changed (added_personas, removed_personas);
- }
-
- /* Done. */
- return false;
- }
-
-#if HAS_EDS_3_41
- private void _contacts_changed_cb (GLib.SList<E.Contact> contacts)
- {
-#else
- // The binding was using the wrong list type
- private void _contacts_changed_cb (GLib.List<E.Contact> _contacts)
- {
- unowned GLib.SList<E.Contact> contacts = (GLib.SList<E.Contact>)_contacts;
-#endif
- GLib.SList<E.Contact> copy = contacts.copy_deep ((GLib.CopyFunc<E.Contact>) GLib.Object.ref);
- this._idle_queue (() => { return this._contacts_changed_idle (copy); });
- }
-
- private bool _contacts_changed_idle (GLib.SList<E.Contact> contacts)
- {
- foreach (unowned E.Contact c in contacts)
- {
- string? _iid = Edsf.Persona.build_iid_from_contact (this.id, c);
-
- if (_iid == null)
- {
- debug ("Ignoring contact %p as UID is not set", c);
- continue;
- }
-
- unowned string iid = (!) _iid;
- Persona? persona = this._personas.get (iid);
- if (persona != null)
- {
- ((!) persona)._update (c);
- }
- }
- return false;
- }
-
-#if HAS_EDS_3_41
- private void _contacts_removed_cb (GLib.SList<string> contacts_ids)
- {
-#else
- // The binding was using the wrong list type
- private void _contacts_removed_cb (GLib.List<string> _contacts_ids)
- {
- unowned GLib.SList<string> contacts_ids = (GLib.SList<string>)_contacts_ids;
-#endif
- GLib.SList<string> copy = contacts_ids.copy_deep ((GLib.CopyFunc<string>) string.dup);
- this._idle_queue (() => { return this._contacts_removed_idle (copy); });
- }
-
- private bool _contacts_removed_idle (GLib.SList<string> contacts_ids)
- {
- var removed_personas = new HashSet<Persona> ();
-
- foreach (unowned string contact_id in contacts_ids)
- {
- /* Not sure how this could happen, but better to be safe. We do not
- * allow empty UIDs. */
- if (contact_id == "")
- continue;
-
- var iid = Edsf.Persona.build_iid (this.id, contact_id);
- Persona? persona = _personas.get (iid);
- if (persona != null)
- {
- removed_personas.add ((!) persona);
- this._personas.unset (((!) persona).iid);
-
- /* Handle the case where a contact is removed before the persona
- * store has reached quiescence. */
- if (this._pending_personas != null)
- {
- this._pending_personas.remove ((!) persona);
- }
- }
- }
-
- if (removed_personas.size > 0)
- {
- this._emit_personas_changed (null, removed_personas);
- }
- return false;
- }
-
- private void _contacts_complete_cb (Error? err)
- {
- /* Handle errors. We treat an error in the first _contacts_complete_cb()
- * callback as unrecoverable, since it's being reported from the address
- * book's view creation code. Subsequent errors may be recoverable, since
- * they might be transient errors in refreshing the contact list. */
- if (err != null)
- {
- warning ("Error in address book view query: %s", err.message);
- }
-
- Internal.profiling_point ("initial query complete in " +
- "Edsf.PersonaStore (ID: %s)", this.id);
-
- /* Do the rest in an idle, so we don't signal that we are quiescent
- * before we actually have everyone. */
- this._idle_queue (() => { return this._contacts_complete_idle_cb (err); });
- }
-
- private bool _contacts_complete_idle_cb (Error? err)
- {
- /* The initial query is complete, so signal that we've reached
- * quiescence (even if there was an error). */
- if (this._is_quiescent == false)
- {
- /* Handle initial errors. */
- if (err != null)
- {
- warning ("Error is considered unrecoverable. " +
- "Removing persona store.");
- this.removed ();
- return false;
- }
-
- /* Emit a notification about all the personas which were found in the
- * initial query. They're queued up in _contacts_added_cb() and only
- * emitted here as _contacts_added_cb() may be called many times
- * before _contacts_complete_cb() is called. For example, EDS seems to
- * like emitting contacts in batches of 16 at the moment.
- * Queueing the personas up and emitting a single notification is a
- * lot more efficient for the individual aggregator to handle. */
- if (this._pending_personas != null)
- {
- this._emit_personas_changed (this._pending_personas, null);
- this._pending_personas = null;
- }
-
- this._is_quiescent = true;
- this.notify_property ("is-quiescent");
- }
-
- return false;
- }
-
- /* Convert an EClientError or EBookClientError to a Folks.PersonaStoreError
- * for contact additions. */
- private PersonaStoreError e_client_error_to_persona_store_error (
- GLib.Error error_in)
- {
- if (error_in is BookClientError)
- {
- /* We don't expect to receive any of the error codes below: */
- if (error_in is BookClientError.CONTACT_NOT_FOUND ||
- error_in is BookClientError.NO_SUCH_BOOK ||
- error_in is BookClientError.CONTACT_ID_ALREADY_EXISTS ||
- error_in is BookClientError.NO_SUCH_SOURCE ||
- error_in is BookClientError.NO_SPACE)
- {
- /* Fall out */
- }
- }
- else if (error_in.domain == Client.error_quark ())
- {
- switch ((ClientError) error_in.code)
- {
- case ClientError.PERMISSION_DENIED:
- return new PersonaStoreError.PERMISSION_DENIED (
- /* Translators: the first parameter is an error message. */
- _("Permission denied when creating new contact: %s"),
- error_in.message);
- case ClientError.REPOSITORY_OFFLINE:
- return new PersonaStoreError.STORE_OFFLINE (
- /* Translators: the first parameter is an error message. */
- _("Address book is offline and a new contact cannot be " +
- "created: %s"), error_in.message);
- case ClientError.NOT_SUPPORTED:
- case ClientError.AUTHENTICATION_REQUIRED:
- /* TODO: Support authentication. bgo#653339 */
- return new PersonaStoreError.CREATE_FAILED (
- /* Translators: the first parameter is a non-human-readable
- * property name and the second parameter is an error
- * message. */
- _("New contact is not writeable: %s"), error_in.message);
- case ClientError.INVALID_ARG:
- return new PersonaStoreError.INVALID_ARGUMENT (
- /* Translators: the first parameter is an error message. */
- _("Invalid value in contact: %s"), error_in.message);
- case ClientError.BUSY:
- case ClientError.DBUS_ERROR:
- case ClientError.OTHER_ERROR:
- /* Fall through. */
- /* We don't expect to receive any of the error codes below: */
- case ClientError.COULD_NOT_CANCEL:
- case ClientError.AUTHENTICATION_FAILED:
- case ClientError.TLS_NOT_AVAILABLE:
- case ClientError.OFFLINE_UNAVAILABLE:
- case ClientError.UNSUPPORTED_AUTHENTICATION_METHOD:
- case ClientError.SEARCH_SIZE_LIMIT_EXCEEDED:
- case ClientError.SEARCH_TIME_LIMIT_EXCEEDED:
- case ClientError.INVALID_QUERY:
- case ClientError.QUERY_REFUSED:
- default:
- /* Fall out */
- break;
- }
- }
-
- /* Fallback error. */
- return new PersonaStoreError.CREATE_FAILED (
- /* Translators: the first parameter is an error message. */
- _("Unknown error adding contact: %s"), error_in.message);
- }
-
- /* Convert an EClientError or EBookClientError to a Folks.PropertyError for
- * property modifications. */
- private PropertyError e_client_error_to_property_error (string property_name,
- GLib.Error error_in)
- {
- if (error_in is BookClientError)
- {
- /* We don't expect to receive any of the error codes below: */
- if (error_in is BookClientError.CONTACT_NOT_FOUND ||
- error_in is BookClientError.NO_SUCH_BOOK ||
- error_in is BookClientError.CONTACT_ID_ALREADY_EXISTS ||
- error_in is BookClientError.NO_SUCH_SOURCE ||
- error_in is BookClientError.NO_SPACE)
- {
- /* Fall out */
- }
- }
- else if (error_in.domain == Client.error_quark ())
- {
- switch ((ClientError) error_in.code)
- {
- case ClientError.REPOSITORY_OFFLINE:
- case ClientError.PERMISSION_DENIED:
- case ClientError.NOT_SUPPORTED:
- case ClientError.AUTHENTICATION_REQUIRED:
- /* TODO: Support authentication. bgo#653339 */
- return new PropertyError.NOT_WRITEABLE (
- /* Translators: the first parameter is a non-human-readable
- * property name and the second parameter is an error
- * message. */
- _("Property ‘%s’ is not writeable: %s"), property_name,
- error_in.message);
- /* We expect to receive these, but they don't need special
- * error codes: */
- case ClientError.INVALID_ARG:
- return new PropertyError.INVALID_VALUE (
- /* Translators: the first parameter is a non-human-readable
- * property name and the second parameter is an error
- * message. */
- _("Invalid value for property ‘%s’: %s"), property_name,
- error_in.message);
- case ClientError.BUSY:
- case ClientError.DBUS_ERROR:
- case ClientError.OTHER_ERROR:
- /* Fall through. */
- /* We don't expect to receive any of the error codes below: */
- case ClientError.COULD_NOT_CANCEL:
- case ClientError.AUTHENTICATION_FAILED:
- case ClientError.TLS_NOT_AVAILABLE:
- case ClientError.OFFLINE_UNAVAILABLE:
- case ClientError.UNSUPPORTED_AUTHENTICATION_METHOD:
- case ClientError.SEARCH_SIZE_LIMIT_EXCEEDED:
- case ClientError.SEARCH_TIME_LIMIT_EXCEEDED:
- case ClientError.INVALID_QUERY:
- case ClientError.QUERY_REFUSED:
- default:
- /* Fall out */
- break;
- }
- }
-
- /* Fallback error. */
- return new PropertyError.UNKNOWN_ERROR (
- /* Translators: the first parameter is a non-human-readable
- * property name and the second parameter is an error message. */
- _("Unknown error setting property ‘%s’: %s"), property_name,
- error_in.message);
- }
-
- private bool _backend_name_matches (string backend_name)
- {
- if (this.source.has_extension (SOURCE_EXTENSION_ADDRESS_BOOK))
- {
- unowned E.SourceAddressBook extension = (E.SourceAddressBook)
- this.source.get_extension (SOURCE_EXTENSION_ADDRESS_BOOK);
-
- return (extension.get_backend_name () == backend_name);
- }
-
- return false;
- }
-
- /* Try and work out whether this address book is Google Contacts. If so, we
- * can enable things like setting favourite status based on Android groups. */
- internal bool _is_google_contacts_address_book ()
- {
- /* Should only ever be called from property getters/setters. */
- assert (this._source_registry != null);
-
- /* backend name should be ‘google’ for Google Contacts address books */
- return this._backend_name_matches ("google");
- }
-
- private bool _is_in_source_registry ()
- {
- /* Should only ever be called from a callback from the source list itself,
- * so we can assert that the source list is non-null. */
- assert (this._source_registry != null);
-
- E.Source? needle = ((!) this._source_registry).ref_source (this.id);
- if (needle != null && needle.has_extension (SOURCE_EXTENSION_ADDRESS_BOOK))
- {
- /* We've found ourself. */
- return ((!) this._source_registry).check_enabled (needle);
- }
-
- return false;
- }
-
- /* Detect removal of the address book. We can't do this in Eds.Backend because
- * it has no way to tell the PersonaStore that it's been removed without
- * uglifying the store's public API. */
- private void _source_registry_changed_cb (E.Source list)
- {
- /* If we can't find our source, this persona store's address book has
- * been removed. */
- if (this._is_in_source_registry () == false)
- {
- /* Marshal the personas from a Collection to a Set. */
- var removed_personas = new HashSet<Persona> ();
- var iter = this._personas.map_iterator ();
-
- while (iter.next () == true)
- {
- removed_personas.add (iter.get_value ());
- }
-
- this._emit_personas_changed (null, removed_personas);
- this.removed ();
- }
- }
-
- /* This isn't perfect, since we want to base our trust of the address book on
- * whether *other people* can write to it (and potentially maliciously affect
- * the linking our aggregator does). However, since we can't know that, we
- * assume that if we can write to the address book we're probably in full
- * control of it. If we can't, either nobody/a sysadmin is (e.g. LDAP) or
- * or somebody else (who we can't trust) is (e.g. a read-only view of someone
- * else's WebDAV address book).
- */
- private void _update_trust_level ()
- {
- /* We may be called before prepare() has finished (and it may then fail),
- * but _addressbook should always be non-null when we're called. */
- assert (this._source_registry != null);
- assert (this._addressbook != null);
-
- /* backend_name should be ‘ldap’ for LDAP based address books */
- if (this._backend_name_matches ("ldap"))
- {
- this.trust_level = PersonaStoreTrust.PARTIAL;
- return;
- }
-
- if (this._is_google_contacts_address_book ())
- this.trust_level = PersonaStoreTrust.FULL;
-
- if (((!) this._addressbook).readonly)
- this.trust_level = PersonaStoreTrust.PARTIAL;
- else
- this.trust_level = PersonaStoreTrust.FULL;
- }
-
- private void _source_changed_cb ()
- {
- this._notify_if_default ();
- }
-
- private void _notify_if_default ()
- {
- bool is_default = false;
-
- /* By peeking at the default source instead of checking the value of
- * the "default" property, we include EDS's fallback logic for the
- * "system" address book */
- if (this._source_registry != null)
- {
- var default_source = this._source_registry.ref_default_address_book ();
- if (default_source != null &&
- this.source.get_uid () == ((!) default_source).get_uid ())
- {
- is_default = true;
- }
-
- if (is_default != this.is_user_set_default)
- {
- this.is_user_set_default = is_default;
- }
- }
- }
-}
diff --git a/backends/eds/lib/edsf-persona.vala b/backends/eds/lib/edsf-persona.vala
deleted file mode 100644
index 4369d558..00000000
--- a/backends/eds/lib/edsf-persona.vala
+++ /dev/null
@@ -1,2314 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- * Copyright (C) 2013, 2016 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Marco Barisione <marco.barisione@collabora.co.uk>
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- */
-
-using E;
-using Folks;
-using Gee;
-using GLib;
-using Xml;
-
-/**
- * A persona subclass which represents a single EDS contact.
- *
- * Each {@link Edsf.Persona} instance represents a single EDS {@link E.Contact}.
- * When the contact is modified (either by this folks client, or a different
- * client), the {@link Edsf.Persona} remains the same, but is assigned a new
- * {@link E.Contact}. It then updates its properties from this new contact.
- */
-public class Edsf.Persona : Folks.Persona,
- AntiLinkable,
- AvatarDetails,
- BirthdayDetails,
- EmailDetails,
- ExtendedInfo,
- FavouriteDetails,
- GenderDetails,
- GroupDetails,
- ImDetails,
- LocalIdDetails,
- LocationDetails,
- NameDetails,
- NoteDetails,
- PhoneDetails,
- RoleDetails,
- UrlDetails,
- PostalAddressDetails,
- WebServiceDetails
-{
- /* The following 4 definitions are used by the tests */
- /**
- * vCard field names for telephone numbers.
- *
- * @since 0.6.0
- */
- public const string[] phone_fields = {
- "assistant_phone", "business_phone", "business_phone_2", "callback_phone",
- "car_phone", "company_phone", "home_phone", "home_phone_2", "isdn_phone",
- "mobile_phone", "other_phone", "primary_phone"
- };
- /**
- * vCard field names for postal addresses.
- *
- * @since 0.6.0
- */
- public const string[] address_fields = {
- "address_home", "address_other", "address_work"
- };
- /**
- * vCard field names for e-mail addresses.
- *
- * @since 0.6.0
- */
- public const string[] email_fields = {
- "email_1", "email_2", "email_3", "email_4"
- };
-
- /**
- * vCard field names for miscellaneous URIs.
- *
- * @since 0.6.0
- */
- [Version (deprecated = true, deprecated_since = "0.6.3",
- replacement = "Folks.UrlFieldDetails.PARAM_TYPE_BLOG")]
- public const string[] url_properties = {
- "blog_url", "fburl", "homepage_url", "video_url"
- };
-
- /* Some types of URLs are represented in EDS using custom vCard fields rather
- * than the X-URIS field. Here are mappings between the custom vCard field
- * names which EDS uses, and the TYPE values which folks uses which map to
- * them. */
- internal struct UrlTypeMapping
- {
- unowned string vcard_field_name;
- unowned string folks_type;
- }
-
- internal const UrlTypeMapping[] _url_properties =
- {
- { "homepage_url", UrlFieldDetails.PARAM_TYPE_HOME_PAGE },
- { "blog_url", UrlFieldDetails.PARAM_TYPE_BLOG },
- { "fburl", "x-free-busy" },
- { "video_url", "x-video" }
- };
-
- /**
- * Name of folks’ custom parameter indicating automatic fields
- *
- * Folks can create extra fields to improve linking between personas.
- * These fields have a boolean-typed parameter added with this name,
- * and the value ‘TRUE’. This allows clients to detect such fields
- * and (for example) ignore them in the UI.
- *
- * @since 0.9.7
- */
- public const string folks_field_attribute_name = "X-FOLKS-FIELD";
-
- /**
- * The vCard attribute used to specify a Contact's gender
- *
- * Based on:
- * [[http://tools.ietf.org/html/draft-ietf-vcarddav-vcardrev-22]]
- *
- * Note that the above document is a draft and the gender property
- * is still considered experimental, hence the "X-" prefix in the
- * attribute name. So this might change.
- *
- * @since 0.6.0
- */
- public const string gender_attribute_name = "X-GENDER";
-
- /**
- * The value used to define the male gender for the
- * X-GENDER vCard property.
- *
- * Based on:
- * [[http://tools.ietf.org/html/draft-ietf-vcarddav-vcardrev-22]]
- *
- * @since 0.6.0
- */
- public const string gender_male = "M";
-
- /**
- * The value used to define the female gender for the
- * X-GENDER vCard property.
- *
- * Based on:
- * [[http://tools.ietf.org/html/draft-ietf-vcarddav-vcardrev-22]]
- *
- * @since 0.6.0
- */
- public const string gender_female = "F";
-
- private const string[] _linkable_properties =
- {
- "im-addresses",
- "email-addresses",
- "local-ids",
- "web-service-addresses",
- null /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=682698 */
- };
-
- private static GLib.HashTable<unowned string, E.ContactField>? _im_eds_map = null;
-
- private E.Contact _contact; /* should be set on construct */
-
- /**
- * The e-d-s contact represented by this Persona
- */
- public E.Contact contact
- {
- get { return this._contact; }
- construct { this._contact = value; }
- }
-
- /* NOTE: Other properties support lazy initialisation, but
- * web-service-addresses doesn't as it's a linkable property, so always has to
- * be loaded anyway. */
- private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses;
-
- /**
- * {@inheritDoc}
- *
- * This is stored in the X-FOLKS-WEB-SERVICES-IDS vCard field in EDS.
- */
- [CCode (notify = false)]
- public MultiMap<string, WebServiceFieldDetails> web_service_addresses
- {
- get { return this._web_service_addresses; }
- set { this.change_web_service_addresses.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_web_service_addresses (
- MultiMap<string, WebServiceFieldDetails> web_service_addresses)
- throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_web_service_addresses (this,
- web_service_addresses);
- }
-
- /* NOTE: Other properties support lazy initialisation, but local-ids doesn't
- * as it's a linkable property, so always has to be loaded anyway. */
- private SmallSet<string> _local_ids = new SmallSet<string> ();
- private Set<string> _local_ids_ro;
-
- /**
- * IDs used to link {@link Edsf.Persona}s.
- *
- * This is stored in the X-FOLKS-CONTACTS-IDS vCard field in EDS.
- */
- [CCode (notify = false)]
- public Set<string> local_ids
- {
- get
- {
- if (this._local_ids.contains (this.iid) == false)
- {
- this._local_ids.add (this.iid);
- }
- return this._local_ids_ro;
- }
- set { this.change_local_ids.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_local_ids (Set<string> local_ids)
- throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_local_ids (this, local_ids);
- }
-
- private Location? _location = null;
- /**
- * {@inheritDoc}
- *
- * @since 0.9.2
- */
- [CCode (notify = false)]
- public Location? location
- {
- get { return this._location; }
- set { this.change_location.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.2
- */
- public async void change_location (Location? location) throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_location (this, location);
- }
-
- private SmallSet<PostalAddressFieldDetails>? _postal_addresses = null;
- private Set<PostalAddressFieldDetails>? _postal_addresses_ro = null;
-
- /**
- * The postal addresses of the contact.
- *
- * A list of postal addresses associated to the contact.
- *
- * @since 0.6.0
- */
- [CCode (notify = false)]
- public Set<PostalAddressFieldDetails> postal_addresses
- {
- get
- {
- this._update_addresses (true, false);
- return this._postal_addresses_ro;
- }
- set { this.change_postal_addresses.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_postal_addresses (
- Set<PostalAddressFieldDetails> postal_addresses) throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_postal_addresses (this,
- postal_addresses);
- }
-
- private SmallSet<PhoneFieldDetails>? _phone_numbers = null;
- private Set<PhoneFieldDetails>? _phone_numbers_ro = null;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- [CCode (notify = false)]
- public Set<PhoneFieldDetails> phone_numbers
- {
- get
- {
- this._update_phones (true, false);
- return this._phone_numbers_ro;
- }
- set { this.change_phone_numbers.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_phone_numbers (
- Set<PhoneFieldDetails> phone_numbers) throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_phones (this, phone_numbers);
- }
-
- private SmallSet<EmailFieldDetails>? _email_addresses =
- new SmallSet<EmailFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- private Set<EmailFieldDetails> _email_addresses_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- [CCode (notify = false)]
- public Set<EmailFieldDetails> email_addresses
- {
- get { return this._email_addresses_ro; }
- set { this.change_email_addresses.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_email_addresses (
- Set<EmailFieldDetails> email_addresses) throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_emails (this,
- email_addresses);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.11.0
- */
- public ExtendedFieldDetails? get_extended_field (string name)
- {
- return ((Edsf.PersonaStore) this.store)._get_extended_field (this, name);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.11.0
- */
- public async void change_extended_field (
- string name, ExtendedFieldDetails value) throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._change_extended_field (this, name, value);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.11.0
- */
- public async void remove_extended_field (string name) throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._remove_extended_field (this, name);
- }
-
- private SmallSet<NoteFieldDetails>? _notes = null;
- private Set<NoteFieldDetails>? _notes_ro = null;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- [CCode (notify = false)]
- public Set<NoteFieldDetails> notes
- {
- get
- {
- this._update_notes (true, false);
- return this._notes_ro;
- }
- set { this.change_notes.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_notes (Set<NoteFieldDetails> notes)
- throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_notes (this, notes);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override string[] linkable_properties
- {
- get { return Persona._linkable_properties; }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override string[] writeable_properties
- {
- get { return this.store.always_writeable_properties; }
- }
-
- private LoadableIcon? _avatar = null;
- /**
- * An avatar for the Persona.
- *
- * See {@link Folks.AvatarDetails.avatar}.
- *
- * @since 0.6.0
- */
- [CCode (notify = false)]
- public LoadableIcon? avatar
- {
- get { return this._avatar; }
- set { this.change_avatar.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_avatar (LoadableIcon? avatar) throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_avatar (this, avatar);
- }
-
- private StructuredName? _structured_name = null;
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- [CCode (notify = false)]
- public StructuredName? structured_name
- {
- get { return this._structured_name; }
- set { this.change_structured_name.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_structured_name (StructuredName? structured_name)
- throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_structured_name (this,
- structured_name);
- }
-
- /**
- * The e-d-s contact uid
- *
- * This is guaranteed to be a non-empty string, unique within the persona
- * store.
- *
- * @since 0.6.0
- */
- public string contact_id { get; construct; }
-
- private string _full_name = "";
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- [CCode (notify = false)]
- public string full_name
- {
- get { return this._full_name; }
- set { this.change_full_name.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_full_name (string full_name) throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_full_name (this, full_name);
- }
-
- private string _nickname = "";
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- [CCode (notify = false)]
- public string nickname
- {
- get { return this._nickname; }
- set { this.change_nickname.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_nickname (string nickname) throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_nickname (this, nickname);
- }
-
- private Gender _gender;
- /**
- * {@inheritDoc}
- *
- * This is stored in the {@link Edsf.Persona.gender_attribute_name} vCard
- * field in EDS.
- *
- * @since 0.6.0
- */
- [CCode (notify = false)]
- public Gender gender
- {
- get { return this._gender; }
- set { this.change_gender.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_gender (Gender gender) throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_gender (this, gender);
- }
-
- private SmallSet<UrlFieldDetails>? _urls = null;
- private Set<UrlFieldDetails>? _urls_ro = null;
- /**
- * {@inheritDoc}
- *
- * This is stored in the X-URIS vCard field in EDS.
- *
- * @since 0.6.0
- */
- [CCode (notify = false)]
- public Set<UrlFieldDetails> urls
- {
- get
- {
- this._update_urls (true, false);
- return this._urls_ro;
- }
- set { this.change_urls.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_urls (Set<UrlFieldDetails> urls) throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_urls (this, urls);
- }
-
- /* NOTE: Other properties support lazy initialisation, but im-addresses
- * doesn't as it's a linkable property, so always has to be loaded anyway. */
- private HashMultiMap<string, ImFieldDetails> _im_addresses =
- new HashMultiMap<string, ImFieldDetails> (null, null,
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- [CCode (notify = false)]
- public MultiMap<string, ImFieldDetails> im_addresses
- {
- get { return this._im_addresses; }
- set { this.change_im_addresses.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_im_addresses (
- MultiMap<string, ImFieldDetails> im_addresses) throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_im_fds (this, im_addresses);
- }
-
- private SmallSet<string>? _groups = null;
- private Set<string>? _groups_ro = null;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- [CCode (notify = false)]
- public Set<string> groups
- {
- get
- {
- this._update_groups (true, false);
- return this._groups_ro;
- }
- set { this.change_groups.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public async void change_group (string group, bool is_member)
- throws GLib.Error
- {
- /* NOTE: This method specifically accesses this.groups rather than
- * this._groups, so that lazy loading is guaranteed to happen if
- * necessary. */
- /* Nothing to do? */
- if ((is_member == true && this.groups.contains (group) == true) ||
- (is_member == false && this.groups.contains (group) == false))
- {
- return;
- }
-
- /* Replace the current set of groups with a modified one. */
- var new_groups = SmallSet<string>.copy (this.groups);
-
- if (is_member == false)
- {
- new_groups.remove (group);
- }
- else
- {
- new_groups.add (group);
- }
-
- yield this.change_groups (new_groups);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_groups (Set<string> groups) throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_groups (this, groups);
- }
-
- /**
- * Change the contact's system groups.
- *
- * The system groups are a property exposed by Google Contacts address books,
- * and can include any combination of the following identifier:
- * - "Contacts"
- * - "Family"
- * - "Friends"
- * - "Coworkers"
- *
- * Setting the system groups will also change the group membership to include
- * the localized version of those groups, and may change the value of
- * {@link Edsf.Persona.in_google_personal_group}.
- *
- * Attempting to call this method on a persona belonging to a PersonaStore which
- * is not Google will throw a PropertyError.
- *
- * It's preferred to call this rather than setting {@link Persona.system_groups}
- * directly, as this method gives error notification and will only return once
- * the groups have been written to the relevant backing store (or the
- * operation's failed).
- *
- * @param system_groups the complete set of system group ids the contact should be a member of
- * @throws PropertyError if setting the groups failed
- * @since 0.9.0
- */
- public async void change_system_groups (Set<string> system_groups) throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_system_groups (this, system_groups);
- }
-
- private const string GOOGLE_PERSONAL_GROUP_NAME = "Contacts";
-
- /**
- * Change whether this contact belongs to the personal group or not.
- *
- * The personal contact group is a concept that exists only in Google
- * address books. Other backends will throw a PropertyError.
- *
- * It's preferred to call this rather than setting {@link Persona.in_google_personal_group}
- * directly, as this method gives error notification and will only return once
- * the membership has been written to the relevant backing store (or the
- * operation's failed).
- *
- * @param in_personal Whether to add or remove the personal group membership
- * @throws PropertyError if the address book is not Google, or if setting the property failed
- * @since 0.9.0
- */
- public async void change_in_google_personal_group (bool in_personal) throws PropertyError
- {
- if (in_personal == this._in_google_personal_group)
- {
- return;
- }
-
- var new_system_groups = SmallSet<string>.copy (this._system_groups);
-
- if (in_personal)
- {
- new_system_groups.add (GOOGLE_PERSONAL_GROUP_NAME);
- }
- else
- {
- new_system_groups.remove (GOOGLE_PERSONAL_GROUP_NAME);
- }
-
- yield ((Edsf.PersonaStore) this.store)._set_system_groups (this, new_system_groups);
- }
-
- /**
- * {@inheritDoc}
- *
- * e-d-s has no equivalent field, so this is unsupported.
- *
- * @since 0.6.2
- */
- [CCode (notify = false)]
- public string? calendar_event_id
- {
- get { return null; } /* unsupported */
- set { this.change_calendar_event_id.begin (value); } /* not writeable */
- }
-
- /* We cache the timezone we use for converting birthdays to UTC since creating
- * it requires mmapping /etc/localtime, which means lots of syscalls. */
- private static TimeZone _local_time_zone = new TimeZone.local ();
-
- private DateTime? _birthday = null;
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- [CCode (notify = false)]
- public DateTime? birthday
- {
- get { return this._birthday; }
- set { this.change_birthday.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_birthday (DateTime? bday)
- throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_birthday (this,
- bday);
- }
-
- private SmallSet<RoleFieldDetails>? _roles = null;
- private Set<RoleFieldDetails>? _roles_ro = null;
-
- /**
- * {@inheritDoc}
- *
- * This is stored in the X-ROLES vCard field in EDS.
- *
- * @since 0.6.2
- */
- [CCode (notify = false)]
- public Set<RoleFieldDetails> roles
- {
- get
- {
- this._update_roles (true, false);
- return this._roles_ro;
- }
- set { this.change_roles.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_roles (Set<RoleFieldDetails> roles)
- throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_roles (this, roles);
- }
-
- private bool _is_favourite = false;
-
- /**
- * Whether this contact is a user-defined favourite.
- *
- * This is stored in the X-FOLKS-FAVOURITE vCard field in EDS.
- *
- * @since 0.6.5
- */
- [CCode (notify = false)]
- public bool is_favourite
- {
- get
- {
- this._update_groups (true, false); /* also checks for favourites */
- return this._is_favourite;
- }
- set { this.change_is_favourite.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.5
- */
- public async void change_is_favourite (bool is_favourite) throws PropertyError
- {
- if (this._is_favourite == is_favourite)
- {
- return;
- }
-
- yield ((Edsf.PersonaStore) this.store)._set_is_favourite (this,
- is_favourite);
- }
-
- private SmallSet<string> _anti_links;
- private Set<string> _anti_links_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.7.3
- */
- [CCode (notify = false)]
- public Set<string> anti_links
- {
- get { return this._anti_links_ro; }
- set { this.change_anti_links.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.7.3
- */
- public async void change_anti_links (Set<string> anti_links)
- throws PropertyError
- {
- yield ((Edsf.PersonaStore) this.store)._set_anti_links (this, anti_links);
- }
-
- private SmallSet<string>? _system_groups = null;
- private Set<string>? _system_groups_ro = null;
- private bool _in_google_personal_group;
-
- /**
- * The complete set of system group identifiers the contact belongs to.
- * See {@link Persona.change_system_groups} for details.
- *
- * This is stored in the X-GOOGLE-SYSTEM-GROUP-IDS vCard field in EDS.
- *
- * @since 0.9.0
- */
- [CCode (notify = false)]
- public Set<string>? system_groups
- {
- get
- {
- this._update_groups (true);
- return this._system_groups_ro;
- }
-
- set { this.change_system_groups.begin (value); }
- }
-
- /**
- * Whether this contact is in the “My Contacts” section of the user’s address
- * book, rather than the “Other” section.
- *
- * @since 0.7.3
- */
- [CCode (notify = false)]
- public bool in_google_personal_group
- {
- get
- {
- this._update_groups (true); /* also checks for the personal group */
- return this._in_google_personal_group;
- }
-
- set { this.change_in_google_personal_group.begin (value); }
- }
-
- /**
- * Build a IID.
- *
- * This requires the UID field of the contact to be set to a non-empty value
- * already; if not, `null` will be returned.
- *
- * @param store_id the {@link PersonaStore.id}
- * @param contact the Contact, which must have a UID field set
- * @return a valid IID, or `null` if none could be constructed
- *
- * @since 0.6.0
- */
- internal static string? build_iid_from_contact (string store_id,
- E.Contact contact)
- {
- unowned var contact_id = contact.get_const<string>(E.ContactField.UID);
-
- /* If the contact has no UID, then we cannot support it. Callers must
- * this first. */
- if (contact_id == null || contact_id == "")
- return null;
-
- return Edsf.Persona.build_iid (store_id, contact_id);
- }
-
- /**
- * Build a IID.
- *
- * @param store_id the {@link PersonaStore.id}, which must be non-empty
- * @param contact_id the ID belonging to the contact, which must be non-empty
- * @return a valid IID
- *
- * @since 0.6.0
- */
- internal static string build_iid (string store_id, string contact_id)
- requires (store_id != "")
- requires (contact_id != "")
- {
- return "%s:%s".printf (store_id, contact_id);
- }
-
-
- /**
- * Create a new persona.
- *
- * Create a new persona for the {@link PersonaStore} ``store``, representing
- * the EDS contact given by ``contact``.
- *
- * @param store the store which will contain the persona
- * @param contact the EDS contact being represented by the persona
- *
- * @since 0.6.0
- */
- public Persona (PersonaStore store, E.Contact contact)
- {
- unowned var _contact_id = contact.get_const<string>(E.ContactField.UID);
- assert (_contact_id != null && _contact_id != "");
- unowned var contact_id = (!) _contact_id;
-
- var uid = Folks.Persona.build_uid (BACKEND_NAME, store.id, contact_id);
- var iid = Edsf.Persona.build_iid (store.id, contact_id);
- var is_user = BookClient.is_self (contact);
-
- /* Use the IID as the display ID since no other suitable identifier is
- * available which we can guarantee is unique within the store. */
- Object (display_id: iid,
- uid: uid,
- iid: iid,
- store: store,
- is_user: is_user,
- contact_id: contact_id,
- contact: contact);
- }
-
- construct
- {
- debug ("Creating new Edsf.Persona with IID '%s'", this.iid);
-
- this._gender = Gender.UNSPECIFIED;
- this._phone_numbers = new SmallSet<PhoneFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._phone_numbers_ro = this._phone_numbers.read_only_view;
- this._email_addresses = new SmallSet<EmailFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._email_addresses_ro = this._email_addresses.read_only_view;
- this._notes = new SmallSet<NoteFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._notes_ro = this._notes.read_only_view;
- this._urls = new SmallSet<UrlFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._urls_ro = this._urls.read_only_view;
- this._postal_addresses = new SmallSet<PostalAddressFieldDetails> (
- AbstractFieldDetails<PostalAddress>.hash_static,
- AbstractFieldDetails<PostalAddress>.equal_static);
- this._postal_addresses_ro = this._postal_addresses.read_only_view;
- this._local_ids = new SmallSet<string> ();
- this._local_ids_ro = this._local_ids.read_only_view;
- this._location = null;
- this._web_service_addresses =
- new HashMultiMap<string, WebServiceFieldDetails> (
- null, null, AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._groups = new SmallSet<string> ();
- this._groups_ro = this._groups.read_only_view;
- this._roles = new SmallSet<RoleFieldDetails> (
- AbstractFieldDetails<Role>.hash_static,
- AbstractFieldDetails<Role>.equal_static);
- this._roles_ro = this._roles.read_only_view;
- this._anti_links = new SmallSet<string> ();
- this._anti_links_ro = this._anti_links.read_only_view;
-
- this._update (this._contact);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override void linkable_property_to_links (string prop_name,
- Folks.Persona.LinkablePropertyCallback callback)
- {
- if (prop_name == "im-addresses")
- {
- var iter = this._im_addresses.map_iterator ();
-
- while (iter.next ())
- callback (iter.get_key () + ":" + iter.get_value ().value);
- }
- else if (prop_name == "local-ids")
- {
- /* Note: we need to use this.local_ids and not this._local_ids,
- * otherwise this can have a different behaviour depending
- * on the state of the current Persona depending on whether
- * this.local_ids was called before or not. */
- foreach (var id in this.local_ids)
- {
- callback (id);
- }
- }
- else if (prop_name == "web-service-addresses")
- {
- var iter = this.web_service_addresses.map_iterator ();
-
- while (iter.next ())
- callback (iter.get_key () + ":" + iter.get_value ().value);
- }
- else if (prop_name == "email-addresses")
- {
- foreach (var email in this._email_addresses)
- callback (email.value);
- }
- else
- {
- /* Chain up */
- base.linkable_property_to_links (prop_name, callback);
- }
- }
-
- ~Persona ()
- {
- debug ("Destroying Edsf.Persona '%s': %p", this.uid, this);
- }
-
- /**
- * Update attribs of the persona.
- */
- internal void _update (E.Contact updated_contact)
- {
- this.freeze_notify ();
-
- /* We get a new E.Contact instance from EDS containing all the updates,
- * so replace our existing contact with it. */
- this._contact = updated_contact;
- this.notify_property ("contact");
-
- this._update_names ();
- this._update_avatar ();
- this._update_urls (false);
- this._update_phones (false);
- this._update_addresses (false);
- this._update_emails ();
-
- /* Note: because we assume certain e-mail addresses
- * (@gmail, @msn, etc) to also be IM IDs we /must/
- * update the latter after we've taken care of the former.
- */
- this._update_im_addresses ();
-
- this._update_groups (false);
- this._update_notes (false);
- this._update_local_ids ();
- this._update_location ();
- this._update_web_services_addresses ();
- this._update_gender ();
- this._update_birthday ();
- this._update_roles (false);
- this._update_favourite ();
- this._update_anti_links ();
-
- this.thaw_notify ();
- }
-
- private void _update_params (AbstractFieldDetails details,
- E.VCardAttribute attr)
- {
- foreach (unowned E.VCardAttributeParam param in attr.get_params ())
- {
- string param_name = param.get_name ().down ();
- foreach (unowned string param_value in param.get_values ())
- {
- if (param_name == AbstractFieldDetails.PARAM_TYPE)
- {
- details.add_parameter (param_name, param_value.down ());
- }
- else
- {
- details.add_parameter (param_name, param_value);
- }
- }
- }
- }
-
- private void _update_gender ()
- {
- var gender = Gender.UNSPECIFIED;
- unowned E.VCardAttribute gender_attr =
- this.contact.get_attribute (Edsf.Persona.gender_attribute_name);
-
- if (gender_attr != null)
- {
- unowned var val = get_vcard_attr_value ((!) gender_attr);
- if (val != null)
- {
- switch (((!) val).up ())
- {
- case Edsf.Persona.gender_male:
- gender = Gender.MALE;
- break;
- case Edsf.Persona.gender_female:
- gender = Gender.FEMALE;
- break;
- default:
- /* Unspecified, as above */
- break;
- }
- }
- }
-
- if (this._gender != gender)
- {
- this._gender = gender;
- this.notify_property ("gender");
- }
- }
-
- private void _update_birthday ()
- {
- var _bday = this._get_property<E.ContactDate> ("birth_date");
-
- if (_bday != null)
- {
- unowned var bday = (!) _bday;
-
- /* Since e-d-s stores birthdays as a plain date, we take the
- * given date in local time and convert it to UTC as mandated
- * by the BirthdayDetails interface.
- * We cache the timezone since creating it requires mmapping
- * /etc/localtime, which means lots of syscalls. */
- var d = new DateTime (Persona._local_time_zone,
- (int) bday.year, (int) bday.month, (int) bday.day, 0, 0, 0.0);
-
- /* d might be null if their birthday in e-d-s is something that
- * doesn't make sense, like 31st February. If so, ignore it. */
- if (d != null && (this._birthday == null ||
- (this._birthday != null &&
- !((!) this._birthday).equal (d.to_utc ()))))
- {
- this._birthday = d.to_utc ();
- this.notify_property ("birthday");
- }
- }
- else
- {
- if (this._birthday != null)
- {
- this._birthday = null;
- this.notify_property ("birthday");
- }
- }
- }
-
- private void _update_roles (bool create_if_not_exist, bool emit_notification = true)
- {
- /* See the comments in Folks.Individual about the lazy instantiation
- * strategy for roles. */
- if (this._roles == null && create_if_not_exist == false)
- {
- if (emit_notification)
- {
- this.notify_property ("roles");
- }
- return;
- }
- else if (this._roles == null)
- {
- this._roles = new SmallSet<RoleFieldDetails> (
- AbstractFieldDetails<Role>.hash_static,
- AbstractFieldDetails<Role>.equal_static);
- this._roles_ro = this._roles.read_only_view;
- }
-
- var new_roles = new SmallSet<RoleFieldDetails> (
- AbstractFieldDetails<Role>.hash_static,
- AbstractFieldDetails<Role>.equal_static);
-
- var default_role_fd = this._get_default_role ();
- if (default_role_fd != null)
- {
- new_roles.add ((!) default_role_fd);
- }
-
- unowned E.VCard vcard = (E.VCard) this.contact;
- foreach (unowned E.VCardAttribute attr in vcard.get_attributes ())
- {
- if (attr.get_name () != "X-ROLES")
- continue;
-
- unowned var val = get_vcard_attr_value (attr);
- if (val == null || (!) val == "")
- {
- continue;
- }
-
- var role = new Role ("", "");
- role.role = (!) val;
- var role_fd = new RoleFieldDetails (role);
-
- foreach (unowned E.VCardAttributeParam param in
- attr.get_params ())
- {
- string param_name = param.get_name ().down ();
-
- if (param_name == "organisation_name")
- {
- foreach (unowned string param_value in
- param.get_values ())
- {
- role.organisation_name = param_value;
- break;
- }
- }
- else if (param_name == "title")
- {
- foreach (unowned string param_value in
- param.get_values ())
- {
- role.title = param_value;
- break;
- }
- }
- else
- {
- foreach (unowned string param_value in
- param.get_values ())
- {
- role_fd.add_parameter (param_name, param_value);
- }
- }
- }
-
- new_roles.add (role_fd);
- }
-
- if (!Folks.Internal.equal_sets<RoleFieldDetails> (new_roles, this._roles))
- {
- this._roles = new_roles;
- this._roles_ro = new_roles.read_only_view;
- if (emit_notification)
- {
- this.notify_property ("roles");
- }
- }
- }
-
- private RoleFieldDetails? _get_default_role ()
- {
- RoleFieldDetails? _default_role = null;
-
- unowned var org = this._get_string_property ("org");
- unowned var org_unit = this._get_string_property ("org_unit");
- unowned var office = this._get_string_property ("office");
- unowned var title = this._get_string_property ("title");
- unowned var role = this._get_string_property ("role");
- unowned var manager = this._get_string_property ("manager");
- unowned var assistant = this._get_string_property ("assistant");
-
- if (org != null ||
- org_unit != null ||
- office != null ||
- title != null ||
- role != null ||
- manager != null ||
- assistant != null)
- {
- var new_role = new Role (title, org);
- if (role != null && (!) role != "")
- new_role.role = (!) role;
-
- /* Check if it's non-empty. */
- if (!new_role.is_empty ())
- {
- var default_role = new RoleFieldDetails (new_role);
-
- if (org_unit != null && org_unit != "")
- default_role.set_parameter ("org_unit", (!) org_unit);
-
- if (office != null && office != "")
- default_role.set_parameter ("office", (!) office);
-
- if (manager != null && manager != "")
- default_role.set_parameter ("manager", (!) manager);
-
- if (assistant != null && manager != "")
- default_role.set_parameter ("assistant", (!) assistant);
-
- _default_role = default_role;
- }
- }
-
- return _default_role;
- }
-
- private void _update_web_services_addresses ()
- {
- var new_services = new HashMultiMap<string, WebServiceFieldDetails> (
- null, null, AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- unowned E.VCardAttribute services =
- this.contact.get_attribute ("X-FOLKS-WEB-SERVICES-IDS");
- if (services != null)
- {
- foreach (unowned VCardAttributeParam service in ((!) services).get_params ())
- {
- var service_name = service.get_name ().down ();
- foreach (unowned string service_id in service.get_values ())
- {
- if (service_id == "")
- {
- continue;
- }
-
- new_services.set (service_name,
- new WebServiceFieldDetails (service_id));
- }
- }
- }
-
- if (!Utils.multi_map_str_afd_equal (new_services,
- this._web_service_addresses))
- {
- this._web_service_addresses = new_services;
- this.notify_property ("web-service-addresses");
- }
- }
-
- private void _update_emails (bool emit_notification = true)
- {
- var new_email_addresses = new SmallSet<EmailFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- var attrs = this.contact.get_attributes (E.ContactField.EMAIL);
- foreach (unowned E.VCardAttribute attr in attrs)
- {
- unowned var val = get_vcard_attr_value (attr);
- if (val == null || (!) val == "")
- {
- continue;
- }
-
- var email_fd = new EmailFieldDetails ((!) val);
- this._update_params (email_fd, attr);
- new_email_addresses.add (email_fd);
- }
-
- if (!Folks.Internal.equal_sets<EmailFieldDetails> (new_email_addresses,
- this._email_addresses))
- {
- this._email_addresses = new_email_addresses;
- this._email_addresses_ro = new_email_addresses.read_only_view;
- if (emit_notification)
- {
- this.notify_property ("email-addresses");
- }
- }
- }
-
- private void _update_notes (bool create_if_not_exist, bool emit_notification = true)
- {
- /* See the comments in Folks.Individual about the lazy instantiation
- * strategy for notes. */
- if (this._notes == null && create_if_not_exist == false)
- {
- if (emit_notification)
- {
- this.notify_property ("notes");
- }
- return;
- }
- else if (this._notes == null)
- {
- this._notes = new SmallSet<NoteFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._notes_ro = this._notes.read_only_view;
- }
-
- var new_notes = new SmallSet<NoteFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- unowned var n = this._get_string_property ("note");
- if (n != null && n != "")
- {
- var note = new NoteFieldDetails ((!) n);
- new_notes.add (note);
- }
-
- if (!Folks.Internal.equal_sets<NoteFieldDetails> (new_notes, this._notes))
- {
- this._notes = new_notes;
- this._notes_ro = this._notes.read_only_view;
- if (emit_notification)
- {
- this.notify_property ("notes");
- }
- }
- }
-
- private void _update_names ()
- {
- unowned var full_name = this._get_string_property ("full_name") ?? "";
- if (this._full_name != full_name)
- {
- this._full_name = full_name;
- this.notify_property ("full-name");
- }
-
- unowned var nickname = this._get_string_property ("nickname") ?? "";
- if (this._nickname != nickname)
- {
- this._nickname = nickname;
- this.notify_property ("nickname");
- }
-
- StructuredName? structured_name = null;
- var _cn = this._get_property<E.ContactName> ("name");
- if (_cn != null)
- {
- unowned var cn = (!) _cn;
-
- unowned string family_name = cn.family;
- unowned string given_name = cn.given;
- unowned string additional_names = cn.additional;
- unowned string prefixes = cn.prefixes;
- unowned string suffixes = cn.suffixes;
- structured_name = new StructuredName (family_name, given_name,
- additional_names, prefixes,
- suffixes);
- }
-
- if (structured_name != null && !((!) structured_name).is_empty ())
- {
- this._structured_name = (!) structured_name;
- this.notify_property ("structured-name");
- }
- else if (this._structured_name != null)
- {
- this._structured_name = null;
- this.notify_property ("structured-name");
- }
- }
-
- private LoadableIcon? _contact_photo_to_loadable_icon (ContactPhoto? _p)
- {
- if (_p == null)
- {
- return null;
- }
-
- unowned var p = (!) _p;
-
- switch (p.type)
- {
- case ContactPhotoType.URI:
- unowned var uri = p.get_uri ();
- if (uri == null)
- {
- return null;
- }
-
- /* Ignore non-local files, or we could end up downloading huge
- * pictures that we really don’t want. Non-local URI-based contact
- * photos are rare anyway.
- *
- * See: https://bugzilla.gnome.org/show_bug.cgi?id=697695 */
- var scheme = Uri.parse_scheme (uri);
- if (scheme == null || scheme != "file")
- {
- warning ("Ignoring contact photo with URI ‘%s’ because it’s " +
- "not a local file.", uri);
- return null;
- }
-
- return new FileIcon (File.new_for_uri ((!) uri));
- case ContactPhotoType.INLINED:
- unowned var data = p.get_inlined ();
- unowned var mime_type = p.get_mime_type ();
- if (data == null || mime_type == null)
- {
- return null;
- }
-
- var bytes = new Bytes ((!) data);
- return new BytesIcon (bytes);
- default:
- return null;
- }
- }
-
- private void _update_avatar ()
- {
- var p = this._get_property<E.ContactPhoto> ("photo");
-
- // Convert the ContactPhoto to a LoadableIcon and store or update it.
- var new_avatar = this._contact_photo_to_loadable_icon (p);
-
- if (this._avatar != null && new_avatar == null)
- {
- this._avatar = null;
- this.notify_property ("avatar");
- }
- else if ((this._avatar == null && new_avatar != null) ||
- (this._avatar != null && new_avatar != null &&
- ((!) this._avatar).equal (new_avatar) == false))
- {
- this._avatar = new_avatar;
- this.notify_property ("avatar");
- }
- }
-
- private void _update_urls (bool create_if_not_exist, bool emit_notification = true)
- {
- /* See the comments in Folks.Individual about the lazy instantiation
- * strategy for URIs. */
- if (this._urls == null && create_if_not_exist == false)
- {
- if (emit_notification)
- {
- this.notify_property ("urls");
- }
- return;
- }
- else if (this._urls == null)
- {
- this._urls = new SmallSet<UrlFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._urls_ro = this._urls.read_only_view;
- }
-
- var new_urls = new SmallSet<UrlFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- /* First we get the standard Evo urls.. */
- foreach (unowned UrlTypeMapping mapping in Persona._url_properties)
- {
- unowned var url_property = mapping.vcard_field_name;
- unowned var folks_type = mapping.folks_type;
-
- unowned var u = this._get_string_property (url_property);
- if (u != null && u != "")
- {
- var fd_u = new UrlFieldDetails ((!) u);
- fd_u.set_parameter (AbstractFieldDetails.PARAM_TYPE, folks_type);
- new_urls.add (fd_u);
- }
- }
-
- /* Now we go for extra URLs */
- unowned E.VCard vcard = (E.VCard) this.contact;
- foreach (unowned E.VCardAttribute attr in vcard.get_attributes ())
- {
- if (attr.get_name () == "X-URIS")
- {
- unowned var val = get_vcard_attr_value (attr);
- if (val == null || (!) val == "")
- {
- continue;
- }
-
- var url_fd = new UrlFieldDetails ((!) val);
- this._update_params (url_fd, attr);
- new_urls.add (url_fd);
- }
- }
-
- if (!Utils.set_afd_equal (new_urls, this._urls))
- {
- this._urls = new_urls;
- this._urls_ro = new_urls.read_only_view;
- if (emit_notification)
- {
- this.notify_property ("urls");
- }
- }
- }
-
- private void _update_im_addresses ()
- {
- unowned var im_eds_map = Persona._get_im_eds_map ();
- var new_im_addresses = new HashMultiMap<string, ImFieldDetails> (null,
- null, AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- foreach (unowned string im_proto in im_eds_map.get_keys ())
- {
- var addresses = this.contact.get_attributes (
- im_eds_map.lookup (im_proto));
- foreach (unowned E.VCardAttribute attr in addresses)
- {
- try
- {
- unowned var addr = get_vcard_attr_value (attr);
- if (addr == null || (!) addr == "")
- {
- continue;
- }
-
- string normalised_addr =
- ImDetails.normalise_im_address ((!) addr, im_proto);
-
- if (normalised_addr == "")
- {
- continue;
- }
-
- var im_fd = new ImFieldDetails (normalised_addr);
- new_im_addresses.set (im_proto, im_fd);
- }
- catch (Folks.ImDetailsError e)
- {
- GLib.warning (
- "Problem when trying to normalise address: %s\n",
- e.message);
- }
- }
- }
-
- /* We consider some e-mail addresses to be IM IDs too. This
- * is pretty much a hack to make sure EDS contacts are
- * automatically linked with their corresponding Telepathy
- * Persona. As an undesired side effect we might end up having
- * IM addresses that aren't actually used as such (i.e.: people
- * who don't actually use GMail or MSN addresses for IM): this can be
- * detected by clients by looking for the
- * {@link Edsf.Persona.folks_field_attribute_name} parameter existing and
- * being set to `TRUE` on the {@link ImFieldDetails}.
- *
- * See: bgo#657142, bgo#723187
- */
- foreach (var email in this._email_addresses)
- {
- unowned var _proto = this._im_proto_from_addr (email.value);
- if (_proto != null)
- {
- unowned var proto = (!) _proto;
-
- /* Has this already been added? */
- var exists = false;
- Collection<ImFieldDetails>? current_im_addrs =
- new_im_addresses.get (proto);
- if (current_im_addrs != null)
- {
- foreach (var cur_im in (!) current_im_addrs)
- {
- if (cur_im.value == email.value)
- {
- exists = true;
- break;
- }
- }
- }
-
- if (exists)
- continue;
-
- try
- {
- string normalised_addr =
- ImDetails.normalise_im_address (email.value, proto);
- var im_fd = new ImFieldDetails (normalised_addr);
-
- im_fd.add_parameter (
- Edsf.Persona.folks_field_attribute_name, "TRUE");
- new_im_addresses.set (proto, im_fd);
- }
- catch (Folks.ImDetailsError e)
- {
- GLib.warning (
- "Problem when trying to normalise address: %s\n",
- e.message);
- }
- }
- }
-
- if (!Utils.multi_map_str_afd_equal (new_im_addresses,
- this._im_addresses))
- {
- this._im_addresses = new_im_addresses;
- this.notify_property ("im-addresses");
- }
- }
-
- private void _update_groups (bool create_if_not_exist, bool emit_notification = true)
- {
- /* See the comments in Folks.Individual about the lazy instantiation
- * strategy for groups. */
- if (this._groups == null && create_if_not_exist == false)
- {
- if (emit_notification)
- {
- this.notify_property ("groups");
- }
- return;
- }
-
- var category_names =
- this._contact.get<GLib.List<string>> (E.ContactField.CATEGORY_LIST);
- var new_categories = new SmallSet<string> ();
- bool any_added_categories = false;
-
- foreach (unowned string category_name in category_names)
- {
- /* Skip the “Starred in Android” group for Google personas; we handle
- * it later. */
- if (((Edsf.PersonaStore) store)._is_google_contacts_address_book () &&
- category_name == Edsf.PersonaStore.android_favourite_group_name)
- {
- continue;
- }
-
- new_categories.add (category_name);
-
- /* Is this a new category? */
- if (this._groups == null || !this._groups.contains (category_name))
- {
- any_added_categories = true;
- }
- }
-
- /* Work out if categories have been removed. */
- bool any_removed_categories = false;
-
- if (this._groups != null)
- {
- foreach (var category_name in this._groups)
- {
- /* Skip the “Starred in Android” group for Google personas; we handle
- * it later. */
- if (((Edsf.PersonaStore) store)._is_google_contacts_address_book () &&
- category_name == Edsf.PersonaStore.android_favourite_group_name)
- {
- continue;
- }
-
- if (!new_categories.contains (category_name))
- {
- any_removed_categories = true;
- }
- }
- }
-
- this._groups = new_categories;
- this._groups_ro = this._groups.read_only_view;
-
- /* Check our new set of system groups if this is a Google address book. */
- unowned var store = (Edsf.PersonaStore) this.store;
- var in_google_personal_group = false;
- var should_notify_sysgroups = false;
-
- if (store._is_google_contacts_address_book ())
- {
- unowned var vcard = (E.VCard) this.contact;
- unowned E.VCardAttribute? attr =
- vcard.get_attribute ("X-GOOGLE-SYSTEM-GROUP-IDS");
- if (attr != null)
- {
- unowned GLib.List<string> system_group_ids = attr.get_values();
- var new_sysgroups = new SmallSet<string> ();
- bool any_added_sysgroups = false;
-
- foreach (unowned string system_group_id in system_group_ids)
- {
- new_sysgroups.add (system_group_id);
-
- if (this._system_groups == null ||
- !this._system_groups.contains (system_group_id))
- {
- any_added_sysgroups = true;
- }
- }
-
- /* Work out if categories have been removed. */
- bool any_removed_sysgroups = false;
-
- if (this._system_groups != null)
- {
- foreach (var system_group_id in this._system_groups)
- {
- if (!new_sysgroups.contains (system_group_id))
- {
- any_removed_sysgroups = true;
- }
- }
- }
-
- /* If we're in the GDATA_CONTACTS_GROUP_CONTACTS group, then
- * we're in the user's "My Contacts" address book, as opposed
- * to their "Other" address book. */
- if (new_sysgroups.contains (GOOGLE_PERSONAL_GROUP_NAME))
- {
- in_google_personal_group = true;
- }
-
- this._system_groups = new_sysgroups;
- this._system_groups_ro = new_sysgroups.read_only_view;
-
- if (any_added_sysgroups || any_removed_sysgroups)
- should_notify_sysgroups = true;
- }
- else
- {
- this._system_groups = new SmallSet<string>();
- this._system_groups_ro = this._system_groups.read_only_view;
- }
- }
-
- /* Check whether our favourite status needs updating. */
- var old_is_favourite = this._is_favourite;
-
- if (store._is_google_contacts_address_book ())
- {
- this._is_favourite = false;
-
- foreach (unowned string category_name in category_names)
- {
- /* We link the “Starred in Android” group to Google Contacts
- * address books. See: bgo#661490. */
- if (category_name ==
- Edsf.PersonaStore.android_favourite_group_name)
- {
- this._is_favourite = true;
- }
- }
- }
-
- /* Notify if anything's changed. */
- this.freeze_notify ();
-
- if ((any_added_categories || any_removed_categories) &&
- emit_notification)
- {
- this.notify_property ("groups");
- }
- if (should_notify_sysgroups && emit_notification)
- {
- this.notify_property ("system-groups");
- }
- if (this._is_favourite != old_is_favourite && emit_notification)
- {
- this.notify_property ("is-favourite");
- }
- if (in_google_personal_group != this._in_google_personal_group)
- {
- this._in_google_personal_group = in_google_personal_group;
- if (emit_notification)
- {
- this.notify_property ("in-google-personal-group");
- }
- }
-
- this.thaw_notify ();
- }
-
- /**
- * build a table of im protocols / im protocol aliases
- */
- internal static unowned GLib.HashTable<unowned string, E.ContactField> _get_im_eds_map ()
- {
- if (Edsf.Persona._im_eds_map == null)
- {
- var table =
- new GLib.HashTable<unowned string, E.ContactField> (str_hash,
- str_equal);
-
- table.insert ("aim", ContactField.IM_AIM);
- table.insert ("yahoo", ContactField.IM_YAHOO);
- table.insert ("groupwise", ContactField.IM_GROUPWISE);
- table.insert ("jabber", ContactField.IM_JABBER);
- table.insert ("msn", ContactField.IM_MSN);
- table.insert ("icq", ContactField.IM_ICQ);
- table.insert ("gadugadu", ContactField.IM_GADUGADU);
- table.insert ("skype", ContactField.IM_SKYPE);
-
- Edsf.Persona._im_eds_map = table;
- }
-
- return (!) Edsf.Persona._im_eds_map;
- }
-
- private void _update_phones (bool create_if_not_exist, bool emit_notification = true)
- {
- /* See the comments in Folks.Individual about the lazy instantiation
- * strategy for phone numbers. */
- if (this._phone_numbers == null && create_if_not_exist == false)
- {
- this.notify_property ("phone-numbers");
- return;
- }
- else if (this._phone_numbers == null)
- {
- this._phone_numbers = new SmallSet<PhoneFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._phone_numbers_ro = this._phone_numbers.read_only_view;
- }
-
- var new_phone_numbers = new SmallSet<PhoneFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- var attrs = this.contact.get_attributes (E.ContactField.TEL);
- foreach (unowned E.VCardAttribute attr in attrs)
- {
- unowned var val = get_vcard_attr_value (attr);
- if (val == null || (!) val == "")
- {
- continue;
- }
-
- var phone_fd = new PhoneFieldDetails ((!) val);
- this._update_params (phone_fd, attr);
- new_phone_numbers.add (phone_fd);
- }
-
- // Does not use phone comparison because this will try to match only
- // numbers and will remove the prefix, and that could cause a wrong result
- // since the the phone number is stored as string
- if (!Utils.set_string_afd_equal (this._phone_numbers, new_phone_numbers))
- {
- this._phone_numbers = new_phone_numbers;
- this._phone_numbers_ro = new_phone_numbers.read_only_view;
- if (emit_notification)
- {
- this.notify_property ("phone-numbers");
- }
- }
- }
-
- private PostalAddress _postal_address_from_attribute (E.VCardAttribute attr)
- {
- unowned GLib.List<string>? values = attr.get_values();
- unowned GLib.List<string>? l = values;
-
- unowned var address_format = "";
- unowned var po_box = "";
- unowned var extension = "";
- unowned var street = "";
- unowned var locality = "";
- unowned var region = "";
- unowned var postal_code = "";
- unowned var country = "";
-
- if (l != null)
- {
- po_box = ((!) l).data;
- l = ((!) l).next;
- }
- if (l != null)
- {
- extension = ((!) l).data;
- l = ((!) l).next;
- }
- if (l != null)
- {
- street = ((!) l).data;
- l = ((!) l).next;
- }
- if (l != null)
- {
- locality = ((!) l).data;
- l = ((!) l).next;
- }
- if (l != null)
- {
- region = ((!) l).data;
- l = ((!) l).next;
- }
- if (l != null)
- {
- postal_code = ((!) l).data;
- l = ((!) l).next;
- }
- if (l != null)
- {
- country = ((!) l).data;
- l = ((!) l).next;
- }
-
- return new PostalAddress (po_box, extension, street,
- locality, region, postal_code, country,
- address_format, null);
- }
-
- /*
- * TODO: we should check if addresses corresponding to different types
- * are the same and if so instantiate only one PostalAddress
- * (with the given types).
- */
- private void _update_addresses (bool create_if_not_exist, bool emit_notification = true)
- {
- /* See the comments in Folks.Individual about the lazy instantiation
- * strategy for addresses. */
- if (this._postal_addresses == null && create_if_not_exist == false)
- {
- if (emit_notification)
- {
- this.notify_property ("postal-addresses");
- }
- return;
- }
- else if (this._postal_addresses == null)
- {
- this._postal_addresses = new SmallSet<PostalAddressFieldDetails> (
- AbstractFieldDetails<PostalAddress>.hash_static,
- AbstractFieldDetails<PostalAddress>.equal_static);
- this._postal_addresses_ro = this._postal_addresses.read_only_view;
- }
-
- var new_postal_addresses = new SmallSet<PostalAddressFieldDetails> (
- AbstractFieldDetails<PostalAddress>.hash_static,
- AbstractFieldDetails<PostalAddress>.equal_static);
-
- var attrs = this.contact.get_attributes (E.ContactField.ADDRESS);
- foreach (unowned E.VCardAttribute attr in attrs)
- {
- var address = this._postal_address_from_attribute (attr);
- if (address.is_empty ())
- {
- continue;
- }
-
- var pa_fd = new PostalAddressFieldDetails (address);
- this._update_params (pa_fd, attr);
- new_postal_addresses.add (pa_fd);
- }
-
- if (!Folks.Internal.equal_sets<PostalAddressFieldDetails> (
- new_postal_addresses,
- this._postal_addresses))
- {
- this._postal_addresses = new_postal_addresses;
- this._postal_addresses_ro = new_postal_addresses.read_only_view;
- if (emit_notification)
- {
- this.notify_property ("postal-addresses");
- }
- }
- }
-
- private void _update_local_ids ()
- {
- var new_local_ids = new SmallSet<string> ();
-
- unowned E.VCardAttribute ids =
- this.contact.get_attribute ("X-FOLKS-CONTACTS-IDS");
- if (ids != null)
- {
- unowned GLib.List<string> ids_v = ((!) ids).get_values ();
-
- foreach (unowned string local_id in ids_v)
- {
- if (local_id != "")
- {
- new_local_ids.add (local_id);
- }
- }
- }
-
- /* Make sure it includes our local id */
- new_local_ids.add (this.iid);
-
- if (!Folks.Internal.equal_sets<string> (new_local_ids, this.local_ids))
- {
- this._local_ids = new_local_ids;
- this._local_ids_ro = this._local_ids.read_only_view;
- this.notify_property ("local-ids");
- }
- }
-
- private void _update_location ()
- {
- var _geo = this._get_property<E.ContactGeo> ("geo");
-
- if (_geo != null)
- {
- if (this._location == null ||
- // Report all changes to the location because someone must
- // have changed the values intentionally.
- !this._location.equal_coordinates (_geo.latitude, _geo.longitude))
- {
- if (this._location != null)
- {
- // Update existing instance instead of destroying it
- // and creating a new one. Minimizes memory thrashing.
- this._location.latitude = _geo.latitude;
- this._location.longitude = _geo.longitude;
- }
- else
- {
- this._location = new Location (_geo.latitude, _geo.longitude);
- }
- this.notify_property ("location");
- }
- }
- else
- {
- if (this._location != null)
- {
- this._location = null;
- this.notify_property ("location");
- }
- }
- }
-
- private void _update_favourite ()
- {
- bool is_fav = false;
-
- unowned E.VCardAttribute fav =
- this.contact.get_attribute ("X-FOLKS-FAVOURITE");
- if (fav != null)
- {
- unowned var val = get_vcard_attr_value ((!) fav);
- if (val != null && ((!) val).down () == "true")
- {
- is_fav = true;
- }
- }
-
- if (is_fav != this._is_favourite)
- {
- this._is_favourite = is_fav;
- this.notify_property ("is-favourite");
- }
- }
-
- private void _update_anti_links ()
- {
- var new_anti_links = new SmallSet<string> ();
-
- unowned var vcard = (E.VCard) this.contact;
- foreach (unowned E.VCardAttribute attr in vcard.get_attributes ())
- {
- if (attr.get_name () != Edsf.PersonaStore.anti_links_attribute_name)
- {
- continue;
- }
-
- unowned var val = get_vcard_attr_value (attr);
- if (val == null || (!) val == "")
- {
- continue;
- }
-
- new_anti_links.add ((!) val);
- }
-
- if (!Folks.Internal.equal_sets<string> (new_anti_links, this._anti_links))
- {
- this._anti_links = new_anti_links;
- this._anti_links_ro = new_anti_links.read_only_view;
- this.notify_property ("anti-links");
- }
- }
-
- internal static T? _get_property_from_contact<T> (E.Contact contact,
- string prop_name)
- {
- T? prop_value = null;
- prop_value = contact.get<T> (E.Contact.field_id (prop_name));
- return prop_value;
- }
-
- private T? _get_property<T> (string prop_name)
- {
- return Edsf.Persona._get_property_from_contact<T> (this.contact,
- prop_name);
- }
-
- // Some helpers to prevent a lot of string copies
- private unowned string? _get_string_property (string prop_name)
- {
- var field = E.Contact.field_id (prop_name);
- return_val_if_fail (E.Contact.field_is_string (field), null);
- return contact.get_const<string> (field);
- }
-
- private unowned string? get_vcard_attr_value (E.VCardAttribute attr)
- {
- unowned var values = attr.get_values ();
- return (values != null)? values.data : null;
- }
-
- private unowned string? _im_proto_from_addr (string addr)
- {
- if (addr.index_of ("@") == -1)
- return null;
-
- var tokens = addr.split ("@", 2);
-
- if (tokens.length != 2)
- return null;
-
- unowned var domain = tokens[1];
- if (domain.index_of (".") == -1)
- return null;
-
- tokens = domain.split (".", 2);
-
- if (tokens.length != 2)
- return null;
-
- domain = tokens[0];
-
- if (domain == "msn" ||
- domain == "hotmail" ||
- domain == "live")
- return "msn";
- else if (domain == "gmail" ||
- domain == "googlemail")
- return "jabber";
- else if (domain == "yahoo")
- return "yahoo";
-
- return null;
- }
-}
diff --git a/backends/eds/lib/folks-eds.deps b/backends/eds/lib/folks-eds.deps
deleted file mode 100644
index 05bde793..00000000
--- a/backends/eds/lib/folks-eds.deps
+++ /dev/null
@@ -1,6 +0,0 @@
-glib-2.0
-gobject-2.0
-folks
-libebook-1.2
-libedataserver-1.2
-gee-0.8
diff --git a/backends/eds/lib/folks-eds.map b/backends/eds/lib/folks-eds.map
deleted file mode 100644
index b9955888..00000000
--- a/backends/eds/lib/folks-eds.map
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-global:
- EDSF_*;
- edsf_*;
-local:
- *;
-};
diff --git a/backends/eds/lib/meson.build b/backends/eds/lib/meson.build
deleted file mode 100644
index 3e3db84c..00000000
--- a/backends/eds/lib/meson.build
+++ /dev/null
@@ -1,99 +0,0 @@
-eds_backendlib_gir_name = 'FolksEds-@0@'.format(folks_api_version)
-
-eds_backendlib_sources = files(
- 'edsf-persona-store.vala',
- 'edsf-persona.vala',
-)
-
-eds_backendlib_sources += configure_file(
- input: namespace_vala_in,
- output: 'namespace.vala',
- configuration: {
- 'BACKENDLIB_GIR_NAME': eds_backendlib_gir_name.split('-')[0],
- 'BACKENDLIB_GIR_VERSION': folks_api_version,
- 'BACKENDLIB_NAMESPACE': 'Edsf',
- },
-)
-
-eds_backendlib_deps = [
- backend_deps,
- libebook_dep,
- libebook_contacts_dep,
- libedataserver_dep,
- libxml_dep,
-]
-
-# FIXME: we need to set these manually for the valadoc target as long as meson
-# doesn't have native support (https://github.com/mesonbuild/meson/issues/894)
-eds_backendlib_doc_deps = [
- '--pkg', 'folks',
- '--pkg', libebook_dep.name(),
-]
-
-eds_backendlib_vala_flags = [
- common_backendlib_vala_flags,
-]
-
-if eds_dep.version().version_compare('>=3.41')
- eds_backendlib_vala_flags += ['-D', 'HAS_EDS_3_41']
-endif
-
-eds_backendlib_c_flags = [
- '-include', 'config.h',
- '-DBACKEND_NAME="@0@"'.format(eds_backend_name),
- '-DG_LOG_DOMAIN="@0@"'.format(eds_backend_name),
-]
-
-eds_backendlib_symbolmap = meson.current_source_dir() / 'folks-@0@.map'.format(eds_backend_name)
-eds_backendlib_link_flags = cc.get_supported_link_arguments(
- '-Wl,--version-script,@0@'.format(eds_backendlib_symbolmap),
-)
-
-eds_backendlib = shared_library('folks-@0@'.format(eds_backend_name),
- eds_backendlib_sources,
- dependencies: eds_backendlib_deps,
- vala_args: eds_backendlib_vala_flags,
- c_args: eds_backendlib_c_flags,
- link_args: eds_backendlib_link_flags,
- link_depends: eds_backendlib_symbolmap,
- version: folks_eds_lib_version,
- vala_header: 'folks/folks-@0@.h'.format(eds_backend_name),
- vala_gir: eds_backendlib_gir_name + '.gir',
- install: true,
- install_dir: [ true, folks_headers_install_dir, true, true ],
-)
-
-# Also make sure to install the VAPI's .deps file
-install_data('folks-eds.deps',
- install_dir: get_option('datadir') / 'vala' / 'vapi',
-)
-
-# FIXME: This comes straight from the Meson docs on how to create/install a
-# typelib file for your Vala shared library. However, as mentioned in
-# https://github.com/mesonbuild/meson/issues/4481, this is not ideal.
-custom_target(eds_backendlib_gir_name + '.typelib',
- command: [ g_ir_compiler,
- '--includedir', libfolks_gir_include_dir,
- '--output', '@OUTPUT@',
- '--shared-library', 'lib' + eds_backendlib.name(),
- meson.current_build_dir() / (eds_backendlib_gir_name + '.gir')
- ],
- output: eds_backendlib_gir_name + '.typelib',
- depends: eds_backendlib,
- install: true,
- install_dir: folks_typelibdir,
-)
-
-eds_backendlib_dep = declare_dependency(
- link_with: eds_backendlib,
- include_directories: include_directories('.'),
-)
-
-# Pkg-config file
-pkgconfig.generate(eds_backendlib,
- name: 'Folks e-d-s support library',
- description: 'Evolution Data Server support library for the Folks meta-contacts library',
- filebase: 'folks-@0@'.format(eds_backend_name),
- requires: [ 'folks', glib_dep, gobject_dep, gee_dep, libebook_dep, libedataserver_dep ],
- variables: common_pkgconf_variables,
-)
diff --git a/backends/eds/meson.build b/backends/eds/meson.build
deleted file mode 100644
index 59585e16..00000000
--- a/backends/eds/meson.build
+++ /dev/null
@@ -1,38 +0,0 @@
-# Evolution-Data-Server (E-D-S) backend
-eds_backend_name = 'eds'
-
-# Backend library
-subdir('lib')
-
-eds_backend_sources = [
- 'eds-backend-factory.vala',
- 'eds-backend.vala',
-]
-
-eds_backend_deps = [
- backend_deps,
- libebook_dep,
- libedataserver_dep,
- eds_backendlib_dep,
-]
-
-eds_backend_vala_flags = [
-]
-
-eds_backend_c_flags = [
- '-include', 'config.h',
- '-DBACKEND_NAME="@0@"'.format(eds_backend_name),
- '-DG_LOG_DOMAIN="@0@"'.format(eds_backend_name),
-]
-
-eds_backend = shared_module('eds',
- eds_backend_sources,
- dependencies: eds_backend_deps,
- vala_args: eds_backend_vala_flags,
- c_args: eds_backend_c_flags,
- name_prefix: '',
- install_dir: folks_backend_dir / eds_backend_name,
- install: true,
-)
-
-folks_backends += eds_backend
diff --git a/backends/key-file/folks-key-file.deps b/backends/key-file/folks-key-file.deps
deleted file mode 100644
index 430a2e84..00000000
--- a/backends/key-file/folks-key-file.deps
+++ /dev/null
@@ -1,2 +0,0 @@
-folks
-gobject-2.0
diff --git a/backends/key-file/kf-backend-factory.vala b/backends/key-file/kf-backend-factory.vala
deleted file mode 100644
index da949fcb..00000000
--- a/backends/key-file/kf-backend-factory.vala
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2009 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>.
- * Copyright (C) 2009 Nokia Corporation.
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip.withnall@collabora.co.uk>
- *
- * This file was originally part of Rygel.
- */
-
-using Folks;
-
-/**
- * The backend module entry point.
- *
- * @backend_store a store to add the key-file backends to
- */
-public void module_init (BackendStore backend_store)
-{
- backend_store.add_backend (new Folks.Backends.Kf.Backend ());
-}
-
-/**
- * The backend module exit point.
- *
- * @param backend_store the store to remove the backends from
- */
-public void module_finalize (BackendStore backend_store)
-{
- /* FIXME: No way to remove backends from the store. */
-}
diff --git a/backends/key-file/kf-backend.vala b/backends/key-file/kf-backend.vala
deleted file mode 100644
index 940003b2..00000000
--- a/backends/key-file/kf-backend.vala
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-using Folks;
-using Folks.Backends.Kf;
-
-extern const string BACKEND_NAME;
-
-/**
- * A backend which loads {@link Persona}s from a simple key file in
- * (XDG_DATA_HOME/folks/) and presents them through a single
- * {@link PersonaStore}.
- *
- * @since 0.1.13
- */
-public class Folks.Backends.Kf.Backend : Folks.Backend
-{
- private bool _is_prepared = false;
- private bool _prepare_pending = false; /* used for unprepare() too */
- private bool _is_quiescent = false;
- private HashMap<string, PersonaStore> _persona_stores;
- private Map<string, PersonaStore> _persona_stores_ro;
-
- /**
- * Whether this Backend has been prepared.
- *
- * See {@link Folks.Backend.is_prepared}.
- *
- * @since 0.3.0
- */
- public override bool is_prepared
- {
- get { return this._is_prepared; }
- }
-
- /**
- * Whether this Backend has reached a quiescent state.
- *
- * See {@link Folks.Backend.is_quiescent}.
- *
- * @since 0.6.2
- */
- public override bool is_quiescent
- {
- get { return this._is_quiescent; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override string name { get { return BACKEND_NAME; } }
-
- /**
- * {@inheritDoc}
- */
- public override Map<string, Folks.PersonaStore> persona_stores
- {
- get { return this._persona_stores_ro; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override void enable_persona_store (Folks.PersonaStore store)
- {
- if (this._persona_stores.has_key (store.id) == false)
- {
- this._add_store ((Kf.PersonaStore) store);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public override void disable_persona_store (Folks.PersonaStore store)
- {
- if (this._persona_stores.has_key (store.id))
- {
- this._store_removed_cb (store);
- }
- }
-
- private File _get_default_file (string basename = "relationships")
- {
- string filename = basename + ".ini";
- File file = File.new_for_path (Environment.get_user_data_dir ());
- file = file.get_child ("folks");
- file = file.get_child (filename);
- return file;
- }
-
- /**
- * {@inheritDoc}
- * In this implementation storeids are assumed to be base filenames for
- * ini files under user_data_dir()/folks/ like the default relationships
- * {@link PersonaStore}.
- */
- public override void set_persona_stores (Set<string>? storeids)
- {
- /* All ids represent ini files in user_data_dir/folks/ */
-
- bool added_stores = false;
- PersonaStore[] removed_stores = {};
-
- /* First handle adding any missing persona stores. */
- foreach (string id in storeids)
- {
- if (this._persona_stores.has_key (id) == false)
- {
- File file = this._get_default_file (id);
-
- PersonaStore store = new Kf.PersonaStore (file);
- this._add_store (store, false);
- added_stores = true;
- }
- }
-
- foreach (PersonaStore store in this._persona_stores.values)
- {
- if (!storeids.contains (store.id))
- {
- removed_stores += store;
- }
- }
-
- for (int i = 0; i < removed_stores.length; ++i)
- {
- this._remove_store ((Kf.PersonaStore) removed_stores[i], false);
- }
-
- /* Finally, if anything changed, emit the persona-stores notification. */
- if (added_stores || removed_stores.length > 0)
- {
- this.notify_property ("persona-stores");
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public Backend ()
- {
- Object ();
- }
-
- construct
- {
- this._persona_stores = new HashMap<string, PersonaStore> ();
- this._persona_stores_ro = this._persona_stores.read_only_view;
- }
-
- /**
- * {@inheritDoc}
- */
- public override async void prepare () throws GLib.Error
- {
- var profiling = Internal.profiling_start ("preparing Kf.Backend");
-
- if (this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
- this.freeze_notify ();
-
- File file;
- unowned string path = Environment.get_variable (
- "FOLKS_BACKEND_KEY_FILE_PATH");
- if (path == null)
- {
- file = this._get_default_file ();
-
- debug ("Using built-in key file '%s' (override with " +
- "environment variable FOLKS_BACKEND_KEY_FILE_PATH)",
- file.get_path ());
- }
- else
- {
- file = File.new_for_path (path);
- debug ("Using environment variable " +
- "FOLKS_BACKEND_KEY_FILE_PATH = '%s' to load the key " +
- "file.", path);
- }
-
- /* Create the PersonaStore for the key file */
- PersonaStore store = new Kf.PersonaStore (file);
-
- this._add_store (store);
-
- this._is_prepared = true;
- this.notify_property ("is-prepared");
-
- this._is_quiescent = true;
- this.notify_property ("is-quiescent");
- }
- finally
- {
- this.thaw_notify ();
- this._prepare_pending = false;
- }
-
- Internal.profiling_end ((owned) profiling);
- }
-
- /**
- * Utility function to add a persona store.
- *
- * @param store the store to add.
- * @param notify whether or not to emit notification signals.
- */
- private void _add_store (PersonaStore store, bool notify = true)
- {
- this._persona_stores.set (store.id, store);
- store.removed.connect (this._store_removed_cb);
- this.persona_store_added (store);
- if (notify)
- {
- this.notify_property ("persona-stores");
- }
- }
-
- /**
- * Utility function to remove a persona store.
- *
- * @param store the store to remove.
- * @param notify whether or not to emit notification signals.
- */
- private void _remove_store (PersonaStore store, bool notify = true)
- {
- store.removed.disconnect (this._store_removed_cb);
- this._persona_stores.unset (store.id);
- this.persona_store_removed (store);
-
- if (notify)
- {
- this.notify_property ("persona-stores");
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public override async void unprepare () throws GLib.Error
- {
- if (!this._is_prepared || this._prepare_pending == true)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
- this.freeze_notify ();
-
- foreach (var persona_store in this._persona_stores.values)
- {
- this.persona_store_removed (persona_store);
- }
-
- this._persona_stores.clear ();
- this.notify_property ("persona-stores");
-
- this._is_quiescent = false;
- this.notify_property ("is-quiescent");
-
- this._is_prepared = false;
- this.notify_property ("is-prepared");
- }
- finally
- {
- this.thaw_notify ();
- this._prepare_pending = false;
- }
- }
-
- private void _store_removed_cb (Folks.PersonaStore store)
- {
- this._remove_store ((Kf.PersonaStore) store);
- }
-}
diff --git a/backends/key-file/kf-persona-store.vala b/backends/key-file/kf-persona-store.vala
deleted file mode 100644
index d234990a..00000000
--- a/backends/key-file/kf-persona-store.vala
+++ /dev/null
@@ -1,491 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-using Folks;
-using Folks.Backends.Kf;
-
-/**
- * A persona store which is associated with a single simple key file. It will
- * create a {@link Persona} for each of the groups in the key file.
- *
- * @since 0.1.13
- */
-public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
-{
- private HashMap<string, Persona> _personas;
- private Map<string, Persona> _personas_ro;
- private GLib.KeyFile _key_file;
- private unowned Cancellable _save_key_file_cancellable = null;
- private bool _is_prepared = false;
- private bool _prepare_pending = false;
- private bool _is_quiescent = false;
-
- private const string[] _always_writeable_properties =
- {
- "alias",
- "im-addresses",
- "web-service-addresses",
- "local-ids",
- "anti-links",
- null /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=682698 */
- };
-
- internal const string anti_links_key_name = "__anti-links";
-
- /**
- * {@inheritDoc}
- */
- public override string type_id { get { return BACKEND_NAME; } }
-
- /**
- * Whether this PersonaStore can add {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_add_personas}.
- *
- * @since 0.3.1
- */
- public override MaybeBool can_add_personas
- {
- get { return MaybeBool.TRUE; }
- }
-
- /**
- * Whether this PersonaStore can set the alias of {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_alias_personas}.
- *
- * @since 0.3.1
- */
- public override MaybeBool can_alias_personas
- {
- get { return MaybeBool.TRUE; }
- }
-
- /**
- * Whether this PersonaStore can set the groups of {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_group_personas}.
- *
- * @since 0.3.1
- */
- public override MaybeBool can_group_personas
- {
- get { return MaybeBool.FALSE; }
- }
-
- /**
- * Whether this PersonaStore can remove {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_remove_personas}.
- *
- * @since 0.3.1
- */
- public override MaybeBool can_remove_personas
- {
- get { return MaybeBool.TRUE; }
- }
-
- /**
- * Whether this PersonaStore has been prepared.
- *
- * See {@link Folks.PersonaStore.is_prepared}.
- *
- * @since 0.3.0
- */
- public override bool is_prepared
- {
- get { return this._is_prepared; }
- }
-
- /**
- * Whether this PersonaStore has reached a quiescent state.
- *
- * See {@link Folks.PersonaStore.is_quiescent}.
- *
- * @since 0.6.2
- */
- public override bool is_quiescent
- {
- get { return this._is_quiescent; }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public override string[] always_writeable_properties
- {
- get { return Kf.PersonaStore._always_writeable_properties; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override Map<string, Folks.Persona> personas
- {
- get { return this._personas_ro; }
- }
-
- /**
- * File containing the persona store data.
- *
- * This must be in GLib key file format.
- *
- * @since 0.6.6
- */
- public File file { get; construct; }
-
- /**
- * Create a new PersonaStore.
- *
- * Create a new persona store to expose the {@link Persona}s provided by the
- * different groups in the key file given by ``key_file``.
- */
- public PersonaStore (File key_file)
- {
- var id = key_file.get_basename ();
-
- Object (id: id,
- display_name: id,
- file: key_file);
- }
-
- construct
- {
- this.trust_level = PersonaStoreTrust.FULL;
- this._personas = new HashMap<string, Persona> ();
- this._personas_ro = this._personas.read_only_view;
- }
-
- /**
- * {@inheritDoc}
- */
- public override async void prepare ()
- {
- var profiling = Internal.profiling_start ("preparing Kf.PersonaStore (ID: %s)", this.id);
-
- if (this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
-
- var filename = this.file.get_path ();
- this._key_file = new GLib.KeyFile ();
-
- /* Load or create the file */
- while (true)
- {
- /* Load the file; if this fails due to the file not existing
- * or having been deleted in the meantime, we can continue
- * below and try to create it instead. */
- try
- {
- uint8[] contents;
-
- yield this.file.load_contents_async (null, out contents,
- null);
- unowned string contents_s = (string) contents;
-
- Internal.profiling_point ("loaded file in " +
- "Kf.PersonaStore (ID: %s)", this.id);
-
- if (contents_s.length > 0)
- {
- this._key_file.load_from_data (contents_s,
- contents_s.length,
- KeyFileFlags.KEEP_COMMENTS);
-
- Internal.profiling_point ("parsed data in " +
- "Kf.PersonaStore (ID: %s)", this.id);
- }
- break;
- }
- catch (Error e1)
- {
- if (!(e1 is IOError.NOT_FOUND))
- {
- warning (
- /* Translators: the first parameter is a filename, and
- * the second is an error message. */
- _("The relationship key file ‘%s’ could not be loaded: %s"),
- filename, e1.message);
- this.removed ();
- return;
- }
- }
-
- /* Ensure the parent directory tree exists for the new file */
- File parent_dir = this.file.get_parent ();
-
- try
- {
- /* Recursively create the directory */
- parent_dir.make_directory_with_parents ();
- }
- catch (Error e3)
- {
- if (!(e3 is IOError.EXISTS))
- {
- warning (
- /* Translators: the first parameter is a path, and the
- * second is an error message. */
- _("The relationship key file directory ‘%s’ could not be created: %s"),
- parent_dir.get_path (), e3.message);
- this.removed ();
- return;
- }
- }
-
- /* Create a new file; if this fails due to the file having been
- * created in the meantime, we can loop back round and try and
- * load it. */
- try
- {
- /* Create the file */
- FileOutputStream stream = yield this.file.create_async (
- FileCreateFlags.PRIVATE, Priority.DEFAULT);
- yield stream.close_async (Priority.DEFAULT);
- }
- catch (Error e2)
- {
- if (!(e2 is IOError.EXISTS))
- {
- warning (
- /* Translators: the first parameter is a filename, and
- * the second is an error message. */
- _("The relationship key file ‘%s’ could not be created: %s"),
- filename, e2.message);
- this.removed ();
- return;
- }
- }
- }
-
- /* We've loaded or created a key file by now, so cycle through the
- * groups: each group is a persona which we have to create and emit */
- var groups = this._key_file.get_groups ();
- var added_personas = new HashSet<Persona> ();
- foreach (var persona_id in groups)
- {
- Persona persona = new Kf.Persona (persona_id, this);
- this._personas.set (persona.iid, persona);
- added_personas.add (persona);
- }
-
- if (this._personas.size > 0)
- {
- /* FIXME: GroupDetails.ChangeReason is not the right enum to
- * use here */
- this._emit_personas_changed (added_personas, null);
- }
-
- this._is_prepared = true;
- this.notify_property ("is-prepared");
-
- /* We've finished loading all the personas we know about */
- this._is_quiescent = true;
- this.notify_property ("is-quiescent");
- }
- finally
- {
- this._prepare_pending = false;
- }
-
- Internal.profiling_end ((owned) profiling);
- }
-
- /**
- * {@inheritDoc}
- */
- public override async void flush ()
- {
- /* If there are any ongoing file operations, wait for them to finish
- * before returning. We have to iterate the main context manually to
- * achieve this, as all the code in this file is run in the main loop (in
- * the main thread). We would cause a deadlock if we used anything as
- * fancy/useful as a GCond. */
- MainContext context = MainContext.default ();
- while (this._save_key_file_cancellable != null)
- context.iteration (true);
- }
-
- /**
- * {@inheritDoc}
- */
- public override async void remove_persona (Folks.Persona persona)
- {
- debug ("Removing Persona '%s' (IID '%s', group '%s')", persona.uid,
- persona.iid, persona.display_id);
-
- try
- {
- this._key_file.remove_group (persona.display_id);
- yield this.save_key_file ();
-
- /* Signal the removal of the Persona */
- var personas = new SmallSet<Folks.Persona> ();
- personas.add (persona);
-
- this._emit_personas_changed (null, personas);
- }
- catch (KeyFileError e)
- {
- /* Ignore the error, since it's only about a missing group */
- }
- }
-
- /**
- * Add a new {@link Persona} to the PersonaStore.
- *
- * Accepted keys for ``details`` are:
- * - PersonaStore.detail_key (PersonaDetail.IM_ADDRESSES)
- * - PersonaStore.detail_key (PersonaDetail.WEB_SERVICE_ADDRESSES)
- *
- * See {@link Folks.PersonaStore.add_persona_from_details}.
- *
- * @throws Folks.PersonaStoreError.CREATE_FAILED if setting the persona’s
- * properties failed
- */
- public override async Folks.Persona? add_persona_from_details (
- HashTable<string, Value?> details) throws Folks.PersonaStoreError
- {
- unowned Value? val = details.lookup (Folks.PersonaStore.detail_key (
- PersonaDetail.IM_ADDRESSES));
- MultiMap<string, ImFieldDetails> im_addresses
- = val != null
- ? (MultiMap<string, ImFieldDetails>) val.get_object ()
- : null;
- unowned Value? val2 = details.lookup
- (Folks.PersonaStore.detail_key (PersonaDetail.WEB_SERVICE_ADDRESSES));
- MultiMap<string, WebServiceFieldDetails> web_service_addresses
- = val2 != null
- ? (MultiMap<string, WebServiceFieldDetails>) val2.get_object ()
- : null;
-
- unowned Value? val3 = details.lookup
- (Folks.PersonaStore.detail_key (PersonaDetail.LOCAL_IDS));
- Set<string> local_ids
- = val3 != null
- ? (Set<string>) val3.get_object ()
- : null;
-
- debug ("Adding Persona from details.");
-
- /* Generate a new random number for the persona's ID, so as to try and
- * ensure that IDs don't get recycled; if they did, anti-links which were
- * made against a key-file persona which used an ID which has been
- * re-used would be applied to the wrong persona (the new one, instead of
- * the old one, which could've been completely different). */
- string persona_id = null;
- do
- {
- persona_id = Random.next_int ().to_string ();
- }
- while (this._key_file.has_group (persona_id) == true);
-
- /* Create a new persona and set its addresses property to update the
- * key file */
- Persona persona = new Kf.Persona (persona_id, this);
- this._personas.set (persona.iid, persona);
-
- try
- {
- if (local_ids != null)
- {
- yield persona.change_local_ids (local_ids);
- }
- if (im_addresses != null)
- {
- yield persona.change_im_addresses (im_addresses);
- }
- if (web_service_addresses != null)
- {
- yield persona.change_web_service_addresses (
- web_service_addresses);
- }
- }
- catch (PropertyError e)
- {
- /* This should never happen. */
- throw new PersonaStoreError.CREATE_FAILED (e.message);
- }
-
- /* FIXME: GroupDetails.ChangeReason is not the right enum to use here */
- var personas = new SmallSet<Persona> ();
- personas.add (persona);
-
- this._emit_personas_changed (personas, null);
-
- return persona;
- }
-
- internal unowned KeyFile get_key_file ()
- {
- return this._key_file;
- }
-
- /* This is safe to call multiple times concurrently (in the same thread).
- * Previous calls will be cancelled when a new call begins. */
- internal async void save_key_file ()
- {
- var key_file_data = this._key_file.to_data ();
- var cancellable = new Cancellable ();
-
- debug ("Saving key file '%s'.", this.file.get_path ());
-
- /* There's no point in having two competing file write operations.
- * We can ensure that only one is running by just checking if a
- * cancellable is set. This is thread safe because the code in this file
- * is all run in the main thread (inside the main loop), so only we touch
- * this._save_key_file_cancellable (albeit in many weird and wonderful
- * orders due to idle handler queuing). */
- if (this._save_key_file_cancellable != null)
- this._save_key_file_cancellable.cancel ();
- this._save_key_file_cancellable = cancellable;
-
- try
- {
- yield this.file.replace_contents_async (key_file_data.data,
- null, false, FileCreateFlags.PRIVATE,
- cancellable, null);
- }
- catch (Error e)
- {
- if (!(e is IOError.CANCELLED))
- {
- /* Translators: the first parameter is a filename, the second is
- * an error message. */
- warning (_("Could not write updated key file ‘%s’: %s"),
- this.file.get_path (), e.message);
- }
- }
-
- if (this._save_key_file_cancellable == cancellable)
- this._save_key_file_cancellable = null;
- }
-}
diff --git a/backends/key-file/kf-persona.vala b/backends/key-file/kf-persona.vala
deleted file mode 100644
index 9642c5c0..00000000
--- a/backends/key-file/kf-persona.vala
+++ /dev/null
@@ -1,547 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-using Folks;
-using Folks.Backends.Kf;
-
-/**
- * A persona subclass which represents a single persona from a simple key file.
- *
- * @since 0.1.13
- */
-public class Folks.Backends.Kf.Persona : Folks.Persona,
- AliasDetails,
- AntiLinkable,
- ImDetails,
- LocalIdDetails,
- WebServiceDetails
-{
- private HashMultiMap<string, ImFieldDetails> _im_addresses;
- private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses;
- private string _alias = ""; /* must not be null */
- private const string[] _linkable_properties =
- {
- "im-addresses",
- "web-service-addresses",
- "local-ids",
- null /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=682698 */
- };
- private const string[] _writeable_properties =
- {
- "alias",
- "im-addresses",
- "web-service-addresses",
- "anti-links",
- null /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=682698 */
- };
-
- /**
- * {@inheritDoc}
- */
- public override string[] linkable_properties
- {
- get { return Kf.Persona._linkable_properties; }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override string[] writeable_properties
- {
- get { return Kf.Persona._writeable_properties; }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.1.15
- */
- [CCode (notify = false)]
- public string alias
- {
- get { return this._alias; }
- set { this.change_alias.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_alias (string alias) throws PropertyError
- {
- /* Deal with badly-behaved callers. */
- if (alias == null)
- {
- alias = "";
- }
-
- if (this._alias == alias)
- {
- return;
- }
-
- debug ("Setting alias of Kf.Persona '%s' to '%s'.", this.uid, alias);
-
- unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
- key_file.set_string (this.display_id, "__alias", alias);
- yield ((Kf.PersonaStore) this.store).save_key_file ();
-
- this._alias = alias;
- this.notify_property ("alias");
- }
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public MultiMap<string, ImFieldDetails> im_addresses
- {
- get { return this._im_addresses; }
- set { this.change_im_addresses.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_im_addresses (
- MultiMap<string, ImFieldDetails> im_addresses) throws PropertyError
- {
- unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
-
- /* Remove the current IM addresses from the key file */
- foreach (var protocol1 in this._im_addresses.get_keys ())
- {
- try
- {
- key_file.remove_key (this.display_id, protocol1);
- }
- catch (KeyFileError e1)
- {
- /* Ignore the error, since it's just a group or key not found
- * error. */
- }
- }
-
- /* Add the new IM addresses to the key file and build a normalised
- * table of them to set as the new property value */
- var new_im_addresses = new HashMultiMap<string, ImFieldDetails> (
- null, null, AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- foreach (var protocol2 in im_addresses.get_keys ())
- {
- var addresses = im_addresses.get (protocol2);
- var normalised_addresses = new SmallSet<string> ();
-
- foreach (var im_fd in addresses)
- {
- string normalised_address;
- try
- {
- normalised_address = ImDetails.normalise_im_address (
- im_fd.value, protocol2);
- }
- catch (ImDetailsError e2)
- {
- throw new PropertyError.INVALID_VALUE (
- /* Translators: this is an error message for if the user
- * provides an invalid IM address. The first parameter is
- * an IM address (e.g. “foo@jabber.org”), the second is
- * the name of a protocol (e.g. “jabber”) and the third is
- * an error message. */
- _("Invalid IM address ‘%s’ for protocol ‘%s’: %s"),
- im_fd.value, protocol2, e2.message);
- }
-
- normalised_addresses.add (normalised_address);
- var new_im_fd = new ImFieldDetails (normalised_address);
- new_im_addresses.set (protocol2, new_im_fd);
- }
-
- string[] addrs = (string[]) normalised_addresses.to_array ();
- addrs.length = normalised_addresses.size;
-
- key_file.set_string_list (this.display_id, protocol2, addrs);
- }
-
- /* Get the PersonaStore to save the key file */
- yield ((Kf.PersonaStore) this.store).save_key_file ();
-
- this._im_addresses = new_im_addresses;
- this.notify_property ("im-addresses");
- }
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public MultiMap<string, WebServiceFieldDetails> web_service_addresses
- {
- get { return this._web_service_addresses; }
- set { this.change_web_service_addresses.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_web_service_addresses (
- MultiMap<string, WebServiceFieldDetails> web_service_addresses)
- throws PropertyError
- {
- unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
-
- /* Remove the current web service addresses from the key file */
- foreach (var web_service1 in this._web_service_addresses.get_keys ())
- {
- try
- {
- key_file.remove_key (this.display_id,
- "web-service." + web_service1);
- }
- catch (KeyFileError e)
- {
- /* Ignore the error, since it's just a group or key not found
- * error. */
- }
- }
-
- /* Add the new web service addresses to the key file and build a
- * table of them to set as the new property value */
- var new_web_service_addresses =
- new HashMultiMap<string, WebServiceFieldDetails> (
- null, null, AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- foreach (var web_service2 in web_service_addresses.get_keys ())
- {
- var ws_fds = web_service_addresses.get (web_service2);
-
- string[] addrs = new string[0];
- foreach (var ws_fd1 in ws_fds)
- addrs += ws_fd1.value;
-
- key_file.set_string_list (this.display_id,
- "web-service." + web_service2, addrs);
-
- foreach (var ws_fd2 in ws_fds)
- new_web_service_addresses.set (web_service2, ws_fd2);
- }
-
- /* Get the PersonaStore to save the key file */
- yield ((Kf.PersonaStore) this.store).save_key_file ();
-
- this._web_service_addresses = new_web_service_addresses;
- this.notify_property ("web-service-addresses");
- }
-
- private SmallSet<string> _anti_links;
- private Set<string> _anti_links_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.7.3
- */
- [CCode (notify = false)]
- public Set<string> anti_links
- {
- get { return this._anti_links_ro; }
- set { this.change_anti_links.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.7.3
- */
- public async void change_anti_links (Set<string> anti_links)
- throws PropertyError
- {
- if (Folks.Internal.equal_sets<string> (anti_links, this.anti_links))
- {
- return;
- }
-
- unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
-
- /* Skip the persona's UID; don't allow reflexive anti-links. */
- anti_links.remove (this.uid);
-
- key_file.set_string_list (this.display_id,
- Kf.PersonaStore.anti_links_key_name, anti_links.to_array ());
-
- /* Get the PersonaStore to save the key file */
- yield ((Kf.PersonaStore) this.store).save_key_file ();
-
- /* Update the stored anti-links. */
- this._anti_links.clear ();
- this._anti_links.add_all (anti_links);
- this.notify_property ("anti-links");
- }
-
- private SmallSet<string> _local_ids;
- private Set<string> _local_ids_ro;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.14.0
- */
-
- [CCode (notify = false)]
- public Set<string> local_ids
- {
- get
- {
- if (this._local_ids.contains (this.iid) == false)
- {
- this._local_ids.add (this.iid);
- }
- return this._local_ids_ro;
- }
- set { this.change_local_ids.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.14.0
- */
- public async void change_local_ids (Set<string> local_ids)
- throws PropertyError
- {
- if (Folks.Internal.equal_sets<string> (local_ids, this._local_ids))
- {
- return;
- }
-
- unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
-
- /* Skip the persona's UID; don't allow reflexive anti-links. */
- //anti_links.remove (this.uid);
-
- key_file.set_string_list (this.display_id,
- "__local-ids", local_ids.to_array ());
-
- /* Get the PersonaStore to save the key file */
- yield ((Kf.PersonaStore) this.store).save_key_file ();
-
- /* Update the stored local_ids. */
- this._local_ids.clear ();
- this._local_ids.add_all (local_ids);
- this.notify_property ("local-ids");
- }
-
- /**
- * Create a new persona.
- *
- * Create a new persona for the {@link PersonaStore} ``store``, representing
- * the Persona given by the group ``uid`` in the key file ``key_file``.
- */
- public Persona (string id, Folks.PersonaStore store)
- {
- var iid = store.id + ":" + id;
- var uid = Folks.Persona.build_uid ("key-file", store.id, id);
-
- Object (display_id: id,
- iid: iid,
- uid: uid,
- store: store,
- is_user: false);
- }
-
- construct
- {
- debug ("Adding key-file Persona '%s' (IID '%s', group '%s')", this.uid,
- this.iid, this.display_id);
-
- this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
- null, null, AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._web_service_addresses =
- new HashMultiMap<string, WebServiceFieldDetails> (
- null, null, AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._anti_links = new SmallSet<string> ();
- this._anti_links_ro = this._anti_links.read_only_view;
- this._local_ids = new SmallSet<string> ();
- this._local_ids_ro = this._local_ids.read_only_view;
-
- /* Load the IM addresses from the key file */
- unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
-
- try
- {
- var keys = key_file.get_keys (this.display_id);
- foreach (unowned string key in keys)
- {
- /* Alias */
- if (key == "__alias")
- {
- this._alias = key_file.get_string (this.display_id, key);
-
- if (this._alias == null)
- {
- this._alias = "";
- }
-
- debug (" Loaded alias '%s'.", this._alias);
- continue;
- }
-
- /* Anti-links. */
- if (key == Kf.PersonaStore.anti_links_key_name)
- {
- var anti_link_array =
- key_file.get_string_list (this.display_id, key);
-
- if (anti_link_array != null)
- {
- foreach (var anti_link in anti_link_array)
- {
- this._anti_links.add (anti_link);
- }
-
- debug (" Loaded %u anti-links.",
- anti_link_array.length);
- continue;
- }
- }
- /* Local-ids. */
- if (key == "__local_ids")
- {
- var local_ids_array =
- key_file.get_string_list (this.display_id, key);
-
- if (local_ids_array != null)
- {
- foreach (var local_id in local_ids_array)
- {
- this.local_ids.add (local_id);
- }
-
- debug (" Loaded %u local_ids.",
- local_ids_array.length);
- continue;
- }
- }
-
-
- /* Web service addresses */
- var decomposed_key = key.split(".", 2);
- if (decomposed_key.length == 2 &&
- decomposed_key[0] == "web-service")
- {
- unowned string web_service = decomposed_key[1];
- var web_service_addresses = key_file.get_string_list (
- this.display_id, web_service);
-
- foreach (var web_service_address in web_service_addresses)
- {
- this._web_service_addresses.set (web_service,
- new WebServiceFieldDetails (web_service_address));
- }
-
- continue;
- }
-
- /* IM addresses */
- unowned string protocol = key;
- var im_addresses = key_file.get_string_list (
- this.display_id, protocol);
-
- foreach (var im_address in im_addresses)
- {
- string address;
- try
- {
- address = ImDetails.normalise_im_address (im_address,
- protocol);
- }
- catch (ImDetailsError e)
- {
- /* Warn of and ignore any invalid IM addresses */
- warning (e.message);
- continue;
- }
-
- var im_fd = new ImFieldDetails (address);
- this._im_addresses.set (protocol, im_fd);
- }
- }
- }
- catch (KeyFileError e)
- {
- /* We get a GROUP_NOT_FOUND exception if we're creating a new
- * Persona, since it doesn't yet exist in the key file. We shouldn't
- * get any other exceptions, since we're iterating through a list of
- * keys we've just retrieved. */
- if (!(e is KeyFileError.GROUP_NOT_FOUND))
- {
- /* Translators: the parameter is an error message. */
- warning (_("Couldn’t load data from key file: %s"), e.message);
- }
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public override void linkable_property_to_links (string prop_name,
- Folks.Persona.LinkablePropertyCallback callback)
- {
- if (prop_name == "im-addresses")
- {
- var iter = this._im_addresses.map_iterator ();
-
- while (iter.next ())
- callback (iter.get_key () + ":" + iter.get_value ().value);
- }
- else if (prop_name == "local-ids")
- {
- if (this._local_ids != null)
- foreach (var id in this._local_ids)
- {
- callback (id);
- }
- }
- else if (prop_name == "web-service-addresses")
- {
- var iter = this.web_service_addresses.map_iterator ();
-
- while (iter.next ())
- callback (iter.get_key () + ":" + iter.get_value ().value);
- }
- else
- {
- /* Chain up */
- base.linkable_property_to_links (prop_name, callback);
- }
- }
-}
diff --git a/backends/key-file/meson.build b/backends/key-file/meson.build
deleted file mode 100644
index 83733bab..00000000
--- a/backends/key-file/meson.build
+++ /dev/null
@@ -1,33 +0,0 @@
-keyfile_backend_name = 'key-file'
-
-keyfile_backend_sources = [
- 'kf-backend-factory.vala',
- 'kf-backend.vala',
- 'kf-persona-store.vala',
- 'kf-persona.vala',
-]
-
-keyfile_backend_deps = [
- backend_deps,
-]
-
-keyfile_backend_vala_flags = [
-]
-
-keyfile_backend_c_flags = [
- '-include', 'config.h',
- '-DBACKEND_NAME="@0@"'.format(keyfile_backend_name),
- '-DG_LOG_DOMAIN="@0@"'.format(keyfile_backend_name),
-]
-
-keyfile_backend = shared_module(keyfile_backend_name,
- keyfile_backend_sources,
- dependencies: keyfile_backend_deps,
- vala_args: keyfile_backend_vala_flags,
- c_args: keyfile_backend_c_flags,
- name_prefix: '',
- install_dir: folks_backend_dir / keyfile_backend_name,
- install: true,
-)
-
-folks_backends += keyfile_backend
diff --git a/backends/meson.build b/backends/meson.build
deleted file mode 100644
index f3557257..00000000
--- a/backends/meson.build
+++ /dev/null
@@ -1,40 +0,0 @@
-# Common dependencies
-backend_deps = [
- build_conf_dep,
- libfolks_dep,
- libfolks_internal_dep,
-]
-
-# Common flags
-common_backendlib_vala_flags = [
- # make sure the VAPIs expose the right include path
- '--includedir', meson.project_name(),
-]
-
-# Namespace Vala file
-#
-# FIXME: This can be removed once the backend namespaces have been sanitised.
-# https://gitlab.gnome.org/GNOME/folks/issues/73
-#
-# This file sets namespace and version attributes for GIR.
-namespace_vala_in = files('namespace.vala.in')
-
-# A subdirectory for each type of backend
-subdir('dummy')
-subdir('key-file')
-
-if telepathy_backend_enabled
- subdir('telepathy')
-endif
-
-if eds_backend_enabled
- subdir('eds')
-endif
-
-if ofono_backend_enabled
- subdir('ofono')
-endif
-
-if bluez_backend_enabled
- subdir('bluez')
-endif
diff --git a/backends/namespace.vala.in b/backends/namespace.vala.in
deleted file mode 100644
index 9681a74c..00000000
--- a/backends/namespace.vala.in
+++ /dev/null
@@ -1,6 +0,0 @@
-// FIXME: This can be removed once the backend namespaces have been sanitised.
-// https://gitlab.gnome.org/GNOME/folks/issues/73
-//
-// This file sets namespace and version attributes for GIR.
-[CCode (gir_namespace = "@BACKENDLIB_GIR_NAME@", gir_version = "@BACKENDLIB_GIR_VERSION@")]
-namespace @BACKENDLIB_NAMESPACE@ { }
diff --git a/backends/ofono/meson.build b/backends/ofono/meson.build
deleted file mode 100644
index 44460519..00000000
--- a/backends/ofono/meson.build
+++ /dev/null
@@ -1,36 +0,0 @@
-ofono_backend_name = 'ofono'
-
-ofono_backend_sources = [
- 'ofono-backend-factory.vala',
- 'ofono-backend.vala',
- 'ofono-persona-store.vala',
- 'ofono-persona.vala',
- 'org-ofono.vala',
-]
-
-ofono_backend_deps = [
- backend_deps,
- libebook_dep,
- libedataserver_dep,
-]
-
-ofono_backend_vala_flags = [
-]
-
-ofono_backend_c_flags = [
- '-include', 'config.h',
- '-DBACKEND_NAME="@0@"'.format(ofono_backend_name),
- '-DG_LOG_DOMAIN="@0@"'.format(ofono_backend_name),
-]
-
-ofono_backend = shared_module(ofono_backend_name,
- ofono_backend_sources,
- dependencies: ofono_backend_deps,
- vala_args: ofono_backend_vala_flags,
- c_args: ofono_backend_c_flags,
- name_prefix: '',
- install_dir: folks_backend_dir / ofono_backend_name,
- install: true,
-)
-
-folks_backends += ofono_backend
diff --git a/backends/ofono/ofono-backend-factory.vala b/backends/ofono/ofono-backend-factory.vala
deleted file mode 100644
index de2c9460..00000000
--- a/backends/ofono/ofono-backend-factory.vala
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2009 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>.
- * Copyright (C) 2009 Nokia Corporation.
- * Copyright (C) 2012 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Jeremy Whiting <jeremy.whiting@collabora.co.uk>
- *
- * Based on kf-backend-factory.vala by:
- * Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using Folks;
-
-/**
- * The backend module entry point.
- *
- * @param backend_store the {@link BackendStore} to use in this factory.
- *
- * @since 0.9.0
- */
-public void module_init (BackendStore backend_store)
-{
- backend_store.add_backend (new Folks.Backends.Ofono.Backend ());
-}
-
-/**
- * The backend module exit point.
- *
- * @param backend_store the {@link BackendStore} used in this factory.
- *
- * @since 0.9.0
- */
-public void module_finalize (BackendStore backend_store)
-{
- /* FIXME: No way to remove backends from the store. */
-}
diff --git a/backends/ofono/ofono-backend.vala b/backends/ofono/ofono-backend.vala
deleted file mode 100644
index f0f374b8..00000000
--- a/backends/ofono/ofono-backend.vala
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * Copyright (C) 2012 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Jeremy Whiting <jeremy.whiting@collabora.co.uk>
- *
- * Based on kf-backend.vala by:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-using Folks;
-using Folks.Backends.Ofono;
-using org.ofono;
-
-extern const string BACKEND_NAME;
-
-/* FIXME: Once we depend on gettext 0.18.3, translatable strings can once more
- * be split over multiple lines without breaking the .po file. */
-
-/**
- * A backend which loads {@link Persona}s from Modem
- * devices using the Ofono Phonebook D-Bus API and presents them
- * using one {@link PersonaStore} per device.
- *
- * @since 0.9.0
- */
-public class Folks.Backends.Ofono.Backend : Folks.Backend
-{
- private bool _is_prepared = false;
- private bool _prepare_pending = false; /* used for unprepare() too */
- private bool _is_quiescent = false;
- private HashMap<string, PersonaStore> _persona_stores;
- private Map<string, PersonaStore> _persona_stores_ro;
- private ModemProperties[] _modems;
-
- /**
- * {@inheritDoc}
- */
- public override bool is_prepared
- {
- get { return this._is_prepared; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override bool is_quiescent
- {
- get { return this._is_quiescent; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override string name { get { return BACKEND_NAME; } }
-
- /**
- * {@inheritDoc}
- */
- public override Map<string, Folks.PersonaStore> persona_stores
- {
- get { return this._persona_stores_ro; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override void disable_persona_store (Folks.PersonaStore store)
- {
- if (this._persona_stores.has_key (store.id))
- {
- this._store_removed_cb (store);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public override void enable_persona_store (Folks.PersonaStore store)
- {
- if (this._persona_stores.has_key (store.id) == false)
- {
- this._add_store ((Ofono.PersonaStore) store);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public override void set_persona_stores (Set<string>? storeids)
- {
- bool added_stores = false;
- PersonaStore[] removed_stores = {};
-
- /* First handle adding any missing persona stores. */
- foreach (ModemProperties modem in this._modems)
- {
- if (modem.path in storeids &&
- this._persona_stores.has_key (modem.path) == false)
- {
- string alias = this._modem_alias (modem.properties);
- PersonaStore store = new Ofono.PersonaStore (modem.path, alias);
- this._add_store (store, false);
- added_stores = true;
- }
- }
-
- foreach (PersonaStore store in this._persona_stores.values)
- {
- if (!storeids.contains (store.id))
- {
- removed_stores += store;
- }
- }
-
- for (int i = 0; i < removed_stores.length; ++i)
- {
- this._remove_store ((Ofono.PersonaStore) removed_stores[i], false);
- }
-
- /* Finally, if anything changed, emit the persona-stores notification. */
- if (added_stores || removed_stores.length > 0)
- {
- this.notify_property ("persona-stores");
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public Backend ()
- {
- Object ();
- }
-
- construct
- {
- this._persona_stores = new HashMap<string, PersonaStore> ();
- this._persona_stores_ro = this._persona_stores.read_only_view;
- }
-
- private void _add_modem (ObjectPath path, string alias)
- {
- PersonaStore store =
- new Ofono.PersonaStore (path, alias);
-
- this._add_store (store);
- }
-
- /**
- * {@inheritDoc}
- */
- public override async void prepare () throws DBusError
- {
- var profiling = Internal.profiling_start ("preparing Ofono.Backend");
-
- if (this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
- this.freeze_notify ();
-
- /* New modem devices can be caught in notifications */
- Manager manager;
-
- try
- {
- manager = yield Bus.get_proxy (BusType.SYSTEM, "org.ofono", "/");
- manager.ModemAdded.connect (this._modem_added);
- manager.ModemRemoved.connect (this._modem_removed);
-
- this._modems = manager.GetModems ();
- }
- catch (GLib.Error e1)
- {
- throw new DBusError.SERVICE_UNKNOWN (
- _("No oFono object manager running, so the oFono backend will be inactive. Either oFono isn’t installed or the service can’t be started."));
- }
-
- foreach (ModemProperties modem in this._modems)
- {
- this._modem_added (modem.path, modem.properties);
- }
-
- this._is_prepared = true;
- this.notify_property ("is-prepared");
-
- this._is_quiescent = true;
- this.notify_property ("is-quiescent");
- }
- finally
- {
- this.thaw_notify ();
- this._prepare_pending = false;
- }
-
- Internal.profiling_end ((owned) profiling);
- }
-
- /**
- * {@inheritDoc}
- */
- public override async void unprepare () throws GLib.Error
- {
- if (!this._is_prepared || this._prepare_pending == true)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
- this.freeze_notify ();
-
- foreach (var persona_store in this._persona_stores.values)
- {
- this.persona_store_removed (persona_store);
- }
-
- this._persona_stores.clear ();
- this.notify_property ("persona-stores");
-
- this._is_quiescent = false;
- this.notify_property ("is-quiescent");
-
- this._is_prepared = false;
- this.notify_property ("is-prepared");
- }
- finally
- {
- this.thaw_notify ();
- this._prepare_pending = false;
- }
- }
-
- /**
- * Utility function to extract a modem's alias from its properties.
- *
- * @param properties, the properties of the modem.
- * @return the alias to use for this modem.
- */
- private string _modem_alias (HashTable<string, Variant> properties)
- {
- string alias = "";
-
- /* Name is more user friendly than Manufacturer, but both are optional,
- * so use Name if it's there, otherwise Manufacturer, otherwise leave
- * it blank. */
- Variant? name_variant = properties.get ("Name");
- Variant? manufacturer_variant = properties.get ("Manufacturer");
- if (name_variant != null)
- {
- alias = name_variant.get_string ();
- }
- else if (manufacturer_variant != null)
- {
- alias = manufacturer_variant.get_string ();
- }
- return alias;
- }
-
- private void _modem_added (ObjectPath path, HashTable<string, Variant> properties)
- {
- bool has_sim = false;
- bool has_phonebook = false;
-
- Variant? features_variant = properties.get ("Features");
- if (features_variant != null)
- {
- var features = features_variant.get_strv ();
- /* FIXME: can't use the ‘in’ operator because of
- * https://bugzilla.gnome.org/show_bug.cgi?id=709672 */
- foreach (var feature in features)
- {
- if (feature == "sim")
- {
- has_sim = true;
- break;
- }
- }
- }
-
- /* If the modem doesn't have a SIM, don't go any further. */
- if (has_sim == false)
- return;
-
- Variant? interfaces_variant = properties.get ("Interfaces");
- if (interfaces_variant != null)
- {
- var interfaces = interfaces_variant.get_strv ();
- /* FIXME: and here */
- foreach (var interf in interfaces)
- {
- if (interf == "org.ofono.Phonebook")
- {
- has_phonebook = true;
- break;
- }
- }
- }
-
- if (has_phonebook == false)
- return;
-
- /* The modem has both a SIM and a phonebook, so can be wrapped by a
- * persona store. */
- string alias = this._modem_alias (properties);
- this._add_modem (path, alias);
- }
-
- /**
- * Utility function to add a persona store.
- *
- * @param store the store to add.
- * @param notify whether or not to emit notification signals.
- */
- private void _add_store (PersonaStore store, bool notify = true)
- {
- this._persona_stores.set (store.id, store);
- store.removed.connect (this._store_removed_cb);
- this.persona_store_added (store);
- if (notify)
- {
- this.notify_property ("persona-stores");
- }
- }
-
- /**
- * Utility function to remove a persona store.
- *
- * @param store the store to remove.
- * @param notify whether or not to emit notification signals.
- */
- private void _remove_store (PersonaStore store, bool notify = true)
- {
- store.removed.disconnect (this._store_removed_cb);
- this._persona_stores.unset (store.id);
- this.persona_store_removed (store);
-
- if (notify)
- {
- this.notify_property ("persona-stores");
- }
- }
-
-
- private void _modem_removed (ObjectPath path)
- {
- if (this._persona_stores.has_key (path))
- {
- this._store_removed_cb (this._persona_stores.get (path));
- }
- }
-
- private void _store_removed_cb (Folks.PersonaStore store)
- {
- this._remove_store ((Ofono.PersonaStore) store);
- }
-}
diff --git a/backends/ofono/ofono-persona-store.vala b/backends/ofono/ofono-persona-store.vala
deleted file mode 100644
index ae3dd6fb..00000000
--- a/backends/ofono/ofono-persona-store.vala
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- * Copyright (C) 2012 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Jeremy Whiting <jeremy.whiting@collabora.co.uk>
- *
- * Based on kf-persona-store.vala by:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-using Folks;
-using Folks.Backends.Ofono;
-
-/**
- * A persona store which is associated with a single Ofono device. It will
- * create a {@link Persona} for each contact on the SIM card phonebook.
- *
- * @since 0.9.0
- */
-public class Folks.Backends.Ofono.PersonaStore : Folks.PersonaStore
-{
- private HashMap<string, Persona> _personas;
- private Map<string, Persona> _personas_ro;
- private bool _is_prepared = false;
- private bool _prepare_pending = false;
- private bool _is_quiescent = false;
-
- private static string[] _always_writeable_properties = {};
- private ObjectPath? _path = null;
-
- private org.ofono.Phonebook? _ofono_phonebook = null;
-
- /**
- * {@inheritDoc}
- */
- public override string type_id { get { return BACKEND_NAME; } }
-
- /**
- * {@inheritDoc}
- */
- public override MaybeBool can_add_personas
- {
- get { return MaybeBool.FALSE; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override MaybeBool can_alias_personas
- {
- get { return MaybeBool.FALSE; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override MaybeBool can_group_personas
- {
- get { return MaybeBool.FALSE; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override MaybeBool can_remove_personas
- {
- get { return MaybeBool.FALSE; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override bool is_prepared
- {
- get { return this._is_prepared; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override bool is_quiescent
- {
- get { return this._is_quiescent; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override string[] always_writeable_properties
- {
- get { return Ofono.PersonaStore._always_writeable_properties; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override Map<string, Folks.Persona> personas
- {
- get { return this._personas_ro; }
- }
-
- /**
- * Create a new PersonaStore.
- *
- * Create a new persona store to expose the {@link Persona}s provided by the
- * modem with the given address.
- *
- * @param path the D-Bus object path of this modem
- * @param alias the name this modem should display to users
- *
- * @since 0.9.0
- */
- public PersonaStore (ObjectPath path, string alias)
- {
- Object (id: path,
- display_name: alias);
-
- this.trust_level = PersonaStoreTrust.FULL;
- this._personas = new HashMap<string, Persona> ();
- this._personas_ro = this._personas.read_only_view;
- this._path = path;
- }
-
- private void _property_changed (string property, Variant value)
- {
- if (property == "Present" && value.get_boolean () == false)
- {
- this._remove_self ();
- }
- }
-
- private void _remove_self ()
- {
- /* Marshal the personas from a Collection to a Set. */
- var removed_personas = new HashSet<Persona> ();
- var iter = this._personas.map_iterator ();
-
- while (iter.next () == true)
- {
- removed_personas.add (iter.get_value ());
- }
-
- this._emit_personas_changed (null, removed_personas);
- this.removed ();
- }
-
- private string[] _split_all_vcards (string all_vcards)
- {
- /* Ofono vcards are in vcard 3.0 format and can include the following:
- * FN, CATEGORIES, EMAIL and IMPP fields. */
- string[] lines = all_vcards.split ("\n");
- string[] vcards = {};
- string vcard = "";
-
- foreach (string line in lines)
- {
- /* Skip whitespace between vCards. */
- if (vcard == "" && line.strip () == "")
- continue;
-
- vcard += line;
- vcard += "\n";
-
- if (line.strip () == "END:VCARD")
- {
- vcards += vcard;
- vcard = "";
- }
- }
-
- return vcards;
- }
-
- /**
- * {@inheritDoc}
- */
- public override async void prepare () throws IOError, DBusError
- {
- var profiling = Internal.profiling_start ("preparing Ofono.PersonaStore (ID: %s)",
- this.id);
-
- if (this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
- this._ofono_phonebook = yield Bus.get_proxy (BusType.SYSTEM,
- "org.ofono",
- this._path);
-
- org.ofono.SimManager sim_manager = yield Bus.get_proxy (BusType.SYSTEM,
- "org.ofono",
- this._path);
- sim_manager.PropertyChanged.connect (this._property_changed);
-
- string all_vcards = this._ofono_phonebook.Import ();
-
- string[] vcards = this._split_all_vcards (all_vcards);
-
- HashSet<Persona> added_personas = new HashSet<Persona> ();
-
- foreach (string vcard in vcards)
- {
- Persona persona = new Persona (vcard, this);
- this._personas.set (persona.iid, persona);
- added_personas.add (persona);
- }
-
- if (this._personas.size > 0)
- {
- this._emit_personas_changed (added_personas, null);
- }
- }
- catch (GLib.DBusError e)
- {
- warning ("DBus Error has occurred when fetching ofono phonebook, %s", e.message);
- this._remove_self ();
- }
- catch (GLib.IOError e)
- {
- warning ("IO Error has occurred when fetching ofono phonebook, %s", e.message);
- this._remove_self ();
- }
- finally
- {
- this._is_prepared = true;
- this.notify_property ("is-prepared");
-
- /* We've finished loading all the personas we know about */
- this._is_quiescent = true;
- this.notify_property ("is-quiescent");
-
- this._prepare_pending = false;
- }
-
- Internal.profiling_end ((owned) profiling);
- }
-
- /**
- * Remove a {@link Persona} from the PersonaStore.
- *
- * See {@link Folks.PersonaStore.remove_persona}.
- *
- * @throws Folks.PersonaStoreError.READ_ONLY every time since the
- * Ofono backend is read-only.
- *
- * @param persona the {@link Persona} to remove.
- *
- * @since 0.9.0
- */
- public override async void remove_persona (Folks.Persona persona)
- throws Folks.PersonaStoreError
- {
- throw new PersonaStoreError.READ_ONLY (
- "Personas cannot be removed from this store.");
- }
-
- /**
- * Add a new {@link Persona} to the PersonaStore.
- *
- * See {@link Folks.PersonaStore.add_persona_from_details}.
- *
- * @throws Folks.PersonaStoreError.READ_ONLY every time since the
- * Ofono backend is read-only.
- *
- * @param details the details of the {@link Persona} to add.
- *
- * @since 0.9.0
- */
- public override async Folks.Persona? add_persona_from_details (
- HashTable<string, Value?> details) throws Folks.PersonaStoreError
- {
- throw new PersonaStoreError.READ_ONLY (
- "Personas cannot be added to this store.");
- }
-}
diff --git a/backends/ofono/ofono-persona.vala b/backends/ofono/ofono-persona.vala
deleted file mode 100644
index b4e4cb3d..00000000
--- a/backends/ofono/ofono-persona.vala
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2012 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Jeremy Whiting <jeremy.whiting@collabora.co.uk>
- *
- * Based on kf-persona.vala by:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-using Folks;
-using Folks.Backends.Ofono;
-
-/**
- * A persona subclass which represents a single persona from a simple key file.
- *
- * @since 0.9.0
- */
-public class Folks.Backends.Ofono.Persona : Folks.Persona,
- EmailDetails,
- NameDetails,
- PhoneDetails
-{
- private StructuredName? _structured_name = null;
- private string _full_name = "";
- private string _nickname = "";
- private SmallSet<PhoneFieldDetails> _phone_numbers;
- private Set<PhoneFieldDetails> _phone_numbers_ro;
- private SmallSet<EmailFieldDetails> _email_addresses;
- private Set<EmailFieldDetails> _email_addresses_ro;
-
- private const string[] _linkable_properties =
- {
- "phone-numbers",
- "email-addresses",
- null /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=682698 */
- };
- private static string[] _writeable_properties = {};
-
- /**
- * {@inheritDoc}
- */
- public override string[] linkable_properties
- {
- get { return Ofono.Persona._linkable_properties; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override string[] writeable_properties
- {
- get { return Ofono.Persona._writeable_properties; }
- }
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public Set<PhoneFieldDetails> phone_numbers
- {
- get { return this._phone_numbers_ro; }
- set { this.change_phone_numbers.begin (value); } /* not writeable */
- }
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public StructuredName? structured_name
- {
- get { return this._structured_name; }
- set { this.change_structured_name.begin (value); } /* not writeable */
- }
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public string full_name
- {
- get { return this._full_name; }
- set { this.change_full_name.begin (value); } /* not writeable */
- }
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public string nickname
- {
- get { return this._nickname; }
- set { this.change_nickname.begin (value); } /* not writeable */
- }
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public Set<EmailFieldDetails> email_addresses
- {
- get { return this._email_addresses_ro; }
- set { this.change_email_addresses.begin (value); } /* not writeable */
- }
-
- /**
- * Create a new persona.
- *
- * Create a new persona for the given vCard contents.
- *
- * @param vcard the vCard data to use for this {@link Persona}.
- * @param store the {@link PersonaStore} this {@link Persona} belongs to.
- *
- * @since 0.9.0
- */
- public Persona (string vcard, Folks.PersonaStore store)
- {
- var iid = Checksum.compute_for_string (ChecksumType.SHA1, vcard);
- var uid = Folks.Persona.build_uid ("ofono", store.id, iid);
-
- /* Use the IID as the display ID since no other suitable identifier is
- * available which we can guarantee is unique within the store. */
- Object (display_id: iid,
- iid: iid,
- uid: uid,
- store: store,
- is_user: false);
- this._set_vcard (vcard);
- }
-
- construct
- {
- debug ("Adding Ofono Persona '%s' (IID '%s', group '%s')", this.uid,
- this.iid, this.display_id);
-
- this._phone_numbers = new SmallSet<PhoneFieldDetails> ();
- this._phone_numbers_ro = this._phone_numbers.read_only_view;
-
- this._email_addresses = new SmallSet<EmailFieldDetails> ();
- this._email_addresses_ro = this._email_addresses.read_only_view;
- }
-
- private void _set_vcard (string vcard)
- {
- E.VCard card = new E.VCard.from_string (vcard);
-
- E.VCardAttribute? attribute = card.get_attribute ("TEL");
- if (attribute != null)
- {
- this._phone_numbers.add (new PhoneFieldDetails (attribute.get_value_decoded ().str) );
- }
-
- attribute = card.get_attribute ("FN");
- if (attribute != null)
- {
- this._full_name = attribute.get_value_decoded ().str;
- }
-
- attribute = card.get_attribute ("NICKNAME");
- if (attribute != null)
- {
- this._nickname = attribute.get_value_decoded ().str;
- }
-
- attribute = card.get_attribute ("N");
- if (attribute != null)
- {
- unowned GLib.List<StringBuilder> values = attribute.get_values_decoded ();
- if (values.length () >= 5)
- {
- this._structured_name = new StructuredName (values.nth_data (0).str,
- values.nth_data (1).str,
- values.nth_data (2).str,
- values.nth_data (3).str,
- values.nth_data (4).str);
- }
- else
- {
- warning ("Expected 5 components to N value of vcard, got %u", values.length ());
- }
- }
-
- attribute = card.get_attribute ("EMAIL");
- if (attribute != null)
- {
- this._email_addresses.add (new EmailFieldDetails (attribute.get_value_decoded ().str) );
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public override void linkable_property_to_links (string prop_name,
- Folks.Persona.LinkablePropertyCallback callback)
- {
- if (prop_name == "phone-numbers")
- {
- foreach (var phone_number in this._phone_numbers)
- {
- if (phone_number.value != null)
- callback (phone_number.value);
- }
- }
- else if (prop_name == "email-addresses")
- {
- foreach (var email_address in this._email_addresses)
- {
- if (email_address.value != null)
- callback (email_address.value);
- }
- }
- else
- {
- /* Chain up */
- base.linkable_property_to_links (prop_name, callback);
- }
- }
-}
diff --git a/backends/ofono/org-ofono.vala b/backends/ofono/org-ofono.vala
deleted file mode 100644
index 751ed188..00000000
--- a/backends/ofono/org-ofono.vala
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2012 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Jeremy Whiting <jeremy.whiting@collabora.co.uk>
- */
-
-using GLib;
-
-namespace org
- {
- namespace ofono
- {
- public struct ModemProperties
- {
- ObjectPath path;
- HashTable<string, Variant> properties;
- }
-
- [DBus (name = "org.ofono.Manager")]
- public interface Manager : Object
- {
- [DBus (name = "GetModems")]
- public abstract ModemProperties[] GetModems() throws DBusError, IOError;
-
- public signal void ModemAdded (ObjectPath path, HashTable<string, Variant> properties);
- public signal void ModemRemoved (ObjectPath path);
- }
-
- [DBus (name = "org.ofono.Phonebook")]
- public interface Phonebook : Object
- {
- [DBus (name = "Import")]
- public abstract string Import() throws DBusError, IOError;
- }
-
- [DBus (name = "org.ofono.SimManager")]
- public interface SimManager : Object
- {
- [DBus (name = "GetProperties")]
- public abstract HashTable<string, Variant> GetProperties() throws DBusError, IOError;
-
- public signal void PropertyChanged (string property, Variant value);
- }
- }
- }
diff --git a/backends/telepathy/lib/folks-telepathy.deps b/backends/telepathy/lib/folks-telepathy.deps
deleted file mode 100644
index dec8d50a..00000000
--- a/backends/telepathy/lib/folks-telepathy.deps
+++ /dev/null
@@ -1,4 +0,0 @@
-glib-2.0
-gobject-2.0
-folks
-telepathy-glib
diff --git a/backends/telepathy/lib/folks-telepathy.map b/backends/telepathy/lib/folks-telepathy.map
deleted file mode 100644
index d4084018..00000000
--- a/backends/telepathy/lib/folks-telepathy.map
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-global:
- tpf_*;
-local:
- *;
-};
diff --git a/backends/telepathy/lib/meson.build b/backends/telepathy/lib/meson.build
deleted file mode 100644
index f4943db6..00000000
--- a/backends/telepathy/lib/meson.build
+++ /dev/null
@@ -1,176 +0,0 @@
-telepathy_backendlib_gir_name = 'FolksTelepathy-@0@'.format(folks_api_version)
-
-# Low-level library
-tp_lowlevel_sources = [
- 'tp-lowlevel.c',
-]
-
-tp_lowlevel_deps = [
- gio_dep,
- gobject_dep,
- telepathy_glib_dep,
-]
-
-tp_lowlevel = static_library('tp-lowlevel',
- sources: tp_lowlevel_sources,
- dependencies: tp_lowlevel_deps,
-)
-
-tp_lowlevel_gir = gnome.generate_gir(tp_lowlevel,
- sources: [ 'tp-lowlevel.h' ],
- includes: [ 'GObject-2.0', 'TelepathyGLib-0.12' ],
- namespace: 'TpLowlevel',
- nsversion: folks_api_version,
- identifier_prefix: 'FolksTpLowlevel',
- extra_args: [
- '--c-include=tp-lowlevel.h',
- ],
-)
-
-tp_lowlevel_vapi = gnome.generate_vapi('tp-lowlevel',
- sources: tp_lowlevel_gir.get(0),
- packages: [ 'gio-2.0', 'telepathy-glib' ],
-)
-
-tp_lowlevel_dep = declare_dependency(
- dependencies: tp_lowlevel_vapi,
- link_with: tp_lowlevel,
-)
-
-# TP zeitgeist helper library
-tp_zeitgeist_deps = [
- gee_dep,
- libfolks_dep,
- telepathy_glib_dep,
-]
-
-if zeitgeist_enabled
- tp_zeitgeist_lib = static_library('tp-zeitgeist',
- 'tp-zeitgeist.vala',
- dependencies: [ tp_zeitgeist_deps, zeitgeist_dep ],
- )
-
- tp_zeitgeist_dep = declare_dependency(
- link_with: tp_zeitgeist_lib,
- dependencies: [ tp_zeitgeist_deps, zeitgeist_dep ],
- )
-else
- tp_zeitgeist_dummy_lib = static_library('tp-zeitgeist-dummy',
- 'tp-zeitgeist-dummy.vala',
- dependencies: tp_zeitgeist_deps,
- )
-
- tp_zeitgeist_dummy_dep = declare_dependency(
- link_with: tp_zeitgeist_dummy_lib,
- dependencies: tp_zeitgeist_deps,
- )
-endif
-
-# Actual backend library
-telepathy_backendlib_sources = files(
- 'tpf-logger.vala',
- 'tpf-persona-store-cache.vala',
- 'tpf-persona-store.vala',
- 'tpf-persona.vala',
-)
-
-telepathy_backendlib_sources += configure_file(
- input: namespace_vala_in,
- output: 'namespace.vala',
- configuration: {
- 'BACKENDLIB_GIR_NAME': telepathy_backendlib_gir_name.split('-')[0],
- 'BACKENDLIB_GIR_VERSION': folks_api_version,
- 'BACKENDLIB_NAMESPACE': 'Tpf',
- },
-)
-
-telepathy_backendlib_deps = [
- backend_deps,
- telepathy_glib_dep,
- tp_lowlevel_dep,
-]
-
-# FIXME: we need to set these manually for the valadoc target as long as meson
-# doesn't have native support (https://github.com/mesonbuild/meson/issues/894)
-telepathy_backendlib_doc_deps = [
- '--pkg', 'folks',
- '--pkg', 'tp-lowlevel', '--vapidir', meson.current_build_dir(),
- '--pkg', telepathy_glib_dep.name(),
-]
-
-if zeitgeist_enabled
- telepathy_backendlib_deps += tp_zeitgeist_dep
- telepathy_backendlib_doc_deps += [
- '--pkg', 'tp-zeitgeist',
- ]
-else
- telepathy_backendlib_deps += tp_zeitgeist_dummy_dep
- telepathy_backendlib_doc_deps += [
- '--pkg', 'tp-zeitgeist-dummy',
- ]
-endif
-
-telepathy_backendlib_vala_flags = [
- common_backendlib_vala_flags,
-]
-
-telepathy_backendlib_c_flags = [
- '-include', 'config.h',
- '-DBACKEND_NAME="@0@"'.format(telepathy_backend_name),
- '-DG_LOG_DOMAIN="@0@"'.format(telepathy_backend_name),
-]
-
-telepathy_backendlib_symbolmap = meson.current_source_dir() / 'folks-@0@.map'.format(telepathy_backend_name)
-telepathy_backendlib_link_flags = cc.get_supported_link_arguments(
- '-Wl,--version-script,@0@'.format(telepathy_backendlib_symbolmap),
-)
-
-telepathy_backendlib = shared_library('folks-@0@'.format(telepathy_backend_name),
- telepathy_backendlib_sources,
- dependencies: telepathy_backendlib_deps,
- vala_args: telepathy_backendlib_vala_flags,
- c_args: telepathy_backendlib_c_flags,
- link_args: telepathy_backendlib_link_flags,
- link_depends: telepathy_backendlib_symbolmap,
- version: folks_telepathy_lib_version,
- vala_header: 'folks/folks-@0@.h'.format(telepathy_backend_name),
- vala_gir: telepathy_backendlib_gir_name + '.gir',
- install: true,
- install_dir: [ true, folks_headers_install_dir, true, true ],
-)
-
-# Also make sure to install the VAPI's .deps file
-install_data('folks-telepathy.deps',
- install_dir: get_option('datadir') / 'vala' / 'vapi',
-)
-
-# FIXME: This comes straight from the Meson docs on how to create/install a
-# typelib file for your Vala shared library. However, as mentioned in
-# https://github.com/mesonbuild/meson/issues/4481, this is not ideal.
-custom_target(telepathy_backendlib_gir_name + '.typelib',
- command: [ g_ir_compiler,
- '--includedir', libfolks_gir_include_dir,
- '--output', '@OUTPUT@',
- '--shared-library', 'lib' + telepathy_backendlib.name(),
- meson.current_build_dir() / (telepathy_backendlib_gir_name + '.gir')
- ],
- output: telepathy_backendlib_gir_name + '.typelib',
- depends: telepathy_backendlib,
- install: true,
- install_dir: folks_typelibdir,
-)
-
-telepathy_backendlib_dep = declare_dependency(
- link_with: telepathy_backendlib,
- include_directories: include_directories('.'),
- dependencies: telepathy_glib_dep,
-)
-
-# Pkg-config file
-pkgconfig.generate(telepathy_backendlib,
- name: 'Folks telepathy support library',
- description: 'Telepathy support library for the Folks meta-contacts library',
- filebase: 'folks-@0@'.format(telepathy_backend_name),
- requires: [ 'folks', glib_dep, gobject_dep, gee_dep, telepathy_glib_dep ],
- variables: common_pkgconf_variables,
-)
diff --git a/backends/telepathy/lib/tp-lowlevel.c b/backends/telepathy/lib/tp-lowlevel.c
deleted file mode 100644
index ffc9c526..00000000
--- a/backends/telepathy/lib/tp-lowlevel.c
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2007-2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Xavier Claessens <xavier.claessens@collabora.co.uk>
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <glib.h>
-#include <glib/gi18n.h>
-#include <gio/gio.h>
-#include <telepathy-glib/telepathy-glib.h>
-
-#include "tp-lowlevel.h"
-
-static void
-set_contact_alias_cb (TpConnection *conn,
- const GError *error,
- gpointer user_data,
- GObject *weak_object)
-{
- GTask *task = G_TASK (user_data);
-
- if (error != NULL)
- {
- g_task_return_error (task, g_error_copy (error));
- }
- else
- {
- g_task_return_boolean (task, TRUE);
- }
-}
-
-/**
- * folks_tp_lowlevel_connection_set_contact_alias_async:
- * @conn: the connection to use
- * @handle: handle of the contact whose alias is to be changed
- * @alias: new human-readable alias for the contact
- * @callback: function to call on completion
- * @user_data: user data to pass to @callback
- *
- * Change the alias of the contact identified by @handle to @alias.
- */
-void
-folks_tp_lowlevel_connection_set_contact_alias_async (
- TpConnection *conn,
- guint handle,
- const gchar *alias,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- g_autoptr(GTask) task = NULL;
- g_autoptr(GHashTable) ht = NULL;
-
- ht = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
- g_hash_table_insert (ht, GUINT_TO_POINTER (handle), g_strdup (alias));
-
- task = g_task_new (conn, NULL, callback, user_data);
- g_task_set_source_tag (task,
- folks_tp_lowlevel_connection_set_contact_alias_async);
-
- tp_cli_connection_interface_aliasing_call_set_aliases (conn, -1,
- ht, set_contact_alias_cb, g_steal_pointer (&task), g_object_unref,
- G_OBJECT (conn));
-}
-
-/**
- * folks_tp_lowlevel_connection_set_contact_alias_finish:
- * @result: a #GAsyncResult
- * @error: return location for a #GError, or %NULL
- *
- * Finish an asynchronous call to
- * folks_tp_lowlevel_connection-set_contact_alias_async().
- */
-void
-folks_tp_lowlevel_connection_set_contact_alias_finish (
- GAsyncResult *result,
- GError **error)
-{
- TpConnection *conn;
-
- g_return_if_fail (G_IS_TASK (result));
-
- conn = TP_CONNECTION (g_task_get_source_object (G_TASK (result)));
- g_return_if_fail (TP_IS_CONNECTION (conn));
-
- g_return_if_fail (g_task_is_valid (result, conn));
- g_return_if_fail (g_task_get_source_tag (G_TASK (result)) ==
- folks_tp_lowlevel_connection_set_contact_alias_async);
-
- /* Note: We throw away the boolean return value here */
- g_task_propagate_boolean (G_TASK (result), error);
-}
diff --git a/backends/telepathy/lib/tp-zeitgeist-dummy.vala b/backends/telepathy/lib/tp-zeitgeist-dummy.vala
deleted file mode 100644
index 991b2621..00000000
--- a/backends/telepathy/lib/tp-zeitgeist-dummy.vala
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2013 Philip Withnall
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
-
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
-
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Authors:
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using Folks;
-using GLib;
-using TelepathyGLib;
-
-/**
- * Dummy interface for the Zeitgeist code for libfolks-telepathy.la. This must
- * implement exactly the same interface as tp-zeitgeist.vala, but without
- * linking to Zeitgeist.
- *
- * See the note in Makefile.am, and
- * [[https://bugzilla.gnome.org/show_bug.cgi?id=701099]].
- */
-public class FolksTpZeitgeist.Controller : Object
-{
- [CCode (has_target = false)]
- public delegate void IncreasePersonaCounter (Persona p,
- DateTime converted_datetime);
-
- public Controller (PersonaStore store, TelepathyGLib.Account account,
- IncreasePersonaCounter im_interaction_cb,
- IncreasePersonaCounter last_call_interaction_cb)
- {
- /* Dummy. */
- }
-
- public async void populate_counters ()
- {
- /* Dummy. */
- }
-}
diff --git a/backends/telepathy/lib/tp-zeitgeist.vala b/backends/telepathy/lib/tp-zeitgeist.vala
deleted file mode 100644
index 2b999956..00000000
--- a/backends/telepathy/lib/tp-zeitgeist.vala
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2013 Philip Withnall
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
-
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
-
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Authors:
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using Folks;
-using GLib;
-using Gee;
-using TelepathyGLib;
-using Zeitgeist;
-
-/**
- * Zeitgeist code for libfolks-telepathy.la. This is separated out from
- * tpf-persona-store.vala so that it can be conditionally compiled out.
- *
- * See the note in Makefile.am, and
- * [[https://bugzilla.gnome.org/show_bug.cgi?id=701099]].
- */
-public class FolksTpZeitgeist.Controller : Object
-{
- private Zeitgeist.Log? _log = null;
- private Zeitgeist.Monitor? _monitor = null;
- private string _protocol;
- private TelepathyGLib.Account _account;
-
- /* This object is owned by the PersonaStore, so we don't want a cyclic
- * reference. */
- private unowned PersonaStore _store;
-
- [CCode (has_target = false)]
- public delegate void IncreasePersonaCounter (Persona p,
- DateTime converted_datetime);
-
- private IncreasePersonaCounter _im_interaction_cb;
- private IncreasePersonaCounter _last_call_interaction_cb;
-
- public Controller (PersonaStore store, TelepathyGLib.Account account,
- IncreasePersonaCounter im_interaction_cb,
- IncreasePersonaCounter last_call_interaction_cb)
- {
- this._store = store;
- this._account = account;
- this._protocol = account.protocol_name;
- this._im_interaction_cb = im_interaction_cb;
- this._last_call_interaction_cb = last_call_interaction_cb;
- }
-
- ~Controller ()
- {
- if (this._monitor != null)
- {
- this._log.remove_monitor (this._monitor);
- this._monitor = null;
- }
- }
-
- public async void populate_counters ()
- {
- if (this._log == null)
- {
- this._log = new Zeitgeist.Log ();
- }
-
- /* Get all events for this account from Zeitgeist and increase the
- * the counters of the personas */
- try
- {
- TimeVal tm = TimeVal ();
- int64 end_timestamp = tm.tv_sec;
- /* We want events from the last 30 days only, A day has 86400 seconds.
- * start_timestamp = end_timestamp - 30 days in seconds*/
- int64 start_timestamp = end_timestamp - (86400 * 30);
- GLib.GenericArray<Zeitgeist.Event> events =
- this._get_zeitgeist_event_templates ();
- var results = yield this._log.find_events (
- new TimeRange (start_timestamp * 1000, end_timestamp * 1000),
- events, StorageState.ANY, 0, ResultType.MOST_RECENT_EVENTS,
- null);
-
- foreach (var e in results)
- {
- var interaction_type = e.get_subject (0).interpretation;
- for (var i = 1; i < e.num_subjects (); i++)
- {
- var id =
- this._get_iid_from_event_metadata (e.get_subject (i).uri);
- if (id == null || interaction_type == null)
- continue;
-
- var persona = this._store.personas.get (id);
- if (persona == null)
- continue;
-
- persona.freeze_notify ();
- this._increase_persona_counter (persona, interaction_type, e);
- }
- }
-
- /* Go back through and thaw notifications. */
- foreach (var e in results)
- {
- var interaction_type = e.get_subject (0).interpretation;
- for (var i = 1; i < e.num_subjects (); i++)
- {
- var id =
- this._get_iid_from_event_metadata (e.get_subject (i).uri);
- if (id == null || interaction_type == null)
- continue;
-
- var persona = this._store.personas.get (id);
- if (persona == null)
- continue;
-
- persona.thaw_notify ();
- }
- }
- }
- catch
- {
- debug ("Failed to fetch events from Zeitgeist");
- }
-
- /* Prepare a monitor and install for this account to populate persona
- * counters upon interaction changes.*/
- if (this._monitor == null)
- {
- GLib.GenericArray<Zeitgeist.Event> monitor_events =
- this._get_zeitgeist_event_templates ();
- this._monitor = new Zeitgeist.Monitor (
- new Zeitgeist.TimeRange.from_now (), monitor_events);
- this._monitor.events_inserted.connect (this._handle_new_interaction);
- try
- {
- this._log.install_monitor (this._monitor);
- }
- catch
- {
- warning ("Failed to install monitor for Zeitgeist");
- this._monitor = null;
- }
- }
- }
-
- private string? _get_iid_from_event_metadata (string? uri)
- {
- /* Format a proper id represting a persona in the store.
- * Zeitgeist uses x-telepathy-identifier as a prefix for telepathy, which
- * is stored as the uri of a subject of an event. */
- if (uri == null)
- {
- return null;
- }
- var new_uri = uri.replace ("x-telepathy-identifier:", "");
- return this._protocol + ":" + new_uri;
- }
-
- private void _increase_persona_counter (Persona persona,
- string interaction_type, Event event)
- {
- /* Increase the appropriate interaction counter, to signify that an
- * interaction was successfully counted. */
- var timestamp = (uint) (event.timestamp / 1000);
- var converted_datetime = new DateTime.from_unix_utc (timestamp);
- var interpretation = event.interpretation;
-
- /* Invalid timestamp? Ignore it. */
- if (converted_datetime == null)
- return;
-
- /* Only count send/receive for IM interactions */
- if (interaction_type == Zeitgeist.NMO.IMMESSAGE &&
- (interpretation == Zeitgeist.ZG.SEND_EVENT ||
- interpretation == Zeitgeist.ZG.RECEIVE_EVENT))
- {
- this._im_interaction_cb (persona, converted_datetime);
- }
- /* Only count successful call for call interactions */
- else if (interaction_type == Zeitgeist.NFO.AUDIO &&
- interpretation == Zeitgeist.ZG.LEAVE_EVENT)
- {
- this._last_call_interaction_cb (persona, converted_datetime);
- }
- }
-
- private void _handle_new_interaction (TimeRange timerange, ResultSet events)
- {
- foreach (var e in events)
- {
- for (var i = 1; i < e.num_subjects (); i++)
- {
- var id =
- this._get_iid_from_event_metadata (e.get_subject (i).uri);
- var interaction_type = e.get_subject (0).interpretation;
- if (id == null || interaction_type == null)
- continue;
-
- var persona = this._store.personas.get (id);
- if (persona == null)
- continue;
-
- this._increase_persona_counter (persona, interaction_type, e);
- }
- }
- }
-
- private GLib.GenericArray<Zeitgeist.Event> _get_zeitgeist_event_templates ()
- {
- /* To fetch events from Zeitgeist about the interaction with contacts we
- * create templates reflecting how the telepathy-logger stores events in
- * Zeitgeist */
- var origin = "x-telepathy-account-path:" +
- this._account.get_path_suffix ();
- Event ev1 = new Event.full ("", "",
- "dbus://org.freedesktop.Telepathy.Logger.service");
- ev1.origin = origin;
- var templates = new GLib.GenericArray<Zeitgeist.Event> ();
- templates.add (ev1);
- return templates;
- }
-}
diff --git a/backends/telepathy/lib/tpf-logger.vala b/backends/telepathy/lib/tpf-logger.vala
deleted file mode 100644
index 2be81aed..00000000
--- a/backends/telepathy/lib/tpf-logger.vala
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-using TelepathyGLib;
-using Folks;
-
-private struct AccountFavourites
-{
- ObjectPath account_path;
- string[] ids;
-}
-
-[DBus (name = "org.freedesktop.Telepathy.Logger.DRAFT")]
-private interface LoggerIface : Object
-{
- public abstract async AccountFavourites[] get_favourite_contacts ()
- throws GLib.Error;
- public abstract async void add_favourite_contact (
- ObjectPath account_path, string id) throws GLib.Error;
- public abstract async void remove_favourite_contact (
- ObjectPath account_path, string id) throws GLib.Error;
-
- public abstract signal void favourite_contacts_changed (
- ObjectPath account_path, string[] added, string[] removed);
-}
-
-/* See: https://mail.gnome.org/archives/vala-list/2011-June/msg00008.html */
-[Compact]
-private class DelegateWrapper
-{
- public SourceFunc cb;
-}
-
-internal class Logger : GLib.Object
-{
- private static DBusConnection _dbus_conn;
- private static LoggerIface _logger;
- private static DelegateWrapper[] _prepare_waiters = null;
-
- private uint _logger_watch_id;
-
- public signal void invalidated ();
- public signal void favourite_contacts_changed (string[] added,
- string[] removed);
-
- /**
- * D-Bus object path of the {@link TelepathyGLib.Account} to watch for
- * favourite contacts.
- *
- * @since 0.6.6
- */
- public string account_path { get; construct; }
-
- public Logger (string account_path)
- {
- Object (account_path: account_path);
- }
-
- ~Logger ()
- {
- /* Can only be 0 if prepare() hasn't been called. */
- if (this._logger_watch_id > 0)
- {
- Bus.unwatch_name (this._logger_watch_id);
- }
- }
-
- public async void prepare () throws GLib.Error
- {
- if (Logger._logger == null && Logger._prepare_waiters == null)
- {
- /* If this is the first call to prepare(), start some async calls. We
- * then yield to the main thread. Any subsequent calls to prepare()
- * will have their continuations added to the _prepare_waiters list,
- * and will be signalled once the first call returns.
- * See: https://bugzilla.gnome.org/show_bug.cgi?id=677633 */
- Logger._prepare_waiters = new DelegateWrapper[0];
-
- /* Create a logger proxy for favourites support */
- var dbus_conn = yield Bus.get (BusType.SESSION);
- Logger._logger = yield dbus_conn.get_proxy<LoggerIface> (
- "org.freedesktop.Telepathy.Logger",
- "/org/freedesktop/Telepathy/Logger");
-
- if (Logger._logger != null)
- {
- Logger._dbus_conn = dbus_conn;
- }
-
- /* Wake up any waiters. */
- foreach (unowned DelegateWrapper wrapper in Logger._prepare_waiters)
- {
- Idle.add ((owned) wrapper.cb);
- }
-
- Logger._prepare_waiters = null;
- }
- else if (Logger._logger == null && Logger._prepare_waiters != null)
- {
- /* Yield until the first ongoing prepare() call finishes. */
- var wrapper = new DelegateWrapper ();
- wrapper.cb = prepare.callback;
- Logger._prepare_waiters += (owned) wrapper;
- yield;
- }
-
- /* Failure? */
- if (Logger._logger == null)
- {
- this.invalidated ();
- return;
- }
-
- this._logger_watch_id = Bus.watch_name_on_connection (Logger._dbus_conn,
- "org.freedesktop.Telepathy.Logger", BusNameWatcherFlags.NONE,
- null, this._logger_vanished);
-
- Logger._logger.favourite_contacts_changed.connect ((ap, a, r) =>
- {
- if (ap != this._account_path)
- return;
-
- this.favourite_contacts_changed (a, r);
- });
- }
-
- private void _logger_vanished (DBusConnection? conn, string name)
- {
- /* The logger has vanished on the bus, so it and we are no longer valid */
- Logger._logger = null;
- Logger._dbus_conn = null;
- this.invalidated ();
- }
-
- public async string[] get_favourite_contacts () throws GLib.Error
- {
- /* Invalidated */
- if (Logger._logger == null)
- return {};
-
- /* Use an intermediate, since this._logger could disappear before this
- * async function finishes */
- var logger = Logger._logger;
- AccountFavourites[] favs = yield logger.get_favourite_contacts ();
-
- foreach (AccountFavourites account in favs)
- {
- /* We only want the favourites from this account */
- if (account.account_path == this._account_path)
- return account.ids;
- }
-
- return {};
- }
-
- public async void add_favourite_contact (string id) throws GLib.Error
- {
- /* Invalidated */
- if (Logger._logger == null)
- return;
-
- /* Use an intermediate, since this._logger could disappear before this
- * async function finishes */
- var logger = Logger._logger;
- yield logger.add_favourite_contact (
- new ObjectPath (this._account_path), id);
- }
-
- public async void remove_favourite_contact (string id) throws GLib.Error
- {
- /* Invalidated */
- if (Logger._logger == null)
- return;
-
- /* Use an intermediate, since this._logger could disappear before this
- * async function finishes */
- var logger = Logger._logger;
- yield logger.remove_favourite_contact (
- new ObjectPath (this._account_path), id);
- }
-}
diff --git a/backends/telepathy/lib/tpf-persona-store-cache.vala b/backends/telepathy/lib/tpf-persona-store-cache.vala
deleted file mode 100644
index 4fce9b54..00000000
--- a/backends/telepathy/lib/tpf-persona-store-cache.vala
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-using Folks;
-
-/**
- * An object cache class which implements caching of sets of
- * {@link Tpf.Persona}s from a given {@link Tpf.PersonaStore}.
- *
- * Each {@link Tpf.Persona} is stored as a serialised {@link Variant} which is
- * a tuple containing the following fields:
- * # UID (``s``)
- * # IID (``s``)
- * # IM address (``s``)
- * # Protocol (``s``)
- * # Set of group names (``as``)
- * # Favourite? (``b``)
- * # Alias (``s``)
- * # In contact list? (``b``)
- * # Avatar file URI (``s``)
- * # Birthday date as a Unix timestamp (``s``)
- * # Set of e-mail addresses and parameters (``a(sa(ss))``)
- * # Full name (``s``)
- *
- * @since 0.6.0
- */
-internal class Tpf.PersonaStoreCache : Folks.ObjectCache<Tpf.Persona>
-{
- /**
- * The {@link Tpf.PersonaStore} associated with this cache.
- *
- * @since 0.6.6
- */
- public weak PersonaStore store { get; construct; }
-
- /* Version number of the variant type returned by
- * get_serialised_object_type(). This must be modified whenever that variant
- * type or its semantics are changed, since that would necessitate a cache
- * refresh. */
- private const uint8 _FILE_FORMAT_VERSION = 2;
-
- internal PersonaStoreCache (PersonaStore store)
- {
- Object (type_id: "tpf-persona-stores",
- id: store.id,
- store: store);
- }
-
- protected override VariantType? get_serialised_object_type (
- uint8 object_version)
- {
- // Maximum version?
- if (object_version == uint8.MAX)
- {
- object_version = PersonaStoreCache._FILE_FORMAT_VERSION;
- }
-
- if (object_version == 1)
- {
- return new VariantType.tuple ({
- VariantType.STRING, // UID
- VariantType.STRING, // IID
- VariantType.STRING, // ID
- VariantType.STRING, // Protocol
- new VariantType.array (VariantType.STRING), // Groups
- VariantType.BOOLEAN, // Favourite?
- VariantType.STRING, // Alias
- VariantType.BOOLEAN, // In contact list?
- VariantType.BOOLEAN, // Is user?
- new VariantType.maybe (VariantType.STRING) // Avatar
- });
- }
- else if (object_version == 2 || object_version == uint8.MAX)
- {
- return new VariantType.tuple ({
- VariantType.STRING, // UID
- VariantType.STRING, // IID
- VariantType.STRING, // ID
- VariantType.STRING, // Protocol
- new VariantType.array (VariantType.STRING), // Groups
- VariantType.BOOLEAN, // Favourite?
- VariantType.STRING, // Alias
- VariantType.BOOLEAN, // In contact list?
- VariantType.BOOLEAN, // Is user?
- new VariantType.maybe (VariantType.STRING), // Avatar
- new VariantType.maybe (VariantType.INT64), // Birthday
- VariantType.STRING, // Full name
- new VariantType.array (new VariantType.tuple ({
- VariantType.STRING, // E-mail address
- new VariantType.array (new VariantType.tuple ({
- VariantType.STRING, // Key
- VariantType.STRING // Value
- })) // Parameters
- })), // E-mail addresses
- new VariantType.array (new VariantType.tuple ({
- VariantType.STRING, // Phone number
- new VariantType.array (new VariantType.tuple ({
- VariantType.STRING, // Key
- VariantType.STRING // Value
- })) // Parameters
- })), // Phone numbers
- new VariantType.array (new VariantType.tuple ({
- VariantType.STRING, // URL
- new VariantType.array (new VariantType.tuple ({
- VariantType.STRING, // Key
- VariantType.STRING // Value
- })) // Parameters
- })) // URLs
- });
- }
-
- // Unsupported version
- return null;
- }
-
- protected override uint8 get_serialised_object_version ()
- {
- return PersonaStoreCache._FILE_FORMAT_VERSION;
- }
-
- private Variant[] serialise_abstract_field_details (
- Set<AbstractFieldDetails<string>> field_details_set)
- {
- Variant[] output_variants = new Variant[field_details_set.size];
-
- uint i = 0;
- foreach (var afd in field_details_set)
- {
- Variant[] parameters = new Variant[afd.parameters.size];
-
- uint f = 0;
-
- var iter = afd.parameters.map_iterator ();
-
- while (iter.next ())
- parameters[f++] = new Variant.tuple ({
- new Variant.string (iter.get_key ()),
- new Variant.string (iter.get_value ())
- });
-
- output_variants[i++] = new Variant.tuple ({
- afd.value, // Variant value (e.g. e-mail address)
- new Variant.array (new VariantType.tuple ({
- VariantType.STRING, // Key
- VariantType.STRING // Value
- }), parameters)
- });
- }
-
- return output_variants;
- }
-
- protected override Variant serialise_object (Tpf.Persona persona)
- {
- // Sort out the groups
- Variant[] groups = new Variant[persona.groups.size];
-
- uint i = 0;
- foreach (var group in persona.groups)
- {
- groups[i++] = new Variant.string (group);
- }
-
- // Sort out the IM addresses (there's guaranteed to only be one)
- string? im_protocol = null;
-
- var iter = persona.im_addresses.map_iterator ();
-
- if (iter.next ())
- im_protocol = iter.get_key ();
-
- // Avatar
- var avatar_file = (persona.avatar != null && persona.avatar is FileIcon) ?
- (persona.avatar as FileIcon).get_file () : null;
- var avatar_variant = (avatar_file != null) ?
- new Variant.string (avatar_file.get_uri ()) : null;
-
- // Birthday
- var birthday_variant = (persona.birthday != null) ?
- new Variant.int64 (persona.birthday.to_unix ()) : null;
-
- // Sort out the e-mail addresses, phone numbers and URLs
- var email_addresses =
- this.serialise_abstract_field_details (persona.email_addresses);
- var phone_numbers =
- this.serialise_abstract_field_details (persona.phone_numbers);
- var urls = this.serialise_abstract_field_details (persona.urls);
-
- // Serialise the persona
- return new Variant.tuple ({
- new Variant.string (persona.uid),
- new Variant.string (persona.iid),
- new Variant.string (persona.display_id),
- new Variant.string (im_protocol),
- new Variant.array (VariantType.STRING, groups),
- new Variant.boolean (persona.is_favourite),
- new Variant.string (persona.alias),
- new Variant.boolean (persona.is_in_contact_list),
- new Variant.boolean (persona.is_user),
- new Variant.maybe (VariantType.STRING, avatar_variant),
- new Variant.maybe (VariantType.INT64, birthday_variant),
- new Variant.string (persona.full_name),
- new Variant.array (new VariantType.tuple ({
- VariantType.STRING, // E-mail address
- new VariantType.array (new VariantType.tuple ({
- VariantType.STRING, // Key
- VariantType.STRING // Value
- })) // Parameters
- }), email_addresses),
- new Variant.array (new VariantType.tuple ({
- VariantType.STRING, // Phone number
- new VariantType.array (new VariantType.tuple ({
- VariantType.STRING, // Key
- VariantType.STRING // Value
- })) // Parameters
- }), phone_numbers),
- new Variant.array (new VariantType.tuple ({
- VariantType.STRING, // URL
- new VariantType.array (new VariantType.tuple ({
- VariantType.STRING, // Key
- VariantType.STRING // Value
- })) // Parameters
- }), urls)
- });
- }
-
- private delegate void AfdDeserialisationCallback (string val,
- HashMultiMap<string, string> parameters);
-
- private void deserialise_abstract_field_details (Variant input_variants,
- AfdDeserialisationCallback cb)
- {
- for (uint i = 0; i < input_variants.n_children (); i++)
- {
- var input_variant = input_variants.get_child_value (i);
-
- var val = input_variant.get_child_value (0).get_string ();
-
- var parameters = new HashMultiMap<string, string> ();
- var params_variants = input_variant.get_child_value (1);
- for (uint f = 0; f < params_variants.n_children (); f++)
- {
- var params_variant = params_variants.get_child_value (f);
-
- parameters.set (
- params_variant.get_child_value (0).get_string (),
- params_variant.get_child_value (1).get_string ());
- }
-
- // Output
- cb (val, parameters);
- }
- }
-
- protected override Tpf.Persona deserialise_object (Variant variant,
- uint8 object_version)
- {
- // Deserialise the persona
- var uid = variant.get_child_value (0).get_string ();
- var iid = variant.get_child_value (1).get_string ();
- var display_id = variant.get_child_value (2).get_string ();
- var im_protocol = variant.get_child_value (3).get_string ();
- var groups = variant.get_child_value (4);
- var is_favourite = variant.get_child_value (5).get_boolean ();
- var alias = variant.get_child_value (6).get_string ();
- var is_in_contact_list = variant.get_child_value (7).get_boolean ();
- var is_user = variant.get_child_value (8).get_boolean ();
- var avatar_variant = variant.get_child_value (9).get_maybe ();
-
- // Deserialise the groups
- var group_set = new SmallSet<string> ();
- for (uint i = 0; i < groups.n_children (); i++)
- {
- group_set.add (groups.get_child_value (i).get_string ());
- }
-
- // Deserialise the avatar
- var avatar = (avatar_variant != null) ?
- new FileIcon (File.new_for_uri (avatar_variant.get_string ())) :
- null;
-
- // Deserialise the birthday
- DateTime? birthday = null;
- if (object_version == 2)
- {
- var birthday_variant = variant.get_child_value (10).get_maybe ();
- if (birthday_variant != null)
- {
- /* Note: This may return a null value if the stored value is
- * invalid (e.g. out of range). */
- birthday =
- new DateTime.from_unix_utc (birthday_variant.get_int64 ());
- }
- }
-
- var full_name = "";
- if (object_version == 2)
- {
- full_name = variant.get_child_value (11).get_string();
- }
-
- var email_address_set = new SmallSet<EmailFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- var phone_number_set = new SmallSet<PhoneFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- var url_set = new SmallSet<UrlFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- if (object_version == 2)
- {
- /* Make sure that the extracted value is not empty as caches created
- * before bgo#675144 was fixed may have stored empty values. */
- this.deserialise_abstract_field_details (variant.get_child_value (12),
- (v, p) =>
- {
- if (v != "")
- {
- email_address_set.add (new EmailFieldDetails (v, p));
- }
- });
- this.deserialise_abstract_field_details (variant.get_child_value (13),
- (v, p) =>
- {
- if (v != "")
- {
- phone_number_set.add (new PhoneFieldDetails (v, p));
- }
- });
- this.deserialise_abstract_field_details (variant.get_child_value (14),
- (v, p) =>
- {
- if (v != "")
- {
- url_set.add (new UrlFieldDetails (v, p));
- }
- });
- }
-
- return new Tpf.Persona.from_cache (this._store, uid, iid, display_id,
- im_protocol, group_set, is_favourite, alias, is_in_contact_list,
- is_user, avatar, birthday, full_name, email_address_set,
- phone_number_set, url_set);
- }
-}
-
-/* vim: filetype=vala textwidth=80 tabstop=2 expandtab: */
diff --git a/backends/telepathy/lib/tpf-persona-store.vala b/backends/telepathy/lib/tpf-persona-store.vala
deleted file mode 100644
index 2f390f12..00000000
--- a/backends/telepathy/lib/tpf-persona-store.vala
+++ /dev/null
@@ -1,1701 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- * Copyright (C) 2013 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip.withnall@collabora.co.uk>
- * Xavier Claessens <xavier.claessens@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-using TelepathyGLib;
-using Folks;
-
-extern const string G_LOG_DOMAIN;
-extern const string BACKEND_NAME;
-
-/**
- * A persona store which is associated with a single Telepathy account. It will
- * create {@link Persona}s for each of the contacts in the account's
- * contact list.
- *
- * User must define contact features it wants on the #TpSimpleClientFactory of
- * the default #TpAccountManager returned by tp_account_manager_dup() *before*
- * preparing telepathy stores. Note that this is a behaviour change since
- * 0.7.0, folks won't force preparing any feature anymore.
- */
-public class Tpf.PersonaStore : Folks.PersonaStore
-{
- private string[] _always_writeable_properties = {};
-
- /* Sets of Personas exposed by this store.
- * This is the roster + self_contact */
- private HashMap<string, Persona> _personas;
- private Map<string, Persona> _personas_ro;
- private HashSet<Persona> _persona_set;
-
- /* Map from weakly-referenced TpContacts to their Persona.
- * This map contains all the TpContact we know about, could be more than the
- * the roster. Persona is kept in the map until its TpContact is disposed. */
- private HashMap<unowned Contact, Persona> _contact_persona_map;
-
- /* TpContact IDs. Note that this should *not* be cleared in _reset().
- * See bgo#630822. */
- private SmallSet<string> _favourite_ids = new SmallSet<string> ();
-
- /* Mapping from Persona IIDs to their avatars. This allows avatars to persist
- * between the cached (offline) personas and the online personas. Note that
- * this should *not* be cleared in _reset(). */
- private HashMap<string, File> _avatars = new HashMap<string, File> ();
-
- private Connection? _conn; /* null when disconnected */
- private AccountManager? _account_manager; /* only null before prepare() */
- private Logger _logger;
- private Persona? _self_persona;
-
- /* Connection's capabilities */
- private MaybeBool _can_add_personas = MaybeBool.UNSET;
- private MaybeBool _can_alias_personas = MaybeBool.UNSET;
- private MaybeBool _can_group_personas = MaybeBool.UNSET;
- private MaybeBool _can_remove_personas = MaybeBool.UNSET;
-
- private bool _is_prepared = false;
- private bool _prepare_pending = false;
- private bool _is_quiescent = false;
- private bool _got_initial_members = false;
- private bool _got_initial_self_contact = false;
- /* true iff in the middle of storing/loading the cache while disconnecting */
- private bool _disconnect_pending = false;
- /* true iff the store should be removed after disconnection is complete */
- private bool _removal_pending = false;
-
- private Debug _debug;
- private PersonaStoreCache _cache;
- private Cancellable? _load_cache_cancellable = null;
- /* Whether data in memory is dirty and needs flushing to the cache at some
- * point. For example, this will be true if a contact has updated their avatar
- * while we've been online. */
- private bool _cache_needs_update = false;
-
- /* marshalled from ContactInfo.SupportedFields */
- private SmallSet<string> _supported_fields;
- private Set<string> _supported_fields_ro;
-
- private Account _account;
-
- private FolksTpZeitgeist.Controller _zg_controller;
-
- /**
- * The Telepathy account this store is based upon.
- */
- [Description(nick = "basis account",
- blurb = "Telepathy account this store is based upon")]
- public Account account
- {
- get { return this._account; }
- construct
- {
- this._account = value;
- this._account.invalidated.connect (this._account_invalidated_cb);
- }
- }
-
- /**
- * The type of persona store this is.
- *
- * See {@link Folks.PersonaStore.type_id}.
- */
- public override string type_id { get { return BACKEND_NAME; } }
-
- /**
- * Whether this PersonaStore can add {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_add_personas}.
- *
- * @since 0.3.1
- */
- public override MaybeBool can_add_personas
- {
- get { return this._can_add_personas; }
- }
-
- /**
- * Whether this PersonaStore can set the alias of {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_alias_personas}.
- *
- * @since 0.3.1
- */
- public override MaybeBool can_alias_personas
- {
- get { return this._can_alias_personas; }
- }
-
- /**
- * Whether this PersonaStore can set the groups of {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_group_personas}.
- *
- * @since 0.3.1
- */
- public override MaybeBool can_group_personas
- {
- get { return this._can_group_personas; }
- }
-
- /**
- * Whether this PersonaStore can remove {@link Folks.Persona}s.
- *
- * See {@link Folks.PersonaStore.can_remove_personas}.
- *
- * @since 0.3.1
- */
- public override MaybeBool can_remove_personas
- {
- get { return this._can_remove_personas; }
- }
-
- /**
- * Whether this PersonaStore has been prepared.
- *
- * See {@link Folks.PersonaStore.is_prepared}.
- *
- * @since 0.3.0
- */
- public override bool is_prepared
- {
- get { return this._is_prepared; }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public override string[] always_writeable_properties
- {
- get { return this._always_writeable_properties; }
- }
-
- /*
- * Whether this PersonaStore has reached a quiescent state.
- *
- * See {@link Folks.PersonaStore.is_quiescent}.
- *
- * @since 0.6.2
- */
- public override bool is_quiescent
- {
- get { return this._is_quiescent; }
- }
-
- private void _notify_if_is_quiescent ()
- {
- if (this._got_initial_members == true &&
- this._got_initial_self_contact == true &&
- this._is_quiescent == false)
- {
- this._is_quiescent = true;
- this.notify_property ("is-quiescent");
- }
- }
-
- private void _force_quiescent ()
- {
- this._got_initial_self_contact = true;
- this._got_initial_members = true;
- this._notify_if_is_quiescent ();
- }
-
- /**
- * The {@link Persona}s exposed by this PersonaStore.
- *
- * See {@link Folks.PersonaStore.personas}.
- */
- public override Map<string, Folks.Persona> personas
- {
- get { return this._personas_ro; }
- }
-
- internal Set<string> supported_fields
- {
- get { return this._supported_fields_ro; }
- }
-
- /**
- * Create a new PersonaStore.
- *
- * Create a new persona store to store the {@link Persona}s for the contacts
- * in the Telepathy account provided by ``account``.
- *
- * @param account the Telepathy account being represented by the persona store
- */
- public PersonaStore (Account account)
- {
- Object (account: account,
- display_name: account.display_name,
- id: account.get_object_path ());
- }
-
- construct
- {
- debug ("Creating new Tpf.PersonaStore %p ('%s') for TpAccount %p.",
- this, this.id, this.account);
-
- this._debug = Debug.dup ();
- this._debug.print_status.connect (this._debug_print_status);
-
- // Add to the map of persona stores by account
- PersonaStore._add_store_to_map (this);
-
- // Set up the cache
- this._cache = new PersonaStoreCache (this);
-
- this._reset ();
- }
-
- ~PersonaStore ()
- {
- debug ("Destroying Tpf.PersonaStore %p ('%s').", this, this.id);
-
- this._reset ();
-
- // Remove from the map of persona stores by account
- PersonaStore._remove_store_from_map (this);
-
- this._debug.print_status.disconnect (this._debug_print_status);
- this._debug = null;
- if (this._logger != null)
- this._logger.invalidated.disconnect (this._logger_invalidated_cb);
-
- this._account.invalidated.disconnect (this._account_invalidated_cb);
-
- if (this._account_manager != null)
- {
- this._account_manager.invalidated.disconnect (
- this._account_manager_invalidated_cb);
- this._account_manager = null;
- }
-
- this._zg_controller = null;
- }
-
- private string _format_maybe_bool (MaybeBool input)
- {
- switch (input)
- {
- case MaybeBool.UNSET:
- return "unset";
- case MaybeBool.TRUE:
- return "true";
- case MaybeBool.FALSE:
- return "false";
- default:
- assert_not_reached ();
- }
- }
-
- private void _debug_print_status (Debug debug)
- {
- const string domain = Debug.STATUS_LOG_DOMAIN;
- const LogLevelFlags level = LogLevelFlags.LEVEL_INFO;
-
- debug.print_heading (domain, level, "Tpf.PersonaStore (%p)", this);
- debug.print_key_value_pairs (domain, level,
- "ID", this.id,
- "Prepared?", this._is_prepared ? "yes" : "no",
- "Has initial members?", this._got_initial_members ? "yes" : "no",
- "Has self contact?", this._got_initial_self_contact ? "yes" : "no",
- "TpConnection", "%p".printf (this._conn),
- "TpAccountManager", "%p".printf (this._account_manager),
- "Self-Persona", "%p".printf (this._self_persona),
- "Can add personas?", this._format_maybe_bool (this._can_add_personas),
- "Can alias personas?",
- this._format_maybe_bool (this._can_alias_personas),
- "Can group personas?",
- this._format_maybe_bool (this._can_group_personas),
- "Can remove personas?",
- this._format_maybe_bool (this._can_remove_personas)
- );
-
- debug.print_line (domain, level, "%u Personas:", this._persona_set.size);
- debug.indent ();
-
- foreach (var persona in this._persona_set)
- {
- debug.print_heading (domain, level, "Persona (%p)", persona);
- debug.print_key_value_pairs (domain, level,
- "UID", persona.uid,
- "IID", persona.iid,
- "Display ID", persona.display_id,
- "User?", persona.is_user ? "yes" : "no",
- "In contact list?", persona.is_in_contact_list ? "yes" : "no",
- "TpContact", "%p".printf (persona.contact)
- );
- }
-
- debug.unindent ();
-
- debug.print_line (domain, level, "%u TpContact–Persona mappings:",
- this._contact_persona_map.size);
- debug.indent ();
-
- var iter1 = this._contact_persona_map.map_iterator ();
- while (iter1.next () == true)
- {
- debug.print_line (domain, level,
- "%s → %p", iter1.get_key ().get_identifier (), iter1.get_value ());
- }
-
- debug.unindent ();
-
- debug.print_line (domain, level, "%u favourite TpContact IDs:",
- this._favourite_ids.size);
- debug.indent ();
-
- foreach (var id in this._favourite_ids)
- {
- debug.print_line (domain, level, "%s", id);
- }
-
- debug.unindent ();
-
- debug.print_line (domain, level, "Cached avatars for %u personas:",
- this._avatars.size);
- debug.indent ();
-
- foreach (var id in this._avatars.keys)
- {
- debug.print_line (domain, level, "%s", id);
- }
-
- debug.unindent ();
-
- /* Finish with a blank line. The format string must be non-empty. */
- debug.print_line (domain, level, "%s", "");
- }
-
- private void _reset ()
- {
- debug ("Resetting Tpf.PersonaStore %p ('%s')", this, this.id);
-
- /* We do not trust local-xmpp or IRC at all, since Persona UIDs can be
- * faked by just changing hostname/username or nickname. */
- if (account.protocol_name == "local-xmpp" ||
- account.protocol_name == "irc")
- this.trust_level = PersonaStoreTrust.NONE;
- else
- this.trust_level = PersonaStoreTrust.PARTIAL;
-
- this._personas = new HashMap<string, Persona> ();
- this._personas_ro = this._personas.read_only_view;
- this._persona_set = new HashSet<Persona> ();
- this._cache_needs_update = false;
-
- if (this._conn != null)
- {
- this._conn.notify["self-contact"].disconnect (
- this._self_contact_changed_cb);
- this._conn.notify["contact-list-state"].disconnect (
- this._contact_list_state_changed_cb);
- this._conn.contact_list_changed.disconnect (
- this._contact_list_changed_cb);
-
- this._conn = null;
- }
-
- if (this._contact_persona_map != null)
- {
- var iter = this._contact_persona_map.map_iterator ();
- while (iter.next () == true)
- {
- var contact = iter.get_key ();
- contact.weak_unref (this._contact_weak_notify_cb);
- }
- }
-
- this._contact_persona_map = new HashMap<unowned Contact, Persona> ();
-
- this._supported_fields = new SmallSet<string> ();
- this._supported_fields_ro = this._supported_fields.read_only_view;
- this._self_persona = null;
- }
-
- private void _remove_store (Set<Persona> old_personas)
- {
- if (this._disconnect_pending == true)
- {
- /* The removal will be completed in _notify_connection_cb().
- * See: bgo#683390. */
- debug ("Delaying removing store %s (%p) due to pending disconnect.",
- this.id, this);
- this._removal_pending = true;
- }
- else
- {
- debug ("Removing store %s (%p)", this.id, this);
- this._removal_pending = false;
-
- this._emit_personas_changed (null, old_personas);
- this._cache.clear_cache.begin ();
-
- this.removed ();
- }
- }
-
- /**
- * Prepare the PersonaStore for use.
- *
- * See {@link Folks.PersonaStore.prepare}.
- *
- * @throws GLib.Error currently unused
- */
- public override async void prepare () throws GLib.Error
- {
- var profiling_prepare = Internal.profiling_start ("preparing Tpf.PersonaStore (ID: %s)", this.id);
-
- if (this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
-
- this._account_manager = AccountManager.dup ();
-
- /* FIXME: Add all contact features on AM's factory. We should not
- * force preparing all features but let app define what it needs,
- * but this is for backward compatibility.
- * Note that if application already prepared TpContacts before
- * preparing this store, this will have no effect on existing
- * contacts. */
- var factory = this._account_manager.get_factory ();
- factory.add_contact_features ({
- ContactFeature.ALIAS,
- ContactFeature.AVATAR_DATA,
- ContactFeature.AVATAR_TOKEN,
- ContactFeature.CAPABILITIES,
- ContactFeature.CLIENT_TYPES,
- ContactFeature.PRESENCE,
- ContactFeature.CONTACT_INFO,
- ContactFeature.CONTACT_GROUPS
- });
-
- this._account_manager.invalidated.connect (
- this._account_manager_invalidated_cb);
-
- /* Note: For the three signal handlers below, we do *not* need to
- * store personas to the cache before removing the store, as
- * _remove_store() deletes the cache file. */
- this._account_manager.account_removed.connect ((a) =>
- {
- if (this.account == a)
- {
- debug ("Account %p (‘%s’) removed.", a, a.display_name);
- this._remove_store (this._persona_set);
- }
- });
- this._account_manager.account_validity_changed.connect (
- (a, valid) =>
- {
- if (!valid && this.account == a)
- {
- debug ("Account %p (‘%s’) invalid.", a,
- a.display_name);
- this._remove_store (this._persona_set);
- }
- });
- this._account_manager.account_disabled.connect ((a) =>
- {
- if (this.account == a)
- {
- debug ("Account %p (‘%s’) disabled.", a, a.display_name);
- this._remove_store (this._persona_set);
- }
- });
-
- Internal.profiling_point ("created account manager in " +
- "Tpf.PersonaStore (ID: %s)", this.id);
-
- this._avatars.clear ();
-
- this._favourite_ids.clear ();
- this._logger = new Logger (this.id);
- this._logger.invalidated.connect (
- this._logger_invalidated_cb);
- this._logger.favourite_contacts_changed.connect (
- this._favourite_contacts_changed_cb);
-
- var profiling = Internal.profiling_start ("initialising favourite contacts in " +
- "Tpf.PersonaStore (ID: %s)", this.id);
- this._initialise_favourite_contacts.begin ((o, r) =>
- {
- try
- {
- this._initialise_favourite_contacts.end (r);
- Internal.profiling_end ((owned) profiling);
- }
- catch (GLib.Error e)
- {
- debug ("Failed to initialise favourite contacts: %s",
- e.message);
- this._logger = null;
- }
- });
-
- Internal.profiling_point ("created logger in Tpf.PersonaStore " +
- "(ID: %s)", this.id);
-
- this.account.notify["connection"].connect (
- this._notify_connection_cb);
-
- /* immediately handle accounts which are not currently being
- * disconnected */
- if (this.account.connection != null)
- {
- this._notify_connection_cb (this.account, null);
- }
- else
- {
- /* If we're disconnected, advertise personas from the cache
- * instead. */
- yield this._load_cache (null);
- this._force_quiescent ();
- }
-
- Internal.profiling_point ("loaded cache in Tpf.PersonaStore " +
- "(ID: %s)", this.id);
-
- this._is_prepared = true;
- this.notify_property ("is-prepared");
- }
- finally
- {
- this._prepare_pending = false;
- }
-
- Internal.profiling_end ((owned) profiling_prepare);
- }
-
- private void _account_manager_invalidated_cb (uint domain, int code,
- string message)
- {
- debug ("TpAccountManager invalidated (%u, %i, “%s”) for " +
- "Tpf.PersonaStore %p (‘%s’).", domain, code, message, this, this.id);
- this._remove_store (this._persona_set);
- }
-
- private void _account_invalidated_cb (uint domain, int code, string message)
- {
- debug ("TpAccount invalidated (%u, %i, “%s”) for " +
- "Tpf.PersonaStore %p (‘%s’).", domain, code, message, this, this.id);
- this._remove_store (this._persona_set);
- }
-
- private void _logger_invalidated_cb ()
- {
- this._logger.invalidated.disconnect (this._logger_invalidated_cb);
-
- debug ("Lost connection to the telepathy-logger service.");
- this._logger = null;
- }
-
- /* This method is not safe to call multiple times concurrently. */
- private async void _initialise_favourite_contacts () throws GLib.Error
- {
- if (this._logger == null)
- return;
-
- yield this._logger.prepare ();
-
- var contacts = yield this._logger.get_favourite_contacts ();
- this._favourite_contacts_changed_cb (contacts, {});
-
- this._always_writeable_properties += "is-favourite";
- this.notify_property ("always-writeable-properties");
- }
-
- private Persona? _lookup_persona_by_id (string id)
- {
- /* This is not efficient, but better than doing DBus roundtrip to get a
- * TpContact. */
- var iter = this._contact_persona_map.map_iterator ();
- while (iter.next ())
- {
- if (iter.get_key ().get_identifier () == id)
- {
- return iter.get_value ();
- }
- }
- return null;
- }
-
- private void _favourite_contacts_changed_cb (string[] added, string[] removed)
- {
- foreach (var id in added)
- {
- this._favourite_ids.add (id);
- var p = this._lookup_persona_by_id (id);
- if (p != null)
- {
- p._set_is_favourite (true);
- }
- }
- foreach (var id in removed)
- {
- this._favourite_ids.remove (id);
- var p = this._lookup_persona_by_id (id);
- if (p != null)
- {
- p._set_is_favourite (false);
- }
- }
- }
-
- /* This is called when we go online, when the user chooses to go offline, or
- * when a CM crashes. */
- private void _notify_connection_cb (Object s, ParamSpec? p)
- {
- var account = s as TelepathyGLib.Account;
-
- debug ("Account '%s' connection changed to %p", this.id,
- account.connection);
-
- /* account disconnected */
- if (account.connection == null)
- {
- this._supported_fields.clear ();
- this.notify_property ("supported-fields");
-
- /* When disconnecting, we want the PersonaStore to remain alive, but
- * all its Personas to be removed. We do *not* want the PersonaStore
- * to be destroyed, as that makes coming back online hard.
- *
- * We have to start advertising personas from the cache instead.
- * This will implicitly notify about removal of the existing persona
- * set and call this._reset().
- *
- * Before we do this, we store the current set of personas to the
- * cache, assuming we were connected before. */
- if (this._conn != null)
- {
- this._disconnect_pending = true;
-
- /* Call reset immediately, otherwise TpConnection's invalidation
- * will cause all contacts to weak notify. See bug #675141 */
- var old_personas = this._persona_set;
- var old_cache_needs_update = this._cache_needs_update;
- this._reset ();
-
- if (old_cache_needs_update)
- {
- this._set_cache_needs_update ();
- }
-
- this._store_cache.begin (old_personas, (o, r) =>
- {
- this._store_cache.end (r);
-
- this._disconnect_pending = false;
-
- if (this._removal_pending == false)
- {
- this._load_cache.begin (old_personas, (o2, r2) =>
- {
- this._load_cache.end (r2);
- });
- }
- else
- {
- /* If the PersonaStore has been invalidated or disabled,
- * remove it. This is done here rather than in the
- * signal handlers for account-disabled or
- * account-validity-changed so that the cache is handled
- * properly. See: bgo#683390. */
- assert (this._disconnect_pending == false);
- this._remove_store (old_personas);
- }
- });
- }
-
- /* If the persona store starts offline, we've reached a quiescent
- * state. */
- this._force_quiescent ();
-
- return;
- }
-
- this._notify_connection_cb_async.begin ();
- }
-
- private async void _notify_connection_cb_async ()
- {
- debug ("_notify_connection_cb_async() for Tpf.PersonaStore %p ('%s').",
- this, this.id);
-
- var profiling = Internal.profiling_start ("notify connection for Tpf.PersonaStore " +
- "(ID: %s)", this.id);
-
- /* Ensure the connection is prepared as necessary. */
- try
- {
- yield this.account.connection.prepare_async ({
- TelepathyGLib.Connection.get_feature_quark_contact_list (),
- TelepathyGLib.Connection.get_feature_quark_contact_groups (),
- TelepathyGLib.Connection.get_feature_quark_contact_info (),
- TelepathyGLib.Connection.get_feature_quark_connected (),
- TelepathyGLib.Connection.get_feature_quark_aliasing (),
- 0
- });
- }
- catch (GLib.Error e)
- {
- debug ("Failed to connect CM for Tpf.PersonaStore %p ('%s'): %s",
- this, this.id, e.message);
-
- /* If we're disconnected, advertise personas from the cache
- * instead. */
- yield this._load_cache (null);
- this._force_quiescent ();
-
- return;
- }
-
- if (!this.account.connection.has_interface_by_id (
- iface_quark_connection_interface_contact_list ()))
- {
- debug ("Connection does not implement ContactList iface; " +
- "legacy CMs are not supported any more.");
-
- this._remove_store (this._persona_set);
-
- return;
- }
-
- // We're connected, so can stop advertising personas from the cache
- this._unload_cache ();
-
- this._conn = this.account.connection;
-
- /* Connect signals early so that cleaning up is easier if the connection
- * is disconnected during the 'yield' below. */
- this._conn.notify["self-contact"].connect (
- this._self_contact_changed_cb);
- this._conn.notify["contact-list-state"].connect (
- this._contact_list_state_changed_cb);
-
- /* Emit all the notifications after the 'yield' just in case the
- * connection disappears during it. This makes cleaning up easier. */
- this.freeze_notify ();
- this._marshall_supported_fields ();
- this.notify_property ("supported-fields");
-
- if (this._conn.get_group_storage () != ContactMetadataStorageType.NONE)
- {
- this._can_group_personas = MaybeBool.TRUE;
-
- this._always_writeable_properties += "groups";
- this.notify_property ("always-writeable-properties");
- }
- else
- {
- this._can_group_personas = MaybeBool.FALSE;
- }
- this.notify_property ("can-group-personas");
-
- if (this._conn.get_can_change_contact_list ())
- {
- this._can_add_personas = MaybeBool.TRUE;
- this._can_remove_personas = MaybeBool.TRUE;
- }
- else
- {
- this._can_add_personas = MaybeBool.FALSE;
- this._can_remove_personas = MaybeBool.FALSE;
- }
- this.notify_property ("can-add-personas");
- this.notify_property ("can-remove-personas");
-
- var new_can_alias = MaybeBool.FALSE;
-
- if (this._conn.can_set_contact_alias ())
- {
- new_can_alias = MaybeBool.TRUE;
-
- this._always_writeable_properties += "alias";
- this.notify_property ("always-writeable-properties");
- }
-
- this._can_alias_personas = new_can_alias;
- this.notify_property ("can-alias-personas");
-
- this.thaw_notify ();
-
- /* Add the local user */
- this._self_contact_changed_cb (this._conn, null);
- this._contact_list_state_changed_cb (this._conn, null);
-
- Internal.profiling_end ((owned) profiling);
- }
-
- private void _marshall_supported_fields ()
- {
- var connection = this.account.connection;
- if (connection != null)
- {
- this._supported_fields.clear ();
-
- var ci_flags = connection.get_contact_info_flags ();
- if ((ci_flags & ContactInfoFlags.CAN_SET) != 0)
- {
- var field_specs =
- connection.dup_contact_info_supported_fields ();
- foreach (var field_spec in field_specs)
- {
- /* XXX: we ignore the maximum count for each type of
- * field since the common-sense count for each
- * corresponding field (eg, full-name max = 1) in
- * Folks is already reflected in our API and we have
- * no other way to express it; but this seems a very
- * minor problem */
- this._supported_fields.add (field_spec.name);
- }
- }
- }
- }
-
- /**
- * If our account is disconnected, we want to continue to export a static
- * view of personas from the cache. old_personas will be notified as removed.
- *
- * This method is safe to call multiple times concurrently. Previous calls
- * will be cancelled by subsequent calls.
- */
- private async void _load_cache (HashSet<Persona>? old_personas)
- {
- /* Only load from the cache if the account is enabled and valid. */
- if (this.account.enabled == false || this.account.valid == false)
- {
- debug ("Skipping loading cache for Tpf.PersonaStore %p ('%s'): " +
- "enabled: %s, valid: %s.", this, this.id,
- this.account.enabled ? "yes" : "no",
- this.account.valid ? "yes" : "no");
-
- return;
- }
-
- debug ("Loading cache for Tpf.PersonaStore %p ('%s').", this, this.id);
-
- var cancellable = new Cancellable ();
-
- if (this._load_cache_cancellable != null)
- {
- debug (" Cancelling ongoing loading operation (cancellable: %p).",
- this._load_cache_cancellable);
- this._load_cache_cancellable.cancel ();
- }
-
- this._load_cache_cancellable = cancellable;
-
- // Load the persona set from the cache and notify of the change
- var cached_personas = yield this._cache.load_objects (cancellable);
-
- /* If the load operation was cancelled, don't change the state
- * of the persona store at all. */
- if (cancellable.is_cancelled () == true)
- {
- debug (" Cancelled (cancellable: %p).", cancellable);
- return;
- }
-
- this._reset ();
-
- this._persona_set = new HashSet<Persona> ();
- if (cached_personas != null)
- {
- foreach (var p in cached_personas)
- {
- this._add_persona (p);
- }
- }
-
- this._emit_personas_changed (cached_personas, old_personas,
- null, null, GroupDetails.ChangeReason.NONE);
-
- this._can_add_personas = MaybeBool.FALSE;
- this._can_alias_personas = MaybeBool.FALSE;
- this._can_group_personas = MaybeBool.FALSE;
- this._can_remove_personas = MaybeBool.FALSE;
-
- if (this._logger != null)
- {
- this._always_writeable_properties = { "is-favourite" };
- }
- else
- {
- this._always_writeable_properties = {};
- }
-
- this.notify_property ("always-writeable-properties");
- }
-
- /* This method is safe to call multiple times concurrently. */
- public override async void flush ()
- {
- debug ("Flushing Tpf.PersonaStore %p (‘%s’).", this, this.id);
-
- /* Store the cache if it needs an update. */
- yield this._store_cache (this._persona_set);
- }
-
- /**
- * Called when a contact property is set which is not accessible when either
- * the contact is offline or we're offline. For example, a contact's avatar.
- * This will cause the cache to be stored when the PersonaStore is destroyed.
- */
- internal void _set_cache_needs_update ()
- {
- debug ("Setting cache as needing an update for Tpf.PersonaStore " +
- "%p (‘%s’).", this, this.id);
- this._cache_needs_update = true;
- }
-
- /**
- * When we're about to disconnect, store the current set of personas to the
- * cache file so that we can access them once offline.
- *
- * This method is safe to call multiple times concurrently.
- */
- private async void _store_cache (HashSet<Persona> old_personas)
- {
- /* Only store/load the cache if the account is enabled and valid;
- * otherwise, the PersonaStore will get removed and the cache
- * deleted later anyway. */
- if (!this.account.enabled || !this.account.valid)
- {
- debug ("Skipping storing cache for Tpf.PersonaStore %p (‘%s’) as " +
- "its TpAccount is disabled or invalid.", this, this.id);
- return;
- }
- else if (this._cache_needs_update == false)
- {
- debug ("Skipping storing cache for Tpf.PersonaStore %p (‘%s’) as " +
- "it doesn’t need an update.", this, this.id);
- return;
- }
-
- debug ("Storing cache for Tpf.PersonaStore %p ('%s').", this, this.id);
-
- yield this._cache.store_objects (old_personas);
- this._cache_needs_update = false;
- }
-
- /**
- * When our account is connected again, we can unload the the personas which
- * we're advertising from the cache.
- */
- private void _unload_cache ()
- {
- debug ("Unloading cache for Tpf.PersonaStore %p ('%s').", this, this.id);
-
- // If we're in the process of loading from the cache, cancel that
- if (this._load_cache_cancellable != null)
- {
- debug (" Cancelling ongoing loading operation (cancellable: %p).",
- this._load_cache_cancellable);
- this._load_cache_cancellable.cancel ();
- }
-
- this._emit_personas_changed (null, this._persona_set, null, null,
- GroupDetails.ChangeReason.NONE);
-
- this._reset ();
- }
-
- internal void _update_avatar_cache (string persona_iid, File? avatar_file)
- {
- if (avatar_file == null)
- {
- this._avatars.unset (persona_iid);
- }
- else
- {
- this._avatars.set (persona_iid, (!) avatar_file);
- }
- }
-
- internal File? _query_avatar_cache (string persona_iid)
- {
- return this._avatars.get (persona_iid);
- }
-
- private bool _add_persona (Persona p)
- {
- if (this._persona_set.add (p))
- {
- debug ("Add persona %p with uid %s", p, p.uid);
- this._personas.set (p.iid, p);
- return true;
- }
-
- return false;
- }
-
- private bool _remove_persona (Persona p)
- {
- if (this._persona_set.remove (p))
- {
- debug ("Remove persona %p with uid %s", p, p.uid);
- this._personas.unset (p.iid);
- if (this._self_persona == p)
- {
- this._self_persona = null;
- }
-
- return true;
- }
-
- return false;
- }
-
- private void _contact_weak_notify_cb (Object obj)
- {
- if (this._contact_persona_map == null)
- {
- return;
- }
-
- Contact contact = obj as Contact;
- debug ("Weak notify for TpContact %s", contact.get_identifier ());
-
- Persona? persona = null;
- this._contact_persona_map.unset (contact, out persona);
- if (persona == null)
- {
- return;
- }
-
- persona._contact_weak_notify ();
-
- if (this._remove_persona (persona))
- {
- /* This should never happen because TpConnection keeps a ref on
- * self and roster TpContacts, so they should have been removed
- * already. But deal with it just in case... */
- warning ("A TpContact part of the ContactList is disposed");
- var personas = new SmallSet<Persona> ();
- personas.add (persona);
- this._emit_personas_changed (null, personas);
- }
- }
-
- /* Ensure that we have a Persona wrapping this TpContact. This will keep the
- * Persona internally only (won't emit personas_changed) and until the
- * TpContact is destroyed (we keep only weak ref). */
- internal Tpf.Persona _ensure_persona_for_contact (Contact contact)
- {
- Persona? persona = this._contact_persona_map[contact];
- if (persona != null)
- return (!) persona;
-
- persona = new Tpf.Persona (contact, this);
- this._contact_persona_map[contact] = persona;
- contact.weak_ref (this._contact_weak_notify_cb);
-
- var is_favourite = this._favourite_ids.contains (contact.get_identifier ());
- persona._set_is_favourite (is_favourite);
-
- debug ("Persona %p with uid %s created for TpContact %s, favourite: %s",
- persona, persona.uid, contact.get_identifier (),
- is_favourite ? "yes" : "no");
-
- return persona;
- }
-
- private void _self_contact_changed_cb (Object s, ParamSpec? p)
- {
- var contact = this._conn.self_contact;
-
- var personas_added = new SmallSet<Persona> ();
- var personas_removed = new SmallSet<Persona> ();
-
- /* Remove old self persona if not also part of roster. Keep a reference
- * to the persona so _remove_persona() doesn't unset it early. */
- var self_persona = this._self_persona;
-
- if (self_persona != null &&
- !self_persona.is_in_contact_list &&
- this._remove_persona (self_persona))
- {
- personas_removed.add (self_persona);
- }
-
- this._self_persona = null;
-
- if (contact != null)
- {
- /* Add the local user to roster */
- this._self_persona = this._ensure_persona_for_contact (contact);
- if (this._add_persona (this._self_persona))
- personas_added.add (this._self_persona);
- }
-
- this._emit_personas_changed (personas_added, personas_removed);
-
- this._got_initial_self_contact = true;
- this._notify_if_is_quiescent ();
- }
-
- private void _contact_list_state_changed_cb (Object s, ParamSpec? p)
- {
- /* Once the contact list is downloaded from server, state moves to
- * SUCCESS and won't change anymore */
- if (this._conn.contact_list_state != ContactListState.SUCCESS)
- return;
-
- this._conn.contact_list_changed.connect (this._contact_list_changed_cb);
- this._contact_list_changed_cb (this._conn.dup_contact_list (),
- new GLib.GenericArray<weak TelepathyGLib.Contact> ());
-
- this._got_initial_members = true;
- this._populate_counters.begin ();
- this._notify_if_is_quiescent ();
- }
-
- private void _contact_list_changed_cb (GLib.GenericArray<weak TelepathyGLib.Contact> added,
- GLib.GenericArray<weak TelepathyGLib.Contact> removed)
- {
- var personas_added = new HashSet<Persona> ();
- var personas_removed = new HashSet<Persona> ();
-
- debug ("contact list changed: %d added, %d removed",
- added.length, removed.length);
-
- foreach (Contact contact in added.data)
- {
- var persona = this._ensure_persona_for_contact (contact);
-
- if (!persona.is_in_contact_list)
- {
- persona.is_in_contact_list = true;
- }
-
- if (this._add_persona (persona))
- {
- personas_added.add (persona);
- }
- }
-
- foreach (Contact contact in removed.data)
- {
- var persona = this._contact_persona_map[contact];
-
- if (persona == null)
- {
- warning ("Unknown TpContact removed from ContactList: %s",
- contact.get_identifier ());
- continue;
- }
-
- /* If self contact was also part of the roster but got removed,
- * we keep it in our persona store, but with is_in_contact_list=false.
- * This matches behaviour of _self_contact_changed_cb() where we add
- * the self persona into the user-visible set even if it is not part
- * of the roster. */
- if (persona == this._self_persona)
- {
- persona.is_in_contact_list = false;
- continue;
- }
-
- if (this._remove_persona (persona))
- {
- personas_removed.add (persona);
- }
- }
-
- this._emit_personas_changed (personas_added, personas_removed);
- }
-
- /**
- * Remove a {@link Persona} from the PersonaStore.
- *
- * See {@link Folks.PersonaStore.remove_persona}.
- *
- * @throws Folks.PersonaStoreError.UNSUPPORTED_ON_USER if ``persona`` is the
- * local user — removing the local user isn’t supported
- * @throws Folks.PersonaStoreError.REMOVE_FAILED if removing the contact
- * failed
- */
- public override async void remove_persona (Folks.Persona persona)
- throws Folks.PersonaStoreError
- {
- var tp_persona = (Tpf.Persona) persona;
-
- if (tp_persona.contact == null)
- {
- warning ("Skipping server-side removal of Tpf.Persona %p because " +
- "it has no attached TpContact", tp_persona);
- return;
- }
-
- if (persona == this._self_persona &&
- tp_persona.is_in_contact_list == false)
- {
- throw new PersonaStoreError.UNSUPPORTED_ON_USER (
- _("Telepathy contacts representing the local user may not be removed."));
- }
-
- try
- {
- yield tp_persona.contact.remove_async ();
- }
- catch (GLib.Error e)
- {
- throw new PersonaStoreError.REMOVE_FAILED (
- /* Translators: the parameter is an error message. */
- _("Failed to remove a persona from store: %s"), e.message);
- }
- }
-
- /* This method is safe to call multiple times concurrently for the same (or
- * different) contact ID, assuming Telepathy is safe. */
- private async Persona _ensure_persona_for_id (string contact_id)
- throws GLib.Error
- {
- var contact = yield this._conn.dup_contact_by_id_async (contact_id, {});
- return this._ensure_persona_for_contact (contact);
- }
-
- /**
- * Add a new {@link Persona} to the PersonaStore.
- *
- * See {@link Folks.PersonaStore.add_persona_from_details}.
- *
- * This method is safe to call multiple times concurrently for the same (or
- * different) contact IDs (assuming Telepathy is safe).
- *
- * @throws Folks.PersonaStoreError.INVALID_ARGUMENT if the ``contact`` key was
- * not provided in ``details``
- * @throws Folks.PersonaStoreError.STORE_OFFLINE if the CM is offline
- * @throws Folks.PersonaStoreError.CREATE_FAILED if adding the contact failed
- */
- public override async Folks.Persona? add_persona_from_details (
- HashTable<string, Value?> details) throws Folks.PersonaStoreError
- {
- var contact_id = TelepathyGLib.asv_get_string (details, "contact");
- if (contact_id == null)
- {
- throw new PersonaStoreError.INVALID_ARGUMENT (
- /* Translators: the first two parameters are store identifiers and
- * the third is a contact identifier. */
- _("Persona store (%s, %s) requires the following details:\n contact (provided: ‘%s’)\n"),
- this.type_id, this.id, contact_id);
- }
-
- // Optional message to pass to the new persona
- var add_message = TelepathyGLib.asv_get_string (details, "message");
- if (add_message == "")
- add_message = null;
-
- var status = this.account.get_connection_status (null);
- if ((status == TelepathyGLib.ConnectionStatus.DISCONNECTED) ||
- (status == TelepathyGLib.ConnectionStatus.CONNECTING) ||
- this._conn == null)
- {
- throw new PersonaStoreError.STORE_OFFLINE (
- _("Cannot create a new Telepathy contact while offline."));
- }
-
- try
- {
- var persona = yield this._ensure_persona_for_id (contact_id);
- var already_exists = persona.is_in_contact_list;
- var tp_persona = (Tpf.Persona) persona;
- yield tp_persona.contact.request_subscription_async (add_message);
-
- /* This function is supposed to return null if the persona was already
- * in the contact list. */
- return already_exists ? null : persona;
- }
- catch (GLib.Error e)
- {
- throw new PersonaStoreError.CREATE_FAILED (
- /* Translators: the parameter is an error message. */
- _("Failed to add a persona from details: %s"), e.message);
- }
- }
-
- /**
- * Change the favourite status of a persona in this store.
- *
- * This function is idempotent, but relies upon having a connection to the
- * Telepathy logger service, so may fail if that connection is not present.
- */
- internal async void change_is_favourite (Folks.Persona persona,
- bool is_favourite) throws PropertyError
- {
- /* It's possible for us to not be able to connect to the logger;
- * see _connection_ready_cb() */
- if (this._logger == null)
- {
- throw new PropertyError.UNKNOWN_ERROR (
- /* Translators: "telepathy-logger" is the name of an application,
- * and should not be translated. */
- _("Failed to change favorite without a connection to the telepathy-logger service."));
- }
-
- if (((Tpf.Persona) persona).contact == null)
- {
- throw new PropertyError.INVALID_VALUE (
- _("Failed to change favorite status of Telepathy Persona because it has no attached TpContact."));
- }
-
- try
- {
- /* Add or remove the persona to the list of favourites as
- * appropriate. */
- unowned string id = ((Tpf.Persona) persona).contact.get_identifier ();
-
- if (is_favourite)
- yield this._logger.add_favourite_contact (id);
- else
- yield this._logger.remove_favourite_contact (id);
- }
- catch (GLib.Error e)
- {
- throw new PropertyError.UNKNOWN_ERROR (
- /* Translators: the parameter is a contact identifier. */
- _("Failed to change favorite status for Telepathy contact ‘%s’."),
- ((Tpf.Persona) persona).contact.identifier);
- }
- }
-
- internal async void change_alias (Tpf.Persona persona, string alias)
- throws PropertyError
- {
- /* Deal with badly-behaved callers */
- if (alias == null)
- {
- alias = "";
- }
-
- if (persona.contact == null)
- {
- warning ("Skipping Tpf.Persona %p alias change to '%s' because it " +
- "has no attached TpContact", persona, alias);
- return;
- }
-
- try
- {
- debug ("Changing alias of persona %s to '%s'.",
- persona.contact.get_identifier (), alias);
- yield FolksTpLowlevel.connection_set_contact_alias_async (this._conn,
- (Handle) persona.contact.handle, alias);
- }
- catch (GLib.Error e1)
- {
- throw new PropertyError.UNKNOWN_ERROR (
- /* Translators: the parameter is an error message. */
- _("Failed to change contact’s alias: %s"), e1.message);
- }
- }
-
- internal async void change_user_birthday (Tpf.Persona persona,
- DateTime? birthday) throws PersonaStoreError
- {
- string birthday_str;
-
- if (birthday == null)
- birthday_str = "";
- else
- birthday_str = birthday.to_string ();
-
- var info_set = new SmallSet<ContactInfoField> ();
- string[] values = { birthday_str };
- string[] parameters = { null };
-
- var field = new ContactInfoField ("bday", parameters, values);
- info_set.add (field);
-
- yield this._change_user_contact_info (persona, info_set);
- }
-
- internal async void change_user_full_name (Tpf.Persona persona,
- string full_name) throws PersonaStoreError
- {
- /* Deal with badly-behaved callers */
- if (full_name == null)
- {
- full_name = "";
- }
-
- var info_set = new SmallSet<ContactInfoField> ();
- string[] values = { full_name };
- string[] parameters = { null };
-
- var field = new ContactInfoField ("fn", parameters, values);
- info_set.add (field);
-
- yield this._change_user_contact_info (persona, info_set);
- }
-
- internal async void _change_user_details (
- Tpf.Persona persona, Set<AbstractFieldDetails<string>> details,
- string field_name)
- throws PersonaStoreError
- {
- var info_set = new SmallSet<ContactInfoField> ();
-
- foreach (var afd in details)
- {
- string[] values = { afd.value };
- string[] parameters = {};
-
- var iter = afd.parameters.map_iterator ();
-
- while (iter.next ())
- {
- var param_name = iter.get_key ();
- var param_value = iter.get_value ();
-
- parameters += @"$param_name=$param_value";
- }
-
- if (parameters.length == 0)
- parameters = { null };
-
- var field = new ContactInfoField (field_name, parameters, values);
- info_set.add (field);
- }
-
- yield this._change_user_contact_info (persona, info_set);
- }
-
- private async void _change_user_contact_info (Tpf.Persona persona,
- SmallSet<ContactInfoField> info_set) throws PersonaStoreError
- {
- if (!persona.is_user)
- {
- throw new PersonaStoreError.UNSUPPORTED_ON_NON_USER (
- _("Extended information may only be set on the user’s Telepathy contact."));
- }
-
- var info_list = PersonaStore._contact_info_set_to_list (info_set);
- if (this.account.connection != null)
- {
- GLib.Error? error = null;
- bool success = false;
- try
- {
- success =
- yield this._conn.set_contact_info_async (
- info_list);
- }
- catch (GLib.Error e)
- {
- error = e;
- }
-
- if (error != null || !success)
- {
- warning ("Failed to set extended information on user's " +
- "Telepathy contact: %s",
- error != null ? error.message : "(reason unknown)");
- }
- }
- else
- {
- throw new PersonaStoreError.STORE_OFFLINE (
- _("Extended information cannot be written because the store is disconnected."));
- }
- }
-
- private static GLib.List<ContactInfoField> _contact_info_set_to_list (
- SmallSet<ContactInfoField> info_set)
- {
- var info_list = new GLib.List<ContactInfoField> ();
- foreach (var info_field in info_set)
- {
- info_list.prepend (new ContactInfoField (
- info_field.field_name, info_field.parameters,
- info_field.field_value));
- }
- info_list.reverse ();
-
- return info_list;
- }
-
- /* Must be locked before being accessed. A ref. is held on each PersonaStore,
- * and they're only removed when they're finalised or their removed signal is
- * emitted. The map as a whole is lazily constructed and destroyed according
- * to when PersonaStores are constructed and destroyed. */
- private static HashMap<string /* Account object path */, PersonaStore>
- _persona_stores_by_account = null;
- private static Map<string, PersonaStore> _persona_stores_by_account_ro = null;
-
- /**
- * Get a map of all the currently constructed {@link Tpf.PersonaStore}s.
- *
- * If a {@link Folks.BackendStore} has been prepared, this map will be
- * complete, containing every store known to the Telepathy account manager. If
- * no {@link Folks.BackendStore} has been prepared, this map will only contain
- * the stores which have been created by calling
- * {@link Tpf.PersonaStore.dup_for_account}.
- *
- * This map is read-only. Use {@link Folks.BackendStore} or
- * {@link Tpf.PersonaStore.dup_for_account} to add stores.
- *
- * @return map from {@link Folks.PersonaStore.id} to {@link Tpf.PersonaStore}
- * @since 0.6.6
- */
- public static unowned Map<string, PersonaStore> list_persona_stores ()
- {
- unowned Map<string, PersonaStore> store;
-
- if (PersonaStore._persona_stores_by_account == null)
- {
- PersonaStore._persona_stores_by_account =
- new HashMap<string, PersonaStore> ();
- PersonaStore._persona_stores_by_account_ro =
- PersonaStore._persona_stores_by_account.read_only_view;
- }
-
- store = PersonaStore._persona_stores_by_account_ro;
-
- return store;
- }
-
- private static void _store_removed_cb (Folks.PersonaStore store)
- {
- /* Remove the store from the map. */
- PersonaStore._remove_store_from_map ((Tpf.PersonaStore) store);
- }
-
- private static void _add_store_to_map (PersonaStore store)
- {
- debug ("Adding PersonaStore %p ('%s') to map.", store, store.id);
-
- /* Lazy construction. */
- if (PersonaStore._persona_stores_by_account == null)
- {
- PersonaStore._persona_stores_by_account =
- new HashMap<string, PersonaStore> ();
- PersonaStore._persona_stores_by_account_ro =
- PersonaStore._persona_stores_by_account.read_only_view;
- }
-
- /* Bail if a store already exists for this account. */
- return_if_fail (
- !PersonaStore._persona_stores_by_account.has_key (store.id));
-
- /* Add the store. */
- PersonaStore._persona_stores_by_account.set (store.id, store);
- store.removed.connect (PersonaStore._store_removed_cb);
- }
-
- private static void _remove_store_from_map (PersonaStore store)
- {
- debug ("Removing PersonaStore %p ('%s') from map.", store, store.id);
-
- /* Bail if no store existed for this account. This can happen if the
- * store emits its removed() signal (correctly) before being
- * finalised; we remove the store from the map in both cases. */
- if (PersonaStore._persona_stores_by_account == null ||
- !PersonaStore._persona_stores_by_account.unset (store.id))
- {
- return;
- }
-
- store.removed.disconnect (PersonaStore._store_removed_cb);
-
- /* Lazy destruction. */
- if (PersonaStore._persona_stores_by_account.size == 0)
- {
- PersonaStore._persona_stores_by_account_ro = null;
- PersonaStore._persona_stores_by_account = null;
- }
- }
-
- /**
- * Look up a {@link Tpf.PersonaStore} by its {@link TelepathyGLib.Account}.
- *
- * If found, a new reference to the persona store will be returned. If not
- * found, a new {@link Tpf.PersonaStore} will be created for the account.
- *
- * See the documentation for {@link Tpf.PersonaStore.list_persona_stores} for
- * information on the lifecycle of these stores when a
- * {@link Folks.BackendStore} is and is not present.
- *
- * @param account the Telepathy account of the persona store
- * @return the persona store associated with the account
- * @since 0.6.6
- */
- public static PersonaStore dup_for_account (Account account)
- {
- PersonaStore? store = null;
-
- debug ("Tpf.PersonaStore.dup_for_account (%p):", account);
-
- /* If the store already exists, return it. */
- if (PersonaStore._persona_stores_by_account != null)
- {
- store =
- PersonaStore._persona_stores_by_account.get (
- account.get_object_path ());
- }
-
- /* Otherwise, we have to create it. It's added to the map in its
- * constructor. */
- if (store == null)
- {
- debug (" Creating new PersonaStore.");
- store = new PersonaStore (account);
- }
- else
- {
- debug (" Found existing PersonaStore %p ('%s').", store,
- store.id);
- }
-
- return store;
- }
-
- /* This method is safe to call multiple times concurrently. */
- private async void _populate_counters ()
- {
- this._zg_controller = new FolksTpZeitgeist.Controller (this,
- this.account, (p, dt) =>
- {
- var persona = p as Tpf.Persona;
- assert (persona != null);
- persona._increase_im_interaction_counter (dt);
- },
- (p, dt) =>
- {
- var persona = p as Tpf.Persona;
- assert (persona != null);
- persona._increase_last_call_interaction_counter (dt);
- });
- yield this._zg_controller.populate_counters ();
- this._notify_if_is_quiescent ();
- }
-}
diff --git a/backends/telepathy/lib/tpf-persona.vala b/backends/telepathy/lib/tpf-persona.vala
deleted file mode 100644
index f3defd38..00000000
--- a/backends/telepathy/lib/tpf-persona.vala
+++ /dev/null
@@ -1,1426 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- * Copyright (C) 2013 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using Gee;
-using GLib;
-using TelepathyGLib;
-using Folks;
-
-/**
- * A persona subclass which represents a single instant messaging contact from
- * Telepathy.
- *
- * There is a one-to-one correspondence between {@link Tpf.Persona}s and
- * {@link TelepathyGLib.Contact}s, although at any time the
- * {@link Tpf.Persona.contact} property of a persona may be ``null`` if the
- * contact's Telepathy connection isn't available (e.g. due to being offline).
- * In this case, the persona's properties persist from a local cache.
- */
-public class Tpf.Persona : Folks.Persona,
- AliasDetails,
- AvatarDetails,
- BirthdayDetails,
- EmailDetails,
- FavouriteDetails,
- GroupDetails,
- InteractionDetails,
- ImDetails,
- NameDetails,
- PhoneDetails,
- PresenceDetails,
- UrlDetails
-{
- private const string[] _linkable_properties =
- {
- "im-addresses",
- null /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=682698 */
- };
- private string[] _writeable_properties = null;
-
- /* Whether we've finished being constructed; this is used to prevent
- * unnecessary trips to the Telepathy service to tell it about properties
- * being set which are actually just being set from data it's just given us.
- */
- private bool _is_constructed = false;
-
- /**
- * Whether the Persona is in the user's contact list.
- *
- * This will be true for most {@link Folks.Persona}s, but may not be true for
- * personas where {@link Folks.Persona.is_user} is true. If it's false in
- * this case, it means that the persona has been retrieved from the Telepathy
- * connection, but has not been added to the user's contact list.
- *
- * @since 0.3.5
- */
- public bool is_in_contact_list { get; set; }
-
- private LoadableIcon? _avatar = null;
-
- /**
- * An avatar for the Persona.
- *
- * See {@link Folks.AvatarDetails.avatar}.
- *
- * @since 0.6.0
- */
- [CCode (notify = false)]
- public LoadableIcon? avatar
- {
- get { return this._avatar; }
- set { this.change_avatar.begin (value); } /* not writeable */
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.4
- */
- [CCode (notify = false)]
- public StructuredName? structured_name
- {
- get { return null; }
- set { this.change_structured_name.begin (value); } /* not writeable */
- }
-
- private string _full_name = ""; /* must never be null */
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.4
- */
- [CCode (notify = false)]
- public string full_name
- {
- get { return this._full_name; }
- set { this.change_full_name.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.4
- */
- public async void change_full_name (string full_name) throws PropertyError
- {
- var tpf_store = this.store as Tpf.PersonaStore;
-
- if (full_name == this._full_name)
- return;
-
- if (this._is_constructed)
- {
- try
- {
- yield tpf_store.change_user_full_name (this, full_name);
- }
- catch (PersonaStoreError.INVALID_ARGUMENT e1)
- {
- throw new PropertyError.NOT_WRITEABLE (e1.message);
- }
- catch (PersonaStoreError.STORE_OFFLINE e2)
- {
- throw new PropertyError.UNKNOWN_ERROR (e2.message);
- }
- catch (PersonaStoreError e3)
- {
- throw new PropertyError.UNKNOWN_ERROR (e3.message);
- }
- }
-
- /* the change will be notified when we receive changes to
- * contact.contact_info */
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.4
- */
- [CCode (notify = false)]
- public string nickname
- {
- get { return ""; }
- set { this.change_nickname.begin (value); } /* not writeable */
- }
-
- /**
- * {@inheritDoc}
- *
- * ContactInfo has no equivalent field, so this is unsupported.
- *
- * @since 0.6.4
- */
- [CCode (notify = false)]
- public string? calendar_event_id
- {
- get { return null; } /* unsupported */
- set { this.change_calendar_event_id.begin (value); } /* not writeable */
- }
-
- private DateTime? _birthday = null;
- /**
- * {@inheritDoc}
- *
- * @since 0.6.4
- */
- [CCode (notify = false)]
- public DateTime? birthday
- {
- get { return this._birthday; }
- set { this.change_birthday.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.4
- */
- public async void change_birthday (DateTime? birthday) throws PropertyError
- {
- var tpf_store = this.store as Tpf.PersonaStore;
-
- if (birthday != null && this._birthday != null &&
- birthday.equal (this._birthday))
- {
- return;
- }
-
- if (this._is_constructed)
- {
- try
- {
- yield tpf_store.change_user_birthday (this, birthday);
- }
- catch (PersonaStoreError.INVALID_ARGUMENT e1)
- {
- throw new PropertyError.NOT_WRITEABLE (e1.message);
- }
- catch (PersonaStoreError.STORE_OFFLINE e2)
- {
- throw new PropertyError.UNKNOWN_ERROR (e2.message);
- }
- catch (PersonaStoreError e3)
- {
- throw new PropertyError.UNKNOWN_ERROR (e3.message);
- }
- }
-
- /* the change will be notified when we receive changes to
- * contact.contact_info */
- }
-
- /**
- * The Persona's presence type.
- *
- * See {@link Folks.PresenceDetails.presence_type}.
- */
- public Folks.PresenceType presence_type { get; set; }
-
- /**
- * The Persona's presence status.
- *
- * See {@link Folks.PresenceDetails.presence_status}.
- *
- * @since 0.6.0
- */
- public string presence_status { get; set; }
-
- /**
- * The Persona's presence message.
- *
- * See {@link Folks.PresenceDetails.presence_message}.
- */
- public string presence_message { get; set; }
-
- /**
- * The Persona's client types.
- *
- * See {@link Folks.PresenceDetails.client_types}.
- *
- * @since 0.9.5
- */
- public string[] client_types { get; set; }
-
- /**
- * The names of the Persona's linkable properties.
- *
- * See {@link Folks.Persona.linkable_properties}.
- */
- public override string[] linkable_properties
- {
- get { return Tpf.Persona._linkable_properties; }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override string[] writeable_properties
- {
- get { return this._writeable_properties; }
- }
-
- private string _alias = ""; /* must never be null */
-
- /**
- * An alias for the Persona.
- *
- * See {@link Folks.AliasDetails.alias}.
- */
- [CCode (notify = false)]
- public string alias
- {
- get { return this._alias; }
- set { this.change_alias.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_alias (string alias) throws PropertyError
- {
- if (this._alias == alias)
- {
- return;
- }
-
- if (this._is_constructed)
- {
- yield ((Tpf.PersonaStore) this.store).change_alias (this, alias);
- }
-
- /* The change will be notified when we receive changes from the store. */
- }
-
- private bool _is_favourite = false;
-
- /**
- * Whether this Persona is a user-defined favourite.
- *
- * See {@link Folks.FavouriteDetails.is_favourite}.
- */
- [CCode (notify = false)]
- public bool is_favourite
- {
- get { return this._is_favourite; }
- set { this.change_is_favourite.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_is_favourite (bool is_favourite) throws PropertyError
- {
- if (this._is_favourite == is_favourite)
- {
- return;
- }
-
- if (this._is_constructed)
- {
- yield ((Tpf.PersonaStore) this.store).change_is_favourite (this,
- is_favourite);
- }
-
- /* The change will be notified when we receive changes from the store. */
- }
-
- /* Note: Only ever called by Tpf.PersonaStore. */
- internal void _set_is_favourite (bool is_favourite)
- {
- if (this._is_favourite == is_favourite)
- {
- return;
- }
-
- this._is_favourite = is_favourite;
- this.notify_property ("is-favourite");
-
- /* Mark the cache as needing to be updated. */
- ((Tpf.PersonaStore) this.store)._set_cache_needs_update ();
- }
-
- private SmallSet<EmailFieldDetails>? _email_addresses = null;
- private Set<EmailFieldDetails>? _email_addresses_ro = null;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.4
- */
- [CCode (notify = false)]
- public Set<EmailFieldDetails> email_addresses
- {
- get
- {
- this._contact_notify_contact_info (true, false);
- return this._email_addresses_ro;
- }
- set { this.change_email_addresses.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.4
- */
- public async void change_email_addresses (
- Set<EmailFieldDetails> email_addresses) throws PropertyError
- {
- yield this._change_details<EmailFieldDetails> (email_addresses,
- this._email_addresses, "email");
- }
-
- /* NOTE: Other properties support lazy initialisation, but im-addresses
- * doesn't as it's a linkable property, so always has to be loaded anyway. */
- private HashMultiMap<string, ImFieldDetails> _im_addresses =
- new HashMultiMap<string, ImFieldDetails> (null, null,
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- /**
- * A mapping of IM protocol to an (unordered) set of IM addresses.
- *
- * See {@link Folks.ImDetails.im_addresses}.
- */
- [CCode (notify = false)]
- public MultiMap<string, ImFieldDetails> im_addresses
- {
- get { return this._im_addresses; }
- set { this.change_im_addresses.begin (value); }
- }
-
- private uint _im_interaction_count = 0;
-
- /**
- * A counter for IM interactions (send/receive message) with the persona.
- *
- * See {@link Folks.InteractionDetails.im_interaction_count}
- *
- * @since 0.7.1
- */
- public uint im_interaction_count
- {
- get { return this._im_interaction_count; }
- }
-
- internal DateTime? _last_im_interaction_datetime = null;
-
- /**
- * The latest datetime for IM interactions (send/receive message) with the
- * persona.
- *
- * See {@link Folks.InteractionDetails.last_im_interaction_datetime}
- *
- * @since 0.7.1
- */
- public DateTime? last_im_interaction_datetime
- {
- get { return this._last_im_interaction_datetime; }
- }
-
- private uint _call_interaction_count = 0;
-
- /**
- * A counter for call interactions (only successful calls) with the persona.
- *
- * See {@link Folks.InteractionDetails.call_interaction_count}
- *
- * @since 0.7.1
- */
- public uint call_interaction_count
- {
- get { return this._call_interaction_count; }
- }
-
- internal DateTime? _last_call_interaction_datetime = null;
-
- /**
- * The latest datetime for call interactions (only successful calls) with the
- * persona.
- *
- * See {@link Folks.InteractionDetails.last_call_interaction_datetime}
- *
- * @since 0.7.1
- */
- public DateTime? last_call_interaction_datetime
- {
- get { return this._last_call_interaction_datetime; }
- }
-
- private SmallSet<string> _groups = new SmallSet<string> ();
- private Set<string> _groups_ro;
-
- /**
- * A set group IDs for the groups the contact is a member of.
- *
- * See {@link Folks.GroupDetails.groups}.
- */
- [CCode (notify = false)]
- public Set<string> groups
- {
- get { return this._groups_ro; }
- set { this.change_groups.begin (value); }
- }
-
- /**
- * Add or remove the Persona from the specified group.
- *
- * See {@link Folks.GroupDetails.change_group}.
- *
- * @throws Folks.PropertyError.UNKNOWN_ERROR if changing group membership
- * failed
- */
- public async void change_group (string group, bool is_member)
- throws GLib.Error
- {
- /* Ensure we have a strong ref to the contact for the duration of the
- * operation. */
- var contact = (Contact?) this._contact;
-
- if (contact == null)
- {
- /* The Tpf.Persona is being served out of the cache. */
- throw new PropertyError.UNAVAILABLE (
- _("Failed to change group membership: %s"),
- /* Translators: "account" refers to an instant messaging
- * account. */
- _("Account is offline."));
- }
-
- try
- {
- if (is_member && !this._groups.contains (group))
- {
- yield contact.add_to_group_async (group);
- }
- else if (!is_member && this._groups.contains (group))
- {
- yield contact.remove_from_group_async (group);
- }
- }
- catch (GLib.Error e)
- {
- throw new PropertyError.UNKNOWN_ERROR (
- /* Translators: the parameter is an error message. */
- _("Failed to change group membership: %s"), e.message);
- }
-
- /* The change will be notified when we receive changes from the store. */
- }
-
- /* Note: Only ever called as a result of signals from Telepathy. */
- private void _contact_groups_changed (string[] added, string[] removed)
- {
- var changed = false;
-
- foreach (var group in added)
- {
- if (this._groups.add (group) == true)
- {
- changed = true;
- this.group_changed (group, true);
- }
- }
-
- foreach (var group in removed)
- {
- if (this._groups.remove (group) == true)
- {
- changed = true;
- this.group_changed (group, false);
- }
- }
-
- /* Notify if anything changed. */
- if (changed == true)
- {
- this.notify_property ("groups");
-
- /* Mark the cache as needing to be updated. */
- ((Tpf.PersonaStore) this.store)._set_cache_needs_update ();
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_groups (Set<string> groups) throws PropertyError
- {
- var contact = (Contact?) this._contact;
-
- if (contact == null)
- {
- /* The Tpf.Persona is being served out of the cache. */
- throw new PropertyError.UNAVAILABLE (
- _("Failed to change group membership: %s"),
- /* Translators: "account" refers to an instant messaging
- * account. */
- _("Account is offline."));
- }
-
- try
- {
- yield contact.set_contact_groups_async (groups.to_array ());
- }
- catch (GLib.Error e)
- {
- throw new PropertyError.UNKNOWN_ERROR (
- /* Translators: the parameter is an error message. */
- _("Failed to change group membership: %s"), e.message);
- }
-
- /* The change will be notified when we receive changes from the store. */
- }
-
- /* We handle the weak ref ourself to be able to notify the "contact" property.
- * It is TpfPersonaStore who weak_ref() the TpContact and calls
- * _contact_weak_notify() on the persona.
- *
- * This isn't as easy as it seems, see bgo#702165 */
- private unowned Contact? _contact;
-
- internal void _contact_weak_notify ()
- {
- if (this._contact == null)
- return;
-
- debug ("TpContact %p destroyed; setting ._contact = null in Persona %p",
- this._contact, this);
-
- this._contact = null;
- this.notify_property ("contact");
- }
-
- /**
- * The Telepathy contact represented by this persona.
- *
- * Note that this may be ``null`` if the {@link PersonaStore} providing this
- * {@link Persona} isn't currently available (e.g. due to not being connected
- * to the network). In this case, most other properties of the {@link Persona}
- * are being retrieved from a cache and may not be current (though there's no
- * way to tell this).
- */
- public Contact? contact
- {
- get
- {
- /* FIXME: This property should be changed to transfer its reference
- * when the API is next broken. This is necessary because the
- * TpfPersona doesn't hold a strong ref to the TpContact, so any
- * pointer which is returned might be invalidated before reaching the
- * caller. Probably not a problem in practice since folks won't be
- * run multi-threaded. */
- return this._contact;
- }
-
- construct
- {
- this._contact = value;
- }
- }
-
- private SmallSet<PhoneFieldDetails>? _phone_numbers = null;
- private Set<PhoneFieldDetails>? _phone_numbers_ro = null;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.4
- */
- [CCode (notify = false)]
- public Set<PhoneFieldDetails> phone_numbers
- {
- get
- {
- this._contact_notify_contact_info (true, false);
- return this._phone_numbers_ro;
- }
- set { this.change_phone_numbers.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.4
- */
- public async void change_phone_numbers (
- Set<PhoneFieldDetails> phone_numbers) throws PropertyError
- {
- yield this._change_details<PhoneFieldDetails> (phone_numbers,
- this._phone_numbers, "tel");
- }
-
- private SmallSet<UrlFieldDetails>? _urls = null;
- private Set<UrlFieldDetails>? _urls_ro = null;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.4
- */
- [CCode (notify = false)]
- public Set<UrlFieldDetails> urls
- {
- get
- {
- this._contact_notify_contact_info (true, false);
- return this._urls_ro;
- }
- set { this.change_urls.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.4
- */
- public async void change_urls (Set<UrlFieldDetails> urls) throws PropertyError
- {
- yield this._change_details<UrlFieldDetails> (urls,
- this._urls, "url");
- }
-
- private async void _change_details<T> (
- Set<AbstractFieldDetails<string>> details,
- Set<AbstractFieldDetails<string>>? member_set,
- string field_name)
- throws PropertyError
- {
- var tpf_store = this.store as Tpf.PersonaStore;
-
- if (member_set != null &&
- Folks.Internal.equal_sets<T> (details, member_set))
- {
- return;
- }
-
- if (this._is_constructed)
- {
- try
- {
- yield tpf_store._change_user_details (this, details, field_name);
- }
- catch (PersonaStoreError.INVALID_ARGUMENT e1)
- {
- throw new PropertyError.NOT_WRITEABLE (e1.message);
- }
- catch (PersonaStoreError.STORE_OFFLINE e2)
- {
- throw new PropertyError.UNKNOWN_ERROR (e2.message);
- }
- catch (PersonaStoreError e3)
- {
- throw new PropertyError.UNKNOWN_ERROR (e3.message);
- }
- }
-
- /* the change will be notified when we receive changes to
- * contact.contact_info */
- }
-
- /**
- * Create a new persona.
- *
- * Create a new persona for the {@link PersonaStore} ``store``, representing
- * the Telepathy contact given by ``contact``.
- *
- * @param contact the Telepathy contact being represented by the persona
- * @param store the persona store to place the persona in
- */
- public Persona (Contact contact, PersonaStore store)
- {
- unowned string id = contact.get_identifier ();
- var connection = contact.connection;
- var account = connection.get_account ();
- var uid = Folks.Persona.build_uid (store.type_id, store.id, id);
- var is_user = false;
-
- if (connection.self_contact != null)
- {
- is_user = (contact.handle == connection.self_contact.handle);
- }
-
- Object (contact: contact,
- display_id: id,
- /* FIXME: This IID format should be moved out to the ImDetails
- * interface along with the code in
- * Kf.Persona.linkable_property_to_links(), but that depends on
- * bgo#624842 being fixed. */
- iid: account.protocol_name + ":" + id,
- uid: uid,
- store: store,
- is_user: is_user);
-
- debug ("Created new Tpf.Persona '%s' for service-specific UID '%s': %p",
- uid, id, this);
- }
-
- construct
- {
- this._groups_ro = this._groups.read_only_view;
-
- /* Contact can be null if we've been created from the cache. All the code
- * below this point is for non-cached personas. */
- var contact = (Contact?) this._contact;
-
- if (contact == null)
- {
- return;
- }
-
- /* Set our alias. */
- this._alias = contact.get_alias ();
-
- contact.notify["alias"].connect ((s, p) =>
- {
- var c = (Contact?) this._contact;
- assert (c != null); /* should never be called while cached */
-
- /* Tp guarantees that aliases are always non-null. */
- assert (c.alias != null);
-
- if (this._alias != c.alias)
- {
- this._alias = c.alias;
- this.notify_property ("alias");
-
- /* Mark the cache as needing to be updated. */
- ((Tpf.PersonaStore) this.store)._set_cache_needs_update ();
- }
- });
-
- /* Set our single IM address */
- var connection = contact.connection;
- var account = connection.get_account ();
-
- try
- {
- var im_addr = ImDetails.normalise_im_address (this.display_id,
- account.protocol_name);
- var im_fd = new ImFieldDetails (im_addr);
- this._im_addresses.set (account.protocol_name, im_fd);
- }
- catch (ImDetailsError e)
- {
- /* This should never happen…but if it does, warn of it and continue */
- warning (e.message);
- }
-
- contact.notify["avatar-file"].connect ((s, p) =>
- {
- this._contact_notify_avatar ();
- });
- this._contact_notify_avatar ();
-
- contact.notify["presence-message"].connect ((s, p) =>
- {
- this._contact_notify_presence_message ();
- });
- contact.notify["presence-type"].connect ((s, p) =>
- {
- this._contact_notify_presence_type ();
- });
- contact.notify["presence-status"].connect ((s, p) =>
- {
- this._contact_notify_presence_status ();
- });
- contact.notify["client-types"].connect ((s, p) =>
- {
- this._contact_notify_client_types ();
- });
-
- this._contact_notify_presence_message ();
- this._contact_notify_presence_type ();
- this._contact_notify_presence_status ();
- this._contact_notify_client_types ();
-
- contact.notify["contact-info"].connect ((s, p) =>
- {
- this._contact_notify_contact_info (false);
- });
- this._contact_notify_contact_info (false);
-
- contact.contact_groups_changed.connect ((added, removed) =>
- {
- this._contact_groups_changed (added, removed);
- });
- this._contact_groups_changed (contact.get_contact_groups (), {});
-
- var tpf_store = this.store as Tpf.PersonaStore;
-
- if (this.is_user)
- {
- tpf_store.notify["supported-fields"].connect ((s, p) =>
- {
- this._update_writeable_properties ();
- });
- }
-
- tpf_store.notify["always-writeable-properties"].connect ((s, p) =>
- {
- this._update_writeable_properties ();
- });
-
- this._update_writeable_properties ();
- }
-
- /* Called after all construction-time properties have been set. */
- public override void constructed ()
- {
- this._is_constructed = true;
- }
-
- private void _update_writeable_properties ()
- {
- var tpf_store = this.store as Tpf.PersonaStore;
- this._writeable_properties = this.store.always_writeable_properties;
-
- if (this.is_user)
- {
- if ("bday" in tpf_store.supported_fields)
- this._writeable_properties += "birthday";
- if ("email" in tpf_store.supported_fields)
- this._writeable_properties += "email-addresses";
- if ("fn" in tpf_store.supported_fields)
- this._writeable_properties += "full-name";
- if ("tel" in tpf_store.supported_fields)
- this._writeable_properties += "phone-numbers";
- if ("url" in tpf_store.supported_fields)
- this._writeable_properties += "urls";
- }
- }
-
- private void _contact_notify_contact_info (bool create_if_not_exists, bool emit_notification = true)
- {
- assert ((
- (this._email_addresses == null) &&
- (this._phone_numbers == null) &&
- (this._urls == null)
- ) || (
- (this._email_addresses != null) &&
- (this._phone_numbers != null) &&
- (this._urls != null)
- ));
-
- /* See the comments in Folks.Individual about the lazy instantiation
- * strategy for URIs, etc.
- *
- * It's necessary to notify for all three properties here, as this
- * function is called identically for all of them. */
- if (this._urls == null && create_if_not_exists == false)
- {
- if (emit_notification)
- {
- this.notify_property ("email-addresses");
- this.notify_property ("phone-numbers");
- this.notify_property ("urls");
- }
- return;
- }
- else if (this._urls == null)
- {
- this._urls = new SmallSet<UrlFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._urls_ro = this._urls.read_only_view;
-
- this._email_addresses = new SmallSet<EmailFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._email_addresses_ro = this._email_addresses.read_only_view;
-
- this._phone_numbers = new SmallSet<PhoneFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._phone_numbers_ro = this._phone_numbers.read_only_view;
- }
-
- var contact = (Contact?) this._contact;
- if (contact == null)
- {
- /* If operating from the cache, bail out early. */
- return;
- }
-
- var changed = false;
- var new_birthday_str = "";
- var new_full_name = "";
- var new_email_addresses = new SmallSet<EmailFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- var new_phone_numbers = new SmallSet<PhoneFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- var new_urls = new SmallSet<UrlFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- var contact_info = contact.dup_contact_info ();
- foreach (var info in contact_info)
- {
- if (info.field_name == "") {}
- else if (info.field_name == "bday")
- {
- new_birthday_str = info.field_value[0] ?? "";
- }
- else if (info.field_name == "email")
- {
- foreach (var email_addr in info.field_value)
- {
- if (email_addr != "")
- {
- var parameters = this._afd_params_from_strv (info.parameters);
- var email_fd = new EmailFieldDetails (email_addr, parameters);
- new_email_addresses.add (email_fd);
- }
- }
- }
- else if (info.field_name == "fn")
- {
- new_full_name = info.field_value[0];
- if (new_full_name == null)
- new_full_name = "";
- }
- else if (info.field_name == "tel")
- {
- foreach (var phone_num in info.field_value)
- {
- if (phone_num != "")
- {
- var parameters = this._afd_params_from_strv (info.parameters);
- var phone_fd = new PhoneFieldDetails (phone_num, parameters);
- new_phone_numbers.add (phone_fd);
- }
- }
- }
- else if (info.field_name == "url")
- {
- foreach (var url in info.field_value)
- {
- if (url != "")
- {
- var parameters = this._afd_params_from_strv (info.parameters);
- var url_fd = new UrlFieldDetails (url, parameters);
- new_urls.add (url_fd);
- }
- }
- }
- }
-
- if (new_birthday_str != "")
- {
- var timeval = TimeVal ();
- if (timeval.from_iso8601 (new_birthday_str))
- {
- var d = new DateTime.from_timeval_utc (timeval);
-
- /* d might be null if their birthday in Telepathy is something
- * that doesn't make sense, like 31st February. If so, ignore
- * it. */
- if (d != null && (this._birthday == null ||
- (this._birthday != null &&
- !this._birthday.equal (d.to_utc ()))))
- {
- this._birthday = d.to_utc ();
- if (emit_notification)
- {
- this.notify_property ("birthday");
- }
- changed = true;
- }
- }
- else
- {
- debug ("Failed to parse new birthday string '%s'",
- new_birthday_str);
- }
- }
- else
- {
- if (this._birthday != null)
- {
- this._birthday = null;
- if (emit_notification)
- {
- this.notify_property ("birthday");
- }
- changed = true;
- }
- }
-
- if (!Folks.Internal.equal_sets<EmailFieldDetails> (new_email_addresses,
- this._email_addresses))
- {
- this._email_addresses = new_email_addresses;
- this._email_addresses_ro = new_email_addresses.read_only_view;
- if (emit_notification)
- {
- this.notify_property ("email-addresses");
- }
- changed = true;
- }
-
- if (new_full_name != this._full_name)
- {
- this._full_name = new_full_name;
- this.notify_property ("full-name");
- changed = true;
- }
-
- if (!Utils.set_string_afd_equal (new_phone_numbers,
- this._phone_numbers))
- {
- this._phone_numbers = new_phone_numbers;
- this._phone_numbers_ro = new_phone_numbers.read_only_view;
- if (emit_notification)
- {
- this.notify_property ("phone-numbers");
- }
- changed = true;
- }
-
- if (!Folks.Internal.equal_sets<UrlFieldDetails> (new_urls, this._urls))
- {
- this._urls = new_urls;
- this._urls_ro = new_urls.read_only_view;
- this.notify_property ("urls");
- changed = true;
- }
-
- if (changed == true)
- {
- /* Mark the cache as needing to be updated. */
- ((Tpf.PersonaStore) this.store)._set_cache_needs_update ();
- }
- }
-
- private MultiMap<string, string> _afd_params_from_strv (string[] parameters)
- {
- var retval = new HashMultiMap<string, string> ();
-
- foreach (var entry in parameters)
- {
- var tokens = entry.split ("=", 2);
- if (tokens.length == 2)
- {
- retval.set (tokens[0], tokens[1]);
- }
- else
- {
- warning ("Failed to parse vCard parameter from string '%s'",
- entry);
- }
- }
-
- return retval;
- }
-
- /**
- * Create a new persona for the {@link PersonaStore} ``store``, representing
- * a cached contact for which we currently have no Telepathy contact.
- *
- * @param store The persona store to place the persona in.
- * @param uid The cached UID of the persona.
- * @param iid The cached IID of the persona.
- * @param im_address The cached IM address of the persona (excluding
- * protocol).
- * @param protocol The cached protocol of the persona.
- * @param groups The cached set of groups the persona is in.
- * @param is_favourite Whether the persona is a favourite.
- * @param alias The cached alias for the persona.
- * @param is_in_contact_list Whether the persona is in the user's contact
- * list.
- * @param is_user Whether the persona is the user.
- * @param avatar The icon for the persona's cached avatar, or ``null`` if they
- * have no avatar.
- * @param birthday The date/time of birth of the persona, or ``null`` if it's
- * unknown.
- * @param full_name The persona's full name, or the empty string if it's
- * unknown.
- * @param email_addresses A set of the persona's e-mail addresses, which may
- * be empty (but may not be ``null``).
- * @param phone_numbers A set of the persona's phone numbers, which may be
- * empty (but may not be ``null``).
- * @param urls A set of the persona's URLs, which may be empty (but may not be
- * ``null``).
- * @return A new {@link Tpf.Persona} representing the cached persona.
- *
- * @since 0.6.0
- */
- internal Persona.from_cache (PersonaStore store, string uid, string iid,
- string im_address, string protocol, SmallSet<string> groups,
- bool is_favourite, string alias, bool is_in_contact_list, bool is_user,
- LoadableIcon? avatar, DateTime? birthday, string full_name,
- SmallSet<EmailFieldDetails> email_addresses,
- SmallSet<PhoneFieldDetails> phone_numbers, SmallSet<UrlFieldDetails> urls)
- {
- Object (contact: null,
- display_id: im_address,
- iid: iid,
- uid: uid,
- store: store,
- is_user: is_user);
-
- debug ("Created new Tpf.Persona '%s' from cache: %p", uid, this);
-
- // IM addresses
- var im_fd = new ImFieldDetails (im_address);
- this._im_addresses.set (protocol, im_fd);
-
- // Groups
- this._groups = groups;
- this._groups_ro = this._groups.read_only_view;
-
- // E-mail addresses
- this._email_addresses = email_addresses;
- this._email_addresses_ro = this._email_addresses.read_only_view;
-
- // Phone numbers
- this._phone_numbers = phone_numbers;
- this._phone_numbers_ro = this._phone_numbers.read_only_view;
-
- // URLs
- this._urls = urls;
- this._urls_ro = this._urls.read_only_view;
-
- // Other properties
- if (alias == null)
- {
- /* Deal with badly-behaved callers */
- alias = "";
- }
-
- if (full_name == null)
- {
- /* Deal with badly-behaved callers */
- full_name = "";
- }
-
- this._alias = alias;
- this._is_favourite = is_favourite;
- this.is_in_contact_list = is_in_contact_list;
- this._birthday = birthday;
- this._full_name = full_name;
-
- // Avatars
- this._avatar = avatar;
- var avatar_file =
- (avatar != null) ? ((FileIcon) avatar).get_file () : null;
- ((Tpf.PersonaStore) store)._update_avatar_cache (iid, avatar_file);
-
- // Make the persona appear offline
- this.presence_type = PresenceType.OFFLINE;
- this.presence_message = "";
- this.presence_status = "offline";
- this.client_types = {};
-
- this._writeable_properties = {};
- }
-
- ~Persona ()
- {
- debug ("Destroying Tpf.Persona '%s': %p", this.uid, this);
- }
-
- private void _contact_notify_presence_message ()
- {
- var contact = (Contact?) this._contact;
- assert (contact != null); /* should never be called while cached */
- this.presence_message = contact.get_presence_message ();
- }
-
- private void _contact_notify_presence_type ()
- {
- var contact = (Contact?) this._contact;
- assert (contact != null); /* should never be called while cached */
- this.presence_type = Tpf.Persona._folks_presence_type_from_tp (
- contact.get_presence_type ());
- }
-
- private void _contact_notify_client_types ()
- {
- var contact = (Contact?) this._contact;
- assert (contact != null); /* should never be called while cached */
- this.client_types = contact.get_client_types ();
- }
-
- private void _contact_notify_presence_status ()
- {
- var contact = (Contact?) this._contact;
- assert (contact != null); /* should never be called while cached */
- this.presence_status = contact.get_presence_status ();
- }
-
- private static PresenceType _folks_presence_type_from_tp (
- TelepathyGLib.ConnectionPresenceType type)
- {
- switch (type)
- {
- case TelepathyGLib.ConnectionPresenceType.AVAILABLE:
- return PresenceType.AVAILABLE;
- case TelepathyGLib.ConnectionPresenceType.AWAY:
- return PresenceType.AWAY;
- case TelepathyGLib.ConnectionPresenceType.BUSY:
- return PresenceType.BUSY;
- case TelepathyGLib.ConnectionPresenceType.ERROR:
- return PresenceType.ERROR;
- case TelepathyGLib.ConnectionPresenceType.EXTENDED_AWAY:
- return PresenceType.EXTENDED_AWAY;
- case TelepathyGLib.ConnectionPresenceType.HIDDEN:
- return PresenceType.HIDDEN;
- case TelepathyGLib.ConnectionPresenceType.OFFLINE:
- return PresenceType.OFFLINE;
- case TelepathyGLib.ConnectionPresenceType.UNKNOWN:
- return PresenceType.UNKNOWN;
- case TelepathyGLib.ConnectionPresenceType.UNSET:
- return PresenceType.UNSET;
- default:
- return PresenceType.UNKNOWN;
- }
- }
-
- private void _contact_notify_avatar ()
- {
- var contact = (Contact?) this._contact;
- assert (contact != null); /* should never be called while cached */
-
- var file = contact.avatar_file;
- var token = contact.avatar_token;
- Icon? icon = null;
- var from_cache = false;
-
- /* Handle all the different cases of avatars. */
- if (token == "")
- {
- /* Definitely know there's no avatar. */
- file = null;
- from_cache = false;
- }
- else if (token != null && file != null)
- {
- /* Definitely know there's some avatar, so leave the file alone. */
- from_cache = false;
- }
- else
- {
- /* Not sure about the avatar; fall back to any cached avatar. */
- file = ((Tpf.PersonaStore) this.store)._query_avatar_cache (this.iid);
- from_cache = true;
- }
-
- if (file != null)
- {
- icon = new FileIcon (file);
- }
-
- if ((this._avatar == null) != (icon == null) || !this._avatar.equal (icon))
- {
- this._avatar = (LoadableIcon) icon;
- this.notify_property ("avatar");
-
- if (from_cache == false)
- {
- /* Mark the persona cache as needing to be updated. */
- ((Tpf.PersonaStore) this.store)._set_cache_needs_update ();
-
- /* Update the avatar cache. */
- ((Tpf.PersonaStore) this.store)._update_avatar_cache (this.iid,
- file);
- }
- }
- }
-
- /**
- * Look up a {@link Tpf.Persona} by its {@link TelepathyGLib.Contact}.
- *
- * If the {@link TelepathyGLib.Account} for the contact's
- * {@link TelepathyGLib.Connection} is ``null``, or if a
- * {@link Tpf.PersonaStore} can't be found for that account, ``null`` will be
- * returned. Otherwise, if a {@link Tpf.Persona} already exists for the given
- * contact, that will be returned; if one doesn't exist a new one will be
- * created and returned. In this case, the {@link Tpf.Persona} will be added
- * to the {@link PersonaStore} associated with the account, and will be
- * removed when ``contact`` is destroyed.
- *
- * @param contact the Telepathy contact of the persona
- * @return the persona associated with the contact, or ``null``
- * @since 0.6.6
- */
- public static Persona? dup_for_contact (Contact contact)
- {
- var account = contact.connection.get_account ();
-
- debug ("Tpf.Persona.dup_for_contact (%p): got account %p", contact,
- account);
-
- /* Account could be null; see the docs for tp_connection_get_account(). */
- if (account == null)
- {
- return null;
- }
-
- var store = PersonaStore.dup_for_account (account);
- return store._ensure_persona_for_contact (contact);
- }
-
- internal void _increase_im_interaction_counter (DateTime converted_datetime)
- {
- this._im_interaction_count++;
- this.notify_property ("im-interaction-count");
- if (this._last_im_interaction_datetime == null ||
- this._last_im_interaction_datetime.compare (converted_datetime) == -1)
- {
- this._last_im_interaction_datetime = converted_datetime;
- this.notify_property ("last-im-interaction-datetime");
- }
- debug ("Persona %s IM interaction details changed:\n" +
- " - count: %u \n - timestamp: %lld",
- this.iid, this._im_interaction_count,
- this._last_im_interaction_datetime.format ("%H %M %S - %d %m %y"));
- }
-
- internal void _increase_last_call_interaction_counter (DateTime converted_datetime)
- {
- this._call_interaction_count++;
- this.notify_property ("call-interaction-count");
- if (this._last_call_interaction_datetime == null ||
- this._last_call_interaction_datetime.compare (converted_datetime) == -1)
- {
- this._last_call_interaction_datetime = converted_datetime;
- this.notify_property ("last-call-interaction-datetime");
- }
- debug ("Persona %s Call interaction details changed:\n" +
- " - count: %u \n - timestamp: %lld",
- this.iid, this._call_interaction_count,
- this._last_call_interaction_datetime.format ("%H %M %S - %d %m %y"));
- }
-}
diff --git a/backends/telepathy/meson.build b/backends/telepathy/meson.build
deleted file mode 100644
index c1fb4e9d..00000000
--- a/backends/telepathy/meson.build
+++ /dev/null
@@ -1,38 +0,0 @@
-# Telepathy backend
-telepathy_backend_name = 'telepathy'
-
-# Backend library
-subdir('lib')
-
-telepathy_backend_sources = [
- 'tp-backend-factory.vala',
- 'tp-backend.vala',
-]
-
-telepathy_backend_deps = [
- backend_deps,
- telepathy_glib_dep,
- tp_lowlevel_vapi,
- telepathy_backendlib_dep,
-]
-
-telepathy_backend_vala_flags = [
-]
-
-telepathy_backend_c_flags = [
- '-include', 'config.h',
- '-DBACKEND_NAME="@0@"'.format(telepathy_backend_name),
- '-DG_LOG_DOMAIN="@0@"'.format(telepathy_backend_name),
-]
-
-telepathy_backend = shared_module(telepathy_backend_name,
- telepathy_backend_sources,
- dependencies: telepathy_backend_deps,
- vala_args: telepathy_backend_vala_flags,
- c_args: telepathy_backend_c_flags,
- name_prefix: '',
- install_dir: folks_backend_dir / telepathy_backend_name,
- install: true,
-)
-
-folks_backends += telepathy_backend
diff --git a/backends/telepathy/tp-backend-factory.vala b/backends/telepathy/tp-backend-factory.vala
deleted file mode 100644
index ed1106ce..00000000
--- a/backends/telepathy/tp-backend-factory.vala
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2009 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>.
- * Copyright (C) 2009 Nokia Corporation.
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
- * Travis Reitter <travis.reitter@collabora.co.uk>
- *
- * This file was originally part of Rygel.
- */
-
-using Folks;
-
-/**
- * The Telepathy backend module entry point.
- *
- * @backend_store a store to add the Telepathy backends to
- */
-public void module_init (BackendStore backend_store)
-{
- backend_store.add_backend (new Folks.Backends.Tp.Backend ());
-}
-
-/**
- * The Telepathy backend module exit point.
- *
- * @param backend_store the store to remove the backends from
- */
-public void module_finalize (BackendStore backend_store)
-{
- /* FIXME: No way to remove backends from the store. */
-}
diff --git a/backends/telepathy/tp-backend.vala b/backends/telepathy/tp-backend.vala
deleted file mode 100644
index 71cfc84d..00000000
--- a/backends/telepathy/tp-backend.vala
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-using TelepathyGLib;
-using Folks;
-using Folks.Backends.Tp;
-
-extern const string BACKEND_NAME;
-
-/**
- * A backend which connects to the Telepathy accounts service and creates a
- * {@link PersonaStore} for each valid account known to Telepathy.
- */
-public class Folks.Backends.Tp.Backend : Folks.Backend
-{
- private AccountManager _account_manager;
- private bool _is_prepared = false;
- private bool _prepare_pending = false; /* used by unprepare() too */
- private bool _is_quiescent = false;
- private Set<string>? _storeids = null;
-
- /**
- * {@inheritDoc}
- */
- public override string name { get { return BACKEND_NAME; } }
-
- /**
- * {@inheritDoc}
- */
- public override Map<string, Folks.PersonaStore> persona_stores
- {
- get { return Tpf.PersonaStore.list_persona_stores (); }
- }
-
- /**
- * {@inheritDoc}
- */
- public override void enable_persona_store (PersonaStore store)
- {
- if (this.persona_stores.has_key (store.id) == false)
- {
- this._add_store (store);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public override void disable_persona_store (PersonaStore store)
- {
- if (this.persona_stores.has_key (store.id))
- {
- this._remove_store (store);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public override void set_persona_stores (Set<string>? storeids)
- {
- this._storeids = storeids;
-
- bool added_stores = false;
- PersonaStore[] removed_stores = {};
-
- /* First handle adding any missing persona stores. */
- var accounts = this._account_manager.dup_valid_accounts ();
- foreach (Account account in accounts)
- {
- string id = account.get_object_path ();
- if (this.persona_stores.has_key (id) == false &&
- id in storeids)
- {
- var store = Tpf.PersonaStore.dup_for_account (account);
- this._add_store (store, false);
- added_stores = true;
- }
- }
-
- foreach (PersonaStore store in this.persona_stores.values)
- {
- if (!storeids.contains (store.id))
- {
- removed_stores += store;
- }
- }
-
- foreach (PersonaStore store in removed_stores)
- {
- this._remove_store ((Tpf.PersonaStore) store, false);
- }
-
- /* Finally, if anything changed, emit the persona-stores notification. */
- if (added_stores || removed_stores.length > 0)
- {
- this.notify_property ("persona-stores");
- }
- }
-
-
- /**
- * {@inheritDoc}
- */
- public Backend ()
- {
- Object ();
- }
-
- /**
- * Whether this Backend has been prepared.
- *
- * See {@link Folks.Backend.is_prepared}.
- *
- * @since 0.3.0
- */
- public override bool is_prepared
- {
- get { return this._is_prepared; }
- }
-
- /**
- * Whether this Backend has reached a quiescent state.
- *
- * See {@link Folks.Backend.is_quiescent}.
- *
- * @since 0.6.2
- */
- public override bool is_quiescent
- {
- get { return this._is_quiescent; }
- }
-
- /**
- * {@inheritDoc}
- */
- public override async void prepare () throws GLib.Error
- {
- var profiling = Internal.profiling_start ("preparing Tp.Backend");
-
- if (this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
- this.freeze_notify ();
-
- this._account_manager = AccountManager.dup ();
- yield this._account_manager.prepare_async (null);
- this._account_manager.account_enabled.connect (
- this._account_enabled_cb);
- this._account_manager.account_validity_changed.connect (
- this._account_validity_changed_cb);
-
- var accounts = this._account_manager.dup_valid_accounts ();
- foreach (Account account in accounts)
- {
- this._account_enabled_cb (account);
- }
-
- this._is_prepared = true;
- this.notify_property ("is-prepared");
-
- this._is_quiescent = true;
- this.notify_property ("is-quiescent");
- }
- finally
- {
- this.thaw_notify ();
- this._prepare_pending = false;
- }
-
- Internal.profiling_end ((owned) profiling);
- }
-
- /**
- * {@inheritDoc}
- */
- public override async void unprepare () throws GLib.Error
- {
- if (!this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
- this.freeze_notify ();
-
- this._account_manager.account_enabled.disconnect (
- this._account_enabled_cb);
- this._account_manager.account_validity_changed.disconnect (
- this._account_validity_changed_cb);
- this._account_manager = null;
-
- this._is_quiescent = false;
- this.notify_property ("is-quiescent");
-
- this._is_prepared = false;
- this.notify_property ("is-prepared");
- }
- finally
- {
- this.thaw_notify ();
- this._prepare_pending = false;
- }
- }
-
- private void _account_validity_changed_cb (Account account, bool valid)
- {
- if (valid)
- this._account_enabled_cb (account);
- }
-
- private void _account_enabled_cb (Account account)
- {
- if (!account.enabled)
- {
- return;
- }
-
- /* Ignore if this account's object path isn't in our storeids */
- if (this._storeids != null && (account.get_object_path () in
- this._storeids) == false)
- {
- return;
- }
-
- var store = Tpf.PersonaStore.dup_for_account (account);
- this._add_store (store);
- }
-
- private void _add_store (PersonaStore store, bool notify = true)
- {
- store.removed.connect (this._store_removed_cb);
- this.persona_store_added (store);
-
- if (notify)
- {
- this.notify_property ("persona-stores");
- }
- }
-
- private void _remove_store (PersonaStore store, bool notify = true)
- {
- store.removed.disconnect (this._store_removed_cb);
- this.persona_store_removed (store);
-
- if (notify)
- {
- this.notify_property ("persona-stores");
- }
- }
-
- private void _store_removed_cb (PersonaStore store)
- {
- this._remove_store (store);
- }
-}
diff --git a/folks/abstract-field-details.vala b/folks/abstract-field-details.vala
deleted file mode 100644
index ed28cd22..00000000
--- a/folks/abstract-field-details.vala
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Marco Barisione <marco.barisione@collabora.co.uk>
- * Travis Reitter <travis.reitter@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-
-/**
- * Object representing any type of value that can have some vCard-like
- * parameters associated with it.
- *
- * Some contact details, like phone numbers or URLs, can have some
- * extra details associated with them.
- * For instance, a phone number expressed in vcard notation as
- * ``tel;type=work,voice:(111) 555-1234`` would be represented as
- * a AbstractFieldDetails with value "(111) 555-1234" and with parameters
- * ``['type': ('work', 'voice')]``.
- *
- * The parameter name "type" with values "work", "home", or "other" are common
- * amongst most vCard attributes (and thus most AbstractFieldDetails-derived
- * classes). A "type" of "pref" may be used to indicate a preferred
- * {@link AbstractFieldDetails.value} amongst many. See specific classes for
- * information on additional parameters and values specific to that class.
- *
- * See [[http://www.ietf.org/rfc/rfc2426.txt|RFC2426]] for more details on
- * pre-defined parameter names and values.
- *
- * @since 0.6.0
- */
-public abstract class Folks.AbstractFieldDetails<T> : Object
-{
- /**
- * Parameter name for classifying the type of value this field contains.
- *
- * For example, the value could be relevant to the contact's home life, or to
- * their work life; values of {@link AbstractFieldDetails.PARAM_TYPE_HOME}
- * and {@link AbstractFieldDetails.PARAM_TYPE_WORK} would be used for the
- * {@link AbstractFieldDetails.PARAM_TYPE} parameter, respectively, in those
- * cases.
- *
- * @since 0.6.3
- */
- public const string PARAM_TYPE = "type";
-
- /**
- * Parameter value for home-related field values.
- *
- * Value for a parameter with name {@link AbstractFieldDetails.PARAM_TYPE}.
- *
- * @since 0.6.3
- */
- public const string PARAM_TYPE_HOME = "home";
-
- /**
- * Parameter value for work-related field values.
- *
- * Value for a parameter with name {@link AbstractFieldDetails.PARAM_TYPE}.
- *
- * @since 0.6.3
- */
- public const string PARAM_TYPE_WORK = "work";
-
- /**
- * Parameter value for miscellaneous field values.
- *
- * Value for a parameter with name {@link AbstractFieldDetails.PARAM_TYPE}.
- *
- * @since 0.6.3
- */
- public const string PARAM_TYPE_OTHER = "other";
-
- private T _value;
- /**
- * The value of the field.
- *
- * The value of the field, the exact type and content of which depends on what
- * the structure is used for.
- *
- * @since 0.6.0
- */
- public virtual T @value
- {
- get { return this._value; }
- construct set { this._value = value; }
- }
-
- /**
- * The {@link GLib.Type} of the {@link AbstractFieldDetails.value}.
- *
- * This is particularly useful for treating collections of different types of
- * {@link AbstractFieldDetails} in a uniform way without needing to name them
- * explicitly.
- *
- * @since 0.6.5
- */
- public Type value_type
- {
- get { return typeof (T); }
- }
-
- private string _id;
- /**
- * A unique ID (if any) for this specific detail.
- *
- * This is primarily intended for {@link PersonaStore}s which need to track
- * specific instances of details (because their backing store is wacky).
- *
- * In most cases, this will be an empty string.
- *
- * The content of this is opaque to all but the package which set it.
- *
- * @since 0.6.5
- */
- public virtual string id
- {
- get { return this._id; }
- set { this._id = (value != null ? value : ""); }
- }
-
- private MultiMap<string, string> _parameters =
- new HashMultiMap<string, string> ();
- /**
- * The parameters associated with the value.
- *
- * A multi-map of the parameters associated with
- * {@link Folks.AbstractFieldDetails.value}. The keys are the names of
- * the parameters, while the values are a list of strings.
- *
- * @since 0.6.0
- */
- public virtual MultiMap<string, string> parameters
- {
- get { return this._parameters; }
- construct set
- {
- if (value == null)
- this._parameters.clear ();
- else
- this._parameters = value;
- }
- }
-
- /**
- * Get the values for a parameter
- *
- * @param parameter_name the parameter name
- * @return a collection of values for ``parameter_name`` or ``null`` (i.e. no
- * collection) if there are no such parameters.
- *
- * @since 0.6.0
- */
- public Collection<string>? get_parameter_values (string parameter_name)
- {
- if (this.parameters.contains (parameter_name) == false)
- {
- return null;
- }
-
- return this.parameters.get (parameter_name).read_only_view;
- }
-
- /**
- * Add a new value for a parameter.
- *
- * If there is already a parameter called ``parameter_name`` then
- * ``parameter_value`` is added to the existing ones.
- *
- * @param parameter_name the name of the parameter
- * @param parameter_value the value to add
- *
- * @since 0.6.0
- */
- public void add_parameter (string parameter_name, string parameter_value)
- {
- this.parameters.set (parameter_name, parameter_value);
- }
-
- /**
- * Set the value of a parameter.
- *
- * Sets the parameter called ``parameter_name`` to be ``parameter_value``.
- * If there were already parameters with the same name they are replaced.
- *
- * @param parameter_name the name of the parameter
- * @param parameter_value the value to add
- *
- * @since 0.6.0
- */
- public void set_parameter (string parameter_name, string parameter_value)
- {
- this.parameters.remove_all (parameter_name);
- this.parameters.set (parameter_name, parameter_value);
- }
-
- /**
- * Extend the existing parameters.
- *
- * Merge the parameters from ``additional`` into the existing ones.
- *
- * @param additional the parameters to add
- *
- * @since 0.6.0
- */
- public void extend_parameters (MultiMap<string, string> additional)
- {
- var iter = additional.map_iterator ();
-
- while (iter.next ())
- this.add_parameter (iter.get_key (), iter.get_value ());
- }
-
- /**
- * Remove all instances of a parameter.
- *
- * @param parameter_name the name of the parameter
- *
- * @since 0.6.0
- */
- public void remove_parameter_all (string parameter_name)
- {
- this.parameters.remove_all (parameter_name);
- }
-
- /**
- * A fairly-strict equality function for {@link AbstractFieldDetails}.
- *
- * This function compares:
- *
- * * {@link AbstractFieldDetails.value}s
- * * {@link AbstractFieldDetails.parameters}
- *
- * And does not compare:
- *
- * * {@link AbstractFieldDetails.id}s
- *
- * See the description of {@link AbstractFieldDetails.values_equal} for
- * details on the value comparison.
- *
- * To check equality not including the parameters, see
- * {@link AbstractFieldDetails.values_equal}.
- *
- * @param that another {@link AbstractFieldDetails}
- *
- * @return whether the elements are equal
- *
- * @see AbstractFieldDetails.parameters_equal
- * @see AbstractFieldDetails.values_equal
- * @since 0.6.0
- */
- public virtual bool equal (AbstractFieldDetails<T> that)
- {
- return (this.get_type () == that.get_type ()) &&
- this.values_equal (that) &&
- this.parameters_equal (that);
- }
-
- /**
- * Same as {@link AbstractFieldDetails.equal}, but static, so we can use
- * libgee 0.8 without an API break.
- *
- * See [[https://bugzilla.gnome.org/show_bug.cgi?id=673918|673918]]
- * This can and should be removed next time we break the API.
- * Note: This uses Gee.EqualDataFunc signature, to avoid having to cast.
- *
- * @param left one {@link AbstractFieldDetails} to compare
- * @param right another {@link AbstractFieldDetails} to compare
- *
- * @return whether the elemants are equal
- *
- * @since 0.9.0
- */
- public static bool equal_static (AbstractFieldDetails left,
- AbstractFieldDetails right)
- {
- GLib.return_val_if_fail (left != null, false);
- GLib.return_val_if_fail (right != null, false);
-
- AbstractFieldDetails left_details = (AbstractFieldDetails) left;
- AbstractFieldDetails right_details = (AbstractFieldDetails) right;
- return left_details.equal (right_details);
- }
-
- /**
- * An equality function which only considers parameters.
- *
- * This function compares:
- *
- * * {@link AbstractFieldDetails.parameters}
- *
- * And does not compare:
- *
- * * {@link AbstractFieldDetails.value}s
- * * {@link AbstractFieldDetails.id}s
- *
- * @param that another {@link AbstractFieldDetails}
- *
- * @return whether the elements' {@link AbstractFieldDetails.value}s are
- * equal.
- *
- * @see AbstractFieldDetails.equal
- * @see AbstractFieldDetails.values_equal
- * @since 0.6.5
- */
- public virtual bool parameters_equal (AbstractFieldDetails<T> that)
- {
- /* Check that the parameter names and their values match exactly in both
- * AbstractFieldDetails objects. */
- if (this.parameters.size != that.parameters.size)
- return false;
-
- foreach (var param in this.parameters.get_keys ())
- {
- /* Since these parameters are meant to model vCard parameters, we
- * should compare on a case-insensitive basis. However, this leads to
- * ambiguity in the case:
- *
- * this.parameters = {"foo": {"bar"}}
- * that.parameters = {"foo": {"bar"}, "FOO": {"qux"}}
- *
- * So parameter names should normalised elsewhere (either in the
- * insertion functions (with a big warning for the clients) or in the
- * clients themselves).
- *
- * Note that parameter values can't be normalised in general, since
- * they can be user-set labels.
- */
- if (!that.parameters.contains (param))
- return false;
-
- var this_param_values = this.parameters.get_values ();
- var that_param_values = that.parameters.get_values ();
-
- if (this_param_values.size != that_param_values.size)
- return false;
-
- foreach (var param_val in this.parameters.get_values ())
- {
- if (!that_param_values.contains (param_val))
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * An equality function which does not consider parameters.
- *
- * Specific classes may override this function to provide "smart" value
- * comparisons (eg, considering the phone number values "+1 555 123 4567" and
- * "123-4567" equal). If you wish to do strict comparisons, simply compare the
- * {@link AbstractFieldDetails.value}s directly.
- *
- * This function compares:
- *
- * * {@link AbstractFieldDetails.value}s
- *
- * And does not compare:
- *
- * * {@link AbstractFieldDetails.parameters}
- * * {@link AbstractFieldDetails.id}s
- *
- * This defaults to string comparison of the
- * {@link AbstractFieldDetails.value}s if the generic type is string;
- * otherwise, direct pointer comparison of the
- * {@link AbstractFieldDetails.value}s.
- *
- * @param that another {@link AbstractFieldDetails}
- *
- * @return whether the elements' {@link AbstractFieldDetails.value}s are
- * equal.
- *
- * @see AbstractFieldDetails.equal
- * @see AbstractFieldDetails.parameters_equal
- * @since 0.6.5
- */
- public virtual bool values_equal (AbstractFieldDetails<T> that)
- {
- EqualFunc equal_func = direct_equal;
-
- if (typeof (T) == typeof (string))
- equal_func = str_equal;
-
- if ((this.get_type () != that.get_type ()) ||
- !equal_func (this.value, that.value))
- {
- return false;
- }
-
- return true;
- }
-
- /**
- * A hash function for the {@link AbstractFieldDetails}.
- *
- * This defaults to a string hash of the
- * {@link AbstractFieldDetails.value} if the generic type is string;
- * otherwise, direct hash of the {@link AbstractFieldDetails.value}.
- *
- * @return the hash value
- *
- * @since 0.6.0
- */
- public virtual uint hash ()
- {
- HashFunc hash_func = direct_hash;
-
- if (typeof (T) == typeof (string))
- hash_func = str_hash;
-
- return hash_func (this.value);
- }
-
- /**
- * Same as {@link AbstractFieldDetails.hash}, but static, so we can use libgee
- * 0.8 without an API break.
- *
- * See [[https://bugzilla.gnome.org/show_bug.cgi?id=673918|673918]]
- * This can and should be removed next time we break the API.
- * Note: This uses Gee.HashDataFunc signature, to avoid having to cast.
- *
- * @param value the value to hash
- *
- * @return the hash value
- *
- * @since 0.9.0
- */
- public static uint hash_static (AbstractFieldDetails value)
- {
- GLib.return_val_if_fail (value != null, 0);
-
- AbstractFieldDetails details = (AbstractFieldDetails) value;
- return details.hash ();
- }
-}
diff --git a/folks/alias-details.vala b/folks/alias-details.vala
deleted file mode 100644
index 38237cd5..00000000
--- a/folks/alias-details.vala
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- * Copyright (C) 2011 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using GLib;
-
-/**
- * Alias for a contact.
- *
- * This allows representation of aliases for contacts, where the user has chosen
- * their own name for the contact to better represent that contact to them. A
- * typical example of this is the use of user-chosen aliases for contacts in
- * instant messaging programs.
- */
-public interface Folks.AliasDetails : Object
-{
- /**
- * An alias for the contact.
- *
- * An alias is a user-given name, to be used in UIs as the sole way to
- * represent the contact to the user.
- *
- * This may not be ``null``: an empty string represents an unset alias.
- */
- public abstract string alias { get; set; }
-
- /**
- * Change the contact's alias.
- *
- * It's preferred to call this rather than setting {@link AliasDetails.alias}
- * directly, as this method gives error notification and will only return
- * once the alias has been written to the relevant backing store (or the
- * operation's failed).
- *
- * @param alias the new alias
- * @throws PropertyError if setting the alias failed
- * @since 0.6.2
- */
- public virtual async void change_alias (string alias) throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Alias is not writeable on this contact."));
- }
-}
diff --git a/folks/anti-linkable.vala b/folks/anti-linkable.vala
deleted file mode 100644
index a1cf8906..00000000
--- a/folks/anti-linkable.vala
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2011, 2012 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using Gee;
-using GLib;
-
-/**
- * Interface for {@link Persona} subclasses from backends which support storage
- * of, anti-linking data.
- *
- * Anti-links are stored as a set of {@link Persona.uid}s with each
- * {@link Persona} (A), specifying that A must not be linked into an
- * {@link Individual} with any of the personas in its anti-links set.
- *
- * @since 0.7.3
- */
-public interface Folks.AntiLinkable : Folks.Persona
-{
- /**
- * UIDs of anti-linked {@link Persona}s.
- *
- * The {@link Persona}s identified by their UIDs in this set are guaranteed to
- * not be linked to this {@link Persona}, even if their linkable properties
- * match.
- *
- * No UIDs may be ``null``. Well-formed but non-existent UIDs (i.e. UIDs which
- * can be successfully parsed, but which don't currently correspond to a
- * {@link Persona} instance) are permitted, as personas may appear and
- * disappear over time.
- *
- * The special UID ``*`` is used as a wildcard to mark the persona as globally
- * anti-linked. See {@link AntiLinkable.has_global_anti_link}.
- *
- * It is expected, but not guaranteed, that anti-links made between personas
- * will be reciprocal. That is, if persona A lists persona B's UID in its
- * {@link AntiLinkable.anti_links} set, persona B will typically also list
- * persona A in its anti-links set.
- *
- * @since 0.7.3
- */
- public abstract Set<string> anti_links { get; set; }
-
- /**
- * Change the {@link Persona}'s set of anti-links.
- *
- * It's preferred to call this rather than setting
- * {@link AntiLinkable.anti_links} directly, as this method gives error
- * notification and will only return once the anti-links have been written
- * to the relevant backing store (or the operation's failed).
- *
- * It should be noted that {@link IndividualAggregator.link_personas} and
- * {@link IndividualAggregator.unlink_individual} will modify the anti-links
- * sets of the personas they touch, in order to remove and add anti-links,
- * respectively. It is expected that these {@link IndividualAggregator}
- * methods will be used to modify anti-links indirectly, rather than calling
- * {@link AntiLinkable.change_anti_links} directly.
- *
- * @param anti_links the new set of anti-links from this persona
- * @throws PropertyError if setting the anti-links failed
- * @since 0.7.3
- */
- public virtual async void change_anti_links (Set<string> anti_links)
- throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Anti-links are not writeable on this contact."));
- }
-
- /**
- * Check for an anti-link with another persona.
- *
- * This will return ``true`` if ``other_persona``'s UID is listed in this
- * persona's anti-links set. Note that this check is not symmetric.
- *
- * @param other_persona the persona to check is anti-linked
- * @return ``true`` if an anti-link exists, ``false`` otherwise
- * @since 0.7.3
- */
- public bool has_anti_link_with_persona (Persona other_persona)
- {
- return (this.has_global_anti_link ()) ||
- (other_persona.uid in this.anti_links);
- }
-
- /**
- * Add anti-links to other personas.
- *
- * The UIDs of all personas in ``other_personas`` will be added to this
- * persona's anti-links set and the changes propagated to backends.
- *
- * Any attempt to anti-link a persona with itself is not an error, but is
- * ignored.
- *
- * This method is safe to call multiple times concurrently (e.g. begin one
- * asynchronous call, then begin another before the first has finished).
- *
- * @param other_personas the personas to anti-link to this one
- * @throws PropertyError if setting the anti-links failed
- * @since 0.7.3
- */
- public async void add_anti_links (Set<Persona> other_personas)
- throws PropertyError
- {
- var new_anti_links = SmallSet<string>.copy (this.anti_links);
-
- foreach (var p in other_personas)
- {
- /* Don't anti-link ourselves. */
- if (p == this)
- {
- continue;
- }
-
- new_anti_links.add (p.uid);
- }
-
- yield this.change_anti_links (new_anti_links);
- }
-
- /**
- * Remove anti-links to other personas.
- *
- * The UIDs of all personas in ``other_personas`` will be removed from this
- * persona's anti-links set and the changes propagated to backends.
- *
- * If the global anti-link is set, this will not have any effect until the
- * global anti-link is removed.
- *
- * This method is safe to call multiple times concurrently (e.g. begin one
- * asynchronous call, then begin another before the first has finished).
- *
- * @param other_personas the personas to remove anti-links from this one
- * @throws PropertyError if setting the anti-links failed
- * @since 0.7.3
- */
- public async void remove_anti_links (Set<Persona> other_personas)
- throws PropertyError
- {
- var new_anti_links = SmallSet<string>.copy (this.anti_links);
-
- foreach (var p in other_personas)
- {
- new_anti_links.remove (p.uid);
- }
-
- yield this.change_anti_links (new_anti_links);
- }
-
- /**
- * Prevent persona from being linked with any other personas
- *
- * This function will add a wildcard ``*`` to the set of anti-links, which will
- * prevent the persona from being linked with any other personas.
- *
- * To make the persona linkable again you need to remove the global anti-link
- *
- * This method is safe to call multiple times concurrently (e.g. begin one
- * asynchronous call, then begin another before the first has finished).
- *
- * @throws PropertyError if setting the anti-links failed
- * @since 0.9.7
- */
- public async void add_global_anti_link()
- throws PropertyError
- {
- if (!this.has_global_anti_link())
- {
- var new_anti_links = SmallSet<string>.copy (this.anti_links);
- new_anti_links.add ("*");
- yield this.change_anti_links (new_anti_links);
- }
- }
-
- /**
- * Allow persona to be linked with other personas
- *
- * This function removes the wildcard ``*`` from the set of anti-links, allowing
- * the persona to be linked again.
- *
- * This method is safe to call multiple times concurrently (e.g. begin one
- * asynchronous call, then begin another before the first has finished).
- *
- * @throws PropertyError if setting the anti-links failed
- * @since 0.9.7
- */
- public async void remove_global_anti_link()
- throws PropertyError
- {
- if (this.has_global_anti_link())
- {
- var new_anti_links = SmallSet<string>.copy (this.anti_links);
- new_anti_links.remove ("*");
- yield this.change_anti_links (new_anti_links);
- }
- }
-
- /**
- * Check if the persona has a global anti link.
- *
- * If the persona has global anti link this means that the persona can not be
- * linked with any other persona.
- *
- * @since 0.9.7
- */
- public bool has_global_anti_link()
- {
- return (this.anti_links.contains ("*"));
- }
-}
-
-/* vim: filetype=vala textwidth=80 tabstop=2 expandtab: */
diff --git a/folks/avatar-cache.vala b/folks/avatar-cache.vala
deleted file mode 100644
index 8dfb839c..00000000
--- a/folks/avatar-cache.vala
+++ /dev/null
@@ -1,319 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-
-/**
- * A singleton persistent cache for avatars in folks.
- *
- * Avatars may be added to the cache, and referred to by a persistent
- * URI from that point onwards. The avatars will be stored on disk in the user's
- * XDG cache directory.
- *
- * The avatar cache is typically used by backends where retrieving avatars is an
- * expensive operation (for example, they have to be downloaded from the network
- * every time they're used).
- *
- * All avatars from all users of the {@link Folks.AvatarCache} are stored in the
- * same namespace, so callers must ensure that the IDs they use for avatars are
- * globally unique (e.g. by using the corresponding {@link Folks.Persona.uid}).
- *
- * Ongoing store operations ({@link Folks.AvatarCache.store_avatar}) are rate
- * limited to try and prevent file descriptor exhaustion. Load operations
- * ({@link Folks.AvatarCache.load_avatar}) must be rate limited by the client,
- * as the file I/O occurs when calling {@link GLib.LoadableIcon.load} rather
- * than when retrieving the {@link GLib.LoadableIcon} from the cache.
- *
- * @since 0.6.0
- */
-public class Folks.AvatarCache : Object
-{
- private static weak AvatarCache? _instance = null; /* needs to be locked */
- private File _cache_directory;
- private uint _n_ongoing_stores = 0;
- private Queue<DelegateWrapper> _pending_stores =
- new Queue<DelegateWrapper> ();
- private const uint _max_n_ongoing_stores = 10;
-
- /**
- * Private constructor for an instance of the avatar cache. The singleton
- * instance should be retrieved by calling {@link AvatarCache.dup()} instead.
- *
- * @since 0.6.0
- */
- private AvatarCache ()
- {
- Object ();
- }
-
- construct
- {
- this._cache_directory =
- File.new_for_path (Environment.get_user_cache_dir ())
- .get_child ("folks")
- .get_child ("avatars");
- }
-
- /**
- * Create or return the singleton {@link Folks.AvatarCache} class instance.
- * If the instance doesn't exist already, it will be created.
- *
- * This function is thread-safe.
- *
- * @return Singleton {@link Folks.AvatarCache} instance
- * @since 0.6.0
- */
- public static AvatarCache dup ()
- {
- var _retval = AvatarCache._instance;
- AvatarCache retval;
-
- if (_retval == null)
- {
- /* use an intermediate variable to force a strong reference */
- retval = new AvatarCache ();
- AvatarCache._instance = retval;
- }
- else
- {
- retval = (!) _retval;
- }
-
- return retval;
- }
-
- ~AvatarCache ()
- {
- /* Manually clear the singleton _instance */
- AvatarCache._instance = null;
- }
-
- /**
- * Fetch an avatar from the cache by its globally unique ID.
- *
- * It is up to the caller to ensure that file I/O is rate-limited when loading
- * many avatars in parallel, by limiting calls to
- * {@link GLib.LoadableIcon.load}.
- *
- * @param id the globally unique ID for the avatar
- * @return Avatar from the cache, or ``null`` if it doesn't exist in the cache
- * @throws GLib.Error if checking for existence of the cache file failed
- * @since 0.6.0
- */
- public async LoadableIcon? load_avatar (string id) throws GLib.Error
- {
- var avatar_file = this._get_avatar_file (id);
-
- debug ("Loading avatar '%s' from file '%s'.", id, avatar_file.get_uri ());
-
- // Return null if the avatar doesn't exist
- if (avatar_file.query_exists () == false)
- {
- return null;
- }
-
- return new FileIcon (avatar_file);
- }
-
- /**
- * Store an avatar in the cache, assigning the given globally unique ID to it,
- * which can later be used to load and remove the avatar from the cache. For
- * example, this ID could be the UID of a persona. The URI of the cached
- * avatar file will be returned.
- *
- * This method may be called multiple times concurrently for the same avatar
- * ID (e.g. an asynchronous call may be made, and a subsequent asynchronous
- * call may begin before the first has finished).
- *
- * Concurrent file I/O may be rate limited within each {@link AvatarCache}
- * instance to avoid file descriptor exhaustion.
- *
- * @param id the globally unique ID for the avatar
- * @param avatar the avatar data to cache
- * @return a URI for the file storing the cached avatar
- * @throws GLib.Error if the avatar data couldn't be loaded, or if creating
- * the avatar directory or cache file failed
- * @since 0.6.0
- */
- public async string store_avatar (string id, LoadableIcon avatar)
- throws GLib.Error
- {
- string avatar_uri = "";
-
- if (this._n_ongoing_stores > AvatarCache._max_n_ongoing_stores)
- {
- /* Add to the pending queue. */
- var wrapper = new DelegateWrapper ();
- wrapper.cb = store_avatar.callback;
- this._pending_stores.push_tail ((owned) wrapper);
- yield;
- }
-
- /* Do the actual store operation. */
- try
- {
- this._n_ongoing_stores++;
- avatar_uri = yield this._store_avatar_unlimited (id, avatar);
- }
- finally
- {
- this._n_ongoing_stores--;
-
- /* If there is a store operation pending, resume it, FIFO-style. */
- var wrapper = this._pending_stores.pop_head ();
- if (wrapper != null)
- {
- wrapper.cb ();
- }
- }
-
- return avatar_uri;
- }
-
- private async string _store_avatar_unlimited (string id, LoadableIcon avatar)
- throws GLib.Error
- {
- var dest_avatar_file = this._get_avatar_file (id);
-
- debug ("Storing avatar '%s' in file '%s'.", id,
- dest_avatar_file.get_uri ());
-
- InputStream src_avatar_stream =
- yield avatar.load_async (-1, null, null);
-
- // Copy the icon data into a file
- while (true)
- {
- OutputStream? dest_avatar_stream = null;
-
- try
- {
- /* In order for this to be concurrency-safe, we assume that
- * replace_async() does an atomic substitution of the new file for
- * the old when the stream is closed. (i.e. It's
- * concurrency-safe). */
- dest_avatar_stream =
- yield dest_avatar_file.replace_async (null, false,
- FileCreateFlags.PRIVATE);
- yield ((!) dest_avatar_stream).splice_async (src_avatar_stream,
- OutputStreamSpliceFlags.NONE);
- yield ((!) dest_avatar_stream).close_async ();
-
- break;
- }
- catch (GLib.Error e)
- {
- /* If the parent directory wasn't found, create it and loop
- * round to try again. */
- if (e is IOError.NOT_FOUND)
- {
- this._create_cache_directory ();
- continue;
- }
-
- if (dest_avatar_stream != null)
- {
- yield ((!) dest_avatar_stream).close_async ();
- }
-
- throw e;
- }
- }
-
- yield src_avatar_stream.close_async ();
-
- return this.build_uri_for_avatar (id);
- }
-
- /**
- * Remove an avatar from the cache, if it exists in the cache. If the avatar
- * exists in the cache but there is a problem in removing it, a
- * {@link GLib.Error} will be thrown.
- *
- * @param id the globally unique ID for the avatar
- * @throws GLib.Error if deleting the cache file failed
- * @since 0.6.0
- */
- public async void remove_avatar (string id) throws GLib.Error
- {
- var avatar_file = this._get_avatar_file (id);
-
- debug ("Removing avatar '%s' in file '%s'.", id, avatar_file.get_uri ());
-
- try
- {
- avatar_file.delete (null);
- }
- catch (GLib.Error e)
- {
- // Ignore file not found errors
- if (!(e is IOError.NOT_FOUND))
- {
- throw e;
- }
- }
- }
-
- /**
- * Build the URI of an avatar file in the cache from a globally unique ID.
- * This will always succeed, even if the avatar doesn't exist in the cache.
- *
- * @param id the globally unique ID for the avatar
- * @return URI of the avatar file with the given globally unique ID
- * @since 0.6.0
- */
- public string build_uri_for_avatar (string id)
- {
- return this._get_avatar_file (id).get_uri ();
- }
-
- private File _get_avatar_file (string id)
- {
- var escaped_uri = Uri.escape_string (id, "", false);
- var file = this._cache_directory.get_child (escaped_uri);
-
- assert (file.has_parent (this._cache_directory) == true);
-
- return file;
- }
-
- private void _create_cache_directory () throws GLib.Error
- {
- try
- {
- this._cache_directory.make_directory_with_parents ();
- }
- catch (GLib.Error e)
- {
- // Ignore errors caused by the directory existing already
- if (!(e is IOError.EXISTS))
- {
- throw e;
- }
- }
- }
-}
-
-/* See: https://mail.gnome.org/archives/vala-list/2011-June/msg00005.html */
-[Compact]
-private class DelegateWrapper
-{
- public SourceFunc cb;
-}
diff --git a/folks/avatar-details.vala b/folks/avatar-details.vala
deleted file mode 100644
index edd2b0ca..00000000
--- a/folks/avatar-details.vala
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- * Copyright (C) 2011 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using GLib;
-
-/**
- * Avatar for a contact.
- *
- * This allows avatars to be associated with contacts. An avatar is a small
- * image file which represents the contact, such as a photo of them.
- *
- * @since 0.6.0
- */
-public interface Folks.AvatarDetails : Object
-{
- /**
- * An avatar for the contact.
- *
- * The avatar may be ``null`` if unset. Otherwise, the image data may be
- * asynchronously loaded using the methods of the {@link GLib.LoadableIcon}
- * implementation.
- *
- * @since 0.6.0
- */
- public abstract LoadableIcon? avatar { get; set; }
-
- /**
- * Change the contact's avatar.
- *
- * It's preferred to call this rather than setting
- * {@link AvatarDetails.avatar} directly, as this method gives error
- * notification and will only return once the avatar has been written to the
- * relevant backing store (or the operation's failed).
- *
- * @param avatar the new avatar (or ``null`` to unset the avatar)
- * @throws PropertyError if setting the avatar failed
- * @since 0.6.2
- */
- public virtual async void change_avatar (LoadableIcon? avatar)
- throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Avatar is not writeable on this contact."));
- }
-}
diff --git a/folks/backend-store.vala b/folks/backend-store.vala
deleted file mode 100644
index 11c37ac9..00000000
--- a/folks/backend-store.vala
+++ /dev/null
@@ -1,985 +0,0 @@
-/*
- * Copyright (C) 2008 Nokia Corporation.
- * Copyright (C) 2008 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>.
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
- * Travis Reitter <travis.reitter@collabora.co.uk>
- *
- * This file was originally part of Rygel.
- */
-
-using Gee;
-using GLib;
-
-extern const string G_LOG_DOMAIN;
-
-/**
- * Responsible for backend loading.
- *
- * The BackendStore manages the set of available Folks backends. The
- * {@link BackendStore.load_backends} function loads all compatible and enabled
- * backends and the {@link BackendStore.backend_available} signal notifies when
- * these backends are ready.
- */
-public class Folks.BackendStore : Object {
- [CCode (has_target = false)]
- private delegate void ModuleInitFunc (BackendStore store);
- [CCode (has_target = false)]
- private delegate void ModuleFinalizeFunc (BackendStore store);
-
- /* this contains all backends, regardless of enabled or prepared state */
- private HashMap<string,Backend> _backend_hash;
- /* if null, all backends are allowed */
- private SmallSet<string>? _backends_allowed;
- /* if null, no backends are disabled */
- private SmallSet<string>? _backends_disabled;
- private HashMap<string, Backend> _prepared_backends;
- private Map<string, Backend> _prepared_backends_ro;
- private File _config_file;
- private GLib.KeyFile _backends_key_file;
- private HashMap<string,unowned Module> _modules;
- private static weak BackendStore? _instance = null;
- private bool _is_prepared = false;
- private Debug _debug;
-
- /**
- * This keyword in the keyfile acts as a wildcard for all backends not already
- * listed in the same file.
- *
- * This is particularly useful for tests which want to ensure they're only
- * operating with backends they were designed for (and thus may not be able to
- * enumerate entries for).
- *
- * To avoid conflicts, backends must not use this as a name.
- *
- * @since 0.4.0
- */
- public static string KEY_FILE_GROUP_ALL_OTHERS = "all-others";
-
- /**
- * Emitted when a backend has been added to the BackendStore.
- *
- * This will not be emitted until after {@link BackendStore.load_backends}
- * has been called.
- *
- * {@link Backend}s referenced in this signal are also included in
- * {@link BackendStore.enabled_backends}.
- *
- * @param backend the new {@link Backend}
- */
- public signal void backend_available (Backend backend);
-
- /**
- * The list of backends visible to this store which have not been explicitly
- * disabled.
- *
- * This list will be empty before {@link BackendStore.load_backends} has been
- * called.
- *
- * The backends in this list have been prepared and are ready to use.
- *
- * @since 0.5.1
- */
- public Map<string, Backend> enabled_backends
- {
- /* Return a read-only view of the map */
- get { return this._prepared_backends_ro; }
-
- private set {}
- }
-
- /**
- * Whether {@link BackendStore.prepare} has successfully completed for this
- * store.
- *
- * @since 0.3.0
- */
- public bool is_prepared
- {
- get { return this._is_prepared; }
-
- private set {}
- }
-
- /**
- * Create a new BackendStore.
- */
- public static BackendStore dup ()
- {
- if (BackendStore._instance == null)
- {
- /* use an intermediate variable to force a strong reference */
- var new_instance = new BackendStore ();
- BackendStore._instance = new_instance;
-
- return new_instance;
- }
-
- return (!) BackendStore._instance;
- }
-
- private BackendStore ()
- {
- Object ();
- }
-
- construct
- {
- /* Treat this as a library init function */
- var debug_no_colour = Environment.get_variable ("FOLKS_DEBUG_NO_COLOUR");
- var debug_no_color = Environment.get_variable ("FOLKS_DEBUG_NO_COLOR");
- this._debug =
- Debug.dup_with_flags (Environment.get_variable ("G_MESSAGES_DEBUG"),
- (debug_no_colour == null || debug_no_colour == "0") &&
- (debug_no_color == null || debug_no_color == "0"));
-
- /* register the core debug messages */
- this._debug._register_domain (G_LOG_DOMAIN);
-
- this._debug.print_status.connect (this._debug_print_status);
-
- this._modules = new HashMap<string,unowned Module> ();
- this._backend_hash = new HashMap<string,Backend> ();
- this._prepared_backends = new HashMap<string,Backend> ();
- this._prepared_backends_ro = this._prepared_backends.read_only_view;
- }
-
- ~BackendStore ()
- {
- /* Unprepare all existing backends. */
- var iter = this._prepared_backends.map_iterator ();
- while (iter.next () == true)
- {
- var backend = iter.get_value ();
- backend.unprepare.begin ();
- }
-
- this._prepared_backends.clear ();
-
- /* Finalize all the loaded modules that have finalize functions */
- foreach (var module in this._modules.values)
- {
- void* func;
- if (module.symbol ("module_finalize", out func))
- {
- ModuleFinalizeFunc module_finalize = (ModuleFinalizeFunc) func;
- module_finalize (this);
- }
- }
-
- this._modules.clear ();
-
- /* Disconnect from the debug handler */
- this._debug.print_status.disconnect (this._debug_print_status);
-
- /* manually clear the singleton instance */
- BackendStore._instance = null;
- }
-
- private void _debug_print_status (Debug debug)
- {
- const string domain = Debug.STATUS_LOG_DOMAIN;
- const LogLevelFlags level = LogLevelFlags.LEVEL_INFO;
-
- debug.print_heading (domain, level, "BackendStore (%p)", this);
- debug.print_line (domain, level, "%u Backends:",
- this._backend_hash.size);
-
- debug.indent ();
-
- foreach (var backend in this._backend_hash.values)
- {
- debug.print_heading (domain, level, "Backend (%p)", backend);
- debug.print_key_value_pairs (domain, level,
- "Ref. count", this.ref_count.to_string (),
- "Name", backend.name,
- "Prepared?", backend.is_prepared ? "yes" : "no",
- "Quiescent?", backend.is_quiescent ? "yes" : "no"
- );
- debug.print_line (domain, level, "%u PersonaStores:",
- backend.persona_stores.size);
-
- debug.indent ();
-
- foreach (var persona_store in backend.persona_stores.values)
- {
- string? trust_level = null;
-
- switch (persona_store.trust_level)
- {
- case PersonaStoreTrust.NONE:
- trust_level = "none";
- break;
- case PersonaStoreTrust.PARTIAL:
- trust_level = "partial";
- break;
- case PersonaStoreTrust.FULL:
- trust_level = "full";
- break;
- default:
- assert_not_reached ();
- }
-
- var writeable_props = string.joinv (",",
- persona_store.always_writeable_properties);
-
- debug.print_heading (domain, level, "PersonaStore (%p)",
- persona_store);
- debug.print_key_value_pairs (domain, level,
- "Ref. count", this.ref_count.to_string (),
- "ID", persona_store.id,
- "Prepared?", persona_store.is_prepared ? "yes" : "no",
- "Is primary store?",
- persona_store.is_primary_store ? "yes" : "no",
- "Always writeable properties", writeable_props,
- "Quiescent?", persona_store.is_quiescent ? "yes" : "no",
- "Trust level", trust_level,
- "Persona count", persona_store.personas.size.to_string ()
- );
- }
-
- debug.unindent ();
- }
-
- debug.unindent ();
-
- /* Finish with a blank line. The format string must be non-empty. */
- debug.print_line (domain, level, "%s", "");
- }
-
- /**
- * Prepare the BackendStore for use.
- *
- * This must only ever be called before {@link BackendStore.load_backends} is
- * called for the first time. If it isn't called explicitly,
- * {@link BackendStore.load_backends} will call it.
- *
- * This method is safe to call multiple times concurrently (e.g. an
- * asynchronous call may begin between a subsequent asynchronous call
- * beginning and finishing).
- *
- * @since 0.3.0
- */
- public async void prepare ()
- {
- var profiling = Internal.profiling_start ("preparing BackendStore");
-
- /* (re-)load the list of disabled backends */
- yield this._load_disabled_backend_names ();
-
- if (this._is_prepared == false)
- {
- this._is_prepared = true;
- this.notify_property ("is-prepared");
- }
-
- Internal.profiling_end ((owned) profiling);
- }
-
- /**
- * Find, load, and prepare all backends which are not disabled.
- *
- * Backends will be searched for in the path given by the
- * ``FOLKS_BACKEND_PATH`` environment variable, if it's set. If it's not set,
- * backends will be searched for in a path set at compilation time.
- *
- * This method is not safe to call multiple times concurrently.
- *
- * @throws GLib.Error currently unused
- */
- public async void load_backends () throws GLib.Error
- {
- assert (Module.supported());
-
- var profiling = Internal.profiling_start ("loading backends in BackendStore");
-
- yield this.prepare ();
-
- /* unload backends that have been disabled since they were loaded */
- foreach (var backend_existing in this._backend_hash.values)
- {
- yield this._backend_unload_if_needed (backend_existing);
- }
-
- Internal.profiling_point ("unloaded backends in BackendStore");
-
- string? _path = Environment.get_variable ("FOLKS_BACKEND_PATH");
- string path;
-
- if (_path == null)
- {
- path = BuildConf.BACKEND_DIR;
-
- debug ("Using built-in backend dir '%s' (override with " +
- "environment variable FOLKS_BACKEND_PATH)", path);
- }
- else
- {
- path = (!) _path;
-
- debug ("Using environment variable FOLKS_BACKEND_PATH = " +
- "'%s' to look for backends", path);
- }
-
- var modules = new HashMap<string, File> ();
- var path_split = path.split (":");
- foreach (unowned string subpath in path_split)
- {
- var file = File.new_for_path (subpath);
-
- bool is_file;
- bool is_dir;
- yield BackendStore._get_file_info (file, out is_file, out is_dir);
- if (is_file)
- {
- modules.set (subpath, file);
- }
- else if (is_dir)
- {
- var cur_modules = yield this._get_modules_from_dir (file);
- if (cur_modules != null)
- {
- foreach (var entry in ((!) cur_modules).entries)
- {
- modules.set (entry.key, entry.value);
- }
- }
- }
- else
- {
- critical ("FOLKS_BACKEND_PATH component '%s' is not a regular " +
- "file or directory; ignoring...",
- subpath);
- assert_not_reached ();
- }
- }
-
- Internal.profiling_point ("found modules in BackendStore");
-
- /* this will load any new modules found in the backends dir and will
- * prepare and unprepare backends such that they match the state in the
- * backend store key file */
- foreach (var module in modules.values)
- this._load_module_from_file (module);
-
- Internal.profiling_point ("loaded modules in BackendStore");
-
- /* this is populated indirectly from _load_module_from_file(), above */
- var backends_remaining = 1;
- foreach (var backend in this._backend_hash.values)
- {
- backends_remaining++;
- this._backend_load_if_needed.begin (backend, (o, r) =>
- {
- this._backend_load_if_needed.end (r);
- backends_remaining--;
-
- if (backends_remaining == 0)
- {
- this.load_backends.callback ();
- }
- });
- }
- backends_remaining--;
- if (backends_remaining > 0)
- {
- yield;
- }
-
- Internal.profiling_end ((owned) profiling);
- }
-
- /* This method is not safe to call multiple times concurrently, since there's
- * a race in updating this._prepared_backends. */
- private async void _backend_load_if_needed (Backend backend)
- {
- if (this._backend_is_enabled (backend.name))
- {
- if (!this._prepared_backends.has_key (backend.name))
- {
- try
- {
- yield backend.prepare ();
-
- debug ("New backend '%s' prepared", backend.name);
- this._prepared_backends.set (backend.name, backend);
- this.backend_available (backend);
- }
- catch (GLib.Error e)
- {
- if (e is DBusError.SERVICE_UNKNOWN)
- {
- /* Don’t warn if a D-Bus service is unknown; it probably
- * means the backend is deliberately not running and the
- * user is running folks from git, so hasn’t appropriately
- * enabled/disabled backends from building. */
- debug ("Error preparing Backend '%s': %s",
- backend.name, e.message);
- }
- else
- {
- warning ("Error preparing Backend '%s': %s",
- backend.name, e.message);
- }
- }
- }
- }
- }
-
- /* This method is not safe to call multiple times concurrently, since there's
- * a race in updating this._prepared_backends. */
- private async bool _backend_unload_if_needed (Backend backend)
- {
- var unloaded = false;
-
- if (!this._backend_is_enabled (backend.name))
- {
- Backend? backend_existing = this._backend_hash.get (backend.name);
- if (backend_existing != null)
- {
- try
- {
- yield ((!) backend_existing).unprepare ();
- }
- catch (GLib.Error e)
- {
- warning ("Error unpreparing Backend '%s': %s", backend.name,
- e.message);
- }
-
- this._prepared_backends.unset (((!) backend_existing).name);
-
- unloaded = true;
- }
- }
-
- return unloaded;
- }
-
- /**
- * Add a new {@link Backend} to the BackendStore.
- *
- * @param backend the {@link Backend} to add
- */
- public void add_backend (Backend backend)
- {
- /* Purge any other backend with the same name; re-add if enabled */
- Backend? backend_existing = this._backend_hash.get (backend.name);
- if (backend_existing != null && backend_existing != backend)
- {
- ((!) backend_existing).unprepare.begin ();
- this._prepared_backends.unset (((!) backend_existing).name);
- }
-
- this._debug._register_domain (backend.name);
-
- this._backend_hash.set (backend.name, backend);
- }
-
- private bool _backend_is_enabled (string name)
- {
- var all_others_enabled = true;
-
- if (this._backends_allowed != null &&
- !(name in (!) this._backends_allowed))
- return false;
-
- if (this._backends_disabled != null &&
- name in (!) this._backends_disabled)
- return false;
-
- try
- {
- all_others_enabled = this._backends_key_file.get_boolean (
- BackendStore.KEY_FILE_GROUP_ALL_OTHERS, "enabled");
- }
- catch (KeyFileError e)
- {
- if (!(e is KeyFileError.GROUP_NOT_FOUND) &&
- !(e is KeyFileError.KEY_NOT_FOUND))
- {
- warning ("Couldn't determine whether to enable or disable " +
- "backends not listed in backend key file. Defaulting to %s.",
- all_others_enabled ? "enabled" : "disabled");
- }
- else
- {
- debug ("No catch-all entry in the backend key file. %s " +
- "unlisted backends.",
- all_others_enabled ? "Enabling" : "Disabling");
- }
-
- /* fall back to the default in case of any level of failure */
- }
-
- var enabled = true;
- try
- {
- enabled = this._backends_key_file.get_boolean (name, "enabled");
- }
- catch (KeyFileError e)
- {
- /* if there's no entry for this backend, use the default set above */
- if ((e is KeyFileError.GROUP_NOT_FOUND) ||
- (e is KeyFileError.KEY_NOT_FOUND))
- {
- debug ("Found no entry for backend '%s'.enabled in backend " +
- "keyfile. %s according to '%s' setting.",
- name,
- all_others_enabled ? "Enabling" : "Disabling",
- BackendStore.KEY_FILE_GROUP_ALL_OTHERS);
- enabled = all_others_enabled;
- }
- else if (!(e is KeyFileError.GROUP_NOT_FOUND) &&
- !(e is KeyFileError.KEY_NOT_FOUND))
- {
- warning ("Couldn't check enabled state of backend '%s': %s\n" +
- "Disabling backend.",
- name, e.message);
- enabled = false;
- }
- }
-
- return enabled;
- }
-
- /**
- * Get a backend from the store by name. If a backend is returned, its
- * reference count is increased.
- *
- * @param name the backend name to retrieve
- * @return the backend, or ``null`` if none could be found
- *
- * @since 0.3.5
- */
- public Backend? dup_backend_by_name (string name)
- {
- return this._backend_hash.get (name);
- }
-
- /**
- * List the currently loaded backends.
- *
- * @return a list of the backends currently in the BackendStore
- */
- public Collection<Backend> list_backends ()
- {
- return this._backend_hash.values.read_only_view;
- }
-
- /**
- * Enable a backend.
- *
- * Mark a backend as enabled, such that the BackendStore will always attempt
- * to load it when {@link BackendStore.load_backends} is called. This will
- * not load the backend if it's not currently loaded.
- *
- * This method is safe to call multiple times concurrently (e.g. an
- * asynchronous call may begin after a previous asynchronous call for the same
- * backend name has begun and before it has finished).
- *
- * If the backend is disallowed by the FOLKS_BACKENDS_ALLOWED
- * and/or FOLKS_BACKENDS_DISABLED environment variables, this method
- * will store the fact that it should be enabled in future, but will
- * not enable it during this application run.
- *
- * @param name the name of the backend to enable
- * @since 0.3.2
- */
- public async void enable_backend (string name)
- {
- this._backends_key_file.set_boolean (name, "enabled", true);
- yield this._save_key_file ();
- }
-
- /**
- * Disable a backend.
- *
- * Mark a backend as disabled, such that it won't be loaded even when the
- * client application is restarted. This will not remove the backend if it's
- * already loaded.
- *
- * This method is safe to call multiple times concurrently (e.g. an
- * asynchronous call may begin after a previous asynchronous call for the same
- * backend name has begun and before it has finished).
- *
- * @param name the name of the backend to disable
- * @since 0.3.2
- */
- public async void disable_backend (string name)
- {
- this._backends_key_file.set_boolean (name, "enabled", false);
- yield this._save_key_file ();
- }
-
- /* This method is safe to call multiple times concurrently. */
- private async HashMap<string, File>? _get_modules_from_dir (File dir)
- {
- debug ("Searching for modules in folder '%s' ..", dir.get_path ());
-
- var attributes =
- FileAttribute.STANDARD_NAME + "," +
- FileAttribute.STANDARD_TYPE + "," +
- FileAttribute.STANDARD_IS_SYMLINK + "," +
- FileAttribute.STANDARD_SYMLINK_TARGET + "," +
- FileAttribute.STANDARD_CONTENT_TYPE;
-
- GLib.List<FileInfo> infos;
- try
- {
- FileEnumerator enumerator =
- yield dir.enumerate_children_async (attributes,
- FileQueryInfoFlags.NONE, Priority.DEFAULT, null);
-
- infos = yield enumerator.next_files_async (int.MAX,
- Priority.DEFAULT, null);
- }
- catch (Error error)
- {
- /* Translators: the first parameter is a folder path and the second
- * is an error message. */
- critical (_("Error listing contents of folder ‘%s’: %s"),
- dir.get_path (), error.message);
-
- return null;
- }
-
- var modules_final = new HashMap<string, File> ();
-
- string? _path = Environment.get_variable ("FOLKS_BACKEND_PATH");
- foreach (var info in infos)
- {
- var file = dir.get_child (info.get_name ());
-
- /* Handle symlinks by dereferencing them. If we look at two symlinks
- * with the same target, we don’t end up loading that backend twice
- * due to hashing the backend’s absolute path in @modules_final.
- *
- * We can’t just ignore symlinks due to the way Tinycorelinux installs
- * software: /usr/local/lib/folks/41/backends/bluez/bluez.so is a
- * symlink to
- * /tmp/tcloop/folks/usr/local/lib/folks/41/backends/bluez/bluez.so,
- * a loopback squashfs mount of the folks file system. Ignoring
- * symlinks means we would never load backends in that environment. */
- if (info.get_is_symlink ())
- {
- debug ("Handling symlink ‘%s’ to ‘%s’.",
- file.get_path (), info.get_symlink_target ());
-
- var old_file = file;
- file = dir.resolve_relative_path (info.get_symlink_target ());
-
- try
- {
- info =
- yield file.query_info_async (attributes,
- FileQueryInfoFlags.NONE);
- }
- catch (Error error)
- {
- /* Translators: the first parameter is a folder path and the second
- * is an error message. */
- warning (_("Error querying info for target ‘%s’ of symlink ‘%s’: %s"),
- file.get_path (), old_file.get_path (), error.message);
-
- continue;
- }
- }
-
- /* Handle proper files. */
- var file_type = info.get_file_type ();
- unowned string content_type = info.get_content_type ();
-
- string? mime = ContentType.get_mime_type (content_type);
-
- if (file_type == FileType.DIRECTORY)
- {
- var modules = yield this._get_modules_from_dir (file);
- if (modules != null)
- {
- foreach (var entry in ((!) modules).entries)
- {
- modules_final.set (entry.key, entry.value);
- }
- }
- }
- else if (mime == "application/x-sharedlib")
- {
- var path = file.get_path ();
- if (path != null)
- {
- modules_final.set ((!) path, file);
- }
- }
- else if (mime == null)
- {
- warning (
- "The content type of '%s' could not be determined. Have you installed shared-mime-info?",
- file.get_path ());
- }
- /*
- * We should have only .la .so and sub-directories, except if FOLKS_BACKEND_PATH is set.
- * Then we will run into all kinds of files.
- */
- else if (_path == null &&
- mime != "application/x-sharedlib" &&
- mime != "application/x-shared-library-la" &&
- mime != "inode/directory")
- {
- warning ("The content type of '%s' appears to be '%s' which looks suspicious. Have you installed shared-mime-info?", file.get_path (), mime);
- }
- }
-
- debug ("Finished searching for modules in folder '%s'",
- dir.get_path ());
-
- return modules_final;
- }
-
- private void _load_module_from_file (File file)
- {
- var _file_path = file.get_path ();
- if (_file_path == null)
- {
- return;
- }
- var file_path = (!) _file_path;
-
- if (this._modules.has_key (file_path))
- return;
-
- var _module = Module.open (file_path, ModuleFlags.BIND_LOCAL);
- if (_module == null)
- {
- warning ("Failed to load module from path '%s': %s",
- file_path, Module.error ());
-
- return;
- }
- unowned Module module = (!) _module;
-
- void* function;
-
- /* this causes the module to call add_backend() for its backends (adding
- * them to the backend hash); any backends that already existed will be
- * removed if they've since been disabled */
- if (!module.symbol("module_init", out function))
- {
- warning ("Failed to find entry point function '%s' in '%s': %s",
- "module_init",
- file_path,
- Module.error ());
-
- return;
- }
-
- ModuleInitFunc module_init = (ModuleInitFunc) function;
- assert (module_init != null);
-
- this._modules.set (file_path, module);
-
- /* We don't want our modules to ever unload */
- module.make_resident ();
-
- module_init (this);
-
- debug ("Loaded module source: '%s'", module.name ());
- }
-
- /* This method is safe to call multiple times concurrently. */
- private async static void _get_file_info (File file,
- out bool is_file,
- out bool is_dir)
- {
- FileInfo file_info;
- is_file = false;
- is_dir = false;
-
- try
- {
- /* Query for the MIME type; if the file doesn't exist, we'll get an
- * appropriate error back, so this also checks for existence. */
- file_info = yield file.query_info_async (FileAttribute.STANDARD_TYPE,
- FileQueryInfoFlags.NONE, Priority.DEFAULT, null);
- }
- catch (Error error)
- {
- if (error is IOError.NOT_FOUND)
- {
- /* Translators: the parameter is a filename. */
- critical (_("File or directory ‘%s’ does not exist."),
- file.get_path ());
- }
- else
- {
- /* Translators: the parameter is a filename. */
- critical (_("Failed to get content type for ‘%s’."),
- file.get_path ());
- }
-
- return;
- }
-
- is_file = (file_info.get_file_type () == FileType.REGULAR);
- is_dir = (file_info.get_file_type () == FileType.DIRECTORY);
- }
-
- /* This method is safe to call multiple times concurrently. */
- private async void _load_disabled_backend_names ()
- {
- /* If set, this is a list of allowed backends. No others can be enabled,
- * even if the keyfile says they ought to be.
- * The default is equivalent to "all", which allows any backend.
- *
- * Regression tests and benchmarks can use this to restrict themselves
- * to a small set of backends for which they have done the necessary
- * setup/configuration/sandboxing. */
- var envvar = Environment.get_variable ("FOLKS_BACKENDS_ALLOWED");
-
- if (envvar != null)
- {
- /* Allow space, comma or colon separation, consistent with
- * g_parse_debug_string(). */
- var tokens = envvar.split_set (" ,:");
-
- this._backends_allowed = new SmallSet<string> ();
-
- foreach (unowned string s in tokens)
- {
- if (s == "all")
- {
- this._backends_allowed = null;
- break;
- }
-
- if (s != "")
- this._backends_allowed.add (s);
- }
-
- if (this._backends_allowed != null)
- {
- debug ("Backends limited by FOLKS_BACKENDS_ALLOWED:");
-
- foreach (unowned string s in tokens)
- debug ("Backend '%s' is allowed", s);
-
- debug ("All other backends disabled by FOLKS_BACKENDS_ALLOWED");
- }
- }
-
- /* If set, this is a list of disallowed backends.
- * They are not enabled, even if the keyfile says they ought to be. */
- envvar = Environment.get_variable ("FOLKS_BACKENDS_DISABLED");
-
- if (envvar != null)
- {
- var tokens = envvar.split_set (" ,:");
-
- this._backends_disabled = new SmallSet<string> ();
-
- foreach (unowned string s in tokens)
- {
- if (s != "")
- {
- debug ("Backend '%s' disabled by FOLKS_BACKENDS_DISABLED", s);
- this._backends_disabled.add (s);
- }
- }
- }
-
- File file;
- unowned string? path = Environment.get_variable (
- "FOLKS_BACKEND_STORE_KEY_FILE_PATH");
- if (path == null)
- {
- file = File.new_for_path (Environment.get_user_data_dir ());
- file = file.get_child ("folks");
- file = file.get_child ("backends.ini");
-
- debug ("Using built-in backends key file '%s' (override with " +
- "environment variable FOLKS_BACKEND_STORE_KEY_FILE_PATH)",
- file.get_path ());
- }
- else
- {
- file = File.new_for_path ((!) path);
- debug ("Using environment variable " +
- "FOLKS_BACKEND_STORE_KEY_FILE_PATH = '%s' to load the backends " +
- "key file.", (!) path);
- }
-
- this._config_file = file;
-
- /* Load the disabled backends file */
- var key_file = new GLib.KeyFile ();
- try
- {
- uint8[] contents;
-
- yield file.load_contents_async (null, out contents, null);
- unowned string contents_s = (string) contents;
-
- if (contents_s.length > 0)
- {
- key_file.load_from_data (contents_s,
- contents_s.length, KeyFileFlags.KEEP_COMMENTS);
- }
- }
- catch (Error e1)
- {
- if (!(e1 is IOError.NOT_FOUND))
- {
- warning ("The backends key file '%s' could not be loaded: %s",
- file.get_path (), e1.message);
- return;
- }
- }
- finally
- {
- /* Update the key file in memory, whether the new one is empty or
- * full. */
- this._backends_key_file = (owned) key_file;
- }
- }
-
- /* This method is safe to call multiple times concurrently. */
- private async void _save_key_file ()
- {
- var key_file_data = this._backends_key_file.to_data ();
-
- debug ("Saving backend key file '%s'.", this._config_file.get_path ());
-
- try
- {
- /* Note: We have to use key_file_data.size () here to get its length
- * in _bytes_ rather than _characters_. bgo#628930.
- * In Vala >= 0.11, string.size() has been deprecated in favour of
- * string.length (which now returns the byte length, whereas in
- * Vala <= 0.10, it returned the character length). FIXME: We need to
- * take this into account until we depend explicitly on
- * Vala >= 0.11. */
- yield this._config_file.replace_contents_async (key_file_data.data,
- null, false, FileCreateFlags.PRIVATE,
- null, null);
- }
- catch (Error e)
- {
- warning ("Could not write updated backend key file '%s': %s",
- this._config_file.get_path (), e.message);
- }
- }
-}
diff --git a/folks/backend.vala b/folks/backend.vala
deleted file mode 100644
index dcfc4e60..00000000
--- a/folks/backend.vala
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-
-/**
- * A single backend to libfolks, such as Telepathy or evolution-data-server.
- * Each backend provides {@link Persona}s which are aggregated to form
- * {@link Individual}s.
- *
- * After creating a Backend instance, you must connect to the
- * {@link Backend.persona_store_added} and
- * {@link Backend.persona_store_removed} signals, //then// call
- * {@link Backend.prepare}, otherwise a race condition may occur between
- * emission of {@link Backend.persona_store_added} and your code connecting to
- * it.
- */
-public abstract class Folks.Backend : Object
-{
- construct
- {
- debug ("Constructing Backend ‘%s’ (%p)", this.name, this);
- }
-
- ~Backend ()
- {
- debug ("Destroying Backend ‘%s’ (%p)", this.name, this);
- }
-
- /**
- * Whether {@link Backend.prepare} has successfully completed for this
- * backend.
- *
- * @since 0.3.0
- */
- public abstract bool is_prepared { get; default = false; }
-
- /**
- * Whether the backend has reached a quiescent state. This will happen at some
- * point after {@link Backend.prepare} has successfully completed for the
- * backend. A backend is in a quiescent state when all the
- * {@link PersonaStore}s that it originally knows about have been loaded.
- *
- * It's guaranteed that this property's value will only ever change after
- * {@link Backend.is_prepared} has changed to ``true``.
- *
- * When {@link Backend.unprepare} is called, this will be reset to ``false``.
- *
- * @since 0.6.2
- */
- public abstract bool is_quiescent { get; default = false; }
-
- /**
- * A unique name for the backend.
- *
- * This will be used to identify the backend, and should also be used as the
- * {@link PersonaStore.type_id} of the {@link PersonaStore}s used by the
- * backend.
- *
- * This is guaranteed to always be available; even before
- * {@link Backend.prepare} is called.
- */
- public abstract string name { get; }
-
- /**
- * The {@link PersonaStore}s in use by the backend.
- *
- * A backend may expose {@link Persona}s from multiple servers or accounts
- * (for example), so may have a {@link PersonaStore} for each.
- *
- * @since 0.5.1
- */
- public abstract Map<string, PersonaStore> persona_stores { get; }
-
- /**
- * Disable a {@link PersonaStore}.
- *
- * If the given persona store is in this backend {@link Backend.persona_stores},
- * it will be removed, and we will disconnect from its signals.
- *
- * @param store the {@link PersonaStore} to disable.
- *
- * @since 0.9.0
- */
- public abstract void disable_persona_store (PersonaStore store);
-
- /**
- * Enable a {@link PersonaStore}.
- *
- * If the given persona store is not already in this backend
- * {@link Backend.persona_stores}, it will be added to the backend and
- * {@link Backend.persona_stores} property notification will be emitted,
- * along with {@link Backend.persona_store_added}.
- *
- * @param store the {@link PersonaStore} to enable.
- *
- * @since 0.9.0
- */
- public abstract void enable_persona_store (PersonaStore store);
-
- /**
- * Set the {@link PersonaStore}s to use in this backend.
- *
- * This will cause {@link Backend.persona_store_removed} signals to be emitted
- * for all removed stores, followed by {@link Backend.persona_store_added}
- * signals for all added stores. As these signals are emitted, the sets of
- * individuals in any associated {@link IndividualAggregator}s will be
- * updated, and {@link IndividualAggregator.individuals_changed} may be
- * emitted multiple times as appropriate. A property change notification for
- * {@link Backend.persona_stores} will be emitted last.
- * Note: pass null storeids to use all available persona stores.
- *
- * @param storeids a Set of {@link PersonaStore} IDs to use.
- *
- * @since 0.9.0
- */
- public abstract void set_persona_stores (Set<string>? storeids);
-
- /**
- * Emitted when a {@link PersonaStore} is added to the backend.
- *
- * This will not be emitted until after {@link Backend.prepare} has been
- * called.
- *
- * @param store the {@link PersonaStore}
- */
- public abstract signal void persona_store_added (PersonaStore store);
-
- /**
- * Emitted when a {@link PersonaStore} is removed from the backend.
- *
- * This will not be emitted until after {@link Backend.prepare} has been
- * called.
- *
- * @param store the {@link PersonaStore}
- */
- public abstract signal void persona_store_removed (PersonaStore store);
-
- /**
- * Prepare the Backend for use.
- *
- * This connects the Backend to whichever backend-specific services it
- * requires, and causes it to create its {@link PersonaStore}s. This should be
- * called //after// connecting to the {@link Backend.persona_store_added} and
- * {@link Backend.persona_store_removed} signals, or a race condition could
- * occur, with the signals being emitted before your code has connected to
- * them, and {@link PersonaStore}s getting "lost" as a result.
- *
- * This is normally handled transparently by the {@link IndividualAggregator}.
- *
- * If this function throws an error, the Backend will not be functional.
- *
- * This function is guaranteed to be idempotent (since version 0.3.0).
- *
- * Concurrent calls to this function from different threads will block until
- * preparation has completed. However, concurrent calls to this function from
- * a single thread might not, i.e. the first call will block but subsequent
- * calls might return before the first one. (Though they will be safe in every
- * other respect.)
- *
- * @since 0.1.11
- * @throws GLib.Error if preparing the backend-specific services failed — this
- * will be a backend-specific error
- * @throws GLib.DBusError.SERVICE_UNKNOWN if a required D-Bus service was not
- * installed or could not be started
- */
- public abstract async void prepare () throws GLib.Error;
-
- /**
- * Revert the Backend to its pre-prepared state.
- *
- * This will disconnect this Backend and its dependencies from their
- * respective services and the Backend will issue
- * {@link Backend.persona_store_removed} for each of its
- * {@link PersonaStore}s.
- *
- * Most users won't need to use this function.
- *
- * If this function throws an error, the Backend will not be functional.
- *
- * Concurrent calls to this function from different threads will block until
- * preparation has completed. However, concurrent calls to this function from
- * a single thread might not, i.e. the first call will block but subsequent
- * calls might return before the first one. (Though they will be safe in every
- * other respect.)
- *
- * @since 0.3.2
- * @throws GLib.Error if unpreparing the backend-specific services failed —
- * this will be a backend-specific error
- */
- public abstract async void unprepare () throws GLib.Error;
-}
diff --git a/folks/birthday-details.vala b/folks/birthday-details.vala
deleted file mode 100644
index ba5f207a..00000000
--- a/folks/birthday-details.vala
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- * Copyright (C) 2011 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using GLib;
-
-/**
- * Birthday details for a contact.
- *
- * This allows representation of the birth date and associated calendar event ID
- * of a contact.
- *
- * @since 0.4.0
- */
-public interface Folks.BirthdayDetails : Object
-{
- /**
- * The birthday of the {@link Persona} and {@link Individual}. This
- * is assumed to be in UTC.
- *
- * If this is ``null``, the contact's birthday isn't known.
- *
- * @since 0.4.0
- */
- public abstract DateTime? birthday { get; set; }
-
- /**
- * Change the contact's birthday.
- *
- * It's preferred to call this rather than setting
- * {@link BirthdayDetails.birthday} directly, as this method gives error
- * notification and will only return once the birthday has been written to the
- * relevant backing store (or the operation's failed).
- *
- * @param birthday the new birthday (or ``null`` to unset the birthday)
- * @throws PropertyError if setting the birthday failed
- * @since 0.6.2
- */
- public virtual async void change_birthday (DateTime? birthday)
- throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Birthday is not writeable on this contact."));
- }
-
- /**
- * The event ID of the birthday event from the source calendar.
- *
- * If this is ``null``, the birthday event is unknown. The semantics of the
- * event ID are left unspecified by folks.
- *
- * @since 0.4.0
- */
- public abstract string? calendar_event_id { get; set; }
-
- /**
- * Change the contact's birthday event ID.
- *
- * It's preferred to call this rather than setting
- * {@link BirthdayDetails.calendar_event_id} directly, as this method gives
- * error notification and will only return once the event has been written to
- * the relevant backing store (or the operation's failed).
- *
- * @param event_id the new birthday event ID (or ``null`` to unset the event
- * ID)
- * @throws PropertyError if setting the birthday event ID failed
- * @since 0.6.2
- */
- public virtual async void change_calendar_event_id (string? event_id)
- throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Birthday event ID is not writeable on this contact."));
- }
-}
diff --git a/folks/build-conf.vapi b/folks/build-conf.vapi
deleted file mode 100644
index 36853a2a..00000000
--- a/folks/build-conf.vapi
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2008 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>.
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
- * Travis Reitter <travis.reitter@collabora.co.uk>
- *
- * This file was originally part of Rygel.
- */
-
-[CCode (cheader_filename = "config.h")]
-public class Folks.BuildConf
-{
- [CCode (cname = "BACKEND_DIR")]
- public const string BACKEND_DIR;
-
- [CCode (cname = "PACKAGE_NAME")]
- public const string PACKAGE_NAME;
-
- [CCode (cname = "PACKAGE_VERSION")]
- public const string PACKAGE_VERSION;
-
- [CCode (cname = "PACKAGE_STRING")]
- public const string PACKAGE_STRING;
-
- [CCode (cname = "PACKAGE_DATADIR")]
- public const string PACKAGE_DATADIR;
-
- [CCode (cname = "GETTEXT_PACKAGE")]
- public const string GETTEXT_PACKAGE;
-
- [CCode (cname = "LOCALE_DIR")]
- public const string LOCALE_DIR;
-
- [CCode (cname = "HAVE_EDS")]
- public static bool HAVE_EDS;
-
- [CCode (cname = "EDS_SOURCES_SERVICE_NAME")]
- public const string EDS_SOURCES_SERVICE_NAME;
-
- [CCode (cname = "EDS_ADDRESS_BOOK_SERVICE_NAME")]
- public const string EDS_ADDRESS_BOOK_SERVICE_NAME;
-
- [CCode (cname = "HAVE_OFONO")]
- public static bool HAVE_OFONO;
-
- [CCode (cname = "HAVE_BLUEZ")]
- public static bool HAVE_BLUEZ;
-
- [CCode (cname = "HAVE_TELEPATHY")]
- public static bool HAVE_TELEPATHY;
-
- [CCode (cname = "ABS_TOP_BUILDDIR")]
- public const string ABS_TOP_BUILDDIR;
-
- [CCode (cname = "ABS_TOP_SRCDIR")]
- public const string ABS_TOP_SRCDIR;
-
- [CCode (cname = "PKGLIBEXECDIR")]
- public const string PKGLIBEXECDIR;
-
- [CCode (cname = "INSTALLED_TESTS_DIR")]
- public const string INSTALLED_TESTS_DIR;
-
- [CCode (cname = "INSTALLED_TESTS_META_DIR")]
- public const string INSTALLED_TESTS_META_DIR;
-}
diff --git a/folks/debug.vala b/folks/debug.vala
deleted file mode 100644
index 4d1bb02d..00000000
--- a/folks/debug.vala
+++ /dev/null
@@ -1,483 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-
-/* We have to declare our own wrapping of g_log so that it doesn't have the
- * [Diagnostics] attribute, which would cause valac to add the Vala source file
- * name to the message format, which we don't want. This is used in
- * Debug.print_line(). */
-[PrintfFormat]
-private extern void g_log (string? log_domain,
- LogLevelFlags log_level,
- string format,
- ...);
-
-/**
- * Manages debug output and status reporting for all folks objects.
- *
- * All GLib debug logging calls are passed through a log handler in this class,
- * which allows debug domains to be outputted according to whether they've been
- * enabled by being passed to {@link Debug.dup}.
- *
- * @since 0.5.1
- */
-public class Folks.Debug : Object
-{
- private enum Domains {
- /* Zero is used for "no debug spew" */
- CORE = 1 << 0,
- TELEPATHY_BACKEND = 1 << 1,
- KEY_FILE_BACKEND = 1 << 2
- }
-
- /* Needs to be locked when accessed: */
- private static weak Debug? _instance = null;
- private HashSet<string> _domains; /* needs to be locked when accessed */
- private bool _all = false; /* needs _domains to be locked when accessed */
-
- /* The current indentation level, in spaces */
- private uint _indentation = 0;
- private string _indentation_string = "";
-
- private bool _colour_enabled = true;
- private HashSet<string> _domains_handled;
-
- /*
- * Whether colour output is enabled. If true, debug output may include
- * terminal colour escape codes. Disabled by the environment variable
- * FOLKS_DEBUG_NO_COLOUR being set to anything except “0”.
- *
- * This property is thread-safe.
- *
- * @since 0.5.1
- */
- public bool colour_enabled
- {
- get
- {
- return this._colour_enabled;
- }
-
- set
- {
- this._colour_enabled = value;
- }
- }
-
- private bool _debug_output_enabled = false;
-
- /**
- * Whether debug output is enabled. This is orthogonal to the set of enabled
- * debug domains; filtering of debug output as a whole is done after filtering
- * by enabled domains.
- *
- * @since 0.5.1
- */
- public bool debug_output_enabled
- {
- get
- {
- return this._debug_output_enabled;
- }
-
- set
- {
- this._debug_output_enabled = value;
- }
- }
-
- /**
- * Signal emitted in the main thread whenever objects should print their
- * current status. All significant objects in the library should connect
- * to this and print their current status in some suitable format when it's
- * emitted.
- *
- * Client processes should emit this signal by calling
- * {@link Debug.emit_print_status}.
- *
- * @since 0.5.1
- */
- public signal void print_status ();
-
- /**
- * Log domain for the status messages logged as a result of calling
- * {@link Debug.emit_print_status}.
- *
- * This could be used in conjunction with a log handler to redirect the
- * status information to a debug window or log file, for example.
- *
- * @since 0.5.1
- */
- public const string STATUS_LOG_DOMAIN = "folks-status";
-
- private void _print_status_log_handler_cb (string? log_domain,
- LogLevelFlags log_levels,
- string message)
- {
- /* Print directly to stdout without any adornments */
- GLib.stdout.printf ("%s\n", message);
- }
-
- private void _log_handler_cb (string? log_domain,
- LogLevelFlags log_levels,
- string message)
- {
- if (this.debug_output_enabled == false)
- {
- /* Don't output anything if debug output is disabled, even for
- * enabled debug domains. */
- return;
- }
-
- /* Otherwise, pass through to the default log handler */
- Log.default_handler (log_domain, log_levels, message);
- }
-
- /* turn off debug output for the given domain unless it was in the
- * G_MESSAGES_DEBUG environment variable (or 'all' was set) */
- internal void _register_domain (string domain)
- {
- if (this._all || this._domains.contains (domain.down ()))
- {
- this._set_handler (domain, LogLevelFlags.LEVEL_MASK,
- this._log_handler_cb);
- return;
- }
-
- /* Install a log handler which will blackhole the log message.
- * Other log messages will be printed out by the default log handler. */
- this._set_handler (domain, LogLevelFlags.LEVEL_DEBUG,
- (domain_arg, flags, message) => {});
- }
-
- /**
- * Create or return the singleton {@link Folks.Debug} class instance.
- * If the instance doesn't exist already, it will be created with no debug
- * domains enabled.
- *
- * This function is thread-safe.
- *
- * @return Singleton {@link Folks.Debug} instance
- * @since 0.5.1
- */
- public static Debug dup ()
- {
- Debug? _retval = Debug._instance;
- Debug retval;
-
- if (_retval == null)
- {
- /* use an intermediate variable to force a strong reference */
- retval = new Debug ();
- Debug._instance = retval;
- }
- else
- {
- retval = (!) _retval;
- }
-
- return retval;
- }
-
- /**
- * Create or return the singleton {@link Folks.Debug} class instance.
- * If the instance doesn't exist already, it will be created with the given
- * set of debug domains enabled. Otherwise, the existing instance will have
- * its set of enabled domains changed to the provided set.
- *
- * @param debug_flags A comma-separated list of debug domains to enable, or
- * null to disable debug output
- * @param colour_enabled Whether debug output should be coloured using
- * terminal escape sequences
- * @return Singleton {@link Folks.Debug} instance
- * @since 0.5.1
- */
- public static Debug dup_with_flags (string? debug_flags,
- bool colour_enabled)
- {
- var retval = Debug.dup ();
-
- retval._all = false;
- retval._domains = new HashSet<string> ();
-
- if (debug_flags != null && debug_flags != "")
- {
- var domains_split = ((!) debug_flags).split (",");
- foreach (var domain in domains_split)
- {
- var domain_lower = domain.down ();
-
- if (GLib.strcmp (domain_lower, "all") == 0)
- retval._all = true;
- else
- retval._domains.add (domain_lower);
- }
- }
-
- retval.debug_output_enabled = (retval._all || !retval._domains.is_empty);
- retval.colour_enabled = colour_enabled;
-
- return retval;
- }
-
- private Debug ()
- {
- /* Private constructor for singleton */
- Object ();
- }
-
- construct
- {
- this._domains_handled = new HashSet<string> ();
-
- /* Install a log handler for log messages emitted as a result of
- * Debug.print-status being emitted. */
- this._set_handler (Debug.STATUS_LOG_DOMAIN, LogLevelFlags.LEVEL_MASK,
- this._print_status_log_handler_cb);
- }
-
- ~Debug ()
- {
- /* Remove handlers so they don't get called after we're destroyed */
- foreach (var domain in this._domains_handled)
- this._remove_handler (domain, true);
- this._domains_handled.clear ();
-
- /* Manually clear the singleton _instance */
- Debug._instance = null;
- }
-
- private void _set_handler (
- string domain,
- LogLevelFlags flags,
- LogFunc log_func)
- {
- this._remove_handler (domain);
- Log.set_handler (domain, flags, log_func);
- this._domains_handled.add (domain);
- }
-
- private void _remove_handler (string domain, bool keep_in_map = false)
- {
- if (this._domains_handled.contains (domain))
- {
- Log.set_handler (domain,
- (LogLevelFlags.LEVEL_MASK | LogLevelFlags.FLAG_RECURSION |
- LogLevelFlags.FLAG_FATAL),
- Log.default_handler);
-
- if (!keep_in_map)
- this._domains_handled.remove (domain);
- }
- }
-
- /**
- * Causes all significant objects in the library to print their current
- * status to standard output, obeying the options set on this
- * {@link Folks.Debug} instance for colouring and other formatting.
- *
- * @since 0.5.1
- */
- public void emit_print_status ()
- {
- print ("Dumping status information…\n");
- this.print_status ();
- }
-
- /**
- * Increment the indentation level used when printing output through the
- * object.
- *
- * This is intended to be used by backend libraries only.
- *
- * @since 0.5.1
- */
- public void indent ()
- {
- /* We indent in increments of two spaces */
- this._indentation++;
- this._indentation_string = string.nfill (this._indentation * 2, ' ');
- }
-
- /**
- * Decrement the indentation level used when printing output through the
- * object.
- *
- * This is intended to be used by backend libraries only.
- *
- * @since 0.5.1
- */
- public void unindent ()
- {
- this._indentation--;
- this._indentation_string = string.nfill (this._indentation * 2, ' ');
- }
-
- /**
- * Print a debug line with the current indentation level for the specified
- * debug domain.
- *
- * This is intended to be used by backend libraries only.
- *
- * @param domain The debug domain name
- * @param level A set of log level flags for the message
- * @param format A printf-style format string for the heading
- * @param ... Arguments for the format string
- * @since 0.5.1
- */
- [PrintfFormat ()]
- public void print_line (string domain,
- LogLevelFlags level,
- string format,
- ...)
- {
- /* FIXME: store the va_list temporarily to work around bgo#638308 */
- var valist = va_list ();
- string output = format.vprintf (valist);
- g_log (domain, level, "%s%s", this._indentation_string, output);
- }
-
- /**
- * Print a debug line as a heading. It will be coloured according to the
- * current indentation level so that different levels of headings stand out.
- *
- * This is intended to be used by backend libraries only.
- *
- * @param domain The debug domain name
- * @param level A set of log level flags for the message
- * @param format A printf-style format string for the heading
- * @param ... Arguments for the format string
- * @since 0.5.1
- */
- [PrintfFormat ()]
- public void print_heading (string domain,
- LogLevelFlags level,
- string format,
- ...)
- {
- /* Colour the heading according to the current indentation level.
- * ANSI terminal colour codes. */
- const int[] heading_colours =
- {
- 31, /* red */
- 32, /* green */
- 34 /* blue */
- };
-
- var wrapper_format = "%s";
- if (this.colour_enabled == true)
- {
- var indentation =
- this._indentation.clamp (0, heading_colours.length - 1);
- wrapper_format =
- "\033[1;%im%%s\033[0m".printf (heading_colours[indentation]);
- }
-
- /* FIXME: store the va_list temporarily to work around bgo#638308 */
- var valist = va_list ();
- string output = format.vprintf (valist);
- this.print_line (domain, level, wrapper_format, output);
- }
-
- /*
- * Format a potentially null string for printing; if the string is null,
- * “(null)” will be outputted. If coloured output is enabled, this output
- * will be coloured brown. */
- private string _format_nullable_string (string? input)
- {
- if (this.colour_enabled == true && input == null)
- {
- return "\033[1;36m(null)\033[0m"; /* cyan */
- }
- else if (input == null)
- {
- return "(null)";
- }
-
- return (!) input;
- }
-
- struct KeyValuePair
- {
- string key;
- string? val;
- }
-
- /**
- * Print a set of key–value pairs in a table. The width of the key column is
- * automatically set to the width of the longest key. The keys and values
- * must be provided as a null-delimited list of alternating key–value varargs.
- * Values may be null but keys may not.
- *
- * This is intended to be used by backend libraries only.
- *
- * The table will be printed at the current indentation level plus one.
- *
- * @param domain The debug domain name
- * @param level A set of log level flags for the message
- * @param ... Alternating keys and values, terminated with null
- * @since 0.5.1
- */
- public void print_key_value_pairs (string domain,
- LogLevelFlags level,
- ...)
- {
- var valist = va_list ();
- KeyValuePair[] lines = {};
- uint max_key_length = 0;
-
- /* Read in the arguments and calculate the longest key for alignment
- * purposes */
- while (true)
- {
- string? _key = valist.arg ();
- if (_key == null)
- {
- break;
- }
- var key = (!) _key;
-
- string? val = valist.arg ();
-
- /* Keep track of the longest key we've seen */
- max_key_length = uint.max (key.length, max_key_length);
-
- lines += KeyValuePair ()
- {
- key = key,
- val = val
- };
- }
-
- this.indent ();
-
- /* Print out the lines */
- foreach (var line in lines)
- {
- var padding = string.nfill (max_key_length - line.key.length, ' ');
- this.print_line (domain, level, "%s: %s%s", line.key, padding,
- this._format_nullable_string (line.val));
- }
-
- this.unindent ();
- }
-}
diff --git a/folks/email-details.vala b/folks/email-details.vala
deleted file mode 100644
index 56e2bac3..00000000
--- a/folks/email-details.vala
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- * Copyright (C) 2011 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Marco Barisione <marco.barisione@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using GLib;
-using Gee;
-
-/**
- * Object representing a email address that can have some parameters
- * associated with it.
- *
- * See {@link Folks.AbstractFieldDetails} for details on common parameter names
- * and values.
- *
- * @since 0.6.0
- */
-public class Folks.EmailFieldDetails : AbstractFieldDetails<string>
-{
- /**
- * Create a new EmailFieldDetails.
- *
- * @param value the value of the field, which should be a valid, non-empty
- * e-mail address
- * @param parameters initial parameters. See
- * {@link AbstractFieldDetails.parameters}. A ``null`` value is equivalent to
- * an empty map of parameters.
- *
- * @return a new EmailFieldDetails
- *
- * @since 0.6.0
- */
- public EmailFieldDetails (string value,
- MultiMap<string, string>? parameters = null)
- {
- if (value == "")
- {
- warning ("Empty e-mail address passed to EmailFieldDetails.");
- }
-
- this.value = value;
- if (parameters != null)
- this.parameters = (!) parameters;
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override bool equal (AbstractFieldDetails<string> that)
- {
- return base.equal (that);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override uint hash ()
- {
- return base.hash ();
- }
-}
-
-/**
- * Interface for classes that have email addresses, such as {@link Persona}
- * and {@link Individual}.
- *
- * @since 0.3.5
- */
-public interface Folks.EmailDetails : Object
-{
- /**
- * The email addresses of the contact.
- *
- * Each of the values in this property contains just an e-mail address (e.g.
- * “foo@bar.com”), rather than any other way of formatting an e-mail address
- * (such as “John Smith &lt;foo@bar.com&gt;”).
- *
- * @since 0.6.0
- */
- public abstract Set<EmailFieldDetails> email_addresses { get; set; }
-
- /**
- * Change the contact's set of e-mail addresses.
- *
- * It's preferred to call this rather than setting
- * {@link EmailDetails.email_addresses} directly, as this method gives error
- * notification and will only return once the e-mail addresses have been
- * written to the relevant backing store (or the operation's failed).
- *
- * @param email_addresses the new set of e-mail addresses
- * @throws PropertyError if setting the e-mail addresses failed
- * @since 0.6.2
- */
- public virtual async void change_email_addresses (
- Set<EmailFieldDetails> email_addresses) throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("E-mail addresses are not writeable on this contact."));
- }
-}
diff --git a/folks/extended-info.vala b/folks/extended-info.vala
deleted file mode 100644
index b170aa06..00000000
--- a/folks/extended-info.vala
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2013, 2015 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Rodrigo Moya <rodrigo@gnome.org>
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-
-/**
- * Object representing an arbitrary field that can have some parameters
- * associated with it. This is intended to be as general-purpose as, for
- * example, a vCard property. See the documentation for
- * {@link Folks.ExtendedInfo} for information on when using this object is
- * appropriate.
- *
- * See {@link Folks.AbstractFieldDetails} for details on common parameter names
- * and values.
- *
- * @since 0.11.0
- */
-public class Folks.ExtendedFieldDetails : AbstractFieldDetails<string>
-{
- /**
- * Create a new ExtendedFieldDetails.
- *
- * @param value the value of the field, which may be the empty string
- * @param parameters initial parameters. See
- * {@link AbstractFieldDetails.parameters}. A ``null`` value is equivalent to
- * an empty map of parameters.
- *
- * @return a new ExtendedFieldDetails
- *
- * @since 0.11.0
- */
- public ExtendedFieldDetails (string value,
- MultiMap<string, string>? parameters = null)
- {
- this.value = value;
- if (parameters != null)
- this.parameters = (!) parameters;
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.11.0
- */
- public override bool equal (AbstractFieldDetails<string> that)
- {
- return base.equal (that);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.11.0
- */
- public override uint hash ()
- {
- return base.hash ();
- }
-}
-
-/**
- * Arbitrary field interface.
- *
- * This interface allows clients to store arbitrary fields for contacts in
- * backends that support it.
- *
- * This interface should be used for application-specific data, in which case
- * the application should use the vCard approach to prefixing non-standard
- * property names: `X-[APPLICATION NAME]-*’. Note that this is a global
- * namespace, shared between all consumers of the backend’s data, so please
- * namespace application-specific data with the application’s name.
- *
- * This interface should not be used for more general-purpose data which could
- * be better represented with a type-safe interface implemented in libfolks.
- * It must not be used for data which is already represented with a type-safe
- * interface in libfolks.
- *
- * A good example of data which could be stored on this interface is an e-mail
- * application’s setting of whether a content prefers to receive HTML or
- * plaintext e-mail.
- *
- * A good example of data which should not be stored on this interface is a
- * contact’s anniversary. That should be added in a separate interface in
- * libfolks.
- *
- * @since 0.11.0
- */
-public interface Folks.ExtendedInfo : Object
-{
- /**
- * Retrieve the value for an arbitrary field.
- *
- * @return The value of the extended field, which may be empty, or `null` if
- * the field is not set
- *
- * @since 0.11.0
- */
- public abstract ExtendedFieldDetails? get_extended_field (string name);
-
- /**
- * Change the value of an arbitrary field.
- *
- * @param name name of the arbitrary field to change value
- * @param value new value for the arbitrary field
- * @throws PropertyError if setting the value failed
- *
- * @since 0.11.0
- */
- public virtual async void change_extended_field (
- string name, ExtendedFieldDetails value) throws PropertyError
- {
- /* Default implementation */
- throw new PropertyError.NOT_WRITEABLE (
- _("Extended fields are not writeable on this contact."));
- }
-
- /**
- * Remove an arbitrary field.
- *
- * @param name name of the arbitrary field to remove
- * @throws PropertyError if removing the property failed
- *
- * @since 0.11.0
- */
- public virtual async void remove_extended_field (string name)
- throws PropertyError
- {
- /* Default implementation */
- throw new PropertyError.NOT_WRITEABLE (
- _("Extended fields are not writeable on this contact."));
- }
-}
diff --git a/folks/favourite-details.vala b/folks/favourite-details.vala
deleted file mode 100644
index 0d76b6c4..00000000
--- a/folks/favourite-details.vala
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- * Copyright (C) 2011 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-
-/**
- * Favourite status for a contact.
- *
- * This allows user-defined favourite contacts to be specified. A contact is a
- * favourite if the user has selected them as such; the semantics of 'favourite'
- * are left unspecified by folks. Typically, a user might select the contacts
- * that they talk to most frequently as their favourite contacts in an instant
- * messaging program, for example.
- */
-public interface Folks.FavouriteDetails : Object
-{
- /**
- * Whether this contact is a user-defined favourite.
- */
- public abstract bool is_favourite { get; set; }
-
- /**
- * Change whether the contact is a user-defined favourite.
- *
- * It's preferred to call this rather than setting
- * {@link FavouriteDetails.is_favourite} directly, as this method gives error
- * notification and will only return once the favouriteness has been written
- * to the relevant backing store (or the operation's failed).
- *
- * @param is_favourite ``true`` if the contact is a favourite; ``false``
- * otherwise
- * @throws PropertyError if setting the favouriteness failed
- * @since 0.6.2
- */
- public virtual async void change_is_favourite (bool is_favourite)
- throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Favorite status is not writeable on this contact."));
- }
-}
diff --git a/folks/folks-generics.vapi b/folks/folks-generics.vapi
deleted file mode 100644
index c8a06e96..00000000
--- a/folks/folks-generics.vapi
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * generics.vapi - generic Gee interfaces implemented in C
- *
- * Copyright © 2013 Intel Corporation
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301 USA
- *
- * Authors:
- * Simon McVittie <simon.mcvittie@collabora.co.uk>
- */
-
-/* Unfortunately, we have to do this as a .vapi because going from C to
- * GIR to Vala loses the generic types. FIXME: GNOME #639908 would
- * make it possible to go via GIR like tests/lib/telepathy/contactlist does. */
-
-[CCode (gir_namespace = "Folks", gir_version = "0.7")]
-namespace Folks
-{
- [CCode (cheader_filename = "folks/small-set.h")]
- internal class SmallSet<G> : Gee.AbstractSet<G>
- {
- internal static SmallSet<G> empty<G> ();
-
- internal SmallSet (owned Gee.HashDataFunc<G>? item_hash = null,
- owned Gee.EqualDataFunc<G>? item_equals = null);
-
- internal static SmallSet<G> copy<G> (Gee.Iterable<G> iterable,
- owned Gee.HashDataFunc<G>? item_hash = null,
- owned Gee.EqualDataFunc<G>? item_equals = null);
-
-#if FOLKS_COMPILATION
- [CCode (cheader_filename = "folks/small-set-internal.h")]
- public unowned G @get (int i);
-#endif
- }
-}
-
-/* vim:set ft=vala: */
diff --git a/folks/folks-manager.c b/folks/folks-manager.c
new file mode 100644
index 00000000..71131fef
--- /dev/null
+++ b/folks/folks-manager.c
@@ -0,0 +1,244 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Copyright 2023 Collabora, Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <libtracker-sparql/tracker-sparql.h>
+
+#include "folks-persona-store-priv.h"
+#include "folks-manager.h"
+
+struct _FolksManager
+{
+ GObject parent_instance;
+
+ GHashTable *tracker_connections;
+ GPtrArray *persona_stores;
+};
+
+static void folks_manager_initable_iface_init (GInitableIface *iface);
+static void folks_manager_initable_async_iface_init (GAsyncInitableIface *iface);
+static void folks_manager_list_model_iface_init (GListModelInterface *iface);
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (FolksManager, folks_manager, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+ folks_manager_initable_iface_init)
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
+ folks_manager_initable_async_iface_init)
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
+ folks_manager_list_model_iface_init))
+
+enum {
+ PROP_0,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+FolksManager *
+folks_manager_new_sync (GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return g_initable_new (FOLKS_TYPE_MANAGER, cancellable, error, NULL);
+}
+
+void
+folks_manager_new (int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ return g_async_initable_new_async (FOLKS_TYPE_MANAGER, io_priority, cancellable, callback, user_data, NULL);
+}
+
+FolksManager *
+folks_manager_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *object;
+ GObject *source_object;
+
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ source_object = g_async_result_get_source_object (res);
+ g_assert (source_object != NULL);
+ object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
+ res,
+ error);
+ g_object_unref (source_object);
+ if (object != NULL)
+ return FOLKS_MANAGER (object);
+ else
+ return NULL;
+}
+
+static void
+folks_manager_finalize (GObject *object)
+{
+ FolksManager *self = (FolksManager *)object;
+
+ g_clear_pointer (&self->persona_stores, g_ptr_array_unref);
+ g_clear_pointer (&self->tracker_connections, g_hash_table_unref);
+
+ G_OBJECT_CLASS (folks_manager_parent_class)->finalize (object);
+}
+
+static void
+folks_manager_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ FolksManager *self = FOLKS_MANAGER (object);
+
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+folks_manager_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ FolksManager *self = FOLKS_MANAGER (object);
+
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+folks_manager_class_init (FolksManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = folks_manager_finalize;
+ object_class->get_property = folks_manager_get_property;
+ object_class->set_property = folks_manager_set_property;
+}
+
+static gboolean
+folks_manager_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ FolksManager *self = (FolksManager *)initable;
+ g_autoptr(TrackerSparqlConnection) connection = NULL;
+ g_autoptr(TrackerSparqlCursor) cursor = NULL;
+
+ connection = tracker_sparql_connection_bus_new ("org.freedesktop.Tracker3.Miner.Folks", NULL, NULL, error);
+ if (!connection)
+ return FALSE;
+
+ cursor = tracker_sparql_connection_query (connection,
+ "SELECT ?addressbook ?addressbookName WHERE { ?addressbook a nco:ContactList . "
+ "OPTIONAL { "
+ "?addressbook nie:title ?addressbookName ."
+ "} }",
+ cancellable,
+ error);
+ if (!cursor) {
+ tracker_sparql_connection_close (connection);
+ return FALSE;
+ }
+
+ g_hash_table_insert (self->tracker_connections, "org.freedesktop.Tracker3.Miner.Folks", g_object_ref (connection));
+
+ while (tracker_sparql_cursor_next (cursor, cancellable, error)) {
+ g_autoptr(FolksPersonaStore) persona_store = NULL;
+ const char *addressbook_urn;
+ const char *title;
+
+ addressbook_urn = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+ title = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+ persona_store = folks_persona_store_new (connection, addressbook_urn, title);
+ g_ptr_array_add (self->persona_stores, g_steal_pointer (&persona_store));
+ //g_list_model_items_changed (G_LIST_MODEL (self), self->persona_stores->len - 1, 0, 1);
+ }
+
+ tracker_sparql_cursor_close (cursor);
+ if (error && *error != NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+static GType
+folks_manager_get_item_type (GListModel *list)
+{
+ return FOLKS_TYPE_PERSONA_STORE;
+}
+
+static guint
+folks_manager_get_n_items (GListModel *list)
+{
+ FolksManager *self = FOLKS_MANAGER (list);
+
+ g_assert (self->persona_stores);
+
+ return self->persona_stores->len;
+}
+
+static gpointer
+folks_manager_get_item (GListModel *list,
+ guint position)
+{
+ FolksManager *self = FOLKS_MANAGER (list);
+
+ g_assert (self->persona_stores);
+
+ if (position >= self->persona_stores->len)
+ return NULL;
+
+ return g_object_ref (g_ptr_array_index (self->persona_stores, position));
+}
+
+static void
+folks_manager_initable_iface_init (GInitableIface *iface)
+{
+ iface->init = folks_manager_initable_init;
+}
+
+static void
+folks_manager_initable_async_iface_init (GAsyncInitableIface *iface)
+{
+ // Calls GInitable in a thread by default
+}
+
+static void
+folks_manager_list_model_iface_init (GListModelInterface *iface)
+{
+ iface->get_item_type = folks_manager_get_item_type;
+ iface->get_n_items = folks_manager_get_n_items;
+ iface->get_item = folks_manager_get_item;
+}
+
+static void
+folks_manager_init (FolksManager *self)
+{
+ self->tracker_connections = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
+ self->persona_stores = g_ptr_array_new_with_free_func (g_object_unref);
+}
diff --git a/folks/folks-manager.h b/folks/folks-manager.h
new file mode 100644
index 00000000..82c215e5
--- /dev/null
+++ b/folks/folks-manager.h
@@ -0,0 +1,43 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Copyright 2023 Collabora, Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __FOLKS_MANAGER_H__
+#define __FOLKS_MANAGER_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define FOLKS_TYPE_MANAGER (folks_manager_get_type())
+
+G_DECLARE_FINAL_TYPE (FolksManager, folks_manager, FOLKS, MANAGER, GObject)
+
+FolksManager *folks_manager_new_sync (GCancellable *cancellable,
+ GError **error);
+void folks_manager_new (int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+FolksManager *folks_manager_new_finish (GAsyncResult *res,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __FOLKS_MANAGER_H__ */
diff --git a/folks/types.vala b/folks/folks-persona-priv.h
index fe29d4f5..f9d64e7c 100644
--- a/folks/types.vala
+++ b/folks/folks-persona-priv.h
@@ -1,5 +1,7 @@
/*
- * Copyright (C) 2010 Collabora Ltd.
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Copyright 2023 Collabora, Ltd.
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -13,38 +15,14 @@
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
*/
-using GLib;
+#ifndef __FOLKS_PERSONA_PRIV_H__
+#define __FOLKS_PERSONA_PRIV_H__
-/**
- * New general types required by Folks.
- *
- * @since 0.3.1
- */
-namespace Folks
-{
- /**
- * A 'boolean' type that has a distinct 'unset' state.
- *
- * @since 0.3.1
- */
- public enum MaybeBool
- {
- /**
- * This value is explicitly unset.
- */
- UNSET = 0,
- /**
- * False (this value was set from its default of UNSET).
- */
- FALSE = 1,
- /**
- * True (this value was set from its default of UNSET).
- */
- TRUE = 2,
- }
-}
+#include "folks/folks-persona.h"
+
+FolksPersona *folks_persona_new (TrackerSparqlConnection *connection,
+ const char *persona_urn);
+
+#endif /* __FOLKS_PERSONA_PRIV_H__ */
diff --git a/backends/telepathy/lib/tp-lowlevel.h b/folks/folks-persona-store-priv.h
index 0bc15e60..5ec57603 100644
--- a/backends/telepathy/lib/tp-lowlevel.h
+++ b/folks/folks-persona-store-priv.h
@@ -1,5 +1,7 @@
/*
- * Copyright (C) 2010 Collabora Ltd.
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Copyright 2023 Collabora, Ltd.
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -13,34 +15,21 @@
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
*/
-#ifndef FOLKS_TP_LOWLEVEL_H
-#define FOLKS_TP_LOWLEVEL_H
+#ifndef __FOLKS_PERSONA_STORE_PRIV_H__
+#define __FOLKS_PERSONA_STORE_PRIV_H__
-#include <glib.h>
-#include <glib-object.h>
-#include <gio/gio.h>
-#include <telepathy-glib/telepathy-glib.h>
+#include <libtracker-sparql/tracker-sparql.h>
-G_BEGIN_DECLS
+#include "folks/folks-persona-store.h"
-void
-folks_tp_lowlevel_connection_set_contact_alias_async (
- TpConnection *conn,
- guint handle,
- const gchar *alias,
- GAsyncReadyCallback callback,
- gpointer user_data);
+G_BEGIN_DECLS
-void
-folks_tp_lowlevel_connection_set_contact_alias_finish (
- GAsyncResult *result,
- GError **error);
+FolksPersonaStore *folks_persona_store_new (TrackerSparqlConnection *connection,
+ const char *addressbook_urn,
+ const char *title);
G_END_DECLS
-#endif /* FOLKS_TP_LOWLEVEL_H */
+#endif /* __FOLKS_PERSONA_STORE_PRIV_H__ */
diff --git a/folks/folks-persona-store.c b/folks/folks-persona-store.c
new file mode 100644
index 00000000..4bed0320
--- /dev/null
+++ b/folks/folks-persona-store.c
@@ -0,0 +1,275 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Copyright 2023 Collabora, Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gio/gio.h>
+#include <libtracker-sparql/tracker-sparql.h>
+
+#include "folks-persona-store.h"
+#include "folks-persona-store-priv.h"
+#include "folks-persona.h"
+
+struct _FolksPersonaStore
+{
+ GObject parent_instance;
+
+ GPtrArray *personas;
+ TrackerSparqlConnection *connection;
+ char *addressbook_urn;
+ char *title;
+};
+
+
+static void folks_persona_store_list_model_iface_init (GListModelInterface *iface);
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (FolksPersonaStore, folks_persona_store, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
+ folks_persona_store_list_model_iface_init))
+
+enum {
+ PROP_0,
+ PROP_TITLE,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+FolksPersonaStore *
+folks_persona_store_new (TrackerSparqlConnection *connection,
+ const char *addressbook_urn,
+ const char *title)
+{
+ FolksPersonaStore *self = g_object_new (FOLKS_TYPE_PERSONA_STORE, "title", title, NULL);
+ self->connection = g_object_ref (connection);
+ self->addressbook_urn = g_strdup (addressbook_urn);
+ return self;
+}
+
+static void
+folks_persona_store_finalize (GObject *object)
+{
+ FolksPersonaStore *self = (FolksPersonaStore *)object;
+
+ g_clear_object (&self->connection);
+ g_clear_pointer (&self->addressbook_urn, g_free);
+ g_clear_pointer (&self->title, g_free);
+
+ G_OBJECT_CLASS (folks_persona_store_parent_class)->finalize (object);
+}
+
+static void
+folks_persona_store_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ FolksPersonaStore *self = FOLKS_PERSONA_STORE (object);
+
+ switch (prop_id)
+ {
+ case PROP_TITLE:
+ g_value_set_string(value, self->title);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+folks_persona_store_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ FolksPersonaStore *self = FOLKS_PERSONA_STORE (object);
+
+ switch (prop_id)
+ {
+ case PROP_TITLE:
+ self->title = g_value_dup_string(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+folks_persona_store_class_init (FolksPersonaStoreClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = folks_persona_store_finalize;
+ object_class->get_property = folks_persona_store_get_property;
+ object_class->set_property = folks_persona_store_set_property;
+
+
+ properties[PROP_TITLE] =
+ g_param_spec_string ("title", NULL, NULL,
+ NULL /* default value */,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class,
+ N_PROPS,
+ properties);
+}
+
+static GType
+folks_persona_store_get_item_type (GListModel *list)
+{
+ return FOLKS_TYPE_PERSONA;
+}
+
+static guint
+folks_persona_store_get_n_items (GListModel *list)
+{
+ FolksPersonaStore *self = FOLKS_PERSONA_STORE (list);
+
+ g_assert (self->personas);
+
+ return self->personas->len;
+}
+
+static gpointer
+folks_persona_store_get_item (GListModel *list,
+ guint position)
+{
+ FolksPersonaStore *self = FOLKS_PERSONA_STORE (list);
+
+ g_assert (self->personas);
+
+ if (position >= self->personas->len)
+ return NULL;
+
+ return g_ptr_array_index (self->personas, position);
+}
+
+static void
+folks_persona_store_list_model_iface_init (GListModelInterface *iface)
+{
+ iface->get_item_type = folks_persona_store_get_item_type;
+ iface->get_n_items = folks_persona_store_get_n_items;
+ iface->get_item = folks_persona_store_get_item;
+}
+
+static void
+folks_persona_store_init (FolksPersonaStore *self)
+{
+}
+
+static void
+load_persona_store_task (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ FolksPersonaStore *self = source_object;
+ g_autoptr (GError) error = NULL;
+ g_autoptr (TrackerSparqlCursor) cursor = NULL;
+ g_autoptr (TrackerSparqlStatement) stmt = NULL;
+ const char *query = "SELECT ?contacts { <~urn> nco:containsContact ?contacts }";
+
+ g_assert (FOLKS_IS_PERSONA_STORE (self));
+
+ stmt = tracker_sparql_connection_query_statement (self->connection,
+ query,
+ NULL,
+ &error);
+
+ if (!stmt) {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Couldn't create a prepared statement: '%s'",
+ error->message);
+ return;
+ }
+
+ tracker_sparql_statement_bind_string (stmt, "urn", self->addressbook_urn);
+
+ cursor = tracker_sparql_statement_execute (stmt, NULL, &error);
+ if (!cursor) {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Couldn't execute query: '%s'",
+ error->message);
+ return;
+ }
+
+ g_critical ("----");
+ while (tracker_sparql_cursor_next (cursor, NULL, &error)) {
+ g_critical (" - %s", tracker_sparql_cursor_get_string (cursor, 0, NULL));
+ /*g_print ("Result [%d]: %s\n",
+ i++,
+ tracker_sparql_cursor_get_string (cursor, 0, NULL));*/
+ }
+
+ tracker_sparql_cursor_close (cursor);
+ g_task_return_boolean (task, TRUE);
+}
+
+/* Public */
+
+/**
+ * folks_persona_store_load:
+ * @self: a #FolksPersonaStore
+ * @cancellable: optional #GCancellable object
+ * @callback: a #GAsyncReadyCallback to call when the load ended
+ * @user_data: data to pass to the @callback
+ *
+ * Starts a connection with the persona store and retrieve the personas.
+ *
+ * When the operation is finished, @callback will be called. You can then call
+ * folks_persona_store_load_finish() to get the result of the operation.
+ */
+void
+folks_persona_store_load (FolksPersonaStore *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (FOLKS_IS_PERSONA_STORE (self));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, folks_persona_store_load);
+ g_task_run_in_thread (task, load_persona_store_task);
+}
+
+/**
+ * folks_persona_store_load_finish:
+ * @self: a #FolksPersonaStore
+ * @result: a #GAsyncResult
+ * @error: a #GError
+ *
+ * Get the result of the operation started with folks_persona_store_load().
+ *
+ * Returns: %TRUE if the persona store is successfully loaded, %FALSE otherwise
+ * and @error is set.
+ */
+gboolean
+folks_persona_store_load_finish (FolksPersonaStore *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (FOLKS_IS_PERSONA_STORE (self), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/folks/folks-persona-store.h b/folks/folks-persona-store.h
new file mode 100644
index 00000000..f8a3b28c
--- /dev/null
+++ b/folks/folks-persona-store.h
@@ -0,0 +1,43 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Copyright 2023 Collabora, Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __FOLKS_PERSONA_STORE_H__
+#define __FOLKS_PERSONA_STORE_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define FOLKS_TYPE_PERSONA_STORE (folks_persona_store_get_type())
+
+G_DECLARE_FINAL_TYPE (FolksPersonaStore, folks_persona_store, FOLKS, PERSONA_STORE, GObject)
+
+void folks_persona_store_load (FolksPersonaStore *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean folks_persona_store_load_finish (FolksPersonaStore *self,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __FOLKS_PERSONA_STORE_H__ */
diff --git a/folks/folks-persona.c b/folks/folks-persona.c
new file mode 100644
index 00000000..0f568563
--- /dev/null
+++ b/folks/folks-persona.c
@@ -0,0 +1,182 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Copyright 2023 Collabora, Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "folks-persona.h"
+
+#include <libtracker-sparql/tracker-sparql.h>
+
+struct _FolksPersona
+{
+ GObject parent_instance;
+
+ TrackerSparqlConnection *connection;
+ char *persona_urn;
+ char *alias;
+ char *nickname;
+ char *fullname;
+};
+
+G_DEFINE_FINAL_TYPE (FolksPersona, folks_persona, G_TYPE_OBJECT)
+
+enum {
+ PROP_ALIAS = 1,
+ PROP_NICKNAME,
+ PROP_FULLNAME,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+folks_persona_dispose (GObject *object)
+{
+ FolksPersona *self = (FolksPersona *)object;
+
+ g_clear_object (&self->connection);
+
+ G_OBJECT_CLASS (folks_persona_parent_class)->dispose (object);
+}
+
+static void
+folks_persona_finalize (GObject *object)
+{
+ FolksPersona *self = (FolksPersona *)object;
+
+ g_clear_pointer (&self->alias, g_free);
+ g_clear_pointer (&self->nickname, g_free);
+ g_clear_pointer (&self->fullname, g_free);
+ g_clear_pointer (&self->persona_urn, g_free);
+
+ G_OBJECT_CLASS (folks_persona_parent_class)->finalize (object);
+}
+
+static void
+folks_persona_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ FolksPersona *self = FOLKS_PERSONA (object);
+
+ switch (prop_id)
+ {
+ case PROP_ALIAS:
+ g_value_take_string (value, folks_persona_get_alias (self));
+ break;
+ case PROP_NICKNAME:
+ g_value_take_string (value, folks_persona_get_nickname (self));
+ break;
+ case PROP_FULLNAME:
+ g_value_take_string (value, folks_persona_get_fullname (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+folks_persona_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ FolksPersona *self = FOLKS_PERSONA (object);
+
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+folks_persona_class_init (FolksPersonaClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = folks_persona_finalize;
+ object_class->dispose = folks_persona_dispose;
+ object_class->get_property = folks_persona_get_property;
+ object_class->set_property = folks_persona_set_property;
+
+ properties[PROP_ALIAS] =
+ g_param_spec_string ("alias", NULL, NULL,
+ NULL /* default value */,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
+
+ properties[PROP_NICKNAME] =
+ g_param_spec_string ("nickname", NULL, NULL,
+ NULL /* default value */,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
+
+ properties[PROP_FULLNAME] =
+ g_param_spec_string ("fullname", NULL, NULL,
+ NULL /* default value */,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
+
+ g_object_class_install_properties (object_class,
+ N_PROPS,
+ properties);
+}
+
+static void
+folks_persona_init (FolksPersona *self)
+{
+
+}
+
+/* Private */
+
+FolksPersona *
+folks_persona_new (TrackerSparqlConnection *connection,
+ const char *persona_urn)
+{
+ FolksPersona *self;
+ self = g_object_new (FOLKS_TYPE_PERSONA, NULL);
+ self->connection = g_object_ref (connection);
+ self->persona_urn = g_strdup(persona_urn);
+
+ return self;
+}
+
+/* Public */
+
+char *
+folks_persona_get_alias (FolksPersona *self)
+{
+ g_return_val_if_fail (FOLKS_IS_PERSONA (self), NULL);
+
+ return g_strdup (self->alias);
+}
+
+char *
+folks_persona_get_fullname (FolksPersona *self)
+{
+ g_return_val_if_fail (FOLKS_IS_PERSONA (self), NULL);
+
+ return g_strdup (self->fullname);
+}
+
+char *
+folks_persona_get_nickname (FolksPersona *self)
+{
+ g_return_val_if_fail (FOLKS_IS_PERSONA (self), NULL);
+
+ return g_strdup (self->nickname);
+}
diff --git a/folks/folks-persona.h b/folks/folks-persona.h
new file mode 100644
index 00000000..22aced46
--- /dev/null
+++ b/folks/folks-persona.h
@@ -0,0 +1,46 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Copyright 2023 Collabora, Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __FOLKS_PERSONA_H__
+#define __FOLKS_PERSONA_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define FOLKS_TYPE_PERSONA (folks_persona_get_type())
+
+G_DECLARE_FINAL_TYPE (FolksPersona, folks_persona, FOLKS, PERSONA, GObject)
+
+char *folks_persona_get_alias (FolksPersona *self);
+char *folks_persona_get_fullname (FolksPersona *self);
+char *folks_persona_get_nickname (FolksPersona *self);
+gboolean folks_persona_set_alias (FolksPersona *self,
+ const char *alias,
+ GCancellable *cancellable,
+ GError **error);
+// AliasDetails, AvatarDetails, BirthdayDetails, EmailDetails, ExtendedInfo,
+// FavouriteDetails, GenderDetails, GroupDetails, ImDetails, InteractionDetails,
+// LocalIdDetails, LocationDetails, NameDetails, NoteDetails, PresenceDetails,
+// PhoneDetails, PostalAddressDetails, RoleDetails, UrlDetails, WebServiceDetails
+
+G_END_DECLS
+
+#endif /* __FOLKS_PERSONA_H__ */
diff --git a/folks/folks.convert b/folks/folks.convert
deleted file mode 100644
index 8e49cc9a..00000000
--- a/folks/folks.convert
+++ /dev/null
@@ -1,2 +0,0 @@
-[org.freedesktop.folks]
-primary-store = /system/folks/backends/primary_store
diff --git a/folks/folks.deps b/folks/folks.deps
deleted file mode 100644
index 411c6dab..00000000
--- a/folks/folks.deps
+++ /dev/null
@@ -1,4 +0,0 @@
-glib-2.0
-gobject-2.0
-gio-2.0
-gee-0.8
diff --git a/folks/folks-namespace.vala b/folks/folks.h
index 00b271f1..406c563b 100644
--- a/folks/folks-namespace.vala
+++ b/folks/folks.h
@@ -1,5 +1,7 @@
/*
- * Copyright (C) 2013 Collabora Ltd.
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Copyright 2023 Collabora, Ltd.
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -13,17 +15,15 @@
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
*/
-/*
- * This file serves as the representation for the Folks namespace itself (mostly
- * so that we can set its namespace and version attributes for GIR)
- */
+#ifndef __FOLKS_H__
+#define __FOLKS_H__
+
+#include <glib.h>
+
+#include "folks/folks-manager.h"
+#include "folks/folks-persona-store.h"
+#include "folks/folks-persona.h"
-[CCode (gir_namespace = "Folks", gir_version = "0.6")]
-namespace Folks
-{
-}
+#endif /* __FOLKS_H__ */
diff --git a/folks/gender-details.vala b/folks/gender-details.vala
deleted file mode 100644
index 50f0d0d5..00000000
--- a/folks/gender-details.vala
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- * Copyright (C) 2011 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Marco Barisione <marco.barisione@collabora.co.uk>
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using GLib;
-
-/**
- * The gender of a contact
- *
- * @since 0.3.5
- */
-public enum Folks.Gender
-{
- /**
- * The gender of the contact is unknown or the contact didn't specify it.
- */
- UNSPECIFIED,
- /**
- * The contact is male.
- */
- MALE,
- /**
- * The contact is female.
- */
- FEMALE
-}
-
-/**
- * Gender of a contact.
- *
- * This allows representation of the gender of a contact.
- *
- * @since 0.3.5
- */
-public interface Folks.GenderDetails : Object
-{
- /**
- * The gender of the contact.
- *
- * @since 0.3.5
- */
- public abstract Gender gender { get; set; }
-
- /**
- * Change the contact's gender.
- *
- * It's preferred to call this rather than setting
- * {@link GenderDetails.gender} directly, as this method gives error
- * notification and will only return once the gender has been written to the
- * relevant backing store (or the operation's failed).
- *
- * @param gender the contact's gender
- * @throws PropertyError if setting the gender failed
- * @since 0.6.2
- */
- public virtual async void change_gender (Gender gender) throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Gender is not writeable on this contact."));
- }
-}
diff --git a/folks/group-details.vala b/folks/group-details.vala
deleted file mode 100644
index cbb19e1d..00000000
--- a/folks/group-details.vala
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-
-/**
- * Groups for a contact.
- *
- * This allows contacts to be collected into user-defined groups (or categories)
- * for organisational purposes. Groups are non-exclusive and non-hierarchical,
- * so a single contact can be put into many groups, but groups may not
- * themselves be put into groups.
- */
-public interface Folks.GroupDetails : Object
-{
- /**
- * The reason a group member has changed its membership in the group.
- *
- * These closely follow the
- * [[http://telepathy.freedesktop.org/spec/Channel_Interface_Group.html#Channel_Group_Change_Reason|Channel_Group_Change_Reason]]
- * interface in the Telepathy specification.
- */
- public enum ChangeReason
- {
- /**
- * No reason was provided for this change.
- *
- * This is used when a member joins or leaves a group normally.
- */
- NONE = 0,
- /**
- * The change is due to a member going offline.
- *
- * Also used when member is already offline, but this wasn't known
- * previously.
- */
- OFFLINE = 1,
- /**
- * The change is due to a kick operation.
- */
- KICKED = 2,
- /**
- * The change is due to a busy indication.
- */
- BUSY = 3,
- /**
- * The change is due to an invitation.
- */
- INVITED = 4,
- /**
- * The change is due to a kick+ban operation.
- */
- BANNED = 5,
- /**
- * The change is due to an error occurring.
- */
- ERROR = 6,
- /**
- * The change is because the requested member does not exist.
- *
- * For instance, if the user invites a nonexistent contact to a chatroom
- * or attempts to call a nonexistent contact
- */
- INVALID_MEMBER = 7,
- /**
- * The change is because the requested contact did not respond.
- */
- NO_ANSWER = 8,
- /**
- * The change is because a member's unique identifier changed.
- *
- * There must be exactly one member in the removed set and exactly one
- * member in one of the added sets.
- */
- RENAMED = 9,
- /**
- * The change is because there was no permission to contact the requested
- * member.
- */
- PERMISSION_DENIED = 10,
- /**
- * If members are removed with this reason code, the change is because the
- * group has split into unconnected parts which can only communicate
- * within themselves (e.g. netsplits on IRC use this reason code).
- *
- * If members are added with this reason code, the change is because
- * unconnected parts of the group have rejoined. If this channel carries
- * messages (e.g. Text or Tubes channels) applications must assume that
- * the contacts being added are likely to have missed some messages as a
- * result of the separation, and that the contacts in the group are likely
- * to have missed some messages from the contacts being added.
- *
- * Note that from the added contacts' perspective, they have been in the
- * group all along, and the contacts we indicate to be in the group
- * (including the local user) have just rejoined the group with reason
- * Separated. Application protocols in Tubes should be prepared to cope
- * with this situation.
- */
- SEPARATED = 11
- }
-
- /**
- * A set of group IDs for groups containing the member.
- *
- * The complete set of freeform identifiers for all the groups the contact is
- * a member of.
- *
- * @since 0.5.1
- */
- public abstract Set<string> groups { get; set; }
-
- /**
- * Add or remove the contact from the specified group.
- *
- * If ``is_member`` is ``true``, the contact will be added to the ``group``.
- * If it is ``false``, they will be removed from the ``group``.
- *
- * @param group a freeform group identifier
- * @param is_member whether the contact should be a member of the group
- * @throws GLib.Error if changing the group failed in the backing store
- *
- * @since 0.1.11
- */
- public async abstract void change_group (string group, bool is_member)
- throws GLib.Error;
-
- /**
- * Emitted when the contact's membership status changes for a group.
- *
- * This is emitted if the contact becomes a member of a group they weren't in
- * before, or leaves a group they were in.
- *
- * @param group a freeform group identifier for the group being left or joined
- * @param is_member whether the contact is joining or leaving the group
- * @since 0.1.11
- */
- public async signal void group_changed (string group, bool is_member);
-
- /**
- * Change the contact's groups.
- *
- * It's preferred to call this rather than setting {@link GroupDetails.groups}
- * directly, as this method gives error notification and will only return once
- * the groups have been written to the relevant backing store (or the
- * operation's failed).
- *
- * @param groups the complete set of groups the contact should be a member of
- * @throws PropertyError if setting the groups failed
- * @since 0.6.2
- */
- public virtual async void change_groups (Set<string> groups)
- throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Groups are not writeable on this contact."));
- }
-}
diff --git a/folks/im-details.vala b/folks/im-details.vala
deleted file mode 100644
index 698a3c82..00000000
--- a/folks/im-details.vala
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- * Copyright (C) 2011 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-
-/**
- * Errors related to IM addresses and IM address handling.
- */
-public errordomain Folks.ImDetailsError
-{
- /**
- * The specified IM address could not be parsed.
- */
- INVALID_IM_ADDRESS
-}
-
-/**
- * Object representing an IM address value that can have some parameters
- * associated with it.
- *
- * See {@link Folks.AbstractFieldDetails}.
- *
- * @since 0.6.0
- */
-public class Folks.ImFieldDetails : AbstractFieldDetails<string>
-{
- /**
- * Create a new ImFieldDetails.
- *
- * @param value the value of the field, which should be a valid, non-empty
- * IM address
- * @param parameters initial parameters. See
- * {@link AbstractFieldDetails.parameters}. A ``null`` value is equivalent to
- * an empty map of parameters.
- *
- * @return a new ImFieldDetails
- *
- * @since 0.6.0
- */
- public ImFieldDetails (string value,
- MultiMap<string, string>? parameters = null)
- {
- if (value == "")
- {
- warning ("Empty IM address passed to ImFieldDetails.");
- }
-
- this.value = value;
- if (parameters != null)
- this.parameters = (!) parameters;
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override bool equal (AbstractFieldDetails<string> that)
- {
- return base.equal (that);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override uint hash ()
- {
- return base.hash ();
- }
-}
-
-/**
- * IM addresses exposed by an object implementing {@link PresenceDetails}.
- *
- * @since 0.1.13
- */
-public interface Folks.ImDetails : Object
-{
- /**
- * A mapping of IM protocol to an (unordered) set of IM addresses.
- *
- * Each mapping is from an arbitrary protocol identifier to a set of IM
- * addresses on that protocol for the contact, listed in no particular order.
- *
- * There must be no duplicate IM addresses in each set, though a given
- * IM address may be present in the sets for different protocols.
- *
- * All the IM addresses must be normalised using
- * {@link ImDetails.normalise_im_address} before being added to this property.
- *
- * @since 0.5.1
- */
- public abstract MultiMap<string, ImFieldDetails> im_addresses
- {
- get; set;
- }
-
- /**
- * Change the contact's set of IM addresses.
- *
- * It's preferred to call this rather than setting
- * {@link ImDetails.im_addresses} directly, as this method gives error
- * notification and will only return once the IM addresses have been written
- * to the relevant backing store (or the operation's failed).
- *
- * @param im_addresses the new map of protocols to IM addresses
- * @throws PropertyError if setting the IM addresses failed
- * @since 0.6.2
- */
- public virtual async void change_im_addresses (
- MultiMap<string, ImFieldDetails> im_addresses) throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("IM addresses are not writeable on this contact."));
- }
-
- /**
- * Normalise an IM address so that it's suitable for string comparison.
- *
- * IM addresses for various protocols can be represented in different ways,
- * only one of which is canonical. In order to allow simple string comparisons
- * of IM addresses to work, the IM addresses must be normalised beforehand.
- *
- * If the provided IM address is invalid,
- * {@link Folks.ImDetailsError.INVALID_IM_ADDRESS} will be thrown. Note that
- * this isn't guaranteed to be thrown for all invalid addresses, but if it is
- * thrown, the address is guaranteed to be invalid.
- *
- * @param im_address the address to normalise
- * @param protocol the protocol of this im_address
- *
- * @since 0.2.0
- * @throws Folks.ImDetailsError if the provided IM address was invalid
- */
- public static string normalise_im_address (string im_address, string protocol)
- throws Folks.ImDetailsError
- {
- if (protocol == "aim" || protocol == "myspace")
- {
- return im_address.replace (" ", "").down ().normalize ();
- }
- else if (protocol == "irc" || protocol == "yahoo" ||
- protocol == "yahoojp" || protocol == "groupwise")
- {
- return im_address.down ().normalize ();
- }
- else if (protocol == "jabber")
- {
- /* Parse the JID */
- string[] parts = im_address.split ("/", 2);
-
- if (parts.length < 1)
- {
- throw new ImDetailsError.INVALID_IM_ADDRESS (
- /* Translators: the parameter is an IM address. */
- _("The IM address ‘%s’ could not be understood."),
- im_address);
- }
-
- string? resource = null;
- if (parts.length == 2)
- resource = parts[1];
-
- parts = parts[0].split ("@", 2);
-
- if (parts.length < 1)
- {
- throw new ImDetailsError.INVALID_IM_ADDRESS (
- /* Translators: the parameter is an IM address. */
- _("The IM address ‘%s’ could not be understood."),
- im_address);
- }
-
- string? node, _domain;
- if (parts.length == 2)
- {
- node = parts[0];
- _domain = parts[1];
- }
- else
- {
- node = null;
- _domain = parts[0];
- }
-
- if ((node != null && node == "") ||
- (_domain == null || _domain == "") ||
- (resource != null && resource == ""))
- {
- throw new ImDetailsError.INVALID_IM_ADDRESS (
- /* Translators: the parameter is an IM address. */
- _("The IM address ‘%s’ could not be understood."),
- im_address);
- }
-
- string domain = ((!) _domain).down ();
- if (node != null)
- node = ((!) node).down ();
-
- /* Build a new JID */
- string? normalised = null;
-
- if (node != null && resource != null)
- {
- normalised = "%s@%s/%s".printf ((!) node, domain, (!) resource);
- }
- else if (node != null)
- {
- normalised = "%s@%s".printf ((!) node, domain);
- }
- else if (resource != null)
- {
- normalised = "%s/%s".printf (domain, (!) resource);
- }
- else
- {
- throw new ImDetailsError.INVALID_IM_ADDRESS (
- /* Translators: the parameter is an IM address. */
- _("The IM address ‘%s’ could not be understood."),
- im_address);
- }
-
- return ((!) normalised).normalize (-1, NormalizeMode.NFKC);
- }
- else
- {
- /* Fallback */
- return im_address.normalize ();
- }
- }
-}
diff --git a/folks/individual-aggregator.vala b/folks/individual-aggregator.vala
deleted file mode 100644
index 831507b7..00000000
--- a/folks/individual-aggregator.vala
+++ /dev/null
@@ -1,2543 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- * Copyright (C) 2012, 2013 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using Gee;
-using GLib;
-
-/**
- * Errors from {@link IndividualAggregator}s.
- */
-public errordomain Folks.IndividualAggregatorError
-{
- /**
- * Adding a {@link Persona} to a {@link PersonaStore} failed.
- */
- ADD_FAILED,
-
- /**
- * An operation which required the use of a writeable store failed because no
- * writeable store was available.
- *
- * @since 0.1.13
- */
- [Version (deprecated = true, deprecated_since = "0.6.2.1",
- replacement = "IndividualAggregatorError.NO_PRIMARY_STORE")]
- NO_WRITEABLE_STORE,
-
- /**
- * The {@link PersonaStore} was offline (ie, this is a temporary failure).
- *
- * @since 0.3.0
- */
- STORE_OFFLINE,
-
- /**
- * The {@link PersonaStore} did not support writing to a property which the
- * user requested to write to, or which was necessary to write to for storing
- * linking information.
- *
- * @since 0.6.2
- */
- PROPERTY_NOT_WRITEABLE,
-
- /**
- * An operation which required the use of a primary store failed because no
- * primary store was available.
- *
- * @since 0.6.3
- */
- NO_PRIMARY_STORE,
-}
-
-/**
- * Stores {@link Individual}s which have been created through
- * aggregation of all the {@link Persona}s provided by the various
- * {@link Backend}s.
- *
- * This is the main interface for client applications.
- *
- * Linking and unlinking of personas and individuals is performed entirely
- * through the aggregator. Personas may be linked together to form individuals;
- * for example, the personas which form ``individual1`` and ``individual2`` may
- * be linked together with ``another_persona`` to give a new {@link Individual}:
- *
- * {{{
- * var personas = new HashSet<Persona> ();
- * personas.add_all (individual1.personas);
- * personas.add_all (individual2.personas);
- * personas.add (another_persona);
- * yield my_individual_aggregator.link_personas (personas);
- * }}}
- *
- * The individuals which contained those personas will be removed when
- * {@link IndividualAggregator.link_personas} is called. Any personas in those
- * individuals which were not included in the linking call may end up implicitly
- * linked to the new individual, or may be aggregated into other new
- * individuals.
- *
- * For example, consider the situation where ``individual1`` contains two
- * personas, ``persona1A`` and ``persona1B``; ``individual2`` contains one
- * persona, ``persona2A``; and ``another_persona`` comes from ``individual3``,
- * which also contains ``persona3A`` and ``persona3B``. Calling
- * {@link IndividualAggregator.link_personas} on ``persona1A``, ``persona1B``,
- * ``persona2A`` and ``another_persona`` will result in ``individual1`` and
- * ``individual2`` being removed. A new {@link Individual} will be created
- * containing all the personas passed to the linking function. It might also
- * contain ``persona3A`` and ``persona3B``; or they might be in one or two other
- * new individuals.
- *
- * An existing individual may be unlinked to form singleton
- * individuals for each of its personas:
- * {{{
- * yield my_individual_aggregator.unlink_individual (my_individual);
- * }}}
- *
- * Note that to link two individuals together, their two sets of personas must
- * be linked together. There is no API to directly link the individuals
- * themselves, as conceptually folks links {@link Persona}s, not
- * {@link Individual}s.
- *
- * Folks does not support having more than one IndividualAggregator
- * instantiated at the same time. Most clients should use
- * {@link IndividualAggregator.dup} to retrieve the IndividualAggregator
- * singleton.
- *
- */
-public class Folks.IndividualAggregator : Object
-{
- private static weak IndividualAggregator? _instance = null; /* needs to be locked */
-
- private BackendStore _backend_store;
- private HashMap<string, PersonaStore> _stores;
- private unowned PersonaStore? _primary_store = null;
- private SmallSet<Backend> _backends;
-
- private Settings? _primary_store_setting = null;
-
- /* This is conceptually a MultiMap<string, Individual> but it's sufficiently
- * heavily-used that avoiding GObject overhead in favour of inlinable
- * GenericArray access is a significant performance win.
- *
- * key: iid or value of some linkable property (email/IM address etc.)
- * value: owned non-empty set of owned Individual refs
- */
- private HashTable<string, GenericArray<Individual>> _link_map;
-
- private bool _linking_enabled = true;
- private bool _is_prepared = false;
- private bool _prepare_pending = false;
- private Debug _debug;
- private string _configured_primary_store_type_id;
- private string _configured_primary_store_id;
- private const string _FOLKS_GSETTINGS_SCHEMA = "org.freedesktop.folks";
- private const string _PRIMARY_STORE_CONFIG_KEY = "primary-store";
-
- /* The number of persona stores and backends we're waiting to become
- * quiescent. Once these both reach 0, we should be in a quiescent state.
- * We have to count both of them so that we can handle the case where one
- * backend becomes available, and its persona stores all become quiescent,
- * long before any other backend becomes available. In this case, we want
- * the aggregator to signal that it's reached a quiescent state only once
- * all the other backends have also become available. */
- private uint _non_quiescent_persona_store_count = 0;
- /* Same for backends. */
- private uint _non_quiescent_backend_count = 0;
- private bool _is_quiescent = false;
- /* As a precaution against backends/persona stores which never reach
- * quiescence (due to bugs), we implement a timeout after which we forcibly
- * reach quiescence. */
- private uint _quiescent_timeout_id = 0;
-
- private const uint _QUIESCENT_TIMEOUT = 30; /* seconds */
-
- /* We use this to know if the primary PersonaStore has been explicitly
- * set by the user (either via GSettings or an env variable). If that is the
- * case, we don't want to override it with other PersonaStores that
- * announce themselves as default (i.e.: default address book from e-d-s). */
- private bool _user_configured_primary_store = false;
-
- /**
- * Whether {@link IndividualAggregator.prepare} has successfully completed for
- * this aggregator.
- *
- * @since 0.3.0
- */
- public bool is_prepared
- {
- get { return this._is_prepared; }
- }
-
- /**
- * Whether the aggregator has reached a quiescent state. This will happen at
- * some point after {@link IndividualAggregator.prepare} has successfully
- * completed for the aggregator. An aggregator is in a quiescent state when
- * all the {@link PersonaStore}s listed by its backends have reached a
- * quiescent state. Once it's reached a quiescent state, this property will
- * never change again (from ``true`` to ``false``).
- *
- * It's guaranteed that this property's value will only ever change after
- * {@link IndividualAggregator.is_prepared} has changed to ``true``.
- *
- * @since 0.6.2
- */
- public bool is_quiescent
- {
- get { return this._is_quiescent; }
- }
-
- /**
- * Our configured primary (writeable) store.
- *
- * Which one to use is decided (in order or precedence)
- * by:
- *
- * - the FOLKS_PRIMARY_STORE env var (mostly for debugging)
- * - the GSettings key set in ``_PRIMARY_STORE_CONFIG_KEY`` (system set store)
- * - going with the ``key-file`` or ``eds`` store as the fall-back option
- *
- * @since 0.5.0
- */
- public PersonaStore? primary_store
- {
- get { return this._primary_store; }
- }
-
- /**
- * The backend store providing the persona stores for this aggregator.
- *
- * @since 0.9.7
- */
- public BackendStore backend_store
- {
- get { return this._backend_store; }
- construct { this._backend_store = value; }
- }
-
- private Map<string, Individual> _individuals;
- private Map<string, Individual> _individuals_ro;
-
- /**
- * A map from {@link Individual.id}s to their {@link Individual}s.
- *
- * This is the canonical set of {@link Individual}s provided by this
- * IndividualAggregator.
- *
- * {@link Individual}s may be added or removed using
- * {@link IndividualAggregator.add_persona_from_details} and
- * {@link IndividualAggregator.remove_individual}, respectively.
- *
- * @since 0.5.1
- */
- public Map<string, Individual> individuals
- {
- get { return this._individuals_ro; }
- private set
- {
- this._individuals = value;
- this._individuals_ro = this._individuals.read_only_view;
- }
- }
-
- /**
- * The {@link Individual} representing the user.
- *
- * If it exists, this holds the {@link Individual} who is the user: the
- * {@link Individual} containing the {@link Persona}s who are the owners of
- * the accounts for their respective backends.
- *
- * @since 0.3.0
- */
- public Individual? user { get; private set; }
-
- /**
- * Emitted when one or more {@link Individual}s are added to or removed from
- * the aggregator.
- *
- * If more information about the relationships between {@link Individual}s
- * which have been linked and unlinked is needed, consider connecting to
- * {@link IndividualAggregator.individuals_changed_detailed} instead, which is
- * emitted at the same time as this signal.
- *
- * This will not be emitted until after {@link IndividualAggregator.prepare}
- * has been called.
- *
- * @param added a list of {@link Individual}s which have been added
- * @param removed a list of {@link Individual}s which have been removed
- * @param message a string message from the backend, if any
- * @param actor the {@link Persona} who made the change, if known
- * @param reason the reason for the change
- *
- * @since 0.5.1
- */
- [Version (deprecated = true, deprecated_since = "0.6.2",
- replacement = "IndividualAggregator.individuals_changed_detailed")]
- public signal void individuals_changed (Set<Individual> added,
- Set<Individual> removed,
- string? message,
- Persona? actor,
- GroupDetails.ChangeReason reason);
-
- /**
- * Emitted when one or more {@link Individual}s are added to or removed from
- * the aggregator.
- *
- * This is emitted at the same time as
- * {@link IndividualAggregator.individuals_changed}, but includes more
- * information about the relationships between {@link Individual}s which have
- * been linked and unlinked.
- *
- * Individuals which have been linked will be listed in the multi-map as
- * mappings from the old individuals to the single new individual which
- * replaces them (i.e. each of the old individuals will map to the same new
- * individual). This new individual is the one which will be specified as the
- * ``replacement_individual`` in the {@link Individual.removed} signal for the
- * old individuals.
- *
- * Individuals which have been unlinked will be listed in the multi-map as
- * a mapping from the unlinked individual to a set of one or more individuals
- * which replace it.
- *
- * Individuals which have been added will be listed in the multi-map as a
- * mapping from ``null`` to the set of added individuals. If ``null`` doesn't
- * map to anything, no individuals have been added to the aggregator.
- *
- * Individuals which have been removed will be listed in the multi-map as
- * mappings from the removed individual to ``null``.
- *
- * This will not be emitted until after {@link IndividualAggregator.prepare}
- * has been called.
- *
- * @param changes a mapping of old {@link Individual}s to new
- * {@link Individual}s for the individuals which have changed in the
- * aggregator
- *
- * @since 0.6.2
- */
- public signal void individuals_changed_detailed (
- MultiMap<Individual?, Individual?> changes);
-
- /**
- * Create or return the singleton {@link IndividualAggregator} class instance.
- * If the instance doesn't exist already, it will be created with the
- * default {@link BackendStore}.
- *
- * This function is thread-safe.
- *
- * @return Singleton {@link IndividualAggregator} instance
- * @since 0.9.5
- */
- public static IndividualAggregator dup ()
- {
- IndividualAggregator? _retval = IndividualAggregator._instance;
- IndividualAggregator retval;
-
- if (_retval == null)
- {
- /* use an intermediate variable to force a strong reference */
- retval = new IndividualAggregator ();
- IndividualAggregator._instance = retval;
- }
- else
- {
- retval = (!) _retval;
- }
-
- return retval;
- }
-
- /**
- * Create a new IndividualAggregator.
- *
- * Clients should connect to the
- * {@link IndividualAggregator.individuals_changed} signal (or the
- * {@link IndividualAggregator.individuals_changed_detailed} signal), then
- * call {@link IndividualAggregator.prepare} to load the backends and start
- * aggregating individuals.
- *
- * An example of how to set up an IndividualAggregator:
- * {{{
- * IndividualAggregator agg = new IndividualAggregator ();
- * agg.individuals_changed_detailed.connect (individuals_changed_cb);
- * agg.prepare ();
- * }}}
- *
- * Folks does not support having more than one IndividualAggregator
- * instantiated at the same time. So it's recommended to use
- * {@link IndividualAggregator.dup} instead.
- */
- [Version (deprecated = true, deprecated_since = "0.9.5",
- replacement = "IndividualAggregator.dup")]
- public IndividualAggregator ()
- {
- Object (backend_store: BackendStore.dup ());
- }
-
- /**
- * Create or return the singleton {@link IndividualAggregator} class instance
- * with a custom {@link BackendStore}.
- * If the instance doesn't exist already, it will be created with
- * the given {@link BackendStore} rather than the default one.
- * If the instance already exists but is using another {@link BackendStore}
- * then a warning is raised and null is returned.
- *
- * This function is thread-safe.
- *
- * @param store the {@link BackendStore} to use instead of the default one.
-
- * @return Singleton {@link IndividualAggregator} instance, or null
- * @since 0.9.5
- */
- public static IndividualAggregator? dup_with_backend_store (BackendStore store)
- {
- IndividualAggregator? _retval = IndividualAggregator._instance;
- IndividualAggregator retval;
-
- if (_retval == null)
- {
- /* use an intermediate variable to force a strong reference */
- retval = new IndividualAggregator.with_backend_store (store);
- IndividualAggregator._instance = retval;
- }
- else if (_retval._backend_store != store)
- {
- warning ("An aggregator already exists using another backend store");
- return null;
- }
- else
- {
- retval = (!) _retval;
- }
-
- return retval;
- }
-
- /**
- * Create a new IndividualAggregator with a custom {@link BackendStore}.
- *
- * This behaves the same as the default constructor for
- * {@link IndividualAggregator}, but uses the given {@link BackendStore}
- * rather than the default one.
- *
- * @param store the {@link BackendStore} to use instead of the default one.
- *
- * @since 0.9.0
- */
- [Version (deprecated = true, deprecated_since = "0.9.5",
- replacement = "IndividualAggregator.dup_with_backend_store")]
- public IndividualAggregator.with_backend_store (BackendStore store)
- {
- Object (backend_store: store);
- }
-
- construct
- {
- this._stores = new HashMap<string, PersonaStore> ();
- this._individuals = new HashMap<string, Individual> ();
- this._individuals_ro = this._individuals.read_only_view;
- this._link_map = new HashTable<string, GenericArray<Individual>> (
- str_hash, str_equal);
-
- this._backends = new SmallSet<Backend> ();
- this._debug = Debug.dup ();
- this._debug.print_status.connect (this._debug_print_status);
-
- /* Check out the configured primary store */
- var store_config_ids = Environment.get_variable ("FOLKS_PRIMARY_STORE");
- if (store_config_ids == null)
- {
- store_config_ids = Environment.get_variable ("FOLKS_WRITEABLE_STORE");
- if (store_config_ids != null)
- {
- var deprecated_warn = "FOLKS_WRITEABLE_STORE is deprecated, ";
- deprecated_warn += "use FOLKS_PRIMARY_STORE";
- warning (deprecated_warn);
- }
- }
-
- if (store_config_ids != null)
- {
- debug ("Setting primary store IDs from environment variable.");
- this._configure_primary_store ((!) store_config_ids);
- }
- else
- {
- debug ("Setting primary store IDs to defaults.");
- if (BuildConf.HAVE_EDS)
- {
- this._configured_primary_store_type_id = "eds";
- this._configured_primary_store_id = "system-address-book";
- }
- else
- {
- this._configured_primary_store_type_id = "key-file";
- this._configured_primary_store_id = "";
- }
-
- this._primary_store_setting = new Settings (
- IndividualAggregator._FOLKS_GSETTINGS_SCHEMA);
- this._primary_store_setting.changed[IndividualAggregator._PRIMARY_STORE_CONFIG_KEY].connect (
- this._primary_store_setting_changed_cb);
- this._primary_store_setting_changed_cb (_primary_store_setting,
- IndividualAggregator._PRIMARY_STORE_CONFIG_KEY);
- }
-
- debug ("Primary store IDs are '%s' and '%s'.",
- this._configured_primary_store_type_id,
- this._configured_primary_store_id);
-
- var disable_linking = Environment.get_variable ("FOLKS_DISABLE_LINKING");
- if (disable_linking != null)
- disable_linking = ((!) disable_linking).strip ().down ();
- this._linking_enabled = (disable_linking == null ||
- disable_linking == "no" || disable_linking == "0");
-
- debug ("Constructing IndividualAggregator %p", this);
- }
-
- ~IndividualAggregator ()
- {
- debug ("Destroying IndividualAggregator %p", this);
-
- if (this._quiescent_timeout_id != 0)
- {
- Source.remove (this._quiescent_timeout_id);
- this._quiescent_timeout_id = 0;
- }
-
- this._backend_store.backend_available.disconnect (
- this._backend_available_cb);
-
- this._debug.print_status.disconnect (this._debug_print_status);
-
- /* Manually clear the singleton _instance */
- IndividualAggregator._instance = null;
- }
-
- private void _primary_store_setting_changed_cb (Settings settings,
- string key)
- {
- var val = settings.get_string (key);
- if (val != null && val != "")
- {
- debug ("Setting primary store IDs from GSettings.");
- this._configure_primary_store ((!) val);
-
- var store_full_id = this._get_store_full_id (
- this._configured_primary_store_type_id,
- this._configured_primary_store_id);
- if (this._stores.has_key (store_full_id))
- {
- var selected_store = this._stores.get (store_full_id);
- this._set_primary_store (selected_store);
- }
- }
- }
-
- private void _configure_primary_store (string store_config_ids)
- {
- debug ("_configure_primary_store to '%s'", store_config_ids);
- this._user_configured_primary_store = true;
-
- if (store_config_ids.index_of (":") != -1)
- {
- var ids = store_config_ids.split (":", 2);
- this._configured_primary_store_type_id = ids[0];
- this._configured_primary_store_id = ids[1];
- }
- else
- {
- this._configured_primary_store_type_id = store_config_ids;
- this._configured_primary_store_id = "";
- }
- }
-
- private void _debug_print_status (Debug debug)
- {
- const string domain = Debug.STATUS_LOG_DOMAIN;
- const LogLevelFlags level = LogLevelFlags.LEVEL_INFO;
-
- debug.print_heading (domain, level, "IndividualAggregator (%p)", this);
- debug.print_key_value_pairs (domain, level,
- "Ref. count", this.ref_count.to_string (),
- "Primary store", "%p".printf (this._primary_store),
- "Configured store type id", this._configured_primary_store_type_id,
- "Configured store id", this._configured_primary_store_id,
- "Linking enabled?", this._linking_enabled ? "yes" : "no",
- "Prepared?", this._is_prepared ? "yes" : "no",
- "Quiescent?", this._is_quiescent
- ? "yes"
- : "no (%u backends, %u persona stores left)".printf (
- this._non_quiescent_backend_count,
- this._non_quiescent_persona_store_count)
- );
-
- debug.print_line (domain, level,
- "%u Individuals:", this.individuals.size);
- debug.indent ();
-
- foreach (var individual in this.individuals.values)
- {
- string? trust_level = null;
-
- switch (individual.trust_level)
- {
- case TrustLevel.NONE:
- trust_level = "none";
- break;
- case TrustLevel.PERSONAS:
- trust_level = "personas";
- break;
- default:
- assert_not_reached ();
- }
-
- debug.print_heading (domain, level, "Individual (%p)", individual);
- debug.print_key_value_pairs (domain, level,
- "Ref. count", individual.ref_count.to_string (),
- "ID", individual.id,
- "User?", individual.is_user ? "yes" : "no",
- "Trust level", trust_level
- );
- debug.print_line (domain, level, "%u Personas:",
- individual.personas.size);
-
- debug.indent ();
-
- foreach (var persona in individual.personas)
- {
- debug.print_heading (domain, level, "Persona (%p)", persona);
- debug.print_key_value_pairs (domain, level,
- "Ref. count", persona.ref_count.to_string (),
- "UID", persona.uid,
- "IID", persona.iid,
- "Display ID", persona.display_id,
- "User?", persona.is_user ? "yes" : "no"
- );
- }
-
- debug.unindent ();
- }
-
- debug.unindent ();
-
- debug.print_line (domain, level, "%u keys in the link map:",
- this._link_map.size ());
- debug.indent ();
-
- var iter = HashTableIter<string, GenericArray<Individual>> (
- this._link_map);
- unowned string link_key;
- unowned GenericArray<Individual> individuals;
-
- while (iter.next (out link_key, out individuals))
- {
- debug.print_line (domain, level, "%s → {", link_key);
- debug.indent ();
-
- for (uint i = 0; i < individuals.length; i++)
- {
- unowned Individual ind = individuals[i];
-
- debug.print_line (domain, level, "%p", ind);
- }
-
- debug.unindent ();
- debug.print_line (domain, level, "}");
- }
-
- debug.unindent ();
-
- /* Finish with a blank line. The format string must be non-empty. */
- debug.print_line (domain, level, "%s", "");
- }
-
- /**
- * Prepare the IndividualAggregator for use.
- *
- * This loads all the available backends and prepares them for use by the
- * IndividualAggregator. This should be called //after// connecting to the
- * {@link IndividualAggregator.individuals_changed} signal (or
- * {@link IndividualAggregator.individuals_changed_detailed} signal), or a
- * race condition could occur, with the signal being emitted before your code
- * has connected to them, and {@link Individual}s getting "lost" as a result.
- *
- * This function is guaranteed to be idempotent (since version 0.3.0).
- *
- * Concurrent calls to this function from different threads will block until
- * preparation has completed. However, concurrent calls to this function from
- * a single thread might not, i.e. the first call will block but subsequent
- * calls might return before the first one. (Though they will be safe in every
- * other respect.)
- *
- * @throws GLib.Error if preparing any of the backends failed — this error
- * will be passed through from {@link BackendStore.load_backends}
- *
- * @since 0.1.11
- */
- public async void prepare () throws GLib.Error
- {
- var profiling = Internal.profiling_start ("preparing IndividualAggregator");
-
- /* Once this async function returns, all the {@link Backend}s will have
- * been prepared (though no {@link PersonaStore}s are guaranteed to be
- * available yet). This last guarantee is new as of version 0.2.0. */
-
- if (this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- try
- {
- this._prepare_pending = true;
-
- /* Temporarily increase the non-quiescent backend count so that
- * we don't prematurely reach quiescence due to odd timing of the
- * backend-available signals. */
- this._non_quiescent_backend_count++;
-
- this._backend_store.backend_available.connect (
- this._backend_available_cb);
-
- /* Load any backends which already exist. This could happen if the
- * BackendStore has stayed alive after being used by a previous
- * IndividualAggregator instance. */
- var backends = this._backend_store.enabled_backends.values;
- foreach (var backend in backends)
- {
- this._backend_available_cb (this._backend_store, backend);
- }
-
- /* Load any backends which haven't been loaded already. (Typically
- * all of them.) */
- yield this._backend_store.load_backends ();
-
- this._non_quiescent_backend_count--;
-
- this._is_prepared = true;
- this._prepare_pending = false;
- this.notify_property ("is-prepared");
-
- /* Mark the aggregator as having reached a quiescent state if
- * appropriate. This will typically only happen here in cases
- * where the stores were all prepared and quiescent before the
- * aggregator was created. */
- if (this._is_quiescent == false)
- {
- this._notify_if_is_quiescent ();
- }
- }
- finally
- {
- this._prepare_pending = false;
- }
-
- Internal.profiling_end ((owned) profiling);
- }
-
- /**
- * Clean up and release resources used by the aggregator.
- *
- * This will disconnect the aggregator cleanly from any resources it or its
- * persona stores are using. It is recommended to call this method before
- * finalising the individual aggregator, but calling it is not required. If
- * this method is not called then, for example, unsaved changes in backends
- * may not be flushed.
- *
- * Concurrent calls to this function from different threads will block until
- * preparation has completed. However, concurrent calls to this function from
- * a single thread might not, i.e. the first call will block but subsequent
- * calls might return before the first one. (Though they will be safe in every
- * other respect.)
- *
- * @since 0.7.3
- * @throws GLib.Error if unpreparing the backend-specific services failed —
- * this will be a backend-specific error
- */
- public async void unprepare () throws GLib.Error
- {
- if (!this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- try
- {
- /* Flush any PersonaStores which need it. */
- foreach (var p in this._stores.values)
- {
- yield p.flush ();
- }
- }
- finally
- {
- this._prepare_pending = false;
- }
- }
-
- /**
- * Get all matches for a given {@link Individual}.
- *
- * @param matchee the individual to find matches for
- * @param min_threshold the threshold for accepting a match
- * @return a map from matched individuals to the degree with which they match
- * ``matchee`` (which is guaranteed to at least equal ``min_threshold``);
- * if no matches could be found, an empty map is returned
- *
- * @since 0.5.1
- */
- public Map<Individual, MatchResult> get_potential_matches
- (Individual matchee, MatchResult min_threshold = MatchResult.VERY_HIGH)
- {
- HashMap<Individual, MatchResult> matches =
- new HashMap<Individual, MatchResult> ();
- Folks.PotentialMatch matchObj = new Folks.PotentialMatch ();
-
- foreach (var i in this._individuals.values)
- {
- if (i.id == matchee.id)
- continue;
-
- var result = matchObj.potential_match (i, matchee);
- if (result >= min_threshold)
- {
- matches.set (i, result);
- }
- }
-
- return matches;
- }
-
- /**
- * Get all combinations between all {@link Individual}s.
- *
- * @param min_threshold the threshold for accepting a match
- * @return a map from each individual in the aggregator to a map of the
- * other individuals in the aggregator which can be matched with that
- * individual, mapped to the degree with which they match the original
- * individual (which is guaranteed to at least equal ``min_threshold``)
- *
- * @since 0.5.1
- */
- public Map<Individual, Map<Individual, MatchResult>>
- get_all_potential_matches
- (MatchResult min_threshold = MatchResult.VERY_HIGH)
- {
- HashMap<Individual, HashMap<Individual, MatchResult>> matches =
- new HashMap<Individual, HashMap<Individual, MatchResult>> ();
- var individuals = this._individuals.values.to_array ();
- Folks.PotentialMatch matchObj = new Folks.PotentialMatch ();
-
- for (var i = 0; i < individuals.length; i++)
- {
- var a = individuals[i];
-
- HashMap<Individual, MatchResult>? _matches_a = matches.get (a);
- HashMap<Individual, MatchResult> matches_a;
- if (_matches_a == null)
- {
- matches_a = new HashMap<Individual, MatchResult> ();
- matches.set (a, matches_a);
- }
- else
- {
- matches_a = (!) _matches_a;
- }
-
- for (var f = i + 1; f < individuals.length; f++)
- {
- var b = individuals[f];
-
- HashMap<Individual, MatchResult>? _matches_b = matches.get (b);
- HashMap<Individual, MatchResult> matches_b;
- if (_matches_b == null)
- {
- matches_b = new HashMap<Individual, MatchResult> ();
- matches.set (b, matches_b);
- }
- else
- {
- matches_b = (!) _matches_b;
- }
-
- var result = matchObj.potential_match (a, b);
-
- if (result >= min_threshold)
- {
- matches_a.set (b, result);
- matches_b.set (a, result);
- }
- }
- }
-
- return matches;
- }
-
- private void _add_backend (Backend backend)
- {
- if (!this._backends.contains (backend))
- {
- this._backends.add (backend);
-
- backend.persona_store_added.connect (
- this._backend_persona_store_added_cb);
- backend.persona_store_removed.connect (
- this._backend_persona_store_removed_cb);
- backend.notify["is-quiescent"].connect (
- this._backend_is_quiescent_changed_cb);
-
- /* Handle the stores that have already been signaled. Since
- * this might change while we are looping, get a copy first.
- */
- var stores = backend.persona_stores.values.to_array ();
- foreach (var persona_store in stores)
- {
- this._backend_persona_store_added_cb (backend, persona_store);
- }
- }
- }
-
- private void _backend_available_cb (BackendStore backend_store,
- Backend backend)
- {
- /* Increase the number of non-quiescent backends we're waiting for.
- * If we've already reached a quiescent state, this is ignored. If we
- * haven't, this delays us reaching a quiescent state until the
- * _backend_is_quiescent_changed_cb() callback is called for this
- * backend. */
- if (backend.is_quiescent == false)
- {
- this._non_quiescent_backend_count++;
-
- /* Start the timeout to force quiescence if the backend (or its
- * persona stores) misbehave and don't reach quiescence. */
- if (this._quiescent_timeout_id == 0)
- {
- this._quiescent_timeout_id =
- Timeout.add_seconds (IndividualAggregator._QUIESCENT_TIMEOUT,
- this._quiescent_timeout_cb);
- }
- }
-
- this._add_backend (backend);
- }
-
- private void _set_primary_store (PersonaStore store)
- {
- debug ("_set_primary_store()");
-
- if (this._primary_store == store)
- return;
-
- /* We use the configured PersonaStore as the primary PersonaStore.
- *
- * If the type_id is ``eds`` we *must* know the actual store
- * (address book) we are talking about or we might end up using
- * a random store on every run.
- */
- if (store.type_id == this._configured_primary_store_type_id)
- {
- if ((store.type_id != "eds" &&
- this._configured_primary_store_id == "") ||
- this._configured_primary_store_id == store.id)
- {
- debug ("Setting primary store to %p (type ID: %s, ID: %s)",
- store, store.type_id, store.id);
-
- var previous_store = this._primary_store;
- this._primary_store = store;
-
- store.freeze_notify ();
- if (previous_store != null)
- {
- ((!) previous_store).freeze_notify ();
- ((!) previous_store).is_primary_store = false;
- }
- store.is_primary_store = true;
- if (previous_store != null)
- ((!) previous_store).thaw_notify ();
- store.thaw_notify ();
-
- this.notify_property ("primary-store");
- }
- }
- }
-
- private void _backend_persona_store_added_cb (Backend backend,
- PersonaStore store)
- {
- debug ("_backend_persona_store_added_cb(): backend: %s, store: %s (%p)",
- backend.name, store.id, store);
-
- var store_id = this._get_store_full_id (store.type_id, store.id);
-
- this._maybe_configure_as_primary (store);
- this._set_primary_store (store);
-
- this._stores.set (store_id, store);
- store.personas_changed.connect (this._personas_changed_cb);
- store.notify["is-primary-store"].connect (
- this._is_primary_store_changed_cb);
- store.notify["is-quiescent"].connect (
- this._persona_store_is_quiescent_changed_cb);
- store.notify["is-user-set-default"].connect (
- this._persona_store_is_user_set_default_changed_cb);
-
- /* Increase the number of non-quiescent persona stores we're waiting for.
- * If we've already reached a quiescent state, this is ignored. If we
- * haven't, this delays us reaching a quiescent state until the
- * _persona_store_is_quiescent_changed_cb() callback is called for this
- * store. */
- if (store.is_quiescent == false)
- {
- this._non_quiescent_persona_store_count++;
-
- /* Start the timeout to force quiescence if the backend (or its
- * persona stores) misbehave and don't reach quiescence. */
- if (this._quiescent_timeout_id == 0)
- {
- this._quiescent_timeout_id =
- Timeout.add_seconds (IndividualAggregator._QUIESCENT_TIMEOUT,
- this._quiescent_timeout_cb);
- }
- }
-
- /* Handle any pre-existing personas in the store. This can happen if the
- * store existed (and was prepared) before this IndividualAggregator was
- * constructed. */
- if (store.personas.size > 0)
- {
- var persona_set = new HashSet<Persona> ();
- foreach (var p in store.personas.values)
- {
- persona_set.add (p);
- }
-
- this._personas_changed_cb (store, persona_set,
- SmallSet.empty<Persona> (), null, null,
- GroupDetails.ChangeReason.NONE);
- }
-
- /* Prepare the store and receive a load of other personas-changed
- * signals. */
- store.prepare.begin ((obj, result) =>
- {
- try
- {
- store.prepare.end (result);
- }
- catch (GLib.Error e)
- {
- /* Translators: the first parameter is a persona store identifier
- * and the second is an error message. */
- warning (_("Error preparing persona store ‘%s’: %s"), store_id,
- e.message);
- }
- });
- }
-
- private void _backend_persona_store_removed_cb (Backend backend,
- PersonaStore store)
- {
- store.personas_changed.disconnect (this._personas_changed_cb);
- store.notify["is-quiescent"].disconnect (
- this._persona_store_is_quiescent_changed_cb);
- store.notify["is-primary-store"].disconnect (
- this._is_primary_store_changed_cb);
- store.notify["is-user-set-default"].disconnect (
- this._persona_store_is_user_set_default_changed_cb);
-
- /* If we were still waiting on this persona store to reach a quiescent
- * state, stop waiting. */
- if (this._is_quiescent == false && store.is_quiescent == false)
- {
- this._non_quiescent_persona_store_count--;
- this._notify_if_is_quiescent ();
- }
-
- /* Not all stores emit a 'removed' signal under all circumstances.
- * The EDS backend doesn't do it when set_persona_stores() or disable_store()
- * are used to disable a store.
- * Therefore remove this store's personas from all the individuals. Should
- * not have any effect if a store already triggered the 'removed' signals,
- * because then we won't have anything here.
- * See https://bugzilla.gnome.org/show_bug.cgi?id=689146
- */
-
- var removed_personas = new HashSet<Persona> ();
- var iter = store.personas.map_iterator ();
-
- while (iter.next () == true)
- {
- removed_personas.add (iter.get_value ());
- }
- this._personas_changed_cb (store, SmallSet.empty<Persona> (),
- removed_personas, null, null, GroupDetails.ChangeReason.NONE);
-
- if (this._primary_store == store)
- {
- debug ("Unsetting primary store as store %p (type ID: %s, ID: %s) " +
- "has been removed", store, store.type_id, store.id);
- this._primary_store = null;
- this.notify_property ("primary-store");
- }
- this._stores.unset (this._get_store_full_id (store.type_id, store.id));
- }
-
- private string _get_store_full_id (string type_id, string id)
- {
- return type_id + ":" + id;
- }
-
- /* Emit the individuals-changed signal ensuring that null parameters are
- * turned into empty sets, and both sets passed to signal handlers are
- * read-only. */
- private void _emit_individuals_changed (Set<Individual>? added,
- Set<Individual>? removed,
- MultiMap<Individual?, Individual?>? changes,
- string? message = null,
- Persona? actor = null,
- GroupDetails.ChangeReason reason = GroupDetails.ChangeReason.NONE)
- {
- Set<Individual> _added;
- Set<Individual> _removed;
- MultiMap<Individual?, Individual?> _changes;
-
- if ((added == null || ((!) added).size == 0) &&
- (removed == null || ((!) removed).size == 0) &&
- (changes == null || ((!) changes).size == 0))
- {
- /* Don't bother emitting it if nothing's changed */
- return;
- }
-
- Internal.profiling_point ("emitting " +
- "IndividualAggregator::individuals-changed");
-
- _added = (added != null) ? (!) added : SmallSet.empty<Individual> ();
- _removed = (removed != null) ? (!) removed : SmallSet.empty<Individual> ();
-
- if (changes != null)
- {
- _changes = (!) changes;
- }
- else
- {
- _changes = new HashMultiMap<Individual?, Individual?> ();
- }
-
- /* Debug output. */
- if (this._debug.debug_output_enabled == true)
- {
- debug ("Emitting individuals-changed-detailed with %u mappings:",
- _changes.size);
-
- var iter = _changes.map_iterator ();
-
- while (iter.next ())
- {
- var removed_ind = iter.get_key ();
- var added_ind = iter.get_value ();
-
- debug (" %s (%p) → %s (%p)",
- (removed_ind != null) ? ((!) removed_ind).id : "",
- removed_ind,
- (added_ind != null) ? ((!) added_ind).id : "", added_ind);
-
- if (removed_ind != null)
- {
- debug (" Removed individual's personas:");
-
- foreach (var p in ((!) removed_ind).personas)
- {
- debug (" %s (%p)", p.uid, p);
- }
- }
-
- if (added_ind != null)
- {
- debug (" Added individual's personas:");
-
- foreach (var p in ((!) added_ind).personas)
- {
- debug (" %s (%p)", p.uid, p);
- }
- }
- }
- }
-
- this.individuals_changed (_added.read_only_view, _removed.read_only_view,
- message, actor, reason);
- this.individuals_changed_detailed (_changes);
- }
-
- private void _connect_to_individual (Individual individual)
- {
- individual.removed.connect (this._individual_removed_cb);
- this._individuals.set (individual.id, individual);
- }
-
- private void _disconnect_from_individual (Individual individual)
- {
- this._individuals.unset (individual.id);
- individual.removed.disconnect (this._individual_removed_cb);
- }
-
- private void _add_personas (Set<Persona> added, ref Individual? user,
- ref HashMultiMap<Individual?, Individual?> individuals_changes)
- {
- foreach (var persona in added)
- {
- PersonaStoreTrust trust_level = persona.store.trust_level;
-
- /* These are the Individuals whose Personas will be linked together
- * to form the ``final_individual``.
- * Since a given Persona can only be part of one Individual, and the
- * code in Persona._set_personas() ensures that there are no duplicate
- * Personas in a given Individual, ensuring that there are no
- * duplicate Individuals in ``candidate_inds`` (by using a
- * HashSet) guarantees that there will be no duplicate Personas
- * in the ``final_individual``. */
- HashSet<Individual> candidate_inds = new HashSet<Individual> ();
-
- var final_personas = new HashSet<Persona> ();
-
- debug ("Aggregating persona '%s' on '%s'.", persona.uid, persona.iid);
-
- /* If the Persona is the user, we *always* want to link it to the
- * existing this.user. */
- if (this._linking_enabled == true &&
- persona.is_user == true && user != null &&
- ((!) user).has_anti_link_with_persona (persona) == false)
- {
- debug (" Found candidate individual '%s' as user.",
- ((!) user).id);
- candidate_inds.add ((!) user);
- }
-
- /* If we don't trust the PersonaStore at all, we can't link the
- * Persona to any existing Individual */
- if (this._linking_enabled == true &&
- trust_level != PersonaStoreTrust.NONE)
- {
- unowned GenericArray<Individual>? candidates =
- this._link_map.get (persona.iid);
- if (candidates != null)
- {
- for (uint i = 0; i < ((!) candidates).length; i++)
- {
- unowned var candidate_ind = ((!) candidates)[i];
-
- if (candidate_ind.trust_level != TrustLevel.NONE &&
- candidate_ind.has_anti_link_with_persona (
- persona) == false &&
- candidate_inds.add (candidate_ind))
- {
- debug (" Found candidate individual '%s' by " +
- "IID '%s'.", candidate_ind.id, persona.iid);
- }
- }
- }
- }
-
- if (this._linking_enabled == true &&
- persona.store.trust_level == PersonaStoreTrust.FULL)
- {
- /* If we trust the PersonaStore the Persona came from, we can
- * attempt to link based on its linkable properties. */
- foreach (unowned string foo in persona.linkable_properties)
- {
- /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=682698 */
- if (foo == null)
- continue;
-
- /* FIXME: If we just use string prop_name directly in the
- * foreach, Vala doesn't copy it into the closure data, and
- * prop_name ends up as NULL. bgo#628336 */
- unowned string prop_name = foo;
-
- unowned ObjectClass pclass = persona.get_class ();
- if (pclass.find_property (prop_name) == null)
- {
- warning (
- /* Translators: the parameter is a property name. */
- _("Unknown property ‘%s’ in linkable property list."),
- prop_name);
- continue;
- }
-
- persona.linkable_property_to_links (prop_name, (l) =>
- {
- unowned string prop_linking_value = l;
- unowned GenericArray<Individual>? candidates =
- this._link_map.get (prop_linking_value);
-
- if (candidates != null)
- {
- for (uint i = 0; i < ((!) candidates).length; i++)
- {
- var candidate_ind = ((!) candidates)[i];
-
- if (candidate_ind.trust_level !=
- TrustLevel.NONE &&
- candidate_ind.
- has_anti_link_with_persona (
- persona) == false &&
- candidate_inds.add (candidate_ind))
- {
- debug (" Found candidate individual '%s'" +
- " by linkable property '%s' = '%s'.",
- candidate_ind.id, prop_name,
- prop_linking_value);
- }
- }
- }
- });
- }
- }
-
- /* Ensure the original persona makes it into the final individual */
- final_personas.add (persona);
-
- assert (this._linking_enabled == true || candidate_inds.size == 0);
- if (candidate_inds.size > 0 && this._linking_enabled == true)
- {
- /* The Persona's IID or linkable properties match one or more
- * linkable fields which are already in the link map, so we link
- * together all the Individuals we found to form a new
- * final_individual. Later, we remove the Personas from the old
- * Individuals so that the Individuals themselves are removed. */
- foreach (var individual in candidate_inds)
- {
- final_personas.add_all (individual.personas);
- }
- }
- else if (!this._linking_enabled)
- {
- debug (" Linking disabled.");
- }
- else
- {
- debug (" Did not find any candidate individuals.");
- }
-
- /* Create the final linked Individual */
- var final_individual = new Individual (final_personas);
- debug (" Created new individual '%s' (%p) with personas:",
- final_individual.id, final_individual);
- foreach (var p in final_personas)
- {
- debug (" %s (%p)", p.uid, p);
- this._add_persona_to_link_map (p, final_individual);
- }
-
- uint num_mappings_added = 0;
-
- foreach (var i in candidate_inds)
- {
- /* Remove the old individuals from the link map. */
- this._remove_individual_from_link_map (i);
-
- /* Transitively update the individuals_changes. We have to do this
- * in two stages as we can't modify individuals_changes while
- * iterating over it. */
- var transitive_updates = new HashSet<Individual?> ();
-
- var iter = individuals_changes.map_iterator ();
-
- while (iter.next ())
- {
- if (i == iter.get_value ())
- {
- transitive_updates.add (iter.get_key ());
- }
- }
-
- foreach (var k in transitive_updates)
- {
- assert (individuals_changes.remove (k, i) == true);
-
- /* If we're saying the final_individual is replacing some of
- * these candidate individuals, we don't also want to say that
- * it's been added (by also emitting a mapping from
- * null → final_individual). */
- if (k != null)
- {
- individuals_changes.set (k, final_individual);
- num_mappings_added++;
- }
- }
-
- /* If there were no transitive changes to make, it's because this
- * candidate individual existed before this call to
- * _add_personas(), so it's safe to say it's being replaced by
- * the final_individual. */
- if (transitive_updates.size == 0)
- {
- individuals_changes.set (i, final_individual);
- num_mappings_added++;
- }
- }
-
- /* If there were no candidate individuals or they were all freshly
- * added (i.e. mapped from null → candidate_individual), mark the
- * final_individual as added. */
- if (num_mappings_added == 0)
- {
- individuals_changes.set (null, final_individual);
- }
-
- /* If the final Individual is the user, set them as such. */
- if (final_individual.is_user == true)
- user = final_individual;
- }
- }
-
- private void _persona_linkable_property_changed_cb (Object obj,
- ParamSpec pspec)
- {
- /* Ignore it if the link is disabled */
- if (this._linking_enabled == false)
- {
- return;
- }
-
- /* The value of one of the linkable properties of one the personas has
- * changed, so that persona might require re-linking. We do this in a
- * simplistic and hacky way (which should work) by simply treating the
- * persona as if it's been removed and re-added. */
- unowned var persona = (!) (obj as Persona);
-
- debug ("Linkable property '%s' changed for persona '%s' " +
- "(is user: %s, IID: %s).", pspec.name, persona.uid,
- persona.is_user ? "yes" : "no", persona.iid);
-
- var persona_set = new SmallSet<Persona> ();
- persona_set.add (persona);
-
- this._personas_changed_cb (persona.store, persona_set, persona_set,
- null, null, GroupDetails.ChangeReason.NONE);
- }
-
- private void _persona_anti_links_changed_cb (Object obj, ParamSpec pspec)
- {
- unowned var persona = obj as Persona;
-
- /* The anti-links associated with the persona has changed, so that persona
- * might require re-linking. We do this in a simplistic and hacky way
- * (which should work) by simply treating the persona as if it's been
- * removed and re-added. */
- debug ("Anti-links changed for persona '%s' (is user: %s, IID: %s).",
- persona.uid, persona.is_user ? "yes" : "no", persona.iid);
-
- var persona_set = new SmallSet<Persona> ();
- persona_set.add (persona);
-
- this._personas_changed_cb (persona.store, persona_set, persona_set,
- null, null, GroupDetails.ChangeReason.NONE);
- }
-
- private void _connect_to_persona (Persona persona)
- {
- foreach (unowned string prop_name in persona.linkable_properties)
- {
- /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=682698 */
- if (prop_name == null)
- continue;
-
- persona.notify[prop_name].connect (
- this._persona_linkable_property_changed_cb);
- }
-
- unowned var al = persona as AntiLinkable;
- if (al != null)
- {
- al.notify["anti-links"].connect (this._persona_anti_links_changed_cb);
- }
- }
-
- private void _disconnect_from_persona (Persona persona)
- {
- unowned var al = persona as AntiLinkable;
- if (al != null)
- {
- al.notify["anti-links"].disconnect (
- this._persona_anti_links_changed_cb);
- }
-
- foreach (unowned string prop_name in persona.linkable_properties)
- {
- /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=682698 */
- if (prop_name == null)
- continue;
-
- persona.notify[prop_name].disconnect (
- this._persona_linkable_property_changed_cb);
- }
- }
-
- /*
- * Ensure that ``_link_map[key]`` contains ``individual``.
- *
- * Equivalent to MultiMap.set().
- */
- private void _link_map_set (string key, Individual individual)
- {
- unowned GenericArray<Individual>? inds = this._link_map[key];
-
- if (inds == null)
- {
- var new_inds = new GenericArray<Individual> ();
- this._link_map.insert (key, new_inds);
- new_inds.add (individual);
- return;
- }
-
- for (uint i = 0; i < ((!) inds).length; i++)
- {
- if (((!) inds)[i] == individual)
- return;
- }
-
- ((!) inds).add (individual);
- }
-
- private void _add_persona_to_link_map (Persona persona, Individual individual)
- {
- debug ("Connecting to Persona: %s (is user: %s, IID: %s)", persona.uid,
- persona.is_user ? "yes" : "no", persona.iid);
- debug (" Mapping to Individual: %s", individual.id);
-
- /* Add the Persona to the link map. Its trust level will be reflected in
- * final_individual.trust_level, so other Personas won't be linked against
- * it in error if the trust level is NONE. */
- this._link_map_set (persona.iid, individual);
-
- /* Only allow linking on non-IID properties of the Persona if we fully
- * trust the PersonaStore it came from. */
- if (persona.store.trust_level == PersonaStoreTrust.FULL)
- {
- debug (" Inserting links:");
-
- /* Insert maps from the Persona's linkable properties to the
- * Individual. */
- foreach (unowned string prop_name in persona.linkable_properties)
- {
- /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=682698 */
- if (prop_name == null)
- continue;
-
- debug (" %s", prop_name);
-
- unowned ObjectClass pclass = persona.get_class ();
- if (pclass.find_property (prop_name) == null)
- {
- warning (
- /* Translators: the parameter is a property name. */
- _("Unknown property ‘%s’ in linkable property list."),
- prop_name);
- continue;
- }
-
- persona.linkable_property_to_links (prop_name, (l) =>
- {
- unowned string prop_linking_value = l;
-
- debug (" %s", prop_linking_value);
- this._link_map_set (prop_linking_value, individual);
- });
- }
- }
- }
-
- /* We remove individuals as a whole from the link map, rather than iterating
- * through the link map keys generated by their personas (as in
- * _add_persona_to_link_map()) because the values of the personas' linkable
- * properties may well have changed since we added the personas to the link
- * map. If that's the case, we don't want to end up leaving stale entries in
- * the link map, since that *will* cause problems later on. */
- private void _remove_individual_from_link_map (Individual individual)
- {
- debug ("Removing Individual '%s' from the link map.", individual.id);
-
- var iter =
- HashTableIter<string, GenericArray<Individual>> (this._link_map);
- unowned string link_key;
- unowned GenericArray<Individual> inds;
-
- while (iter.next (out link_key, out inds))
- {
- for (uint i = 0; i < inds.length; i++)
- {
- if (inds[i] == individual)
- {
- debug (" %s → %s (%p)",
- link_key, individual.id, individual);
-
- inds.remove_index_fast (i);
-
- if (inds.length == 0)
- iter.remove ();
-
- /* stop looking at inds - it might be invalid now, and in
- * any case, we've already removed @individual */
- break;
- }
- }
- }
- }
-
- private void _personas_changed_cb (PersonaStore store,
- Set<Persona> added,
- Set<Persona> removed,
- string? message,
- Persona? actor,
- GroupDetails.ChangeReason reason)
- {
- var removed_individuals = new HashSet<Individual> ();
- var individuals_changes = new HashMultiMap<Individual?, Individual?> ();
- var relinked_personas = new HashSet<Persona> ();
- var replaced_individuals = new HashMap<Individual, Individual> ();
-
- /* We store the value of this.user locally and only update it at the end
- * of the function to prevent spamming notifications of changes to the
- * property. */
- var user = this.user;
-
- debug ("Removing Personas:");
-
- foreach (var persona in removed)
- {
- debug (" %s (is user: %s, IID: %s)", persona.uid,
- persona.is_user ? "yes" : "no", persona.iid);
-
- /* Find the Individual containing the Persona (if any) and mark them
- * for removal (any other Personas they have which aren't being
- * removed will be re-linked into other Individuals). */
- unowned Individual? ind = persona.individual;
- if (ind != null)
- {
- removed_individuals.add ((!) ind);
- }
-
- /* Stop listening to notifications about the persona's linkable
- * properties. */
- this._disconnect_from_persona (persona);
- }
-
- /* Remove the Individuals which were pointed to by the linkable properties
- * of the removed Personas. We can then re-link the other Personas in
- * those Individuals, since their links may have changed.
- * Note that we remove the Individual from this.individuals, meaning that
- * _individual_removed_cb() ignores this Individual. This allows us to
- * group together the IndividualAggregator.individuals_changed signals
- * for all the removed Individuals. */
- debug ("Removing Individuals due to removed links:");
- foreach (var individual in removed_individuals)
- {
- /* Ensure we don't remove the same Individual twice */
- if (this._individuals.has_key (individual.id) == false)
- continue;
-
- debug (" %s", individual.id);
-
- /* Build a list of Personas which need relinking. Ensure we don't
- * include any of the Personas which have just been removed. */
- foreach (var persona in individual.personas)
- {
- if (removed.contains (persona) == true ||
- relinked_personas.contains (persona) == true)
- continue;
-
- relinked_personas.add (persona);
- }
-
- if (user == individual)
- user = null;
-
- this._disconnect_from_individual (individual);
-
- /* Remove the Individual's links from the link map */
- this._remove_individual_from_link_map (individual);
- }
-
- debug ("Adding Personas:");
- foreach (var persona in added)
- {
- debug (" %s (is user: %s, IID: %s)", persona.uid,
- persona.is_user ? "yes" : "no", persona.iid);
-
- /* Connect to notifications about the persona's linkable
- * properties. */
- this._connect_to_persona (persona);
- }
-
- if (added.size > 0)
- {
- this._add_personas (added, ref user, ref individuals_changes);
- }
-
- debug ("Relinking Personas:");
- foreach (var persona in relinked_personas)
- {
- debug (" %s (is user: %s, IID: %s)", persona.uid,
- persona.is_user ? "yes" : "no", persona.iid);
- }
-
- this._add_personas (relinked_personas, ref user, ref individuals_changes);
-
- /* Work out which final individuals have replaced the removed_individuals
- * and update individuals_changes accordingly. */
- foreach (var individual in removed_individuals)
- {
- var added_mapping = false;
-
- foreach (var persona in individual.personas)
- {
- if (!(persona in removed) || (persona in added))
- {
- individuals_changes.remove (null, persona.individual);
- individuals_changes.set (individual, persona.individual);
- added_mapping = true;
- }
- }
-
- /* Has the individual been removed entirely? */
- if (added_mapping == false)
- {
- individuals_changes.set (individual, null);
- }
-
- individual.personas = null;
- }
-
- /* Notify of changes to this.user */
- this.user = user;
-
- /* Signal the addition of new individuals and removal of old ones to the
- * aggregator */
- if (individuals_changes.size > 0)
- {
- var added_individuals = new HashSet<Individual> ();
-
- /* Extract the deprecated added and removed sets from
- * individuals_changes, to be used in the individuals_changed
- * signal. */
- var iter1 = individuals_changes.map_iterator ();
-
- while (iter1.next ())
- {
- var old_ind = iter1.get_key ();
- var new_ind = iter1.get_value ();
-
- assert (old_ind != null || new_ind != null);
-
- if (old_ind != null)
- {
- removed_individuals.add ((!) old_ind);
- }
-
- if (new_ind != null)
- {
- added_individuals.add ((!) new_ind);
- this._connect_to_individual ((!) new_ind);
- }
-
- if (old_ind != null && new_ind != null)
- {
- replaced_individuals.set ((!) old_ind, (!) new_ind);
- }
- }
-
- this._emit_individuals_changed (added_individuals,
- removed_individuals, individuals_changes);
- }
-
- /* Signal the replacement of various Individuals as a consequence of
- * linking. */
- debug ("Replacing Individuals due to linking:");
- var iter2 = replaced_individuals.map_iterator ();
- while (iter2.next () == true)
- {
- var old_ind = iter2.get_key ();
- var new_ind = iter2.get_value ();
-
- debug (" %s (%p) → %s (%p)", old_ind.id, old_ind,
- new_ind.id, new_ind);
-
- old_ind.replace (new_ind);
- }
-
- /* Validate the link map. */
- if (this._debug.debug_output_enabled == true)
- {
- var link_map_iter =
- HashTableIter<string, GenericArray<Individual>> (this._link_map);
- unowned string link_key;
- unowned GenericArray<Individual> inds;
-
- while (link_map_iter.next (out link_key, out inds))
- {
- for (uint i = 0; i < inds.length; i++)
- {
- var individual = inds[i];
- assert (individual != null);
-
- if (this._individuals.get (individual.id) != individual)
- {
- warning ("Link map contains invalid mapping:\n" +
- " %s → %s (%p)",
- link_key, individual.id, individual);
- warning ("Individual %s (%p) personas:", individual.id,
- individual);
- foreach (var p in individual.personas)
- {
- warning (" %s (%p)", p.uid, p);
- }
- }
-
- for (uint j = i + 1; j < inds.length; j++)
- {
- if (inds[i] == inds[j])
- {
- warning ("Link map contains non-unique " +
- "Individual: %s → %s (%p) twice",
- link_key, individual.id, individual);
- }
- }
- }
- }
- }
- }
-
- private void _is_primary_store_changed_cb (Object object, ParamSpec pspec)
- {
- /* Ensure that we only have one primary PersonaStore */
- var store = (PersonaStore) object;
- assert ((store.is_primary_store == true &&
- store == this._primary_store) ||
- (store.is_primary_store == false &&
- store != this._primary_store));
- }
-
- private void _persona_store_is_quiescent_changed_cb (Object obj,
- ParamSpec pspec)
- {
- /* Have we reached a quiescent state yet? */
- if (this._non_quiescent_persona_store_count > 0)
- {
- this._non_quiescent_persona_store_count--;
- this._notify_if_is_quiescent ();
- }
- }
-
- private void _backend_is_quiescent_changed_cb (Object obj, ParamSpec pspec)
- {
- if (this._non_quiescent_backend_count > 0)
- {
- this._non_quiescent_backend_count--;
- this._notify_if_is_quiescent ();
- }
- }
-
- private void _notify_if_is_quiescent ()
- {
- if (this._non_quiescent_backend_count == 0 &&
- this._non_quiescent_persona_store_count == 0 &&
- this._is_quiescent == false)
- {
- if (this._configured_primary_store_type_id.length > 0 &&
- this._primary_store == null)
- {
- warning ("Failed to find primary PersonaStore with type ID " +
- "'%s' and ID '%s'.\n" +
- "Individuals will not be linked properly " +
- "and creating new links between Personas will not work.\n" +
- "The configured primary PersonaStore's backend may not be " +
- "installed. If you are unsure, check with your " +
- "distribution.",
- this._configured_primary_store_type_id,
- this._configured_primary_store_id);
- }
-
- Internal.profiling_point ("reached quiescence in " +
- "IndividualAggregator");
-
- this._is_quiescent = true;
- this.notify_property ("is-quiescent");
-
- /* Remove the quiescence timeout, if it exists. */
- if (this._quiescent_timeout_id != 0)
- {
- Source.remove (this._quiescent_timeout_id);
- this._quiescent_timeout_id = 0;
- }
- }
- }
-
- private bool _quiescent_timeout_cb ()
- {
- /* If we're not quiescent by the time the timeout is triggered, force
- * quiescence anyway, just so that we don't leave clients hanging if our
- * backends have bugs. */
- if (this._is_quiescent == false)
- {
- warning ("Failed to reach quiescence normally (%u backends and %u " +
- "persona stores still haven't reached quiescence). Forcing " +
- "IndividualAggregator quiescence due to reaching the timeout.",
- this._non_quiescent_backend_count,
- this._non_quiescent_persona_store_count);
-
- this._is_quiescent = true;
- this.notify_property ("is-quiescent");
- }
-
- /* One-shot timeout */
- this._quiescent_timeout_id = 0;
- return false;
- }
-
- private void _persona_store_is_user_set_default_changed_cb (Object obj,
- ParamSpec pspec)
- {
- var store = (PersonaStore) obj;
-
- debug ("PersonaStore.is-user-set-default changed for store %p " +
- "(type ID: %s, ID: %s)", store, store.type_id, store.id);
-
- if (this._maybe_configure_as_primary (store))
- this._set_primary_store (store);
- }
-
- private bool _maybe_configure_as_primary (PersonaStore store)
- {
- debug ("_maybe_configure_as_primary()");
-
- var configured = false;
-
- if (!this._user_configured_primary_store &&
- store.is_user_set_default)
- {
- debug ("Setting primary store IDs to '%s' and '%s'.", store.type_id,
- store.id);
- this._configured_primary_store_type_id = store.type_id;
- this._configured_primary_store_id = store.id;
- configured = true;
- }
-
- return configured;
- }
-
- private void _individual_removed_cb (Individual i, Individual? replacement)
- {
- if (this.user == i)
- this.user = null;
-
- /* Only signal if the individual is still in this.individuals. This allows
- * us to group removals together in, e.g., _personas_changed_cb(). */
- if (this._individuals.get (i.id) != i)
- return;
-
- if (replacement != null)
- {
- debug ("Individual '%s' removed (replaced by '%s')", i.id,
- ((!) replacement).id);
- }
- else
- {
- debug ("Individual '%s' removed (not replaced)", i.id);
- }
-
- /* If the individual has 0 personas, we've already signaled removal */
- if (i.personas.size > 0)
- {
- var changes = new HashMultiMap<Individual?, Individual?> ();
- var individuals = new SmallSet<Individual> ();
-
- individuals.add (i);
- changes.set (i, replacement);
-
- this._emit_individuals_changed (null, individuals, changes);
- }
-
- this._disconnect_from_individual (i);
- }
-
- /**
- * Add a new persona in the given {@link PersonaStore} based on the
- * ``details`` provided.
- *
- * If the target store is offline, this function will throw
- * {@link IndividualAggregatorError.STORE_OFFLINE}. It's the responsibility of
- * the caller to cache details and re-try this function if it wishes to make
- * offline adds work.
- *
- * The details hash is a backend-specific mapping of key, value strings.
- * Common keys include:
- *
- * * contact - service-specific contact ID
- * * message - a user-readable message to pass to the persona being added
- *
- * If a {@link Persona} with the given details already exists in the store, no
- * error will be thrown and this function will return ``null``.
- *
- * @param parent an optional {@link Individual} to add the new {@link Persona}
- * to. This persona will be appended to its ordered list of personas.
- * @param persona_store the {@link PersonaStore} to add the persona to
- * @param details a key-value map of details to use in creating the new
- * {@link Persona}
- * @return the new {@link Persona} or ``null`` if the corresponding
- * {@link Persona} already existed. If non-``null``, the new {@link Persona}
- * will also be added to a new or existing {@link Individual} as necessary.
- * @throws IndividualAggregatorError.STORE_OFFLINE if the persona store was
- * offline
- * @throws IndividualAggregatorError.ADD_FAILED if any other error occurred
- * while adding the persona
- *
- * @since 0.3.5
- */
- public async Persona? add_persona_from_details (Individual? parent,
- PersonaStore persona_store,
- HashTable<string, Value?> details) throws IndividualAggregatorError
- {
- Persona? persona = null;
- try
- {
- var details_copy = this._asv_copy (details);
- persona = yield persona_store.add_persona_from_details (details_copy);
- }
- catch (PersonaStoreError e)
- {
- if (e is PersonaStoreError.STORE_OFFLINE)
- {
- throw new IndividualAggregatorError.STORE_OFFLINE (e.message);
- }
- else
- {
- var full_id = this._get_store_full_id (persona_store.type_id,
- persona_store.id);
-
- throw new IndividualAggregatorError.ADD_FAILED (
- /* Translators: the first parameter is a store identifier
- * and the second parameter is an error message. */
- _("Failed to add contact for persona store ID ‘%s’: %s"),
- full_id, e.message);
- }
- }
-
- if (parent != null && persona != null)
- {
- ((!) parent).personas.add ((!) persona);
- }
-
- return persona;
- }
-
- private HashTable<string, Value?> _asv_copy (HashTable<string, Value?> asv)
- {
- var retval = new HashTable<string, Value?> (str_hash, str_equal);
-
- asv.foreach ((k, v) =>
- {
- retval.insert ((string) k, v);
- });
-
- return retval;
- }
-
- /**
- * Completely remove the individual and all of its personas from their
- * backing stores.
- *
- * This method is safe to call multiple times concurrently (for the same
- * individual or different individuals).
- *
- * @param individual the {@link Individual} to remove
- * @throws GLib.Error if removing the persona failed — this will be passed
- * through from {@link PersonaStore.remove_persona}
- *
- * @since 0.1.11
- */
- public async void remove_individual (Individual individual) throws GLib.Error
- {
- /* Removing personas changes the persona set so we need to make a copy
- * first */
- var personas = SmallSet<Persona>.copy (individual.personas);
-
- foreach (var persona in personas)
- {
- yield persona.store.remove_persona (persona);
- }
- }
-
- /**
- * Completely remove the persona from its backing store.
- *
- * This will leave other personas in the same individual alone.
- *
- * This method is safe to call multiple times concurrently (for the same
- * persona or different personas).
- *
- * @param persona the {@link Persona} to remove
- * @throws GLib.Error if removing the persona failed — this will be passed
- * through from {@link PersonaStore.remove_persona}
- *
- * @since 0.1.11
- */
- public async void remove_persona (Persona persona) throws GLib.Error
- {
- yield persona.store.remove_persona (persona);
- }
-
- /**
- * Link the given {@link Persona}s together.
- *
- * Create links between the given {@link Persona}s so that they form a single
- * {@link Individual}. The new {@link Individual} will be returned via the
- * {@link IndividualAggregator.individuals_changed} signal.
- *
- * Removal of the {@link Individual}s which the {@link Persona}s were in
- * before is signalled by {@link IndividualAggregator.individuals_changed} and
- * {@link Individual.removed}.
- *
- * This method is safe to call multiple times concurrently.
- *
- * @param personas the {@link Persona}s to be linked
- * @throws IndividualAggregatorError.NO_PRIMARY_STORE if no primary store has
- * been configured for the individual aggregator
- * @throws IndividualAggregatorError if adding the linking persona failed —
- * this will be passed through from
- * {@link IndividualAggregator.add_persona_from_details}
- *
- * @since 0.5.1
- */
- public async void link_personas (Set<Persona> personas)
- throws IndividualAggregatorError
- {
- var key_file_store = this._stores.get ("key-file:relationships.ini");
-
- if (key_file_store == null) {
- warning ("Can't link Personas: No keyfile");
- return;
- }
-
-
- /* Don't bother linking if it's just one Persona */
- if (personas.size <= 1)
- return;
-
- /* Disallow linking if it's disabled */
- if (this._linking_enabled == false)
- {
- debug ("Can't link Personas: linking disabled.");
- return;
- }
-
- /* Remove all edges in the connected graph between the personas from the
- * anti-link map to ensure that linking the personas actually succeeds. */
- foreach (var p in personas)
- {
- var al = p as AntiLinkable;
- if (al != null)
- {
- try
- {
- yield ((!) al).remove_anti_links (personas);
- }
- catch (PropertyError e)
- {
- throw new IndividualAggregatorError.PROPERTY_NOT_WRITEABLE (
- _("Anti-links can’t be removed between personas being linked."));
- }
- }
- }
-
- var details = this._build_linking_details (personas);
-
- yield this.add_persona_from_details (null, key_file_store, details);
- }
-
- private HashTable<string, Value?> _build_linking_details (
- Set<Persona> personas)
- {
- /* ``protocols_addrs_set`` will be passed to the new Kf.Persona */
- var protocols_addrs_set = new HashMultiMap<string, ImFieldDetails> (
- null, null, AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- var web_service_addrs_set =
- new HashMultiMap<string, WebServiceFieldDetails> (
- null, null, AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- /* List of local_ids */
- var local_ids = new SmallSet<string> ();
-
- foreach (var persona in personas)
- {
- if (persona is ImDetails)
- {
- ImDetails im_details = (ImDetails) persona;
- var iter = im_details.im_addresses.map_iterator ();
-
- /* protocols_addrs_set = union (all personas' IM addresses) */
- while (iter.next ())
- protocols_addrs_set.set (iter.get_key (), iter.get_value ());
- }
-
- if (persona is WebServiceDetails)
- {
- WebServiceDetails ws_details = (WebServiceDetails) persona;
- var iter = ws_details.web_service_addresses.map_iterator ();
-
- /* web_service_addrs_set = union (all personas' WS addresses) */
- while (iter.next ())
- web_service_addrs_set.set (iter.get_key (), iter.get_value ());
- }
-
- if (persona is LocalIdDetails)
- {
- foreach (var id in ((LocalIdDetails) persona).local_ids)
- {
- local_ids.add (id);
- }
- }
- }
-
- var details = new HashTable<string, Value?> (str_hash, str_equal);
-
- if (protocols_addrs_set.size > 0)
- {
- var im_addresses_value = Value (typeof (MultiMap));
- im_addresses_value.set_object (protocols_addrs_set);
- details.insert (
- (!) PersonaStore.detail_key (PersonaDetail.IM_ADDRESSES),
- im_addresses_value);
- }
-
- if (web_service_addrs_set.size > 0)
- {
- var web_service_addresses_value = Value (typeof (MultiMap));
- web_service_addresses_value.set_object (web_service_addrs_set);
- details.insert (
- (!) PersonaStore.detail_key (PersonaDetail.WEB_SERVICE_ADDRESSES),
- web_service_addresses_value);
- }
-
- if (local_ids.size > 0)
- {
- var local_ids_value = Value (typeof (Set));
- local_ids_value.set_object (local_ids);
- details.insert (
- (!) Folks.PersonaStore.detail_key (PersonaDetail.LOCAL_IDS),
- local_ids_value);
- }
-
- return details;
- }
-
- /**
- * Unlinks the given {@link Individual} into its constituent {@link Persona}s.
- *
- * This completely unlinks the given {@link Individual}, destroying all of
- * its writeable {@link Persona}s.
- *
- * The {@link Individual}'s removal is signalled by
- * {@link IndividualAggregator.individuals_changed} and
- * {@link Individual.removed}.
- *
- * The {@link Persona}s comprising the {@link Individual} will be re-linked
- * into one or more new {@link Individual}s, depending on how much linking
- * data remains (typically only implicit links remain). The addition of these
- * new {@link Individual}s will be signalled by
- * {@link IndividualAggregator.individuals_changed}.
- *
- * This method is safe to call multiple times concurrently, although
- * concurrent calls for the same individual may result in duplicate personas
- * being created.
- *
- * @param individual the {@link Individual} to unlink
- * @throws GLib.Error if removing the linking persona failed — this will be
- * passed through from {@link PersonaStore.remove_persona}
- *
- * @since 0.1.13
- */
- public async void unlink_individual (Individual individual) throws GLib.Error
- {
- var key_file_store = this._stores.get ("key-file:relationships.ini");
-
- if (this._linking_enabled == false)
- {
- debug ("Can't unlink Individual '%s': linking disabled.",
- individual.id);
- return;
- }
-
- debug ("Unlinking Individual '%s':", individual.id);
-
- /* Add all edges in the connected graph between the personas to the
- * anti-link map to ensure that unlinking the personas actually succeeds,
- * and that they aren't immediately re-linked.
- *
- * Perversely, this requires that we ensure the anti-links property is
- * writeable on all personas before continuing. Ignore errors from it in
- * the hope that everything works anyway.
- *
- * In the worst case, this will double the number of personas, since if
- * none of the personas have anti-links writeable, each will have to be
- * linked with a new writeable persona. */
- /* Copy it, since we modify it */
- var individual_personas = SmallSet<Persona>.copy (individual.personas);
-
- var key_file_personas = new SmallSet<Persona> ();
-
- /* Remove personas from the key-file store since they aren't need anymore */
- foreach (var p in individual_personas) {
- if (p.store.type_id == "key-file") {
- debug ("Remove linking persona from key-file store");
- key_file_personas.add (p);
- individual_personas.remove (p);
-
- /* FIXME: we also need to remove the key-file persona from
- * all anti-links of any other possible persona since the key-file persona doesn't exist anymore
- * This should properly be done by remove persona
- */
- yield p.store.remove_persona (p);
- }
- }
-
- foreach (var p in individual_personas) {
- var al = p as AntiLinkable;
- assert (al != null);
- yield al.remove_anti_links (key_file_personas);
- }
-
- debug (" Inserting anti-links:");
- foreach (var pers in individual_personas)
- {
- debug (" Anti-linking persona '%s' (%p)", pers.uid, pers);
-
- Persona writeable_persona;
- if (!_is_anti_link_property_writeable (pers)) {
- debug ("Anti link property isn't writeable, create a persona in the key-file store");
-
- if (key_file_store != null) {
- var personas = new SmallSet<Persona> ();
- personas.add (pers);
-
- var details = this._build_linking_details (personas);
- writeable_persona = yield this.add_persona_from_details (null, key_file_store, details);
- } else {
- debug ("Key file store doesn't exist, can create a new writeable persona");
- continue;
- }
- } else {
- writeable_persona = pers;
- }
-
- /* Make sure not to anti-link the new persona to pers. */
- var anti_link_personas = SmallSet<Persona>.copy (individual_personas);
- anti_link_personas.remove (pers);
-
- var al = writeable_persona as AntiLinkable;
- assert (al != null);
- yield ((!) al).add_anti_links (anti_link_personas);
- debug ("");
- }
- }
-
- private bool _is_anti_link_property_writeable (Persona persona)
- {
- return ("anti-links" in persona.writeable_properties);
- }
-
-
- /**
- * Ensure that the given property is writeable for the given
- * {@link Individual}.
- *
- * This makes sure that there is at least one {@link Persona} in the
- * individual which has ``property_name`` in its
- * {@link Persona.writeable_properties}. If no such persona exists in the
- * individual, a new one will be created and linked to the individual. (Note
- * that due to the design of the aggregator, this will result in the previous
- * individual being removed and replaced by a new one with the new persona;
- * listen to the {@link Individual.removed} signal to see the replacement.)
- *
- * It may not be possible to create a new persona which has the given property
- * as writeable. In that case, a
- * {@link IndividualAggregatorError.NO_PRIMARY_STORE} or
- * {@link IndividualAggregatorError.PROPERTY_NOT_WRITEABLE} error will be
- * thrown.
- *
- * This method is safe to call multiple times concurrently, although
- * concurrent calls for the same individual may result in duplicate personas
- * being created.
- *
- * @param individual the individual for which ``property_name`` should be
- * writeable
- * @param property_name the name of the property which needs to be writeable
- * (this should be in lower case using hyphens, e.g. “web-service-addresses”)
- * @return a persona (new or existing) which has the given property as
- * writeable
- * @throws IndividualAggregatorError.NO_PRIMARY_STORE if no primary store was
- * configured for this individual aggregator
- * @throws IndividualAggregatorError.PROPERTY_NOT_WRITEABLE if the given
- * ``property_name`` referred to a non-writeable property
- * @throws IndividualAggregatorError if adding a new persona (using
- * {@link IndividualAggregator.add_persona_from_details}) failed, or if
- * linking personas (using {@link IndividualAggregator.link_personas}) failed
- *
- * @since 0.6.2
- */
- public async Persona ensure_individual_property_writeable (
- Individual individual, string property_name)
- throws IndividualAggregatorError
- {
- debug ("ensure_individual_property_writeable: %s, %s",
- individual.id, property_name);
-
- var p = yield this._ensure_personas_property_writeable (
- individual.personas, property_name);
- return p;
- }
-
- /* This is safe to call multiple times concurrently, *but* if the set of
- * personas doesn't change, multiple duplicate personas may be created in the
- * writeable store. */
- private async Persona _ensure_personas_property_writeable (
- Set<Persona> personas, string property_name)
- throws IndividualAggregatorError
- {
- /* See if the persona set already contains the property we want. */
- foreach (var p1 in personas)
- {
- if (property_name in p1.writeable_properties)
- {
- debug (" Returning existing persona: %s", p1.uid);
- return p1;
- }
- }
-
- /* Otherwise, create a new persona in the writeable store. If the
- * writeable store doesn't exist or doesn't support writing to the given
- * property, we try the other persona stores. */
- var details = this._build_linking_details (personas);
- Persona? new_persona = null;
-
- if (this._primary_store != null &&
- property_name in
- ((!) this._primary_store).always_writeable_properties)
- {
- try
- {
- debug (" Using writeable store");
- new_persona = yield this.add_persona_from_details (null,
- (!) this._primary_store, details);
- }
- catch (IndividualAggregatorError e1)
- {
- /* Ignore it */
- new_persona = null;
- }
- }
-
- if (new_persona == null)
- {
- foreach (var s in this._stores.values)
- {
- if (s == this._primary_store ||
- !(property_name in s.always_writeable_properties))
- {
- /* Skip the store we've just tried */
- continue;
- }
-
- try
- {
- debug (" Using store %s", s.id);
- new_persona = yield this.add_persona_from_details (null, s,
- details);
- }
- catch (IndividualAggregatorError e2)
- {
- /* Ignore it */
- new_persona = null;
- continue;
- }
- }
- }
-
- /* Throw an error if we haven't managed to find a suitable store */
- if (new_persona == null && this._primary_store == null)
- {
- throw new IndividualAggregatorError.NO_PRIMARY_STORE (
- _("Can’t add personas with no primary store.") + "\n" +
- _("Persona store ‘%s:%s’ is configured as primary, but could not be found or failed to load.") + "\n" +
- _("Check the relevant service is running, or change the default store in that service or using the ‘%s’ GSettings key."),
- this._configured_primary_store_type_id,
- this._configured_primary_store_id,
- "%s %s".printf (IndividualAggregator._FOLKS_GSETTINGS_SCHEMA,
- IndividualAggregator._PRIMARY_STORE_CONFIG_KEY));
- }
- else if (new_persona == null)
- {
- throw new IndividualAggregatorError.PROPERTY_NOT_WRITEABLE (
- _("Can’t write to requested property (‘%s’) of the writeable store."),
- property_name);
- }
-
- /* We can guarantee new_persona != null because we'd have bailed out above
- * otherwise. */
- return (!) new_persona;
- }
-
- /**
- * Look up an individual in the aggregator.
- *
- * This returns the {@link Individual} with the given ``id`` if it exists in
- * the aggregator, and ``null`` otherwise.
- *
- * In future, when lazy-loading of individuals' properties is added to folks,
- * this method guarantees to load all properties of the individual, even if
- * the aggregator hasn't lazy-loaded anything else.
- *
- * This method is safe to call before {@link IndividualAggregator.prepare} has
- * been called, and will call {@link IndividualAggregator.prepare} itself in
- * that case.
- *
- * This method is safe to call multiple times concurrently.
- *
- * @param id ID of the individual to look up
- * @return individual with ``id``, or ``null`` if no such individual was found
- * @throws GLib.Error from {@link IndividualAggregator.prepare}
- *
- * @since 0.7.0
- */
- public async Individual? look_up_individual (string id) throws GLib.Error
- {
- /* Ensure the aggregator's prepared. */
- yield this.prepare ();
-
- /* FIXME: When bgo#648805 is fixed, this needs to support lazy-loading. */
- return this._individuals.get (id);
- }
-}
diff --git a/folks/individual.vala b/folks/individual.vala
deleted file mode 100644
index bc63dc11..00000000
--- a/folks/individual.vala
+++ /dev/null
@@ -1,3140 +0,0 @@
-/*
- * Copyright (C) 2010, 2015 Collabora Ltd.
- * Copyright (C) 2011, 2013 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using Gee;
-using GLib;
-
-/**
- * Trust level for an {@link Individual} for use in the UI.
- *
- * @since 0.1.15
- */
-public enum Folks.TrustLevel
-{
- /**
- * The {@link Individual}'s {@link Persona}s aren't trusted at all.
- *
- * This is the trust level for an {@link Individual} which contains one or
- * more {@link Persona}s which cannot be guaranteed to be the same
- * {@link Persona}s as were originally linked together.
- *
- * For example, an {@link Individual} containing a link-local XMPP
- * {@link Persona} would have this trust level, since someone else could
- * easily spoof the link-local XMPP {@link Persona}'s identity.
- *
- * @since 0.1.15
- */
- NONE,
-
- /**
- * The {@link Individual}'s {@link Persona}s are trusted.
- *
- * This trust level is for {@link Individual}s where it can be guaranteed
- * that all the {@link Persona}s are the same ones as when they were
- * originally linked together.
- *
- * Note that this doesn't guarantee that the user who behind each
- * {@link Persona} is who they claim to be.
- *
- * @since 0.1.15
- */
- PERSONAS
-}
-
-/**
- * A physical person, aggregated from the various {@link Persona}s the person
- * might have, such as their different IM addresses or vCard entries. An
- * individual must always contain at least one {@link Persona}.
- *
- * When choosing the values of single-valued properties (such as
- * {@link Individual.alias} and {@link Individual.avatar}; but not multi-valued
- * properties such as {@link Individual.groups} and
- * {@link Individual.im_addresses}) from the {@link Persona}s in the
- * individual to present as the values of those properties of the individual,
- * it is guaranteed that if the individual contains a persona from the primary
- * persona store (see {@link IndividualAggregator.primary_store}), its property
- * values will be chosen above all others. This means that any changes to
- * property values made through folks (which are normally written to the primary
- * store) will always be used by {@link Folks.Individual}s.
- *
- * No further guarantees are made about the order of preference used for
- * choosing which property values to use for the {@link Folks.Individual}, other
- * than that the order may vary between properties, but is guaranteed to be
- * stable for a given property.
- */
-public class Folks.Individual : Object,
- AliasDetails,
- AvatarDetails,
- BirthdayDetails,
- EmailDetails,
- ExtendedInfo,
- FavouriteDetails,
- GenderDetails,
- GroupDetails,
- ImDetails,
- InteractionDetails,
- LocalIdDetails,
- LocationDetails,
- NameDetails,
- NoteDetails,
- PresenceDetails,
- PhoneDetails,
- PostalAddressDetails,
- RoleDetails,
- UrlDetails,
- WebServiceDetails
-{
- /* Stores the Personas contained in this Individual. */
- private SmallSet<Persona> _persona_set = new SmallSet<Persona> ();
- /* Read-only view of the above set */
- private Set<Persona> _persona_set_ro;
- /* Mapping from PersonaStore -> number of Personas from that store contained
- * in this Individual. There shouldn't be any entries with a number < 1.
- * This is used for working out when to disconnect from store signals. */
- private HashMap<unowned PersonaStore, uint> _stores =
- new HashMap<unowned PersonaStore, uint> (null, null);
- /* The number of Personas in this Individual which have
- * Persona.is_user == true. Iff this is > 0, Individual.is_user == true. */
- private uint _persona_user_count = 0;
-
- /**
- * The trust level of the Individual.
- *
- * This specifies how far the Individual can be trusted to be who it claims
- * to be. See the descriptions for the elements of {@link TrustLevel}.
- *
- * Clients should ''not'' allow linking of Individuals who have a trust level
- * of {@link TrustLevel.NONE}.
- *
- * @since 0.1.15
- */
- public TrustLevel trust_level { get; private set; }
-
- private LoadableIcon? _avatar = null;
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- [CCode (notify = false)]
- public LoadableIcon? avatar
- {
- get { return this._avatar; }
- set { this.change_avatar.begin (value); } /* not writeable */
- }
-
- /*
- * Change the individual's avatar.
- *
- * It's preferred to call this rather than setting {@link Individual.avatar}
- * directly, as this method gives error notification and will only return once
- * the avatar has been written to the relevant backing stores (or the
- * operation's failed).
- *
- * Setting this property is only guaranteed to succeed (and be written to
- * the backing store) if
- * {@link IndividualAggregator.ensure_individual_property_writeable} has been
- * called successfully on the individual for the property name ``avatar``.
- *
- * @param avatar the new avatar (or ``null`` to unset the avatar)
- * @throws PropertyError if setting the avatar failed
- * @since 0.6.3
- */
- public async void change_avatar (LoadableIcon? avatar) throws PropertyError
- {
- /* FIXME: Once https://bugzilla.gnome.org/show_bug.cgi?id=604827 is fixed,
- * this should be rewritten to use async delegates passed to a generic
- * _change_single_valued_property() method. */
- if ((this._avatar != null && ((!) this._avatar).equal (avatar)) ||
- (this._avatar == null && avatar == null))
- {
- return;
- }
-
- debug ("Setting avatar of individual '%s' to '%p'…", this.id, avatar);
-
- PropertyError? persona_error = null;
- var prop_changed = false;
-
- /* Try to write it to only the writeable Personas which have the
- * "avatar" property as writeable. */
- foreach (var p in this._persona_set)
- {
- unowned var _a = p as AvatarDetails;
- if (_a == null)
- {
- continue;
- }
- unowned var a = (!) _a;
-
- if ("avatar" in p.writeable_properties)
- {
- try
- {
- yield a.change_avatar (avatar);
- debug (" written to writeable persona '%s'", p.uid);
- prop_changed = true;
- }
- catch (PropertyError e)
- {
- /* Store the first error so we can throw it if setting the
- * avatar fails on every other persona. */
- if (persona_error == null)
- {
- persona_error = e;
- }
- }
- }
- }
-
- /* Failure? Changing the property failed on every suitable persona found
- * (and potentially zero suitable personas were found). */
- if (prop_changed == false)
- {
- if (persona_error == null)
- {
- persona_error = new PropertyError.NOT_WRITEABLE (
- _("Failed to change property ‘%s’: No suitable personas were found."),
- "avatar");
- }
-
- throw persona_error;
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public Folks.PresenceType presence_type { get; set; }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public string presence_status { get; set; default = ""; }
-
- /**
- * {@inheritDoc}
- */
- public string presence_message { get; set; default = ""; }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.9.5
- */
- public string[] client_types { get; set; }
-
- /**
- * Whether the Individual is the user.
- *
- * Iff the Individual represents the user – the person who owns the
- * account in the backend for each {@link Persona} in the Individual –
- * this is ``true``.
- *
- * It is //not// guaranteed that every {@link Persona} in the Individual has
- * its {@link Persona.is_user} set to the same value as the Individual. For
- * example, the user could own two Telepathy accounts, and have added the
- * other account as a contact in each account. The accounts will expose a
- * {@link Persona} for the user (which will have {@link Persona.is_user} set
- * to ``true``) //and// a {@link Persona} for the contact for the other
- * account (which will have {@link Persona.is_user} set to ``false``).
- *
- * It is guaranteed that iff this property is set to ``true`` on an
- * Individual, there will be at least one {@link Persona} in the Individual
- * with its {@link Persona.is_user} set to ``true``.
- *
- * It is guaranteed that there will only ever be one Individual with this
- * property set to ``true``.
- *
- * @since 0.3.0
- */
- public bool is_user { get; private set; }
-
- /**
- * A unique identifier for the Individual.
- *
- * This uniquely identifies the Individual, and persists across
- * {@link IndividualAggregator} instances. It may not persist across linking
- * the Individual with other Individuals.
- *
- * This is an opaque string and has no structure.
- *
- * If an identifier is required which will be used for a long-lived link
- * between different stored data, it may be more desirable to use the
- * {@link Persona.uid} of the most relevant {@link Persona} in the Individual
- * instead. For example, if storing references to Individuals who are tagged
- * in a photo, it may be safer to store the UID of the Persona whose backend
- * provided the photo (e.g. Facebook).
- *
- * As a special case, the ID defaults to an empty string when the individual
- * has no personas (i.e. if it’s just been constructed).
- */
- public string id { get; private set; default = ""; }
-
- /**
- * Emitted when the last of the Individual's {@link Persona}s has been
- * removed.
- *
- * At this point, the Individual is invalid, so any client referencing it
- * should unreference it and remove it from their UI.
- *
- * @param replacement_individual the individual which has replaced this one
- * due to linking, or ``null`` if this individual was removed for another
- * reason
- * @since 0.1.13
- */
- public signal void removed (Individual? replacement_individual);
-
- private string _display_name = "";
-
- /**
- * The name of this Individual to display in the UI.
- *
- * This value is set according to the following list of possibilities, each
- * one being tried first on the primary persona, then on all other personas in
- * the Individual, before falling back to the next item on the list:
- * # Alias
- * # Full name, structured name or nickname
- * # E-mail address
- * # Phone number
- * # Display ID (e.g. foo@example.org)
- * # Postal address
- * # _("Unnamed Person")
- *
- * @since 0.9.7
- */
- [CCode (notify = false)]
- public string display_name
- {
- get { return this._display_name; }
- }
-
- private string _alias = "";
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public string alias
- {
- get { return this._alias; }
- set { this.change_alias.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_alias (string alias) throws PropertyError
- {
- /* FIXME: Once https://bugzilla.gnome.org/show_bug.cgi?id=604827 is fixed,
- * this should be rewritten to use async delegates passed to a generic
- * _change_single_valued_property() method. */
- if (this._alias == alias)
- {
- return;
- }
-
- debug ("Setting alias of individual '%s' to '%s'…", this.id, alias);
-
- PropertyError? persona_error = null;
- var prop_changed = false;
-
- /* Try to write it to only the writeable Personas which have "alias"
- * as a writeable property. */
- foreach (var p in this._persona_set)
- {
- unowned var _a = p as AliasDetails;
- if (_a == null)
- {
- continue;
- }
- unowned var a = (!) _a;
-
- if ("alias" in p.writeable_properties)
- {
- try
- {
- yield a.change_alias (alias);
- debug (" written to writeable persona '%s'", p.uid);
- prop_changed = true;
- }
- catch (PropertyError e)
- {
- /* Store the first error so we can throw it if setting the
- * alias fails on every other persona. */
- if (persona_error == null)
- {
- persona_error = e;
- }
- }
- }
- }
-
- /* Failure? Changing the property failed on every suitable persona found
- * (and potentially zero suitable personas were found). */
- if (prop_changed == false)
- {
- if (persona_error == null)
- {
- persona_error = new PropertyError.NOT_WRITEABLE (
- _("Failed to change property ‘%s’: No suitable personas were found."),
- "alias");
- }
-
- throw persona_error;
- }
- }
-
- private StructuredName? _structured_name = null;
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public StructuredName? structured_name
- {
- get { return this._structured_name; }
- set { this.change_structured_name.begin (value); } /* not writeable */
- }
-
- private string _full_name = "";
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public string full_name
- {
- get { return this._full_name; }
- set { this.change_full_name.begin (value); } /* not writeable */
- }
-
- private string _nickname = "";
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public string nickname
- {
- get { return this._nickname; }
- set { this.change_nickname.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_nickname (string nickname) throws PropertyError
- {
- /* FIXME: Once https://bugzilla.gnome.org/show_bug.cgi?id=604827 is fixed,
- * this should be rewritten to use async delegates passed to a generic
- * _change_single_valued_property() method. */
-
- // Normalise null values to the empty string
- if (nickname == null)
- {
- nickname = "";
- }
-
- if (this._nickname == nickname)
- {
- return;
- }
-
- debug ("Setting nickname of individual '%s' to '%s'…", this.id, nickname);
-
- PropertyError? persona_error = null;
- var prop_changed = false;
-
- /* Try to write it to only the writeable Personas which have "nickname"
- * as a writeable property. */
- foreach (var p in this._persona_set)
- {
- unowned var _n = p as NameDetails;
- if (_n == null)
- {
- continue;
- }
- unowned var n = (!) _n;
-
- if ("nickname" in p.writeable_properties)
- {
- try
- {
- yield n.change_nickname (nickname);
- debug (" written to writeable persona '%s'", p.uid);
- prop_changed = true;
- }
- catch (PropertyError e)
- {
- /* Store the first error so we can throw it if setting the
- * nickname fails on every other persona. */
- if (persona_error == null)
- {
- persona_error = e;
- }
- }
- }
- }
-
- /* Failure? Changing the property failed on every suitable persona found
- * (and potentially zero suitable personas were found). */
- if (prop_changed == false)
- {
- if (persona_error == null)
- {
- persona_error = new PropertyError.NOT_WRITEABLE (
- _("Failed to change property ‘%s’: No suitable personas were found."),
- "nickname");
- }
-
- throw persona_error;
- }
- }
-
- private Gender _gender = Gender.UNSPECIFIED;
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public Gender gender
- {
- get { return this._gender; }
- set { this.change_gender.begin (value); } /* not writeable */
- }
-
- private SmallSet<UrlFieldDetails>? _urls = null;
- private Set<UrlFieldDetails>? _urls_ro = null;
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public Set<UrlFieldDetails> urls
- {
- get
- {
- this._update_urls (true, false, false);
- return this._urls_ro;
- }
- set { this.change_urls.begin (value); } /* not writeable */
- }
-
- private SmallSet<PhoneFieldDetails>? _phone_numbers = null;
- private Set<PhoneFieldDetails>? _phone_numbers_ro = null;
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public Set<PhoneFieldDetails> phone_numbers
- {
- get
- {
- this._update_phone_numbers (true, false, false);
- return this._phone_numbers_ro;
- }
- set { this.change_phone_numbers.begin (value); } /* not writeable */
- }
-
- private SmallSet<EmailFieldDetails>? _email_addresses = null;
- private Set<EmailFieldDetails>? _email_addresses_ro = null;
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public Set<EmailFieldDetails> email_addresses
- {
- get
- {
- this._update_email_addresses (true, false, false);
- return this._email_addresses_ro;
- }
- set { this.change_email_addresses.begin (value); } /* not writeable */
- }
-
- private SmallSet<RoleFieldDetails>? _roles = null;
- private Set<RoleFieldDetails>? _roles_ro = null;
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public Set<RoleFieldDetails> roles
- {
- get
- {
- this._update_roles (true, false, false);
- return this._roles_ro;
- }
- set { this.change_roles.begin (value); } /* not writeable */
- }
-
- private SmallSet<string>? _local_ids = null;
- private Set<string>? _local_ids_ro = null;
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public Set<string> local_ids
- {
- get
- {
- this._update_local_ids (true, false, false);
- return this._local_ids_ro;
- }
- set { this.change_local_ids.begin (value); } /* not writeable */
- }
-
- private Location? _location = null;
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public Location? location
- {
- get { return this._location; }
- set { this.change_location.begin (value); } /* not writeable */
- }
-
- private DateTime? _birthday = null;
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public DateTime? birthday
- {
- get { return this._birthday; }
- set { this.change_birthday.begin (value); } /* not writeable */
- }
-
- private string? _calendar_event_id = null;
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public string? calendar_event_id
- {
- get { return this._calendar_event_id; }
- set { this.change_calendar_event_id.begin (value); } /* not writeable */
- }
-
- private SmallSet<NoteFieldDetails>? _notes = null;
- private Set<NoteFieldDetails>? _notes_ro = null;
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public Set<NoteFieldDetails> notes
- {
- get
- {
- this._update_notes (true, false, false);
- return this._notes_ro;
- }
- set { this.change_notes.begin (value); } /* not writeable */
- }
-
- private SmallSet<PostalAddressFieldDetails>? _postal_addresses = null;
- private Set<PostalAddressFieldDetails>? _postal_addresses_ro = null;
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public Set<PostalAddressFieldDetails> postal_addresses
- {
- get
- {
- this._update_postal_addresses (true, false, false);
- return this._postal_addresses_ro;
- }
- set { this.change_postal_addresses.begin (value); } /* not writeable */
- }
-
- private bool _is_favourite = false;
-
- /**
- * Whether this Individual is a user-defined favourite.
- *
- * This property is ``true`` if any of this Individual's {@link Persona}s are
- * favourites).
- */
- [CCode (notify = false)]
- public bool is_favourite
- {
- get { return this._is_favourite; }
- set { this.change_is_favourite.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_is_favourite (bool is_favourite) throws PropertyError
- {
- /* FIXME: Once https://bugzilla.gnome.org/show_bug.cgi?id=604827 is fixed,
- * this should be rewritten to use async delegates passed to a generic
- * _change_single_valued_property() method. */
- if (this._is_favourite == is_favourite)
- {
- return;
- }
-
- debug ("Setting '%s' favourite status to %s…", this.id,
- is_favourite ? "TRUE" : "FALSE");
-
- PropertyError? persona_error = null;
- var prop_changed = false;
-
- /* Try to write it to only the Personas which have "is-favourite" as a
- * writeable property.
- *
- * NOTE: We don't check whether the persona's store is writeable, as we
- * want is-favourite status to propagate to all stores, if possible. This
- * is one property which is harmless to propagate. */
- foreach (var p in this._persona_set)
- {
- unowned var _a = p as FavouriteDetails;
- if (_a == null)
- {
- continue;
- }
- unowned var a = (!) _a;
-
- if ("is-favourite" in p.writeable_properties)
- {
- try
- {
- yield a.change_is_favourite (is_favourite);
- debug (" written to persona '%s'", p.uid);
- prop_changed = true;
- }
- catch (PropertyError e)
- {
- /* Store the first error so we can throw it if setting the
- * property fails on every other persona. */
- if (persona_error == null)
- {
- persona_error = e;
- }
- }
- }
- }
-
- /* Failure? Changing the property failed on every suitable persona found
- * (and potentially zero suitable personas were found). */
- if (prop_changed == false)
- {
- if (persona_error == null)
- {
- persona_error = new PropertyError.NOT_WRITEABLE (
- _("Failed to change property ‘%s’: No suitable personas were found."),
- "is-favourite");
- }
-
- throw persona_error;
- }
- }
-
- private SmallSet<string>? _groups = null;
- private Set<string>? _groups_ro = null;
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public Set<string> groups
- {
- get
- {
- this._update_groups (true, false, false);
- return this._groups_ro;
- }
- set { this.change_groups.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.2
- */
- public async void change_groups (Set<string> groups) throws PropertyError
- {
- /* FIXME: Once https://bugzilla.gnome.org/show_bug.cgi?id=604827 is fixed,
- * this should be rewritten to use async delegates passed to a generic
- * _change_single_valued_property() method. */
- debug ("Setting '%s' groups…", this.id);
-
- PropertyError? persona_error = null;
- var prop_changed = false;
-
- /* Try to write it to only the Personas which have "groups" as a
- * writeable property. */
- foreach (var p in this._persona_set)
- {
- unowned var _g = p as GroupDetails;
- if (_g == null)
- {
- continue;
- }
- unowned var g = (!) _g;
-
- if ("groups" in p.writeable_properties)
- {
- try
- {
- yield g.change_groups (groups);
- debug (" written to persona '%s'", p.uid);
- prop_changed = true;
- }
- catch (PropertyError e)
- {
- /* Store the first error so we can throw it if setting the
- * property fails on every other persona. */
- if (persona_error == null)
- {
- persona_error = e;
- }
- }
- }
- }
-
- /* Failure? Changing the property failed on every suitable persona found
- * (and potentially zero suitable personas were found). */
- if (prop_changed == false)
- {
- if (persona_error == null)
- {
- persona_error = new PropertyError.NOT_WRITEABLE (
- _("Failed to change property ‘%s’: No suitable personas were found."),
- "groups");
- }
-
- throw persona_error;
- }
- }
-
- private HashMultiMap<string, ImFieldDetails>? _im_addresses = null;
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public MultiMap<string, ImFieldDetails> im_addresses
- {
- get
- {
- this._update_im_addresses (true, false, false);
- return this._im_addresses;
- }
- set { this.change_im_addresses.begin (value); } /* not writeable */
- }
-
- private HashMultiMap<string, WebServiceFieldDetails>? _web_service_addresses =
- null;
-
- /**
- * {@inheritDoc}
- */
- [CCode (notify = false)]
- public MultiMap<string, WebServiceFieldDetails> web_service_addresses
- {
- get
- {
- this._update_web_service_addresses (true, false, false);
- return this._web_service_addresses;
- }
- /* Not writeable: */
- set { this.change_web_service_addresses.begin (value); }
- }
-
- /**
- * {@inheritDoc}
- */
- public uint im_interaction_count
- {
- get
- {
- uint counter = 0;
- /* Iterate over all personas and sum up their IM interaction counts*/
- foreach (var persona in this._persona_set)
- {
- unowned var my_interaction_details = persona as InteractionDetails;
- if (my_interaction_details != null)
- {
- counter = counter + my_interaction_details.im_interaction_count;
- }
- }
- return counter;
- }
- }
-
- /**
- * {@inheritDoc}
- */
- private DateTime? _last_im_interaction_datetime = null;
-
- public DateTime? last_im_interaction_datetime
- {
- get
- {
- if (this._last_im_interaction_datetime == null)
- {
- /* Iterate over all personas and get the latest IM interaction datetime */
- foreach (var persona in this._persona_set)
- {
- unowned var my_interaction_details = persona as InteractionDetails;
- if (my_interaction_details != null &&
- my_interaction_details.last_im_interaction_datetime != null)
- {
- DateTime interaction_datetime = my_interaction_details.last_im_interaction_datetime;
- if (this._last_im_interaction_datetime == null ||
- interaction_datetime.compare (this._last_im_interaction_datetime) == 1)
- {
- this._last_im_interaction_datetime = my_interaction_details.last_im_interaction_datetime;
- }
- }
- }
- }
- return this._last_im_interaction_datetime;
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public uint call_interaction_count
- {
- get
- {
- uint counter = 0;
- /* Iterate over all personas and sum up their call interaction counts*/
- foreach (var persona in this._persona_set)
- {
- unowned var my_interaction_details = persona as InteractionDetails;
- if (my_interaction_details != null)
- {
- counter = counter + my_interaction_details.call_interaction_count;
- }
- }
- return counter;
- }
- }
-
- /**
- * {@inheritDoc}
- */
- private DateTime? _last_call_interaction_datetime = null;
-
- public DateTime? last_call_interaction_datetime
- {
- get
- {
- if (this._last_call_interaction_datetime == null)
- {
- /* Iterate over all personas and get the latest IM interaction datetime */
- foreach (var persona in this._persona_set)
- {
- var my_interaction_details = persona as InteractionDetails;
- if (my_interaction_details != null &&
- my_interaction_details.last_call_interaction_datetime != null)
- {
- var interaction_datetime = my_interaction_details.last_call_interaction_datetime;
- if (this._last_call_interaction_datetime == null ||
- interaction_datetime.compare (this._last_call_interaction_datetime) > 1)
- {
- this._last_call_interaction_datetime = my_interaction_details.last_call_interaction_datetime;
- }
- }
- }
- }
- return this._last_call_interaction_datetime;
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.11.0
- */
- public ExtendedFieldDetails? get_extended_field (string name)
- {
- debug ("Getting extended field '%s' on '%s'…", name, this.id);
-
- /* Try to get it from the writeable Personas which have "extended-info"
- * as a writeable property. */
- foreach (var p in this._persona_set)
- {
- if ("extended-info" in p.writeable_properties)
- {
- unowned var e = p as ExtendedInfo;
- var details = e.get_extended_field (name);
- if (details != null)
- {
- return details;
- }
- }
- }
-
- return null;
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.11.0
- */
- public async void change_extended_field (
- string name, ExtendedFieldDetails value) throws PropertyError
- {
- debug ("Setting extended field '%s' on '%s'…", name, this.id);
-
- PropertyError? persona_error = null;
- var prop_changed = false;
-
- /* Try to write it to only the writeable Personas which have "extended-info"
- * as a writeable property. */
- foreach (var p in this._persona_set)
- {
- if ("extended-info" in p.writeable_properties)
- {
- unowned var e = p as ExtendedInfo;
- try
- {
- yield e.change_extended_field (name, value);
- debug (" written to writeable persona '%s'", p.uid);
- prop_changed = true;
- }
- catch (PropertyError e)
- {
- /* Store the first error so we can throw it if setting the
- * extended field fails on every other persona. */
- if (persona_error == null)
- {
- persona_error = e;
- }
- }
- }
- }
-
- /* Failure? Changing the property failed on every suitable persona found
- * (and potentially zero suitable personas were found). */
- if (prop_changed == false)
- {
- if (persona_error == null)
- {
- persona_error = new PropertyError.NOT_WRITEABLE (
- _("Failed to change property ‘%s’: No suitable personas were found."),
- "extended-info");
- }
-
- throw persona_error;
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.11.0
- */
- public async void remove_extended_field (string name) throws PropertyError
- {
- debug ("Removing extended field '%s' on '%s'…", name, this.id);
-
- PropertyError? persona_error = null;
-
- /* Try to remove it from all writeable Personas. */
- foreach (var p in this._persona_set)
- {
- if ("extended-info" in p.writeable_properties)
- {
- unowned var e = p as ExtendedInfo;
- try
- {
- yield e.remove_extended_field (name);
- debug (" removed from writeable persona '%s'", p.uid);
- }
- catch (PropertyError e)
- {
- /* Store the first error so we can throw it later. */
- if (persona_error == null)
- {
- persona_error = e;
- }
- }
- }
- }
-
- /* Failure? */
- if (persona_error != null)
- {
- throw persona_error;
- }
- }
-
- /**
- * The set of {@link Persona}s encapsulated by this Individual.
- *
- * There must always be at least one Persona in this set.
- *
- * No order is specified over the set of personas, as such an order may be
- * different across each of the properties implemented by the personas (e.g.
- * should they be ordered by presence, name, star sign, etc.?).
- *
- * Changing the set of personas may cause updates to the aggregated properties
- * provided by the Individual, resulting in property notifications for them.
- *
- * Changing the set of personas will not cause permanent linking/unlinking of
- * the added/removed personas to/from this Individual. To do that, call
- * {@link IndividualAggregator.link_personas} or
- * {@link IndividualAggregator.unlink_individual}, which will ensure the link
- * changes are written to the appropriate backend.
- *
- * @since 0.5.1
- */
- public Set<Persona> personas
- {
- get { return this._persona_set_ro; }
- set { this._set_personas (value, null); }
- }
-
- /**
- * Emitted when one or more {@link Persona}s are added to or removed from
- * the Individual. As the parameters are (unordered) sets, the orders of their
- * elements are undefined.
- *
- * @param added a set of {@link Persona}s which have been added
- * @param removed a set of {@link Persona}s which have been removed
- *
- * @since 0.5.1
- */
- public signal void personas_changed (Set<Persona> added,
- Set<Persona> removed);
-
- private static void _notify_alias_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_alias ();
- }
-
- private static void _notify_avatar_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_avatar ();
- }
-
- private static void _notify_full_name_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_full_name ();
- }
-
- private static void _notify_structured_name_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_structured_name ();
- }
-
- private static void _notify_nickname_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_nickname ();
- }
-
- private void _persona_group_changed_cb (string group, bool is_member)
- {
- this._update_groups (false);
- }
-
- private static void _notify_gender_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_gender ();
- }
-
- private static void _notify_urls_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_urls (false);
- }
-
- private static void _notify_phone_numbers_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_phone_numbers (false);
- }
-
- private static void _notify_postal_addresses_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_postal_addresses (false);
- }
-
- private static void _notify_email_addresses_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_email_addresses (false);
- }
-
- private static void _notify_roles_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_roles (false);
- }
-
- private static void _notify_birthday_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_birthday ();
- }
-
- private static void _notify_notes_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_notes (false);
- }
-
- private static void _notify_local_ids_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_local_ids (false);
- }
-
- private static void _notify_location_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_location ();
- }
-
- /**
- * Add or remove the Individual from the specified group.
- *
- * If ``is_member`` is ``true``, the Individual will be added to the
- * ``group``. If it is ``false``, they will be removed from the ``group``.
- *
- * The group membership change will propagate to every {@link Persona} in
- * the Individual.
- *
- * @param group a freeform group identifier
- * @param is_member whether the Individual should be a member of the group
- * @since 0.1.11
- */
- public async void change_group (string group, bool is_member)
- {
- foreach (var p in this._persona_set)
- {
- if (p is GroupDetails)
- ((GroupDetails) p).change_group.begin (group, is_member);
- }
-
- /* don't notify, since it hasn't happened in the persona backing stores
- * yet; react to that directly */
- }
-
- private static void _notify_presence_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_presence ();
- }
-
- private static void _notify_im_addresses_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_im_addresses (false);
- }
-
- private static void _notify_web_service_addresses_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_web_service_addresses (false);
- }
-
- private static void _notify_is_favourite_cb (Individual self, Persona p, ParamSpec ps)
- {
- self._update_is_favourite ();
- }
-
- private static void _notify_im_interaction_count_cb (Individual self, Persona p, ParamSpec ps)
- {
- /**
- * The property is pull rather than push. This function is called in
- * response to personas emitting a similar notification.
- */
- self.notify_property ("im-interaction-count");
- }
-
- private static void _notify_call_interaction_count_cb (Individual self, Persona p, ParamSpec ps)
- {
- /**
- * The property is pull rather than push. This function is called in
- * response to personas emitting a similar notification.
- */
- self.notify_property ("call-interaction-count");
- }
-
- private static void _notify_last_im_interaction_datetime_cb (Individual self, Persona p, ParamSpec ps)
- {
- /**
- * The property is pull rather than push. This function is called in
- * response to personas emitting a similar notification.
- */
- self._last_im_interaction_datetime = null;
- self.notify_property ("last-im-interaction-datetime");
- }
-
- private static void _notify_last_call_interaction_datetime_cb (Individual self, Persona p, ParamSpec ps)
- {
- /**
- * The property is pull rather than push. This function is called in
- * response to personas emitting a similar notification.
- */
- self._last_call_interaction_datetime = null;
- self.notify_property ("last-call-interaction-datetime");
- }
-
- [CCode (has_target = false)]
- private delegate void _UnboundNotifier (Individual self,
- Persona persona, ParamSpec ps);
-
- private struct _Notifier
- {
- unowned string property;
- _UnboundNotifier notify;
- }
-
- /* This contains static methods that take "this" as an explicit parameter,
- * so that we can have one big lookup table at the class level without
- * having to mess about with delegates.
- *
- * All keys in this array must be unique. */
- private const _Notifier _notifiers[] =
- {
- { "alias", Individual._notify_alias_cb },
- { "avatar", Individual._notify_avatar_cb },
- { "presence-message", Individual._notify_presence_cb },
- { "client-types", Individual._notify_presence_cb },
- { "presence-type", Individual._notify_presence_cb },
- { "im-addresses", Individual._notify_im_addresses_cb },
- { "web-service-addresses", Individual._notify_web_service_addresses_cb },
- { "is-favourite", Individual._notify_is_favourite_cb },
- { "structured-name", Individual._notify_structured_name_cb },
- { "full-name", Individual._notify_full_name_cb },
- { "nickname", Individual._notify_nickname_cb },
- { "gender", Individual._notify_gender_cb },
- { "urls", Individual._notify_urls_cb },
- { "phone-numbers", Individual._notify_phone_numbers_cb },
- { "email-addresses", Individual._notify_email_addresses_cb },
- { "roles", Individual._notify_roles_cb },
- { "birthday", Individual._notify_birthday_cb },
- { "notes", Individual._notify_notes_cb },
- { "postal-addresses", Individual._notify_postal_addresses_cb },
- { "local-ids", Individual._notify_local_ids_cb },
- { "location", Individual._notify_location_cb },
- { "im-interaction-count", Individual._notify_im_interaction_count_cb },
- { "call-interaction-count", Individual._notify_call_interaction_count_cb },
- { "last-im-interaction-datetime", Individual._notify_last_im_interaction_datetime_cb },
- { "last-call-interaction-datetime", Individual._notify_last_call_interaction_datetime_cb },
- };
-
- private void _persona_notify_cb (Object obj, ParamSpec ps)
- {
- unowned var persona = (Persona) obj; /* will abort on failure */
-
- /* It should not be possible for two Individuals to be simultaneously
- * connected to the same Persona (as _connect_to_persona() will disconnect
- * any previous Persona.individual), but warn (rather than asserting) just
- * in case, since this is a critical code path. */
- if (ps.name != "individual" &&
- persona.individual != this &&
- persona.individual != null)
- {
- warning ("Notification on property ‘%s’ of Persona %p (‘%s’) where " +
- "Persona.individual is %p but was expected to be %p.",
- ps.name, persona, persona.uid, persona.individual, this);
- return;
- }
- else if (ps.name == "individual")
- {
- if (persona.individual != this)
- {
- /* Remove the notified persona from our set of personas. */
- var remaining_personas = new SmallSet<Persona> ();
- remaining_personas.add_all (this._persona_set);
- remaining_personas.remove (persona);
-
- this._set_personas (remaining_personas, null);
- }
-
- return;
- }
-
- foreach (unowned _Notifier notifier in Individual._notifiers)
- {
- if (ps.name == notifier.property)
- {
- notifier.notify (this, persona, ps);
- break; /* assume all entries in notifiers are unique */
- }
- }
- }
-
- /**
- * Create a new Individual.
- *
- * The Individual can optionally be seeded with the {@link Persona}s in
- * ``personas``. Otherwise, it will have to have personas added using the
- * {@link Folks.Individual.personas} property after construction.
- *
- * @param personas a list of {@link Persona}s to initialise the
- * {@link Folks.Individual} with, or ``null``
- * @return a new Individual
- *
- * @since 0.5.1
- */
- public Individual (Set<Persona>? personas)
- {
- Object (personas: personas);
-
- debug ("Creating new Individual with %u Personas: %p",
- this._persona_set.size, this);
- }
-
- construct
- {
- this._persona_set_ro = this._persona_set.read_only_view;
- }
-
- ~Individual ()
- {
- debug ("Destroying Individual '%s': %p", this.id, this);
- }
-
- /* Emit the personas-changed signal, turning null parameters into empty sets
- * and ensuring that the signal is emitted with read-only views of the sets
- * so that signal handlers can't modify the sets. */
- private void _emit_personas_changed (Set<Persona>? added,
- Set<Persona>? removed)
- {
- var _added = added;
- var _removed = removed;
-
- if ((added == null || ((!) added).size == 0) &&
- (removed == null || ((!) removed).size == 0))
- {
- /* Emitting it with no added or removed personas is pointless */
- return;
- }
- else if (added == null)
- {
- _added = SmallSet.empty<Persona> ();
- }
- else if (removed == null)
- {
- _removed = SmallSet.empty<Persona> ();
- }
-
- // We've now guaranteed that both _added and _removed are non-null.
- this.personas_changed (((!) _added).read_only_view,
- ((!) _removed).read_only_view);
- }
-
- private void _store_removed_cb (PersonaStore store)
- {
- var remaining_personas = new SmallSet<Persona> ();
-
- /* Build a set of the remaining personas (those which weren't in the
- * removed store. */
- foreach (var persona in this._persona_set)
- {
- if (persona.store != store)
- {
- remaining_personas.add (persona);
- }
- }
-
- this._set_personas (remaining_personas, null);
- }
-
- private void _store_personas_changed_cb (PersonaStore store,
- Set<Persona> added,
- Set<Persona> removed,
- string? message,
- Persona? actor,
- GroupDetails.ChangeReason reason)
- {
- var remaining_personas = new SmallSet<Persona> ();
-
- /* Build a set of the remaining personas (those which aren't in the
- * set of removed personas). */
- foreach (var persona in this._persona_set)
- {
- if (!removed.contains (persona))
- {
- remaining_personas.add (persona);
- }
- }
-
- this._set_personas (remaining_personas, null);
- }
-
- private void _update_fields ()
- {
- this._update_groups (false);
- this._update_presence ();
- this._update_is_favourite ();
- this._update_avatar ();
- this._update_alias ();
- this._update_trust_level ();
- this._update_im_addresses (false);
- this._update_web_service_addresses (false);
- this._update_structured_name ();
- this._update_full_name ();
- this._update_nickname ();
- this._update_gender ();
- this._update_urls (false);
- this._update_phone_numbers (false);
- this._update_email_addresses (false);
- this._update_roles (false);
- this._update_birthday ();
- this._update_notes (false);
- this._update_postal_addresses (false);
- this._update_local_ids (false);
- this._update_location ();
-
- /* Entirely derived fields. */
- this._update_display_name ();
- }
-
- /* Delegate to update the value of a property on this individual from the
- * given chosen persona. The chosen_persona may be null, in which case we have
- * to set a default value.
- *
- * Used in _update_single_valued_property(), below. */
- private delegate void SingleValuedPropertySetter (Persona? chosen_persona);
-
- /* Delegate to filter a persona based on whether a given property is set.
- *
- * Used in _update_single_valued_property(), below. */
- private delegate bool PropertyFilter (Persona persona);
-
- /*
- * Update a single-valued property from the values in the personas.
- *
- * Single-valued properties are ones such as {@link Individual.alias} or
- * {@link Individual.gender} — as opposed to multi-valued ones (which are
- * generally sets) such as {@link Individual.im_addresses} or
- * {@link Individual.groups}.
- *
- * This function uses the given comparison function to order the personas in
- * this individual, with the highest-positioned persona (the “greatest”
- * persona in the total order) finally being passed to the setter function to
- * use in updating the individual's value for the given property. i.e. If
- * ``compare_func(a, b)`` is called and returns > 0, persona ``a`` will be
- * passed to the setter.
- *
- * At a level above ``compare_func``, the function always prefers personas
- * from the primary store (see {@link IndividualAggregator.primary_store})
- * over those which aren't.
- *
- * Note that if a suitable persona isn't found in the individual (if, for
- * example, no personas in the individual implement the desired interface),
- * ``null`` will be passed to ``setter``, which should then set the
- * individual's property to a default value.
- *
- * @param interface_type the type of interface which all personas under
- * consideration must implement ({@link Persona} to select all personas)
- * @param compare_func comparison function to order personas for selection
- * @param prop_name name of the property being set, as used in
- * {@link Persona.writeable_properties}
- * @param setter function to update the individual with the chosen value
- * @since 0.6.2
- */
- private void _update_single_valued_property (Type interface_type,
- PropertyFilter filter_func,
- CompareFunc<Persona> compare_func, string prop_name,
- SingleValuedPropertySetter setter)
- {
- CompareDataFunc<Persona> primary_compare_func = (a, b) =>
- {
- return_val_if_fail (a != null, 0);
- return_val_if_fail (b != null, 0);
-
- /* Always prefer values which are set over those which aren't. */
- var a_is_set = filter_func (a);
- var b_is_set = filter_func (b);
-
- if (a_is_set != b_is_set)
- {
- return (a_is_set ? 1 : 0) - (b_is_set ? 1 : 0);
- }
-
- var a_is_primary = a.store.is_primary_store;
- var b_is_primary = b.store.is_primary_store;
-
- if (a_is_primary != b_is_primary)
- {
- return (a_is_primary ? 1 : 0) - (b_is_primary ? 1 : 0);
- }
-
- /* If both personas have the same is-primary value, prefer personas
- * which have the given property as writeable over those which
- * don't. */
- var a_is_writeable = (prop_name in a.writeable_properties);
- var b_is_writeable = (prop_name in b.writeable_properties);
-
- if (a_is_writeable != b_is_writeable)
- {
- return (a_is_writeable ? 1 : 0) - (b_is_writeable ? 1 : 0);
- }
-
- /* If both personas have the same writeability for this property, fall
- * back to the given comparison function. If the comparison function
- * gives them an equal order, we use the personas' UIDs to ensure that
- * we end up with a total order over all personas in the individual
- * (otherwise we might end up with unstable property values). */
- var order = compare_func (a, b);
-
- if (order == 0)
- {
- order = strcmp (a.uid, b.uid);
- }
-
- return order;
- };
-
- unowned Persona? candidate_p = null;
-
- foreach (var p in this._persona_set)
- {
- /* We only care about personas implementing the given interface. */
- if (p.get_type ().is_a (interface_type))
- {
- if (candidate_p == null ||
- primary_compare_func (p, (!) candidate_p) > 0)
- {
- candidate_p = p;
- }
- }
- }
-
- /* Update the property with the values from the best candidate persona we
- * found. Note that it's possible for candidate_p to be null if (e.g.)
- * none of this._persona_set implemented the interface. */
- setter (candidate_p);
- }
-
- /* Delegate to add the values of a property from all personas to the
- * collection of values for that property in this individual.
- *
- * Used in _update_multi_valued_property(), below. */
- private delegate bool MultiValuedPropertySetter ();
-
- /* Delegate to get whether a multi-valued property in this Individual has not
- * been initialised yet (and is thus still null).
- *
- * Used in _update_multi_valued_property(), below. */
- private delegate bool PropertyIsNull ();
-
- /* Delegate to create a new empty collection for a multi-valued property in
- * this Individual and assign it to the property.
- *
- * Used in _update_multi_valued_property(), below. */
- private delegate void CollectionCreator ();
-
- /*
- * Update a multi-valued property from the values in the personas.
- *
- * Multi-valued properties are ones such as {@link Individual.notes} or
- * {@link Individual.email_addresses} which have multiple values taken as the
- * union of the values listed by the personas for those properties.
- *
- * This function handles lazy instantiation of the multi-valued property. If
- * ``create_if_not_exist`` is ``true``, the property is guaranteed to be
- * created (by ``create_collection``) and set to a non-``null`` value before
- * this function returns.
- *
- * If ``create_if_not_exist`` is ``false``, however, the property may not be
- * instantiated if it hasn't already been accessed through its property
- * getter. In this case, a change notification will be emitted for the
- * property and this function will return immediately.
- *
- * If ``force_update`` is ``true``, then existing values get updated (if
- * the current value is different) or created (according to the
- * ``create_if_not_exist`` value). Otherwise the function only ensures
- * that there is a value (if ``create_if_not_exist`` is set) and leaves
- * existing values unchanged.
- *
- * If the property value is to be instantiated, or already has been
- * instantiated, its value is updated by ``setter`` from the values of the
- * property in the individual's personas.
- *
- * @param prop_name name of the property being set, as used in
- * {@link Persona.writeable_properties}
- * @param create_if_not_exist ``true`` to ensure the property is non-null;
- * ``false`` otherwise
- * @param prop_is_null function returning ``true`` iff the property is
- * currently ``null``
- * @param create_collection function creating a new collection/container for
- * the property values and assigning it to the property (and updating the
- * property's read-only view as necessary)
- * @param setter function which adds the values from the individual's
- * personas' values for the property to the individual's value for the
- * property; it returns ``true`` if the property value has changed
- * @since 0.7.4
- */
- private void _update_multi_valued_property (string prop_name,
- bool create_if_not_exist, PropertyIsNull prop_is_null,
- CollectionCreator create_collection, MultiValuedPropertySetter setter,
- bool emit_notification = true,
- bool force_update = true)
- {
- /* If the set of values doesn't exist, and we're not meant to lazily
- * create it, then simply emit a notification (since the set might've
- * changed — we can't be sure, but emitting is a safe over-estimate) and
- * return. */
- bool created = false;
- if (prop_is_null ())
- {
- /* Notify and return. */
- if (create_if_not_exist == false)
- {
- if (emit_notification)
- {
- this.notify_property (prop_name);
- }
- return;
- }
-
- /* Lazily instantiate the set of IM addresses. */
- create_collection ();
- created = true;
- }
-
- /* Re-populate the collection as the union of the values in the
- * individual's personas. Do this when an empty property was just
- * created or we were asked to explicitly (usually because the caller
- * knows that the current value is out-dated).
- */
- if ((created || force_update) && setter () == true && emit_notification)
- {
- this.notify_property (prop_name);
- }
- }
-
- private void _update_groups (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
- {
- /* If the set of groups doesn't exist, and we're not meant to lazily
- * create it, then simply emit a notification (since the set might've
- * changed — we can't be sure, but emitting is a safe over-estimate) and
- * return. */
- bool created = false;
- if (this._groups == null && create_if_not_exist == false)
- {
- if (emit_notification)
- {
- this.notify_property ("groups");
- }
- return;
- }
-
- /* Lazily instantiate the set of groups. */
- else if (this._groups == null)
- {
- this._groups = new SmallSet<string> ();
- this._groups_ro = this._groups.read_only_view;
- created = true;
- }
-
- /* Don't touch existing content in get(). */
- if (!created && !force_update)
- return;
-
- var new_groups = new SmallSet<string> ();
-
- /* FIXME: this should partition the personas by store (maybe we should
- * keep that mapping in general in this class), and execute
- * "groups-changed" on the store (with the set of personas), to allow the
- * back-end to optimize it (like Telepathy will for MembersChanged for the
- * groups channel list) */
- foreach (var p in this._persona_set)
- {
- if (p is GroupDetails)
- {
- unowned var persona = (GroupDetails) p;
-
- foreach (var group in persona.groups)
- {
- new_groups.add (group);
- }
- }
- }
-
- foreach (var group in new_groups)
- {
- if (this._groups.add (group) && emit_notification)
- {
- this.group_changed (group, true);
- }
- }
-
- /* buffer the removals, so we don't remove while iterating */
- var removes = new GLib.List<string> ();
- foreach (var group in this._groups)
- {
- if (!new_groups.contains (group))
- removes.prepend (group);
- }
-
- removes.foreach ((l) =>
- {
- unowned string group = (string) l;
- this._groups.remove (group);
- if (emit_notification)
- {
- this.group_changed (group, false);
- }
- });
- }
-
- private void _update_presence ()
- {
- this._update_single_valued_property (typeof (PresenceDetails), (p) =>
- {
- return ((PresenceDetails) p).presence_type != PresenceType.UNSET;
- }, (a, b) =>
- {
- var a_presence = ((PresenceDetails) a).presence_type;
- var b_presence = ((PresenceDetails) b).presence_type;
-
- return PresenceDetails.typecmp (a_presence, b_presence);
- }, "presence", (p) =>
- {
- unowned var presence_message = ""; /* must not be null */
- unowned var presence_status = ""; /* must not be null */
- string[] client_types = {};
- var presence_type = Folks.PresenceType.UNSET;
-
- if (p != null)
- {
- presence_type = ((PresenceDetails) p).presence_type;
- presence_message = ((PresenceDetails) p).presence_message;
- presence_status = ((PresenceDetails) p).presence_status;
- client_types = ((PresenceDetails) p).client_types;
- }
-
- /* Only notify if any of the values have changed. */
- if (this.presence_type != presence_type ||
- this.presence_message != presence_message ||
- this.presence_status != presence_status ||
- this.client_types != client_types)
- {
- this.freeze_notify ();
- this.presence_message = presence_message;
- this.presence_type = presence_type;
- this.presence_status = presence_status;
- this.client_types = client_types;
- this.thaw_notify ();
- }
- });
- }
-
- private void _update_is_favourite ()
- {
- this._update_single_valued_property (typeof (FavouriteDetails), (p) =>
- {
- return ((FavouriteDetails) p).is_favourite;
- }, (a, b) =>
- {
- var a_is_favourite = ((FavouriteDetails) a).is_favourite;
- var b_is_favourite = ((FavouriteDetails) b).is_favourite;
-
- return ((a_is_favourite == true) ? 1 : 0) -
- ((b_is_favourite == true) ? 1 : 0);
- }, "is-favourite", (p) =>
- {
- var favourite = false;
-
- if (p != null)
- {
- favourite = ((FavouriteDetails) p).is_favourite;
- }
-
- /* Only notify if the value has changed. We have to set the private
- * member and notify manually, or we'd end up propagating the new
- * favourite status back down to all our Personas. */
- if (this._is_favourite != favourite)
- {
- this._is_favourite = favourite;
- this.notify_property ("is-favourite");
- }
- });
- }
-
- private unowned string _look_up_alias_for_display_name (Persona? p)
- {
- unowned var a = p as AliasDetails;
- if (a != null && a.alias != null)
- {
- return a.alias;
- }
-
- return "";
- }
-
- private string _look_up_name_details_for_display_name (Persona? p)
- {
- unowned var n = p as NameDetails;
- if (n != null)
- {
- if (n.full_name != "")
- {
- return n.full_name;
- }
- else if (n.structured_name != null)
- {
- return n.structured_name.to_string ();
- }
- else if (n.nickname != "")
- {
- return n.nickname;
- }
- }
-
- return "";
- }
-
- private unowned string _look_up_email_address_for_display_name (Persona? p)
- {
- unowned var e = p as EmailDetails;
- if (e != null)
- {
- foreach (var email_fd in ((!) e).email_addresses)
- {
- if (email_fd.value != null)
- {
- return email_fd.value;
- }
- }
- }
-
- return "";
- }
-
- private unowned string _look_up_phone_number_for_display_name (Persona? p)
- {
- unowned var e = p as PhoneDetails;
- if (e != null)
- {
- foreach (var phone_fd in ((!) e).phone_numbers)
- {
- if (phone_fd.value != null)
- {
- return phone_fd.value;
- }
- }
- }
-
- return "";
- }
-
- private unowned string _look_up_display_id_for_display_name (Persona? p)
- {
- // Sometimes, the display_id will fall back to the IID.
- // The last condition makes sure we don't use that as a display name
- if (p != null && p.display_id != null && p.display_id != p.iid)
- {
- return p.display_id;
- }
-
- return "";
- }
-
- private string _look_up_postal_address_for_display_name (Persona? p)
- {
- unowned var address_details = p as PostalAddressDetails;
- if (address_details != null)
- {
- foreach (var pa_fd in ((!) address_details).postal_addresses)
- {
- var pa = pa_fd.value;
- if (pa != null)
- {
- return pa.to_string ();
- }
- }
- }
-
- return "";
- }
-
- private void _update_display_name ()
- {
- unowned Persona? primary_persona = null;
- var new_display_name = "";
-
- /* Find the primary persona first. The primary persona's values will be
- * preferred in every case where they're set. */
- foreach (var p in this._persona_set)
- {
- if (p.store.is_primary_store)
- {
- primary_persona = p;
- break;
- }
- }
-
- /* See if any persona has an alias set. */
- new_display_name = this._look_up_alias_for_display_name (primary_persona);
-
- foreach (var p in this._persona_set)
- {
- if (new_display_name != "")
- {
- break;
- }
-
- new_display_name = this._look_up_alias_for_display_name (p);
- }
-
- /* Try NameDetails next. */
- if (new_display_name == "")
- {
- new_display_name =
- this._look_up_name_details_for_display_name (primary_persona);
-
- foreach (var p in this._persona_set)
- {
- if (new_display_name != "")
- {
- break;
- }
-
- new_display_name =
- this._look_up_name_details_for_display_name (p);
- }
- }
-
- /* Now the e-mail addresses. */
- if (new_display_name == "")
- {
- new_display_name =
- this._look_up_email_address_for_display_name (primary_persona);
-
- foreach (var p in this._persona_set)
- {
- if (new_display_name != "")
- {
- break;
- }
-
- new_display_name =
- this._look_up_email_address_for_display_name (p);
- }
- }
-
- /* Now the phone numbers. */
- if (new_display_name == "")
- {
- new_display_name =
- this._look_up_phone_number_for_display_name (primary_persona);
-
- foreach (var p in this._persona_set)
- {
- if (new_display_name != "")
- {
- break;
- }
-
- new_display_name =
- this._look_up_phone_number_for_display_name (p);
- }
- }
-
- /* Now the display-id. */
- if (new_display_name == "")
- {
- new_display_name =
- this._look_up_display_id_for_display_name (primary_persona);
-
- foreach (var p in this._persona_set)
- {
- if (new_display_name != "")
- {
- break;
- }
-
- new_display_name =
- this._look_up_display_id_for_display_name (p);
- }
- }
-
- /* Finally fall back to the postal address. */
- if (new_display_name == "")
- {
- new_display_name =
- this._look_up_postal_address_for_display_name (primary_persona);
-
- foreach (var p in this._persona_set)
- {
- if (new_display_name != "")
- {
- break;
- }
-
- new_display_name =
- this._look_up_postal_address_for_display_name (p);
- }
- }
-
- /* Ultimate fall back: a static string. */
- if (new_display_name == "")
- {
- /* Translators: This is the default name for an Individual
- * when displayed in the UI if no personal details are available
- * for them. */
- new_display_name = _("Unnamed Person");
- }
-
- if (new_display_name != this._display_name)
- {
- this._display_name = new_display_name;
- debug ("Setting display name ‘%s’", new_display_name);
- this.notify_property ("display-name");
- }
- }
-
- private void _update_alias ()
- {
- this._update_single_valued_property (typeof (AliasDetails), (p) =>
- {
- unowned var alias = ((AliasDetails) p).alias;
- return_val_if_fail (alias != null, false);
-
- return (alias.strip () != ""); /* empty aliases are unset */
- }, (a, b) =>
- {
- unowned var a_alias = ((AliasDetails) a).alias;
- unowned var b_alias = ((AliasDetails) b).alias;
-
- return_val_if_fail (a_alias != null, 0);
- return_val_if_fail (b_alias != null, 0);
-
- var a_is_empty = (a_alias.strip () == "") ? 1 : 0;
- var b_is_empty = (b_alias.strip () == "") ? 1 : 0;
-
- /* We prefer to not have an alias which is the same as the Persona's
- * display-id, since having such an alias implies that it's the
- * default. However, we prefer using such an alias to using the
- * Persona's UID, which is our ultimate fallback (below). */
- var a_is_display_id = (a_alias == a.display_id) ? 1 : 0;
- var b_is_display_id = (b_alias == b.display_id) ? 1 : 0;
-
- return (b_is_empty + b_is_display_id) -
- (a_is_empty + a_is_display_id);
- }, "alias", (p) =>
- {
- string alias = ""; /* must not be null */
-
- if (p != null)
- {
- alias = ((AliasDetails) p).alias.strip ();
- }
-
- /* Only notify if the value has changed. We have to set the private
- * member and notify manually, or we'd end up propagating the new
- * alias back down to all our Personas, even if it's a fallback
- * display ID or something else undesirable. */
- if (this._alias != alias)
- {
- debug ("Setting alias ‘%s’", alias);
- this._alias = (owned) alias;
- this.notify_property ("alias");
-
- this._update_display_name ();
- }
- });
- }
-
- private void _update_avatar ()
- {
- this._update_single_valued_property (typeof (AvatarDetails), (p) =>
- {
- return ((AvatarDetails) p).avatar != null;
- }, (a, b) =>
- {
- /* We can't compare two set avatars efficiently. See: bgo#652721. */
- return 0;
- }, "avatar", (p) =>
- {
- unowned LoadableIcon? avatar = null;
-
- if (p != null)
- {
- avatar = ((AvatarDetails) p).avatar;
- }
-
- /* only notify if the value has changed */
- if ((this._avatar == null && avatar != null) ||
- (this._avatar != null &&
- (avatar == null || !((!) this._avatar).equal (avatar))))
- {
- this._avatar = avatar;
- this.notify_property ("avatar");
- }
- });
- }
-
- private void _update_trust_level ()
- {
- var trust_level = TrustLevel.PERSONAS;
-
- foreach (var p in this._persona_set)
- {
- if (p.is_user == false &&
- p.store.trust_level == PersonaStoreTrust.NONE)
- trust_level = TrustLevel.NONE;
- }
-
- /* Only notify if the value has changed */
- if (this.trust_level != trust_level)
- this.trust_level = trust_level;
- }
-
- private void _update_im_addresses (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
- {
- this._update_multi_valued_property ("im-addresses",
- create_if_not_exist, () => { return this._im_addresses == null; },
- () =>
- {
- this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
- null, null, AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- },
- () =>
- {
- var new_im_addresses = new HashMultiMap<string, ImFieldDetails> (
- null, null, AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- foreach (var persona in this._persona_set)
- {
- /* We only care about personas implementing the given interface. */
- unowned var im_details = persona as ImDetails;
- if (im_details != null)
- {
- var iter = im_details.im_addresses.map_iterator ();
-
- while (iter.next ())
- new_im_addresses.set (iter.get_key (),
- iter.get_value ());
- }
- }
-
- if (!Utils.multi_map_str_afd_equal (new_im_addresses,
- this._im_addresses))
- {
- this._im_addresses = new_im_addresses;
- return true;
- }
-
- return false;
- }, emit_notification, force_update);
- }
-
- private void _update_web_service_addresses (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
- {
- this._update_multi_valued_property ("web-service-addresses",
- create_if_not_exist,
- () => { return this._web_service_addresses == null; },
- () =>
- {
- this._web_service_addresses =
- new HashMultiMap<string, WebServiceFieldDetails> (null, null,
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- },
- () =>
- {
- var new_web_service_addresses =
- new HashMultiMap<string, WebServiceFieldDetails> (null, null,
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- foreach (var persona in this._persona_set)
- {
- /* We only care about personas implementing the given interface. */
- unowned var web_service_details = persona as WebServiceDetails;
- if (web_service_details != null)
- {
- var iter = web_service_details.web_service_addresses.map_iterator ();
-
- while (iter.next ())
- new_web_service_addresses.set (iter.get_key (),
- iter.get_value ());
- }
- }
-
- if (!Utils.multi_map_str_afd_equal (new_web_service_addresses,
- this._web_service_addresses))
- {
- this._web_service_addresses = new_web_service_addresses;
- return true;
- }
-
- return false;
- }, emit_notification, force_update);
- }
-
- /* Note: This causes the Persona to be stolen away from its current
- * Individual. */
- private void _connect_to_persona (Persona persona)
- {
- if (persona.individual != null && persona.individual != this)
- {
- /* Disconnect the previous Individual. This atomically avoids having
- * two Individuals connected to the same Persona simultaneously. */
- persona.individual._disconnect_from_persona (persona, this);
- }
-
- persona.individual = this;
-
- /* We're interested in most, if not all, signals from a persona,
- * so avoid a significant amount of GObject signal overhead by
- * connecting to the entire signal and demultiplexing it ourselves. */
- persona.notify.connect (this._persona_notify_cb);
-
- if (persona is GroupDetails)
- {
- ((GroupDetails) persona).group_changed.connect (
- this._persona_group_changed_cb);
- }
- }
-
- private void _update_structured_name ()
- {
- this._update_single_valued_property (typeof (NameDetails), (p) =>
- {
- unowned var name = ((NameDetails) p).structured_name;
- return (name != null && !((!) name).is_empty ());
- }, (a, b) =>
- {
- /* Can't compare two set names. */
- return 0;
- }, "structured-name", (p) =>
- {
- unowned StructuredName? name = null;
-
- if (p != null)
- {
- name = ((NameDetails) p).structured_name;
-
- if (name != null && ((!) name).is_empty ())
- {
- name = null;
- }
- }
-
- if ((this._structured_name == null && name != null) ||
- (this._structured_name != null &&
- (name == null || !((!) this._structured_name).equal ((!) name))))
- {
- this._structured_name = name;
- this.notify_property ("structured-name");
-
- this._update_display_name ();
- }
- });
- }
-
- private void _update_full_name ()
- {
- this._update_single_valued_property (typeof (NameDetails), (p) =>
- {
- unowned var name = ((NameDetails) p).full_name;
- return_val_if_fail (name != null, false);
-
- return (name.strip () != ""); /* empty names are unset */
- }, (a, b) =>
- {
- /* Can't compare two set names. */
- return 0;
- }, "full-name", (p) =>
- {
- string new_full_name = ""; /* must not be null */
-
- if (p != null)
- {
- new_full_name = ((NameDetails) p).full_name.strip ();
- }
-
- if (new_full_name != this._full_name)
- {
- this._full_name = new_full_name;
- this.notify_property ("full-name");
-
- this._update_display_name ();
- }
- });
- }
-
- private void _update_nickname ()
- {
- this._update_single_valued_property (typeof (NameDetails), (p) =>
- {
- unowned var nickname = ((NameDetails) p).nickname;
- return_val_if_fail (nickname != null, false);
-
- return (nickname.strip () != ""); /* empty names are unset */
- }, (a, b) =>
- {
- /* Can't compare two set names. */
- return 0;
- }, "nickname", (p) =>
- {
- string new_nickname = ""; /* must not be null */
-
- if (p != null)
- {
- new_nickname = ((NameDetails) p).nickname.strip ();
- }
-
- if (new_nickname != this._nickname)
- {
- this._nickname = new_nickname;
- this.notify_property ("nickname");
-
- this._update_display_name ();
- }
- });
- }
-
- private void _disconnect_from_persona (Persona persona,
- Individual? replacement_individual)
- {
- persona.notify.disconnect (this._persona_notify_cb);
-
- if (persona is GroupDetails)
- {
- ((GroupDetails) persona).group_changed.disconnect (
- this._persona_group_changed_cb);
- }
-
- /* Don't update the individual if the persona's been added to the new one
- * already (and thus the new individual has already changed
- * persona.individual).
- *
- * FIXME: Ideally, we'd assert that a persona can't be added to a new
- * individual before it's removed from the old one. However, this
- * currently isn't possible due to the way the aggregator works. When the
- * aggregator's rewritten, it would be nice to fix this. */
- if (persona.individual == this)
- {
- /* It may be the case that the persona's being removed from the
- * individual (i.e. the replacement individual is non-null, but
- * doesn't contain this persona). In this case, we need to set the
- * persona's individual to null. */
- if (replacement_individual != null &&
- persona in ((!) replacement_individual).personas)
- {
- persona.individual = replacement_individual;
- }
- else
- {
- persona.individual = null;
- }
- }
- }
-
- private void _update_gender ()
- {
- this._update_single_valued_property (typeof (GenderDetails), (p) =>
- {
- return ((GenderDetails) p).gender != Gender.UNSPECIFIED;
- }, (a, b) =>
- {
- /* It would be sexist to rank one gender over another.
- * Besides, how often will we see two personas in the same individual
- * which have different genders? */
- return 0;
- }, "gender", (p) =>
- {
- var new_gender = Gender.UNSPECIFIED;
-
- if (p != null)
- {
- new_gender = ((GenderDetails) p).gender;
- }
-
- if (new_gender != this.gender)
- {
- this._gender = new_gender;
- this.notify_property ("gender");
- }
- });
- }
-
- private void _update_urls (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
- {
- this._update_multi_valued_property ("urls", create_if_not_exist,
- () => { return this._urls == null; },
- () =>
- {
- this._urls = new SmallSet<UrlFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._urls_ro = this._urls.read_only_view;
- },
- () =>
- {
- var new_urls = new SmallSet<UrlFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- var urls_set = new HashMap<unowned string,
- unowned UrlFieldDetails> (
- null, null, AbstractFieldDetails<string>.equal_static);
-
- foreach (var persona in this._persona_set)
- {
- /* We only care about personas implementing the given
- * interface. If the same URL exists multiple times we merge
- * the parameters. */
- unowned var url_details = persona as UrlDetails;
- if (url_details != null)
- {
- foreach (var url_fd in ((!) url_details).urls)
- {
- var existing = urls_set.get (url_fd.value);
- if (existing != null)
- {
- existing.extend_parameters (url_fd.parameters);
- }
- else
- {
- var new_url_fd =
- new UrlFieldDetails (url_fd.value);
- new_url_fd.extend_parameters (url_fd.parameters);
- urls_set.set (new_url_fd.value, new_url_fd);
- new_urls.add (new_url_fd);
- }
- }
- }
- }
-
- if (!Utils.set_afd_equal (new_urls, this._urls))
- {
- this._urls = new_urls;
- this._urls_ro = new_urls.read_only_view;
- return true;
- }
-
- return false;
- }, emit_notification, force_update);
- }
-
- private void _update_phone_numbers (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
- {
- this._update_multi_valued_property ("phone-numbers", create_if_not_exist,
- () => { return this._phone_numbers == null; },
- () =>
- {
- this._phone_numbers = new SmallSet<PhoneFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._phone_numbers_ro = this._phone_numbers.read_only_view;
- },
- () =>
- {
- var new_phone_numbers = new SmallSet<PhoneFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- var phone_numbers_set = new HashMap<string, PhoneFieldDetails> (
- null, null, AbstractFieldDetails<string>.equal_static);
-
- foreach (var persona in this._persona_set)
- {
- /* We only care about personas implementing the given
- * interface. If the same phone number exists multiple times
- * we merge the parameters. */
- unowned var phone_details = persona as PhoneDetails;
- if (phone_details != null)
- {
- foreach (var phone_fd in ((!) phone_details).phone_numbers)
- {
- var existing = phone_numbers_set.get (phone_fd.value);
- if (existing != null)
- {
- existing.extend_parameters (phone_fd.parameters);
- }
- else
- {
- var new_fd =
- new PhoneFieldDetails (phone_fd.value);
- new_fd.extend_parameters (phone_fd.parameters);
- phone_numbers_set.set (new_fd.value, new_fd);
- new_phone_numbers.add (new_fd);
- }
- }
- }
- }
-
- if (!Utils.set_string_afd_equal (new_phone_numbers, this._phone_numbers))
- {
- this._phone_numbers = new_phone_numbers;
- this._phone_numbers_ro = new_phone_numbers.read_only_view;
- return true;
- }
-
- return false;
- }, emit_notification, force_update);
- }
-
- private void _update_email_addresses (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
- {
- this._update_multi_valued_property ("email-addresses",
- create_if_not_exist, () => { return this._email_addresses == null; },
- () =>
- {
- this._email_addresses = new SmallSet<EmailFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._email_addresses_ro = this._email_addresses.read_only_view;
- },
- () =>
- {
- var new_email_addresses = new SmallSet<EmailFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- var emails_set = new HashMap<string, EmailFieldDetails> (
- null, null, AbstractFieldDetails<string>.equal_static);
-
- foreach (var persona in this._persona_set)
- {
- /* We only care about personas implementing the given
- * interface. If the same e-mail address exists multiple times
- * we merge the parameters. */
- unowned var email_details = persona as EmailDetails;
- if (email_details != null)
- {
- foreach (var email_fd in ((!) email_details).email_addresses)
- {
- var existing = emails_set.get (email_fd.value);
- if (existing != null)
- {
- existing.extend_parameters (email_fd.parameters);
- }
- else
- {
- var new_email_fd =
- new EmailFieldDetails (email_fd.value,
- email_fd.parameters);
- emails_set.set (new_email_fd.value, new_email_fd);
- new_email_addresses.add (new_email_fd);
- }
- }
- }
- }
-
- if (!Utils.set_afd_equal (new_email_addresses,
- this._email_addresses))
- {
- this._email_addresses = new_email_addresses;
- this._email_addresses_ro = new_email_addresses.read_only_view;
- return true;
- }
-
- return false;
- }, emit_notification, force_update);
- }
-
- private void _update_roles (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
- {
- this._update_multi_valued_property ("roles", create_if_not_exist,
- () => { return this._roles == null; },
- () =>
- {
- this._roles = new SmallSet<RoleFieldDetails> (
- AbstractFieldDetails<Role>.hash_static,
- AbstractFieldDetails<Role>.equal_static);
- this._roles_ro = this._roles.read_only_view;
- },
- () =>
- {
- var new_roles = new SmallSet<RoleFieldDetails> (
- AbstractFieldDetails<Role>.hash_static,
- AbstractFieldDetails<Role>.equal_static);
-
- foreach (var persona in this._persona_set)
- {
- var role_details = persona as RoleDetails;
- if (role_details != null)
- {
- foreach (var role_fd in ((!) role_details).roles)
- {
- new_roles.add (role_fd);
- }
- }
- }
-
- if (!Utils.set_afd_equal (new_roles, this._roles))
- {
- this._roles = new_roles;
- this._roles_ro = new_roles.read_only_view;
- return true;
- }
-
- return false;
- }, emit_notification, force_update);
- }
-
- private void _update_local_ids (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
- {
- this._update_multi_valued_property ("local-ids", create_if_not_exist,
- () => { return this._local_ids == null; },
- () =>
- {
- this._local_ids = new SmallSet<string> ();
- this._local_ids_ro = this._local_ids.read_only_view;
- },
- () =>
- {
- var new_local_ids = new SmallSet<string> ();
-
- foreach (var persona in this._persona_set)
- {
- unowned var local_id_details = persona as LocalIdDetails;
- if (local_id_details != null)
- {
- foreach (var id in ((!) local_id_details).local_ids)
- {
- new_local_ids.add (id);
- }
- }
- }
-
- if (new_local_ids.size != this._local_ids.size ||
- !new_local_ids.contains_all (this._local_ids))
- {
- this._local_ids = new_local_ids;
- this._local_ids_ro = new_local_ids.read_only_view;
- return true;
- }
-
- return false;
- }, emit_notification, force_update);
- }
-
- private void _update_location ()
- {
- this._update_single_valued_property (typeof (LocationDetails), (p) =>
- {
- return ((LocationDetails) p).location != null;
- }, (a, b) =>
- {
- // TODO (https://bugzilla.gnome.org/show_bug.cgi?id=627400): pick the "better" location information. For now, pick more or less randomly.
- return 0;
- }, "location", (p) =>
- {
- unowned Location? new_location = null;
-
- if (p != null)
- {
- new_location = ((LocationDetails) p).location;
- }
-
- if ((new_location == null) != (this.location == null) /* adding or removing a location? */ ||
- new_location != null && !new_location.equal (this.location) /* different value? */)
- {
- this._location = new_location;
- this.notify_property ("location");
- }
- });
- }
-
- private void _update_postal_addresses (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
- {
- /* FIXME: Detect duplicates somehow? */
- this._update_multi_valued_property ("postal-addresses",
- create_if_not_exist, () => { return this._postal_addresses == null; },
- () =>
- {
- this._postal_addresses = new SmallSet<PostalAddressFieldDetails> (
- AbstractFieldDetails<PostalAddress>.hash_static,
- AbstractFieldDetails<PostalAddress>.equal_static);
- this._postal_addresses_ro = this._postal_addresses.read_only_view;
- },
- () =>
- {
- var new_postal_addresses =
- new SmallSet<PostalAddressFieldDetails> (
- AbstractFieldDetails<PostalAddress>.hash_static,
- AbstractFieldDetails<PostalAddress>.equal_static);
-
- foreach (var persona in this._persona_set)
- {
- unowned var postal_address_details = persona as PostalAddressDetails;
- if (postal_address_details != null)
- {
- foreach (var pafd in
- ((!) postal_address_details).postal_addresses)
- {
- new_postal_addresses.add (pafd);
- }
- }
- }
-
- if (!Utils.set_afd_equal (new_postal_addresses,
- this._postal_addresses))
- {
- this._postal_addresses = new_postal_addresses;
- this._postal_addresses_ro =
- new_postal_addresses.read_only_view;
- return true;
- }
-
- return false;
- }, emit_notification, force_update);
- }
-
- private void _update_birthday ()
- {
- this._update_single_valued_property (typeof (BirthdayDetails), (p) =>
- {
- unowned var details = ((BirthdayDetails) p);
- return details.birthday != null && details.calendar_event_id != null;
- }, (a, b) =>
- {
- unowned var a_birthday = ((BirthdayDetails) a).birthday;
- unowned var b_birthday = ((BirthdayDetails) b).birthday;
- unowned var a_event_id = ((BirthdayDetails) a).calendar_event_id;
- unowned var b_event_id = ((BirthdayDetails) b).calendar_event_id;
-
- var a_birthday_is_set = (a_birthday != null) ? 1 : 0;
- var b_birthday_is_set = (b_birthday != null) ? 1 : 0;
-
- /* We consider the empty string as “set” because it's an opaque ID. */
- var a_event_id_is_set = (a_event_id != null) ? 1 : 0;
- var b_event_id_is_set = (b_event_id != null) ? 1 : 0;
-
- /* Prefer personas which have both properties set over those who have
- * only one set. We don't consider the case where the birthdays from
- * different personas don't match, because that's just scary. */
- return (a_birthday_is_set + a_event_id_is_set) -
- (b_birthday_is_set + b_event_id_is_set);
- }, "birthday", (p) =>
- {
- unowned DateTime? bday = null;
- unowned string? calendar_event_id = null;
-
- if (p != null)
- {
- bday = ((BirthdayDetails) p).birthday;
- calendar_event_id = ((BirthdayDetails) p).calendar_event_id;
- }
-
- if ((this._birthday == null && bday != null) ||
- (this._birthday != null &&
- (bday == null || !((!) this._birthday).equal ((!) bday))) ||
- (this._calendar_event_id != calendar_event_id))
- {
- this._birthday = bday;
- this._calendar_event_id = calendar_event_id;
-
- this.freeze_notify ();
- this.notify_property ("birthday");
- this.notify_property ("calendar-event-id");
- this.thaw_notify ();
- }
- });
- }
-
- private void _update_notes (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
- {
- this._update_multi_valued_property ("notes", create_if_not_exist,
- () => { return this._notes == null; },
- () =>
- {
- this._notes = new SmallSet<NoteFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
- this._notes_ro = this._notes.read_only_view;
- },
- () =>
- {
- var new_notes = new SmallSet<NoteFieldDetails> (
- AbstractFieldDetails<string>.hash_static,
- AbstractFieldDetails<string>.equal_static);
-
- foreach (var persona in this._persona_set)
- {
- unowned var note_details = persona as NoteDetails;
- if (note_details != null)
- {
- foreach (var n in ((!) note_details).notes)
- {
- new_notes.add (n);
- }
- }
- }
-
- if (!Utils.set_afd_equal (new_notes, this._notes))
- {
- this._notes = new_notes;
- this._notes_ro = new_notes.read_only_view;
- return true;
- }
-
- return false;
- }, emit_notification, force_update);
- }
-
- private void _set_personas (Set<Persona>? personas,
- Individual? replacement_individual)
- {
- assert (replacement_individual == null || replacement_individual != this);
-
- var added = new SmallSet<Persona> ();
- var removed = new SmallSet<Persona> ();
-
- /* Determine which Personas have been added. If personas == null, we
- * assume it's an empty set. */
- if (personas != null)
- {
- foreach (var p in (!) personas)
- {
- if (!this._persona_set.contains (p))
- {
- /* Keep track of how many Personas are users */
- if (p.is_user)
- this._persona_user_count++;
-
- added.add (p);
-
- this._persona_set.add (p);
- this._connect_to_persona (p);
-
- /* Increment the Persona count for this PersonaStore */
- var store = p.store;
- var num_from_store = this._stores.get (store);
- if (num_from_store == 0)
- {
- this._stores.set (store, num_from_store + 1);
- }
- else
- {
- this._stores.set (store, 1);
-
- store.removed.connect (this._store_removed_cb);
- store.personas_changed.connect (
- this._store_personas_changed_cb);
- }
- }
- }
- }
-
- /* Determine which Personas have been removed */
- foreach (var p in this._persona_set)
- {
- if (personas == null || !((!) personas).contains (p))
- {
- /* Keep track of how many Personas are users */
- if (p.is_user)
- this._persona_user_count--;
-
- removed.add (p);
-
- /* Decrement the Persona count for this PersonaStore */
- var store = p.store;
- var num_from_store = this._stores.get (store);
- if (num_from_store > 1)
- {
- this._stores.set (store, num_from_store - 1);
- }
- else
- {
- store.removed.disconnect (this._store_removed_cb);
- store.personas_changed.disconnect (
- this._store_personas_changed_cb);
-
- this._stores.unset (store);
- }
-
- this._disconnect_from_persona (p, replacement_individual);
- }
- }
-
- foreach (var p in removed)
- {
- this._persona_set.remove (p);
- }
-
- this._emit_personas_changed (added, removed);
-
- /* Update this.is_user */
- var new_is_user = (this._persona_user_count > 0) ? true : false;
- if (new_is_user != this.is_user)
- this.is_user = new_is_user;
-
- /* If all the Personas have been removed, remove the Individual */
- if (this._persona_set.size < 1)
- {
- this.removed (replacement_individual);
- return;
- }
-
- /* Update the ID. We choose the most interesting Persona in the
- * Individual and hash their UID. This is guaranteed to be globally
- * unique, and may not change (for one of the two Individuals) if we link
- * two Individuals together, which is nice though we can't rely on this
- * behaviour.
- *
- * This method of constructing an ID ensures that it'll be unique and
- * stable for a given Individual once the IndividualAggregator reaches
- * a quiescent state after startup. It guarantees that the ID will be
- * the same every time folks is used, until the Individual is linked
- * or unlinked to another Individual.
- *
- * We choose the most interesting Persona by ranking all the Personas
- * in the Individual by:
- * 1. store.is-primary-store
- * 2. store.trust-level
- * 3. store.id (alphabetically)
- * 4. persona.uid (alphabetically)
- *
- * Note that this heuristic shouldn't be changed without careful thought,
- * since stored references to IDs may be broken by the change.
- */
- if (this._persona_set.size > 0)
- {
- unowned Persona? chosen_persona = null;
-
- foreach (var persona in this._persona_set)
- {
- if (chosen_persona == null)
- {
- chosen_persona = persona;
- continue;
- }
-
- unowned var _chosen_persona = (!) chosen_persona;
-
- if ((_chosen_persona.store.is_primary_store == false &&
- persona.store.is_primary_store == true) ||
- (_chosen_persona.store.is_primary_store ==
- persona.store.is_primary_store &&
- _chosen_persona.store.trust_level >
- persona.store.trust_level) ||
- (_chosen_persona.store.is_primary_store ==
- persona.store.is_primary_store &&
- _chosen_persona.store.trust_level ==
- persona.store.trust_level &&
- _chosen_persona.store.id > persona.store.id) ||
- (_chosen_persona.store.is_primary_store ==
- persona.store.is_primary_store &&
- _chosen_persona.store.trust_level ==
- persona.store.trust_level &&
- _chosen_persona.store.id == persona.store.id &&
- _chosen_persona.uid > persona.uid)
- )
- {
- chosen_persona = persona;
- }
- }
-
- /* Hash the chosen persona's UID. We can guarantee chosen_persona is
- * non-null here because it's at least set to the first element of
- * this._persona_set, which we've checked is non-empty. */
- this.id = Checksum.compute_for_string (ChecksumType.SHA1,
- ((!) chosen_persona).uid);
- }
- else
- {
- /* Default if we have no personas. */
- this.id = "";
- }
-
- /* Update our aggregated fields and notify the changes */
- this._update_fields ();
- }
-
- internal void replace (Individual replacement_individual)
- {
- this._set_personas (null, replacement_individual);
- }
-
- /**
- * Anti-linked with a persona?
- *
- * Check whether this individual is anti-linked to {@link Persona} ``p`` at
- * all. If so, ``true`` will be returned — ``false`` will be returned
- * otherwise.
- *
- * Note that this will check for anti-links in either direction, since
- * anti-links are not necessarily symmetric.
- *
- * @param p persona to check for anti-links with
- * @return ``true`` if this individual is anti-linked with persona ``p``;
- * ``false``
- * otherwise
- * @since 0.7.3
- */
- public bool has_anti_link_with_persona (Persona p)
- {
- unowned var al = p as AntiLinkable;
-
- foreach (var persona in this._persona_set)
- {
- unowned var pl = persona as AntiLinkable;
-
- if ((al != null && ((!) al).has_anti_link_with_persona (persona)) ||
- (pl != null && ((!) pl).has_anti_link_with_persona (p)))
- {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Anti-linked with an individual?
- *
- * Check whether this individual is anti-linked to any of the {@link Persona}s
- * in {@link Folks.Individual} ``i``. If so, ``true`` will be returned —
- * ``false`` will be returned otherwise.
- *
- * Note that this will check for anti-links in either direction, since
- * anti-links are not necessarily symmetric.
- *
- * @param i individual to check for anti-links with
- * @return ``true`` if this individual is anti-linked with individual ``i``;
- * ``false`` otherwise
- * @since 0.7.3
- */
- public bool has_anti_link_with_individual (Individual i)
- {
- foreach (var p in i.personas)
- {
- if (this.has_anti_link_with_persona (p) == true)
- {
- return true;
- }
- }
-
- return false;
- }
-}
diff --git a/folks/interaction-details.vala b/folks/interaction-details.vala
deleted file mode 100644
index 23c98a37..00000000
--- a/folks/interaction-details.vala
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2012 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Seif Lotfy <seif.lotfy@collabora.co.uk>
- */
-
-using GLib;
-
-/**
- * Interaction details of a contact.
- *
- * Interaction details are the number and date/time of calls or IM interactions
- * with a contact, giving an idea of the recent interactions the user has had
- * with that contact.
- *
- * @since 0.7.1
- */
-public interface Folks.InteractionDetails : Object
-{
- /**
- * The IM interaction associated with a Persona
- *
- * @since 0.7.1
- */
- public abstract uint im_interaction_count
- {
- get;
- }
-
- /**
- * The latest IM interaction timestamp associated with a Persona
- *
- * @since 0.7.1
- */
- public abstract DateTime? last_im_interaction_datetime
- {
- get;
- }
-
- /**
- * The call interaction associated with a Persona
- *
- * @since 0.7.1
- */
- public abstract uint call_interaction_count
- {
- get;
- }
-
- /**
- * The latest call interaction timestamp associated with a Persona
- *
- * @since 0.7.1
- */
- public abstract DateTime? last_call_interaction_datetime
- {
- get;
- }
-}
diff --git a/folks/internal.vala b/folks/internal.vala
deleted file mode 100644
index 35ec807a..00000000
--- a/folks/internal.vala
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 2011,2023 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- * Corentin Noël <corentin.noel@collabora.com>
- */
-
-using GLib;
-using Gee;
-using Posix;
-
-namespace Folks.Internal
-{
- public static bool equal_sets<G> (Set<G> a, Set<G> b)
- {
- if (a.size != b.size)
- return false;
-
- foreach (var a_elem in a)
- {
- if (!b.contains (a_elem))
- return false;
- }
-
- return true;
- }
-
- /**
- * Emit a profiling point.
- *
- * This emits a profiling point with the given message (printf-style), which
- * can be picked up by profiling tools and timing information extracted.
- *
- * @param format printf-style message format
- * @param ... message arguments
- */
- [PrintfFormat]
- public inline void profiling_point (string format, ...)
- {
-#if ENABLE_PROFILING
- var args = va_list ();
- Sysprof.Collector.log (0, "folks", format.vprintf(args));
-#endif
- }
-
- [Compact]
- public class ProfileBlock
- {
-#if ENABLE_PROFILING
- internal string name;
- internal int64 start;
-
- internal ProfileBlock (owned string name) {
- this.name = (owned) name;
- this.start = Sysprof.CAPTURE_CURRENT_TIME;
- }
-#endif
- }
-
- /**
- * Start a profiling block.
- *
- * This emits a profiling start point with the given message (printf-style),
- * which can be picked up by profiling tools and timing information extracted.
- *
- * This is typically used in a pair with {@link Internal.profiling_end} to
- * delimit blocks of processing which need timing.
- *
- * @param format printf-style message format
- * @param ... message arguments
- */
- public inline ProfileBlock? profiling_start (string format, ...)
- {
-#if ENABLE_PROFILING
- var args = va_list ();
- return new ProfileBlock (format.vprintf (args));
-#else
- return null;
-#endif
- }
-
- /**
- * End a profiling block.
- *
- * This emits a profiling end point with the given message (printf-style),
- * which can be picked up by profiling tools and timing information extracted.
- *
- * This is typically used in a pair with {@link Internal.profiling_start} to
- * delimit blocks of processing which need timing.
- *
- * @param block the ProfileBlock given by profiling_start
- */
- public inline void profiling_end (owned ProfileBlock? block)
- {
-#if ENABLE_PROFILING
- Sysprof.Collector.mark (block.start, Sysprof.CAPTURE_CURRENT_TIME - block.start, "folks", block.name);
-#endif
- }
-}
diff --git a/folks/local-id-details.vala b/folks/local-id-details.vala
deleted file mode 100644
index 8812cdaf..00000000
--- a/folks/local-id-details.vala
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- * Copyright (C) 2011 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using Gee;
-using GLib;
-
-/**
- * This interface represents the list of {@link Persona.iid}s
- * corresponding to {@link Persona}s from backends with write
- * support so that they can be linked.
- *
- * This is necessary so that personas from the same backend
- * can be linked together even if they have no other linkeable
- * properties set.
- *
- * @since 0.5.0
- */
-public interface Folks.LocalIdDetails : Object
-{
- /**
- * The IIDs corresponding to {@link Persona}s in a
- * backend that we fully trust.
- *
- * @since 0.5.1
- */
- public abstract Set<string> local_ids { get; set; }
-
- /**
- * Change the contact's local IDs.
- *
- * It's preferred to call this rather than setting
- * {@link LocalIdDetails.local_ids} directly, as this method gives error
- * notification and will only return once the local IDs have been written to
- * the relevant backing store (or the operation's failed).
- *
- * @param local_ids the set of local IDs
- * @throws PropertyError if setting the local IDs failed
- * @since 0.6.2
- */
- public virtual async void change_local_ids (Set<string> local_ids)
- throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Local IDs are not writeable on this contact."));
- }
-}
diff --git a/folks/location-details.vala b/folks/location-details.vala
deleted file mode 100644
index f4d41b28..00000000
--- a/folks/location-details.vala
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2013 Intel Corp
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Patrick Ohly <patrick.ohly@intel.com>
- */
-
-using GLib;
-
-/**
- * A location. Typically latitude and longitude will
- * be based on WGS84. However, folks often has no
- * way of verifying that and just has to assume
- * it's true.
- *
- * @since 0.9.2
- */
-public class Folks.Location : Object
-{
- /**
- * The latitude.
- *
- * @since 0.9.2
- */
- public double latitude;
- /**
- * The longitude.
- *
- * @since 0.9.2
- */
- public double longitude;
-
- /**
- * Constructs a new instance with the given coordinates.
- * @param latitude latitude of the new instance
- * @param longitude longitude of the new instance
- * @since 0.9.2
- */
- public Location (double latitude, double longitude)
- {
- this.latitude = latitude;
- this.longitude = longitude;
- }
-
- /**
- * Compare this location to another by geographical position.
- *
- * @param other the instance to compare against
- * @return true iff the coordinates are exactly the same
- * @since 0.9.2
- */
- public bool equal (Location other)
- {
- return this.latitude == other.latitude &&
- this.longitude == other.longitude;
- }
-
- /**
- * Compare the geographical position of this location against
- * another position.
- *
- * @param latitude latitude of the other position
- * @param longitude longitude of the other position
- * @return true iff the coordinates are exactly the same
- * @since 0.9.2
- */
- public bool equal_coordinates (double latitude, double longitude)
- {
- return this.latitude == latitude &&
- this.longitude == longitude;
- }
-}
-
-/**
- * Location of a contact. folks tries to keep track of
-* the current location and thus favors live data (say,
- * as advertised by a chat service) over static data (from
- * an address book). Static addresses, such as a contact's home or work address,
- * should be presented using the {@link PostalAddressDetails} interface.
- * {@link LocationDetails} is purely for exposing the contact's current or
- * recent location.
- *
- * Backends are expected to report only relevant changes
- * in a persona's location. For storage backends like EDS,
- * all changes must have been triggered by a person (e.g.
- * editing the contact) and thus all are relevant.
- *
- * A backend pulling in live data, for example from a GPS,
- * is expected to filter the data to minimize noise.
- *
- * folks itself will then apply all changes coming
- * from backends without further filtering.
- *
- * @since 0.9.2
- */
-public interface Folks.LocationDetails : Object
-{
- /**
- * The current location of the contact. Null if the contact’s
- * current location isn’t known, or they’re keeping it private.
- *
- * @since 0.9.2
- */
- public abstract Location? location { get; set; }
-
- /**
- * Set or remove the contact's currently advertised location.
- *
- * It's preferred to call this rather than setting
- * {@link LocationDetails.location} directly, as this method gives error
- * notification and will only return once the location has been written to the
- * relevant backing store (or the operation's failed).
- *
- * @param location the contact's location, null to remove the information
- * @throws PropertyError if setting the location failed
- * @since 0.9.2
- */
- public virtual async void change_location (Location? location) throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Location is not writeable on this contact."));
- }
-}
diff --git a/folks/meson.build b/folks/meson.build
index b86f3cc7..c7368545 100644
--- a/folks/meson.build
+++ b/folks/meson.build
@@ -1,159 +1,70 @@
-folks_build_dir = meson.current_build_dir()
-
-# Internal library
-libfolks_internal_sources = [
- 'internal.vala',
- 'small-set.c',
-]
-
-libfolks_internal_deps = [
- gobject_dep,
- gio_dep,
- gee_dep,
- posix_dep,
-]
-
-libfolks_internal_vala_flags = []
-
-if get_option('profiling')
- libfolks_internal_vala_flags += '--define=ENABLE_PROFILING'
- libfolks_internal_deps += sysprof_dep
-endif
-
-libfolks_internal = static_library('folks-internal',
- libfolks_internal_sources,
- include_directories: include_directories('..'),
- dependencies: libfolks_internal_deps,
- vala_args: libfolks_internal_vala_flags,
+folks_public_sources = files(
+ 'folks-manager.c',
+ 'folks-persona.c',
+ 'folks-persona-store.c',
)
-libfolks_internal_dep = declare_dependency(
- link_with: libfolks_internal,
- dependencies: valac.find_library('folks-generics', dirs: meson.project_source_root() / 'folks'),
+folks_private_headers = files(
+ 'folks-persona-store-priv.h',
+ 'folks-persona-priv.h',
)
-# Core library
-libfolks_gir_name = 'Folks-@0@'.format(folks_api_version)
-
-libfolks_sources = files(
- 'abstract-field-details.vala',
- 'alias-details.vala',
- 'anti-linkable.vala',
- 'avatar-cache.vala',
- 'avatar-details.vala',
- 'backend-store.vala',
- 'backend.vala',
- 'birthday-details.vala',
- 'debug.vala',
- 'email-details.vala',
- 'extended-info.vala',
- 'favourite-details.vala',
- 'folks-namespace.vala',
- 'gender-details.vala',
- 'group-details.vala',
- 'im-details.vala',
- 'individual-aggregator.vala',
- 'individual.vala',
- 'interaction-details.vala',
- 'local-id-details.vala',
- 'location-details.vala',
- 'name-details.vala',
- 'note-details.vala',
- 'object-cache.vala',
- 'persona-store.vala',
- 'persona.vala',
- 'phone-details.vala',
- 'postal-address-details.vala',
- 'potential-match.vala',
- 'presence-details.vala',
- 'query.vala',
- 'role-details.vala',
- 'search-view.vala',
- 'simple-query.vala',
- 'types.vala',
- 'url-details.vala',
- 'utils.vala',
- 'web-service-details.vala',
+folks_public_headers = files(
+ 'folks.h',
+ 'folks-manager.h',
+ 'folks-persona.h',
+ 'folks-persona-store.h',
)
-libfolks_deps = [
+install_headers(folks_public_headers, subdir: 'folks@0@/folks'.format(folks_api_version))
+
+folks_deps = [
gobject_dep,
- gmodule_dep,
gio_dep,
- gee_dep,
-]
-
-libfolks_vala_flags = [
- '--includedir', meson.project_name(),
-]
-
-libfolks_c_flags = [
- '-include', 'config.h',
- '-DG_LOG_DOMAIN="folks"',
+ dependency('tracker-sparql-3.0')
]
-libfolks_lib = shared_library('folks',
- libfolks_sources,
- dependencies: [ libfolks_deps, build_conf_dep, libfolks_internal_dep ],
- include_directories: config_h_dir,
- vala_args: libfolks_vala_flags,
- c_args: libfolks_c_flags,
- vala_header: 'folks/folks.h',
- vala_gir: libfolks_gir_name + '.gir',
- version: folks_lib_version,
+folks_lib = library('folks@0@'.format(folks_api_version),
+ folks_public_sources,
+ folks_public_headers,
+ folks_private_headers,
+ dependencies: folks_deps,
+ include_directories: root_dir,
+ soversion: folks_soversion,
+ version: folks_library_version,
install: true,
- install_dir: [ true, folks_headers_install_dir, true, true ],
)
-# Also make sure to install the VAPI's .deps file
-install_data('folks.deps',
- install_dir: get_option('datadir') / 'vala' / 'vapi',
+pkgconfig.generate(
+ folks_lib,
+ filebase: 'folks@0@'.format(folks_api_version),
+ name: 'Folks',
+ description: 'The Folks meta-contacts library',
+ subdirs: 'folks@0@'.format(folks_api_version),
)
-# FIXME: This comes straight from the Meson docs on how to create/install a
-# typelib file for your Vala shared library. However, as mentioned in
-# https://github.com/mesonbuild/meson/issues/4481, this is not ideal.
-custom_target(libfolks_gir_name + '.typelib',
- command: [ g_ir_compiler,
- '--output', '@OUTPUT@',
- '--shared-library', 'lib' + libfolks_lib.name(),
- meson.current_build_dir() / (libfolks_gir_name + '.gir')
+folks_gir = gnome.generate_gir(folks_lib,
+ sources: [
+ folks_public_sources,
+ folks_public_headers,
],
- output: libfolks_gir_name + '.typelib',
- depends: libfolks_lib,
+ nsversion: folks_api_version.to_string(),
+ namespace: 'Folks',
+ header: 'folks/folks.h',
+ includes: ['GLib-2.0', 'GObject-2.0', 'Gio-2.0'],
install: true,
- install_dir: folks_typelibdir,
-)
-libfolks_gir_include_dir = meson.current_build_dir()
-
-libfolks_dep = declare_dependency(
- link_with: libfolks_lib,
- include_directories: include_directories('.', '..'),
- dependencies: libfolks_deps,
)
-# GSettings
-gsettings_conf = configuration_data()
-gsettings_conf.set('GETTEXT_PACKAGE', meson.project_name())
-gschema_file = configure_file(
- input: 'org.freedesktop.folks.gschema.xml.in',
- output: 'org.freedesktop.folks.gschema.xml',
- configuration: gsettings_conf,
-)
-install_data(gschema_file,
- install_dir: get_option('datadir') / 'glib-2.0' / 'schemas',
+folks_vapi = gnome.generate_vapi('folks@0@'.format(folks_api_version),
+ sources: folks_gir[0],
+ packages: [ 'glib-2.0', 'gobject-2.0', 'gio-2.0' ],
+ install: true,
)
-# GConf file
-install_data('folks.convert',
- install_dir: get_option('datadir') / 'GConf' / 'gsettings',
+folks_dep = declare_dependency(
+ link_with: folks_lib,
+ dependencies: [ folks_vapi, gobject_dep, gio_dep ],
+ include_directories: root_dir,
)
-# Pkg-config file
-pkgconfig.generate(libfolks_lib,
- name: 'Folks',
- description: 'The Folks meta-contacts library',
- filebase: 'folks',
- requires: [ glib_dep, gobject_dep, gee_dep, ],
- variables: common_pkgconf_variables,
-)
+meson.override_dependency('folks1', folks_dep) \ No newline at end of file
diff --git a/folks/name-details.vala b/folks/name-details.vala
deleted file mode 100644
index d5fb1415..00000000
--- a/folks/name-details.vala
+++ /dev/null
@@ -1,494 +0,0 @@
-/*
- * Copyright (C) 2011, 2013 Collabora Ltd.
- * Copyright (C) 2011, 2013 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Marco Barisione <marco.barisione@collabora.co.uk>
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using GLib;
-
-/**
- * Structured name representation for human names.
- *
- * Represents a full name split in its constituent parts (given name,
- * family name, etc.). This structure corresponds to the "N" field in
- * vCards. The parts of the name are never ``null``: an empty string
- * indicates that a property is not set.
- *
- * @since 0.3.5
- */
-public class Folks.StructuredName : Object
-{
- private string _family_name = "";
- /**
- * The family name.
- *
- * The family name (also known as surname or last name) of a contact.
- *
- * @since 0.3.5
- */
- public string family_name
- {
- get { return this._family_name; }
- construct set { this._family_name = value != null ? value : ""; }
- }
-
- private string _given_name = "";
- /**
- * The given name.
- *
- * The family name (also known as first name) of a contact.
- *
- * @since 0.3.5
- */
- public string given_name
- {
- get { return this._given_name; }
- construct set { this._given_name = value != null ? value : ""; }
- }
-
- private string _additional_names = "";
- /**
- * Additional names.
- *
- * The additional names of a contact, for instance the contact's
- * middle name.
- *
- * @since 0.3.5
- */
- public string additional_names
- {
- get { return this._additional_names; }
- construct set { this._additional_names = value != null ? value : ""; }
- }
-
- private string _prefixes = "";
- /**
- * The prefixes of a name.
- *
- * The prefixes used in front of the name (for instance "Mr", "Mrs",
- * "Doctor" or honorific titles).
- *
- * @since 0.3.5
- */
- public string prefixes
- {
- get { return this._prefixes; }
- construct set { this._prefixes = value != null ? value : ""; }
- }
-
- private string _suffixes = "";
- /**
- * The suffixes of a name.
- *
- * The suffixes used after a name (for instance "PhD" or "Junior").
- *
- * @since 0.3.5
- */
- public string suffixes
- {
- get { return this._suffixes; }
- construct set { this._suffixes = value != null ? value : ""; }
- }
-
- /**
- * Create a StructuredName.
- *
- * You can pass ``null`` if a component is not set.
- *
- * @param family_name the family (last) name
- * @param given_name the given (first) name
- * @param additional_names additional names
- * @param prefixes prefixes of the name
- * @param suffixes suffixes of the name
- * @return a new StructuredName
- *
- * @since 0.3.5
- */
- public StructuredName (string? family_name, string? given_name,
- string? additional_names, string? prefixes, string? suffixes)
- {
- Object (family_name: family_name,
- given_name: given_name,
- additional_names: additional_names,
- prefixes: prefixes,
- suffixes: suffixes);
- }
-
- /**
- * Create a StructuredName.
- *
- * Shorthand for the common case of just having the family and given
- * name of a contact. It's equivalent to calling
- * {@link StructuredName.StructuredName} and passing ``null`` for all
- * the other components.
- *
- * @param family_name the family (last) name
- * @param given_name the given (first) name
- * @return a new StructuredName
- *
- * @since 0.3.5
- */
- public StructuredName.simple (string? family_name, string? given_name)
- {
- Object (family_name: family_name,
- given_name: given_name);
- }
-
- /**
- * Whether none of the components is set.
- *
- * @return ``true`` if all the components are the empty string, ``false``
- * otherwise.
- *
- * @since 0.3.5
- */
- public bool is_empty ()
- {
- return this._family_name == "" &&
- this._given_name == "" &&
- this._additional_names == "" &&
- this._prefixes == "" &&
- this._suffixes == "";
- }
-
- /**
- * Whether two StructuredNames are the same.
- *
- * @param other the other structured name to compare with
- * @return ``true`` if all the components are the same, ``false``
- * otherwise.
- *
- * @since 0.5.0
- */
- public bool equal (StructuredName other)
- {
- return this._family_name == other.family_name &&
- this._given_name == other.given_name &&
- this._additional_names == other.additional_names &&
- this._prefixes == other.prefixes &&
- this._suffixes == other.suffixes;
- }
-
- private string _extract_initials (string names)
- {
- /* Extract the first letter of each word (where a word is a group of
- * characters following whitespace or a hyphen.
- * I've made this up since the documentation on
- * http://lh.2xlibre.net/values/name_fmt/ doesn't specify how to extract
- * the initials from a set of names. It should work for Western names,
- * but I'm not so sure about other names. */
- var output = new StringBuilder ();
- var at_start_of_word = true;
- int index = 0;
- unichar c;
-
- while (names.get_next_char (ref index, out c) == true)
- {
- /* Grab a new initial from any word preceded by a space or a hyphen,
- * so (e.g.) ‘Mary-Jane’ becomes ‘MJ’. */
- if (c.isspace () || c == '-')
- {
- at_start_of_word = true;
- }
- else if (at_start_of_word)
- {
- output.append_unichar (c);
- at_start_of_word = false;
- }
- }
-
- return output.str;
- }
-
- /**
- * Formatted version of the structured name.
- *
- * @return name formatted according to the current locale
- * @since 0.4.0
- */
- public string to_string ()
- {
- /* FIXME: Ideally we’d use a format string translated to the locale of the
- * persona whose name is being formatted, but no backend provides
- * information about personas’ locales, so we have to settle for the
- * current user’s locale.
- *
- * We thought about using nl_langinfo(_NL_NAME_NAME_FMT) here, but
- * decided against it because:
- * 1. It’s not the best documented API in the world, and its stability
- * is in question.
- * 2. An attempt to improve the interface in glibc met with a wall of
- * complaints: https://sourceware.org/bugzilla/show_bug.cgi?id=14641.
- *
- * However, we do re-use the string format placeholders from
- * _NL_NAME_NAME_FMT (as documented here:
- * http://lh.2xlibre.net/values/name_fmt/) because there’s a chance glibc
- * might eventually grow a useful interface for this.
- *
- * It does mean we have to implement our own parser for the name_fmt
- * format though, since glibc doesn’t provide a formatting function. */
-
- /* Translators: This is a format string used to convert structured names
- * to a single string. It should be translated to the predominant
- * semi-formal name format for your locale, using the placeholders
- * documented here: http://lh.2xlibre.net/values/name_fmt/. You may be
- * able to re-use the existing glibc format string for your locale on that
- * page if it’s suitable.
- *
- * More explicitly: the supported placeholders are %f, %F, %g, %G, %m, %M,
- * %t. The romanisation modifier (e.g. %Rf) is recognized but ignored.
- * %s, %S and %d are all replaced by the same thing (the ‘Honorific
- * Prefixes’ from vCard) so please avoid using more than one.
- *
- * For example, the format string ‘%g%t%m%t%f’ expands to ‘John Andrew
- * Lees’ when used for a persona with first name ‘John’, additional names
- * ‘Andrew’ and family names ‘Lees’.
- *
- * If you need additional placeholders with other information or
- * punctuation, please file a bug against libfolks:
- * https://gitlab.gnome.org/GNOME/folks/issues
- */
- var name_fmt = _("%g%t%m%t%f");
-
- return this.to_string_with_format (name_fmt);
- }
-
- /**
- * Formatted version of the structured name.
- *
- * This allows a custom format string to be specified, using the placeholders
- * described on [[http://lh.2xlibre.net/values/name_fmt/]]. This ``name_fmt``
- * must almost always be translated to the current locale. (Ideally it would
- * be translated to the locale of the persona whose name is being formatted,
- * but such locale information isn’t available.)
- *
- * @param name_fmt format string for the name
- * @return name formatted according to the given format
- * @since 0.9.7
- */
- public string to_string_with_format (string name_fmt)
- {
- var output = new StringBuilder ();
- var in_field_descriptor = false;
- var field_descriptor_romanised = false;
- var field_descriptor_empty = true;
- int index = 0;
- unichar c;
-
- while (name_fmt.get_next_char (ref index, out c) == true)
- {
- /* Start of a field descriptor. */
- if (c == '%')
- {
- in_field_descriptor = !in_field_descriptor;
-
- /* If entering a field descriptor, reset the state
- * and continue to the next character. */
- if (in_field_descriptor)
- {
- field_descriptor_romanised = false;
- continue;
- }
- }
-
- if (in_field_descriptor)
- {
- /* Romanisation, e.g. using a field descriptor ‘%Rg’. */
- if (c == 'R')
- {
- /* FIXME: Romanisation isn't supported yet. */
- field_descriptor_romanised = true;
- continue;
- }
-
- var val = "";
-
- /* Handle the different types of field descriptor. */
- if (c == 'f')
- {
- val = this._family_name;
- }
- else if (c == 'F')
- {
- val = this._family_name.up ();
- }
- else if (c == 'g')
- {
- val = this._given_name;
- }
- else if (c == 'G')
- {
- val = this._extract_initials (this._given_name);
- }
- else if (c == 'm')
- {
- val = this._additional_names;
- }
- else if (c == 'M')
- {
- val = this._extract_initials (this._additional_names);
- }
- else if (c == 's' || c == 'S' || c == 'd')
- {
- /* FIXME: Not ideal, but prefixes will have to do. */
- val = this._prefixes;
- }
- else if (c == 't')
- {
- val = (field_descriptor_empty == false) ? " " : "";
- }
- else if (c == 'l' || c == 'o' || c == 'p')
- {
- /* FIXME: Not supported. */
- val = "";
- }
-
- /* Append the value of the field descriptor. */
- output.append (val);
- in_field_descriptor = false;
- field_descriptor_empty = (val == "");
- }
- else
- {
- /* Handle non-field descriptor characters. */
- output.append_unichar (c);
- }
- }
-
- return output.str;
- }
-}
-
-/**
- * Interface for classes which represent contacts with names, such as
- * {@link Persona} and {@link Individual}.
- *
- * @since 0.3.5
- */
-public interface Folks.NameDetails : Object
-{
- /**
- * The contact name split in its constituent parts.
- *
- * Note that most of the time the structured name is not set (i.e.
- * it's ``null``) or just some of the components are set.
- * The components are immutable. To get notification of changes of
- * the structured name, you just have to connect to the ``notify`` signal
- * of this property.
- *
- * @since 0.3.5
- */
- public abstract StructuredName? structured_name { get; set; }
-
- /**
- * Change the contact's structured name.
- *
- * It's preferred to call this rather than setting
- * {@link NameDetails.structured_name} directly, as this method gives error
- * notification and will only return once the name has been written to the
- * relevant backing store (or the operation's failed).
- *
- * @param name the structured name (``null`` to unset it)
- * @throws PropertyError if setting the structured name failed
- * @since 0.6.2
- */
- public virtual async void change_structured_name (StructuredName? name)
- throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Structured name is not writeable on this contact."));
- }
-
- /**
- * The full name of the contact.
- *
- * The full name is the name of the contact written in the way the contact
- * prefers. For instance for English names this is usually the given name
- * followed by the family name, but Chinese names are usually the family
- * name followed by the given name.
- * The full name could or could not contain additional names (like a
- * middle name), prefixes or suffixes.
- *
- * The full name must not be ``null``: the empty string represents an unset
- * full name.
- *
- * @since 0.3.5
- */
- public abstract string full_name { get; set; }
-
- /**
- * Change the contact's full name.
- *
- * It's preferred to call this rather than setting
- * {@link NameDetails.full_name} directly, as this method gives error
- * notification and will only return once the name has been written to the
- * relevant backing store (or the operation's failed).
- *
- * @param full_name the full name (empty string to unset it)
- * @throws PropertyError if setting the full name failed
- * @since 0.6.2
- */
- public virtual async void change_full_name (string full_name)
- throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Full name is not writeable on this contact."));
- }
-
- /**
- * The nickname of the contact.
- *
- * The nickname is the name that the contact chose for himself. This is
- * different from {@link AliasDetails.alias} as aliases can be chosen by
- * the user and not by the contacts themselves.
- *
- * Consequently, setting the nickname only makes sense in the context of an
- * address book when updating the information a contact has specified about
- * themselves.
- *
- * The nickname must not be ``null``: the empty string represents an unset
- * nickname.
- *
- * @since 0.3.5
- */
- public abstract string nickname { get; set; }
-
- /**
- * Change the contact's nickname.
- *
- * It's preferred to call this rather than setting
- * {@link NameDetails.nickname} directly, as this method gives error
- * notification and will only return once the name has been written to the
- * relevant backing store (or the operation's failed).
- *
- * @param nickname the nickname (empty string to unset it)
- * @throws PropertyError if setting the nickname failed
- * @since 0.6.2
- */
- public virtual async void change_nickname (string nickname)
- throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Nickname is not writeable on this contact."));
- }
-}
diff --git a/folks/note-details.vala b/folks/note-details.vala
deleted file mode 100644
index 37521cd4..00000000
--- a/folks/note-details.vala
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- * Copyright (C) 2011 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using Gee;
-using GLib;
-
-/**
- * Object representing a note that can have some parameters associated with it.
- *
- * See {@link Folks.AbstractFieldDetails} for details on common parameter names
- * and values.
- *
- * @since 0.6.0
- */
-public class Folks.NoteFieldDetails : AbstractFieldDetails<string>
-{
- private string _id = "";
- /**
- * {@inheritDoc}
- */
- public override string id
- {
- get { return this._id; }
- set { this._id = (value != null ? value : ""); }
- }
-
- /**
- * The UID of the note (if any).
- */
- [Version (deprecated = true, deprecated_since = "0.6.5",
- replacement = "AbstractFieldDetails.id")]
- public string uid
- {
- get { return this.id; }
- set { this.id = value; }
- }
-
- /**
- * Create a new NoteFieldDetails.
- *
- * @param value the value of the field, which should be a non-empty free-form
- * UTF-8 string as entered by the user
- * @param parameters initial parameters. See
- * {@link AbstractFieldDetails.parameters}. A ``null`` value is equivalent to
- * a empty map of parameters.
- * @param uid UID for the note object itself, if known. A ``null`` value means
- * the note has no unique ID.
- *
- * @return a new NoteFieldDetails
- *
- * @since 0.6.0
- */
- public NoteFieldDetails (string value,
- MultiMap<string, string>? parameters = null,
- string? uid = null)
- {
- if (value == "")
- {
- warning ("Empty note passed to NoteFieldDetails.");
- }
-
- Object (value: value,
- id: uid,
- parameters: parameters);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override bool equal (AbstractFieldDetails<string> that)
- {
- return base.equal (that);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override uint hash ()
- {
- return (this.value.hash () + this.id.hash ());
- }
-}
-
-/**
- * This interface represents the list of notes associated
- * to a {@link Persona} and {@link Individual}.
- *
- * @since 0.4.0
- */
-public interface Folks.NoteDetails : Object
-{
- /**
- * The notes about the contact.
- *
- * @since 0.5.1
- */
- public abstract Set<NoteFieldDetails> notes { get; set; }
-
- /**
- * Change the contact's notes.
- *
- * It's preferred to call this rather than setting {@link NoteDetails.notes}
- * directly, as this method gives error notification and will only return once
- * the notes have been written to the relevant backing store (or the
- * operation's failed).
- *
- * @param notes the set of notes
- * @throws PropertyError if setting the notes failed
- * @since 0.6.2
- */
- public virtual async void change_notes (Set<NoteFieldDetails> notes)
- throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Notes are not writeable on this contact."));
- }
-}
diff --git a/folks/object-cache.vala b/folks/object-cache.vala
deleted file mode 100644
index aeb7d615..00000000
--- a/folks/object-cache.vala
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-
-/**
- * A generic abstract cache for sets of objects. This can be used by subclasses
- * to implement caching of homogeneous sets of objects. Subclasses simply have
- * to implement serialisation and deserialisation of the objects to and from
- * {@link GLib.Variant}s.
- *
- * It's intended that this class be used for providing caching layers for
- * {@link PersonaStore}s, for example.
- *
- * @since 0.6.0
- */
-public abstract class Folks.ObjectCache<T> : Object
-{
- /* The version number of the header/wrapper for a cache file. When accompanied
- * by a version number for the serialised object type, this unambiguously
- * keys the variant type describing an entire cache file.
- *
- * The wrapper and object version numbers are stored as the first two bytes
- * of a cache file. They can't be stored as part of the Variant which forms
- * the rest of the file, as to interpret the Variant its entire type has to
- * be known — which depends on the version numbers. */
- private const uint8 _FILE_FORMAT_VERSION = 1;
-
- /* The length of the version header at the beginning of the file. This has
- * to be a multiple of 8 to keep Variant's alignment code happy.
- * As documented above, currently only the first two bytes of this header
- * are used (for version numbers). */
- private const size_t _HEADER_WIDTH = 8; /* bytes */
-
- private File _cache_directory;
- private File _cache_file;
- private string _cache_file_path; /* save calls to _cache_file.get_path() */
-
- /**
- * Get the {@link GLib.VariantType} of the serialised form of an object stored
- * in this cache.
- *
- * If a smooth upgrade path is needed in future due to cache file format
- * changes, this may be modified to take a version parameter.
- *
- * @param object_version the version of the object format to use, or
- * ``uint8.MAX`` for the latest version
- * @return variant type for that object version, or ``null`` if the version is
- * unsupported
- * @since 0.6.0
- */
- protected abstract VariantType? get_serialised_object_type (
- uint8 object_version);
-
- /**
- * Get the version of the variant type returned by
- * {@link ObjectCache.get_serialised_object_type}. This must be incremented
- * every time the variant type changes so that old cache files aren't
- * misinterpreted.
- *
- * @since 0.6.0
- */
- protected abstract uint8 get_serialised_object_version ();
-
- /**
- * Serialise the given ``object`` to a {@link GLib.Variant} and return the
- * variant. The variant must be of the type returned by
- * {@link ObjectCache.get_serialised_object_type}.
- *
- * @param object the object to serialise
- * @return serialised form of ``object``
- *
- * @since 0.6.0
- */
- protected abstract Variant serialise_object (T object);
-
- /**
- * Deserialise the given ``variant`` to a new instance of an object. The
- * variant is guaranteed to have the type returned by
- * {@link ObjectCache.get_serialised_object_type}.
- *
- * @param variant the serialised form to deserialise
- * @param object_version the version of the object format to deserialise from
- * @return the deserialised object
- *
- * @since 0.6.0
- */
- protected abstract T deserialise_object (Variant variant,
- uint8 object_version);
-
- /**
- * A string identifying the type of object being cached.
- *
- * This has to be suitable for use as a directory name; i.e. lower case,
- * hyphen-separated tokens.
- *
- * @since 0.6.6
- */
- public string type_id { get; construct; }
-
- /**
- * A string identifying the particular cache instance.
- *
- * This will form the file name of the cache file, but will be escaped
- * beforehand, so can be an arbitrary non-empty string.
- *
- * @since 0.6.6
- */
- public string id
- {
- get { return this._id; }
- construct { assert (value != ""); this._id = value; }
- }
- private string _id;
-
- /**
- * Create a new cache instance using the given type ID and ID. This is
- * protected as the ``type_id`` will typically be set statically by
- * subclasses.
- *
- * @param type_id A string identifying the type of object being cached. This
- * has to be suitable for use as a directory name; i.e. lower case,
- * hyphen-separated.
- * @param id A string identifying the particular cache instance. This will
- * form the file name of the cache file, but will be escaped beforehand, so
- * can be an arbitrary non-empty string.
- * @return A new cache instance
- *
- * @since 0.6.0
- */
- protected ObjectCache (string type_id, string id)
- {
- Object (type_id: type_id,
- id: id);
- }
-
- construct
- {
- debug ("Creating object cache for type ID '%s' with ID '%s'.",
- this.type_id, this.id);
-
- this._cache_directory =
- File.new_for_path (Environment.get_user_cache_dir ())
- .get_child ("folks")
- .get_child (this.type_id);
- this._cache_file =
- this._cache_directory.get_child (Uri.escape_string (this.id,
- "", false));
- var path = this._cache_file.get_path ();
- this._cache_file_path = (path != null) ? (!) path : "(null)";
- }
-
- /**
- * Load a set of objects from the cache and return them as a new set. If the
- * cache file doesn't exist, ``null`` will be returned. An empty set will be
- * returned if the cache file existed but was empty (i.e. was stored with
- * an empty set originally).
- *
- * Loading the objects can be cancelled using ``cancellable``. If it is,
- * ``null`` will be returned.
- *
- * If any errors are encountered while loading the objects, warnings will be
- * logged as appropriate and ``null`` will be returned.
- *
- * This method is safe to call multiple times concurrently.
- *
- * @param cancellable A {@link GLib.Cancellable} for the operation, or
- * ``null``.
- * @return A set of objects from the cache, or ``null``.
- *
- * @since 0.6.0
- */
- public async Set<T>? load_objects (Cancellable? cancellable = null)
- {
- debug ("Loading cache (type ID '%s', ID '%s') from file '%s'.",
- this.type_id, this._id, this._cache_file_path);
-
- // Read in the file
- uint8[] data;
-
- try
- {
- yield this._cache_file.load_contents_async (cancellable, out data, null);
- }
- catch (Error e)
- {
- if (e is IOError.CANCELLED)
- {
- /* not a true error */
- }
- else if (e is IOError.NOT_FOUND)
- {
- debug ("Couldn't load cache file '%s': %s",
- this._cache_file_path, e.message);
- }
- else
- {
- warning ("Couldn't load cache file '%s': %s",
- this._cache_file_path, e.message);
- }
-
- return null;
- }
-
- // Check the length
- if (data.length < ObjectCache._HEADER_WIDTH)
- {
- warning ("Cache file '%s' was too small. The file was deleted.",
- this._cache_file_path);
- yield this.clear_cache ();
-
- return null;
- }
-
- // Check the version
- var wrapper_version = data[0];
- var object_version = data[1];
-
- if (wrapper_version != ObjectCache._FILE_FORMAT_VERSION)
- {
- warning ("Cache file '%s' was version %u of the file format, " +
- "but only version %u is supported. The file was deleted.",
- this._cache_file_path, wrapper_version,
- ObjectCache._FILE_FORMAT_VERSION);
- yield this.clear_cache ();
-
- return null;
- }
-
- unowned uint8[] variant_data = data[ObjectCache._HEADER_WIDTH:data.length];
-
- // Deserialise the variant according to the given version numbers
- var _variant_type =
- this._get_cache_file_variant_type (wrapper_version, object_version);
-
- if (_variant_type == null)
- {
- warning ("Cache file '%s' was version %u of the object file " +
- "format, which is not supported. The file was deleted.",
- this._cache_file_path, object_version);
- yield this.clear_cache ();
-
- return null;
- }
- var variant_type = (!) _variant_type;
-
- var variant =
- Variant.new_from_data<uint8[]> (variant_type, variant_data, false,
- data);
-
- // Check the variant was deserialised correctly
- if (variant.is_normal_form () == false)
- {
- warning ("Cache file '%s' was corrupt and was deleted.",
- this._cache_file_path);
- yield this.clear_cache ();
-
- return null;
- }
-
- // Unpack the stored data
- var type_id = variant.get_child_value (0).get_string ();
-
- if (type_id != this.type_id)
- {
- warning ("Cache file '%s' had type ID '%s', but '%s' was expected." +
- "The file was deleted.", this._cache_file_path, type_id,
- this.type_id);
- yield this.clear_cache ();
-
- return null;
- }
-
- var id = variant.get_child_value (1).get_string ();
-
- if (id != this._id)
- {
- warning ("Cache file '%s' had ID '%s', but '%s' was expected." +
- "The file was deleted.", this._cache_file_path, id,
- this._id);
- yield this.clear_cache ();
-
- return null;
- }
-
- var objects_variant = variant.get_child_value (2);
-
- var objects = new HashSet<T> ();
-
- for (uint i = 0; i < objects_variant.n_children (); i++)
- {
- var object_variant = objects_variant.get_child_value (i);
- var object = this.deserialise_object (object_variant, object_version);
-
- objects.add (object);
- }
-
- return objects;
- }
-
- /**
- * Store a set of objects to the cache file, overwriting any existing set of
- * objects in the cache, or creating the cache file from scratch if it didn't
- * previously exist.
- *
- * Storing the objects can be cancelled using ``cancellable``. If it is, the
- * cache will be left in a consistent state, but may be storing the old set
- * of objects or the new set.
- *
- * This method is safe to call multiple times concurrently.
- *
- * @param objects A set of objects to store. This may be empty, but may not
- * be ``null``.
- * @param cancellable A {@link GLib.Cancellable} for the operation, or
- * ``null``.
- *
- * @since 0.6.0
- */
- public async void store_objects (Set<T> objects,
- Cancellable? cancellable = null)
- {
- debug ("Storing cache (type ID '%s', ID '%s') to file '%s'.",
- this.type_id, this._id, this._cache_file_path);
-
- var child_type = this.get_serialised_object_type (uint8.MAX);
- assert (child_type != null); // uint8.MAX should always be supported
- Variant[] children = new Variant[objects.size];
-
- // Serialise all the objects in the set
- uint i = 0;
- foreach (var object in objects)
- {
- children[i++] = this.serialise_object (object);
- }
-
- // File format
- var wrapper_version = ObjectCache._FILE_FORMAT_VERSION;
- var object_version = this.get_serialised_object_version ();
-
- var variant = new Variant.tuple ({
- new Variant.string (this.type_id), // Type ID
- new Variant.string (this._id), // ID
- new Variant.array (child_type, children) // Array of objects
- });
-
- var desired_variant_type =
- this._get_cache_file_variant_type (wrapper_version, object_version);
- assert (desired_variant_type != null &&
- variant.get_type ().equal ((!) desired_variant_type));
-
- // Prepend the version numbers to the data
- uint8[] data = new uint8[ObjectCache._HEADER_WIDTH + variant.get_size ()];
- data[0] = wrapper_version;
- data[1] = object_version;
- variant.store (data[ObjectCache._HEADER_WIDTH:data.length]);
-
- // Write the data out to the file
- while (true)
- {
- try
- {
- /* We assume that replace_contents_async() is atomic. */
- yield this._cache_file.replace_contents_async (
- data, null, false,
- FileCreateFlags.PRIVATE, cancellable, null);
- break;
- }
- catch (Error e)
- {
- if (e is IOError.NOT_FOUND)
- {
- try
- {
- yield this._create_cache_directory ();
- continue;
- }
- catch (Error e2)
- {
- warning ("Couldn't create cache directory '%s': %s",
- this._cache_directory.get_path (), e.message);
- return;
- }
- }
- else if (e is IOError.CANCELLED)
- {
- /* We assume the replace_contents_async() call is atomic,
- * so cancelling it is atomic as well. */
- return;
- }
-
- /* Print a warning and delete the cache file so we don't leave
- * stale cached objects lying around. */
- warning ("Couldn't write to cache file '%s', so deleting it: %s",
- this._cache_file_path, e.message);
- yield this.clear_cache ();
-
- return;
- }
- }
- }
-
- /**
- * Clear this cache object, deleting its backing file.
- *
- * @since 0.6.0
- */
- public async void clear_cache ()
- {
- debug ("Clearing cache (type ID '%s', ID '%s'); deleting file '%s'.",
- this.type_id, this._id, this._cache_file_path);
-
- try
- {
- this._cache_file.delete ();
- }
- catch (Error e)
- {
- // Ignore errors
- }
- }
-
- private VariantType? _get_cache_file_variant_type (uint8 wrapper_version,
- uint8 object_version)
- {
- var _object_type = this.get_serialised_object_type (object_version);
-
- if (_object_type == null)
- {
- // Unsupported version
- return null;
- }
- var object_type = (!) _object_type;
-
- return new VariantType.tuple ({
- VariantType.STRING, // Type ID
- VariantType.STRING, // ID
- new VariantType.array (object_type) // Objects
- });
- }
-
- private async void _create_cache_directory () throws Error
- {
- try
- {
- this._cache_directory.make_directory_with_parents ();
- }
- catch (Error e)
- {
- // Ignore errors caused by the directory existing already
- if (!(e is IOError.EXISTS))
- {
- throw e;
- }
- }
- }
-}
-
-/* vim: filetype=vala textwidth=80 tabstop=2 expandtab: */
diff --git a/folks/org.freedesktop.folks.gschema.xml.in b/folks/org.freedesktop.folks.gschema.xml.in
deleted file mode 100644
index 852fde0e..00000000
--- a/folks/org.freedesktop.folks.gschema.xml.in
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<schemalist>
- <schema id="org.freedesktop.folks" path="/org/freedesktop/folks/" gettext-domain="@GETTEXT_PACKAGE@">
- <key name="primary-store" type="s">
- <default>''</default>
- <summary>Primary store ID</summary>
- <description>The ID of the persona store which folks should use as primary (i.e. to store linking data in).
- The type ID of the store may optionally be prepended, separated by a colon.
- For example: ‘eds:system-address-book’ or ‘key-file’.</description>
- </key>
- </schema>
-</schemalist>
diff --git a/folks/persona-store.vala b/folks/persona-store.vala
deleted file mode 100644
index 5bb1d31e..00000000
--- a/folks/persona-store.vala
+++ /dev/null
@@ -1,807 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-
-/**
- * Trust level for a {@link PersonaStore}'s {@link Persona}s for linking
- * purposes.
- *
- * Trust levels are set internally by the backends, and must not be modified by
- * clients.
- *
- * @since 0.1.13
- */
-public enum Folks.PersonaStoreTrust
-{
- /**
- * The {@link Persona}s aren't trusted at all, and cannot be linked.
- *
- * This should be used for {@link PersonaStore}s where even the
- * {@link Persona} UID could be maliciously edited to corrupt {@link Persona}
- * links, or where the UID changes regularly.
- *
- * @since 0.1.13
- */
- NONE,
-
- /**
- * Only the {@link Persona.uid} property is trusted for linking.
- *
- * In practice, this means that {@link Persona}s from this
- * {@link PersonaStore} will not contribute towards the linking process, but
- * can be linked together by their UIDs using data from {@link Persona}s from
- * a fully-trusted {@link PersonaStore}.
- *
- * @since 0.1.13
- */
- PARTIAL,
-
- /**
- * Every property in {@link Persona.linkable_properties} is trusted.
- *
- * This should only be used for user-controlled {@link PersonaStore}s, as if a
- * remote store is compromised, malicious changes could be made to its data
- * which corrupt the user's {@link Persona} links.
- *
- * @since 0.1.13
- */
- FULL
-}
-/**
- * Errors from {@link PersonaStore}s.
- */
-public errordomain Folks.PersonaStoreError
-{
- /**
- * An argument to the method was invalid.
- */
- INVALID_ARGUMENT,
-
- /**
- * Creation of a {@link Persona} failed.
- */
- CREATE_FAILED,
-
- /**
- * Such an operation may not be performed on a {@link Persona} with
- * {@link Persona.is_user} set to ``true``.
- *
- * @since 0.3.0
- */
- UNSUPPORTED_ON_USER,
-
- /**
- * The {@link PersonaStore} was offline (ie, this is a temporary failure).
- *
- * @since 0.3.0
- */
- STORE_OFFLINE,
-
- /**
- * The {@link PersonaStore} doesn't support write operations.
- *
- * @since 0.3.4
- */
- READ_ONLY,
-
- /**
- * The operation was denied due to not having sufficient permissions.
- *
- * @since 0.6.0
- */
- PERMISSION_DENIED,
-
- /**
- * Removal of a {@link Persona} failed. This is a generic error which is used
- * if no other error code (such as, e.g.,
- * {@link PersonaStoreError.PERMISSION_DENIED}) is applicable.
- *
- * @since 0.6.0
- */
- REMOVE_FAILED,
-
- /**
- * Such an operation may only be performed on a {@link Persona} with
- * {@link Persona.is_user} set to ``true``.
- *
- * @since 0.6.4
- */
- UNSUPPORTED_ON_NON_USER,
-}
-
-/**
- * Definition of the available fields to be looked up with
- * {@link PersonaStore.detail_key}.
- *
- * @since 0.5.0
- */
-/* NOTE: Must be kept in sync with
- * {@link Folks.PersonaStore._PERSONA_DETAIL}. */
-public enum Folks.PersonaDetail
-{
- /**
- * Invalid field for use in error returns.
- *
- * @since 0.6.2
- */
- INVALID = -1,
-
- /**
- * Field for {@link AliasDetails.alias}.
- *
- * @since 0.5.0
- */
- ALIAS = 0,
-
- /**
- * Field for {@link AvatarDetails.avatar}.
- *
- * @since 0.5.0
- */
- AVATAR,
-
- /**
- * Field for {@link BirthdayDetails.birthday}.
- *
- * @since 0.5.0
- */
- BIRTHDAY,
-
- /**
- * Field for {@link EmailDetails.email_addresses}.
- *
- * @since 0.5.0
- */
- EMAIL_ADDRESSES,
-
- /**
- * Field for {@link NameDetails.full_name}.
- *
- * @since 0.5.0
- */
- FULL_NAME,
-
- /**
- * Field for {@link GenderDetails.gender}.
- *
- * @since 0.5.0
- */
- GENDER,
-
- /**
- * Field for {@link ImDetails.im_addresses}.
- *
- * @since 0.5.0
- */
- IM_ADDRESSES,
-
- /**
- * Field for {@link FavouriteDetails.is_favourite}.
- *
- * @since 0.5.0
- */
- IS_FAVOURITE,
-
- /**
- * Field for {@link LocalIdDetails.local_ids}.
- *
- * @since 0.5.0
- */
- LOCAL_IDS,
-
- /**
- * Field for {@link LocationDetails.location}.
- *
- * @since 0.9.2
- */
- LOCATION,
-
- /**
- * Field for {@link NameDetails.nickname}.
- *
- * @since 0.5.0
- */
- NICKNAME,
-
- /**
- * Field for {@link NoteDetails.notes}.
- *
- * @since 0.5.0
- */
- NOTES,
-
- /**
- * Field for {@link PhoneDetails.phone_numbers}.
- *
- * @since 0.5.0
- */
- PHONE_NUMBERS,
-
- /**
- * Field for {@link PostalAddressDetails.postal_addresses}.
- *
- * @since 0.5.0
- */
- POSTAL_ADDRESSES,
-
- /**
- * Field for {@link RoleDetails.roles}.
- *
- * @since 0.5.0
- */
- ROLES,
-
- /**
- * Field for {@link NameDetails.structured_name}.
- *
- * @since 0.5.0
- */
- STRUCTURED_NAME,
-
- /**
- * Field for {@link UrlDetails.urls}.
- *
- * @since 0.5.0
- */
- URLS,
-
- /**
- * Field for {@link WebServiceDetails.web_service_addresses}.
- *
- * @since 0.5.0
- */
- WEB_SERVICE_ADDRESSES,
-
- /**
- * Field for {@link GroupDetails.groups}.
- *
- * @since 0.6.2
- */
- GROUPS,
-
- /**
- * Field for {@link InteractionDetails.im_interaction_count}.
- *
- * @since 0.7.1
- */
- IM_INTERACTION_COUNT,
-
- /**
- * Field for {@link InteractionDetails.last_im_interaction_datetime}.
- *
- * @since 0.7.1
- */
- LAST_IM_INTERACTION_DATETIME,
-
- /**
- * Field for {@link InteractionDetails.call_interaction_count}.
- *
- * @since 0.7.1
- */
- CALL_INTERACTION_COUNT,
-
- /**
- * Field for {@link InteractionDetails.last_call_interaction_datetime}.
- *
- * @since 0.7.1
- */
- LAST_CALL_INTERACTION_DATETIME,
-
- /**
- * Field for {@link AntiLinkable.anti_links}.
- *
- * @since 0.7.3
- */
- ANTI_LINKS,
-
- /**
- * Field for {@link ExtendedFieldDetails}.
- *
- * @since 0.11.0
- */
- EXTENDED_INFO,
-}
-
-/**
- * A store for {@link Persona}s.
- *
- * After creating a PersonaStore instance, you must connect to the
- * {@link PersonaStore.personas_changed} signal, //then// call
- * {@link PersonaStore.prepare}, otherwise a race condition may occur between
- * emission of {@link PersonaStore.personas_changed} and your code connecting to
- * it.
- */
-public abstract class Folks.PersonaStore : Object
-{
- construct
- {
- debug ("Constructing PersonaStore ‘%s’ (%p)", this.id, this);
- }
-
- ~PersonaStore ()
- {
- debug ("Destroying PersonaStore ‘%s’ (%p)", this.id, this);
- }
-
- /**
- * The following list of properties are the basic keys
- * that each PersonaStore with write capabilities should
- * support for {@link PersonaStore.add_persona_from_details}.
- *
- * Note that these aren't the only valid keys; backends are
- * allowed to support keys beyond the ones defined here
- * which might be specific to the backend in question.
- *
- * NOTE: MUST be kept in sync with {@link Folks.PersonaDetail}.
- *
- * @since 0.5.0
- */
- private const string _PERSONA_DETAIL[] = {
- "alias",
- "avatar",
- "birthday",
- "email-addresses",
- "full-name",
- "gender",
- "im-addresses",
- "is-favourite",
- "local-ids",
- "location",
- "nickname",
- "notes",
- "phone-numbers",
- "postal-addresses",
- "roles",
- "structured-name",
- "urls",
- "web-service-addresses",
- "groups",
- "im-interaction-count",
- "last-im-interaction-datetime",
- "call-interaction-count",
- "last-call-interaction-datetime",
- "anti-links",
- "extended-info"
- };
-
- /**
- * Returns the key corresponding to @detail, for use in
- * the details param of {@link PersonaStore.add_persona_from_details}.
- *
- * @param detail the {@link PersonaDetail} to lookup
- * @return the corresponding property name, or ``null`` if ``detail`` is
- * invalid
- *
- * @since 0.5.0
- */
- public static unowned string? detail_key (Folks.PersonaDetail detail)
- {
- if (detail == PersonaDetail.INVALID ||
- detail >= PersonaStore._PERSONA_DETAIL.length)
- {
- return null;
- }
-
- return PersonaStore._PERSONA_DETAIL[detail];
- }
-
- /**
- * Emitted when one or more {@link Persona}s are added to or removed from
- * the store.
- *
- * This will not be emitted until after {@link PersonaStore.prepare} has been
- * called.
- *
- * @param added a set of {@link Persona}s which have been removed
- * @param removed a set of {@link Persona}s which have been removed
- * @param message a string message from the backend, if any
- * @param actor the {@link Persona} who made the change, if known
- * @param reason the reason for the change
- *
- * @since 0.5.1
- */
- public signal void personas_changed (Set<Persona> added,
- Set<Persona> removed,
- string? message,
- Persona? actor,
- GroupDetails.ChangeReason reason);
-
- /* Emit the personas-changed signal, turning null parameters into empty sets
- * and only passing a read-only view to the signal handlers. */
- protected void _emit_personas_changed (Set<Persona>? added,
- Set<Persona>? removed,
- string? message = null,
- Persona? actor = null,
- GroupDetails.ChangeReason reason = GroupDetails.ChangeReason.NONE)
- {
- var _added = added;
- var _removed = removed;
-
- if ((added == null || ((!) added).size == 0) &&
- (removed == null || ((!) removed).size == 0))
- {
- /* Don't bother signalling if nothing's changed */
- return;
- }
- else if (added == null)
- {
- _added = new HashSet<Persona> ();
- }
- else if (removed == null)
- {
- _removed = new HashSet<Persona> ();
- }
-
- Internal.profiling_point ("emitting PersonaStore::personas-changed " +
- "(ID: %s, count: %u)", this.id, _added.size + _removed.size);
-
- // We've now guaranteed that both _added and _removed are non-null.
- this.personas_changed (((!) _added).read_only_view,
- ((!) _removed).read_only_view, message, actor, reason);
- }
-
- /**
- * Emitted when the backing store for this PersonaStore has been removed.
- *
- * At this point, the PersonaStore and all its {@link Persona}s are invalid,
- * so any client referencing it should unreference it.
- *
- * This will not be emitted until after {@link PersonaStore.prepare} has been
- * called.
- */
- public abstract signal void removed ();
-
- /**
- * The type of PersonaStore this is.
- *
- * This is the same for all PersonaStores provided by a given {@link Backend}.
- *
- * This is guaranteed to always be available; even before
- * {@link PersonaStore.prepare} is called. It is immutable over the life of
- * the {@link PersonaStore}.
- */
- public abstract string type_id
- {
- /* Note: the type_id must not contain colons because the primary writeable
- * store is configured, either via GSettings or the FOLKS_PRIMARY_STORE
- * env variable, with a string of the form 'type_id:store_id'. */
- get;
- }
-
- /**
- * The human-readable, service-specific name used to represent the
- * PersonaStore to the user.
- *
- * For example: ``foo@@xmpp.example.org``.
- *
- * This should be used whenever the user needs to be presented with a
- * familiar, service-specific name. For instance, in a prompt for the user to
- * select a specific IM account from which to initiate a chat.
- *
- * This is not guaranteed to be unique even within this PersonaStore's
- * {@link Backend}. Its value may change throughout the life of the store.
- *
- * @since 0.1.13
- */
- public string display_name { get; construct; }
-
- /**
- * The instance identifier for this PersonaStore.
- *
- * Since each {@link Backend} can provide multiple different PersonaStores
- * for different accounts or servers (for example), they each need an ID
- * which is unique within the backend.
- *
- * It is immutable over the life of the {@link PersonaStore}.
- */
- public string id { get; construct; }
-
- /**
- * The {@link Persona}s exposed by this PersonaStore.
- *
- * @since 0.5.1
- */
- public abstract Map<string, Persona> personas { get; }
-
- /**
- * Whether this {@link PersonaStore} can add {@link Persona}s.
- *
- * This value may change throughout the life of the {@link PersonaStore}.
- *
- * @since 0.3.1
- */
- public abstract MaybeBool can_add_personas { get; default = MaybeBool.UNSET; }
-
- /**
- * Whether this {@link PersonaStore} can set the alias of {@link Persona}s.
- *
- * @since 0.3.1
- */
- [Version (deprecated = true, deprecated_since = "0.6.3.1",
- replacement = "PersonaStore.always_writeable_properties")]
- public abstract MaybeBool can_alias_personas
- {
- get;
- default = MaybeBool.UNSET;
- }
-
- /**
- * Whether this {@link PersonaStore} can set the groups of {@link Persona}s.
- *
- * @since 0.3.1
- */
- [Version (deprecated = true, deprecated_since = "0.6.3.1",
- replacement = "PersonaStore.always_writeable_properties")]
- public abstract MaybeBool can_group_personas
- {
- get;
- default = MaybeBool.UNSET;
- }
-
- /**
- * Whether this {@link PersonaStore} can remove {@link Persona}s.
- *
- * This value may change throughout the life of the {@link PersonaStore}.
- *
- * @since 0.3.1
- */
- public abstract MaybeBool can_remove_personas
- {
- get;
- default = MaybeBool.UNSET;
- }
-
- /**
- * Whether {@link PersonaStore.prepare} has successfully completed for this
- * store.
- *
- * It’s guaranteed that this will only ever change from ``false`` to ``true``
- * in the lifetime of the {@link PersonaStore}.
- *
- * @since 0.3.0
- */
- public abstract bool is_prepared { get; default = false; }
-
- /**
- * Whether the store has reached a quiescent state. This will happen at some
- * point after {@link PersonaStore.prepare} has successfully completed for the
- * store. A store is in a quiescent state when all the {@link Persona}s that
- * it originally knows about have been loaded.
- *
- * It's guaranteed that this property's value will only ever change after
- * {@link IndividualAggregator.is_prepared} has changed to ``true``.
- *
- * @since 0.6.2
- */
- public abstract bool is_quiescent { get; default = false; }
-
- /**
- * Whether the PersonaStore is writeable.
- *
- * Only if a PersonaStore is writeable will its {@link Persona}s be updated by
- * changes to the {@link Individual}s containing them, and those changes then
- * be written out to the relevant backing store.
- *
- * If this property is ``false``, it doesn't mean that {@link Persona}s in
- * this persona store aren't writeable at all. If their properties are updated
- * through the {@link Persona}, rather than through the {@link Individual}
- * containing that persona, changes may be propagated to the backing store.
- *
- * PersonaStores must not set this property themselves; it will be set as
- * appropriate by the {@link IndividualAggregator}.
- *
- * @since 0.1.13
- */
- [Version (deprecated = true, deprecated_since = "0.6.3",
- replacement = "PersonaStore.is_primary_store")]
- public bool is_writeable { get; set; default = false; }
-
- private PersonaStoreTrust _trust_level = PersonaStoreTrust.NONE;
-
- /**
- * The trust level of the PersonaStore for linking.
- *
- * Each {@link PersonaStore} is assigned a trust level by the
- * IndividualAggregator, designating whether to trust the properties of its
- * {@link Persona}s for linking to produce {@link Individual}s.
- *
- * This value may change throughout the life of the {@link PersonaStore}.
- *
- * The trust level may be queried by clients, but must not be set by them. The
- * setter for this property is for libfolks internal use only.
- *
- * @see PersonaStoreTrust
- * @since 0.1.13
- */
- public PersonaStoreTrust trust_level
- {
- get
- {
- return this._trust_level;
- }
-
- /* FIXME: At the next API break, make this an abstract property and have
- * implemented by the backends, to avoid exposing the setter in the C
- * API. The IndividualAggregator can always disregard the backend’s
- * suggested trust level.
- *
- * https://bugzilla.gnome.org/show_bug.cgi?id=722421 */
- set
- {
- if (value > trust_level)
- {
- this._trust_level = value;
- this.notify_property ("trust-level");
- }
- else
- {
- debug ("Unable to lower Persona Store trust_level");
- }
- }
- }
-
- /**
- * The names of the properties of the {@link Persona}s in this store which are
- * always writeable.
- *
- * If a property name is in this list, setting the property on a persona
- * should result in the updated value being stored in the backend's permanent
- * storage (unless it gets rejected due to being invalid, or a different error
- * occurs).
- *
- * This property value is guaranteed to be constant for a given persona store,
- * but may vary between persona stores in the same backend. It's guaranteed
- * that this will always be a subset of the value of
- * {@link Persona.writeable_properties} for the personas in this persona
- * store.
- *
- * @since 0.6.2
- */
- public abstract string[] always_writeable_properties { get; }
-
- /**
- * Prepare the PersonaStore for use.
- *
- * This connects the PersonaStore to whichever backend-specific services it
- * requires to be able to provide {@link Persona}s. This should be called
- * //after// connecting to the {@link PersonaStore.personas_changed} signal,
- * or a race condition could occur, with the signal being emitted before your
- * code has connected to it, and {@link Persona}s getting "lost" as a result.
- *
- * This is normally handled transparently by the {@link IndividualAggregator}.
- *
- * If this function throws an error, the PersonaStore will not be functional.
- *
- * This function is guaranteed to be idempotent (since version 0.3.0).
- *
- * Concurrent calls to this function from different threads will block until
- * preparation has completed. However, concurrent calls to this function from
- * a single thread might not, i.e. the first call will block but subsequent
- * calls might return before the first one. (Though they will be safe in every
- * other respect.)
- *
- * @throws GLib.Error if preparing the backend-specific services failed — this
- * will be a backend-specific error
- *
- * @since 0.1.11
- */
- public abstract async void prepare () throws GLib.Error;
-
- /**
- * Flush any pending changes to the PersonaStore's backing store.
- *
- * PersonaStores may (transparently) implement caching or I/O queueing which
- * means that changes to their {@link Persona}s may not be immediately written
- * to the PersonaStore's backing store. Calling this function will force all
- * pending changes to be flushed to the backing store.
- *
- * This must not be called before {@link PersonaStore.prepare}.
- *
- * @since 0.1.17
- */
- public virtual async void flush ()
- {
- /* Default implementation doesn't have to do anything */
- }
-
- /**
- * Add a new {@link Persona} to the PersonaStore.
- *
- * The {@link Persona} will be created by the PersonaStore backend from the
- * key-value pairs given in ``details``.
- *
- * All additions through this function will later be emitted through the
- * personas-changed signal to be notified of the new {@link Persona}. The
- * return value is purely for convenience, since it can be complicated to
- * correlate the provided details with the final Persona.
- *
- * If the store is offline (or {@link PersonaStore.prepare} hasn't yet been
- * called successfully), this function will throw
- * {@link PersonaStoreError.STORE_OFFLINE}. It's the responsibility of the
- * caller to cache details and re-try this function if it wishes to make
- * offline adds work.
- *
- * If the details are not recognised or are invalid,
- * {@link PersonaStoreError.INVALID_ARGUMENT} will be thrown. A default set
- * of possible details are defined by {@link Folks.PersonaDetail} but backends
- * can either support a subset or superset of the suggested defaults.
- *
- * If a {@link Persona} with the given details already exists in the store, no
- * error will be thrown and this function will return ``null``.
- *
- * @param details a key-value map of details to use in creating the new
- * {@link Persona}
- *
- * @return the new {@link Persona} or ``null`` if the corresponding Persona
- * already existed. If non-``null``, the new {@link Persona} will also be
- * amongst the {@link Persona}(s) in a future emission of
- * {@link PersonaStore.personas_changed}.
- * @throws PersonaStoreError if adding the persona failed
- */
- public abstract async Persona? add_persona_from_details (
- HashTable<string, Value?> details) throws Folks.PersonaStoreError;
-
- /**
- * Remove a {@link Persona} from the PersonaStore.
- *
- * It isn't guaranteed that the Persona will actually be removed by the time
- * this asynchronous function finishes. The successful removal of the Persona
- * will be signalled through emission of
- * {@link PersonaStore.personas_changed}.
- *
- * If the store is offline (or {@link PersonaStore.prepare} hasn't yet been
- * called successfully), this function will throw
- * {@link PersonaStoreError.STORE_OFFLINE}. It's the responsibility of the
- * caller to cache details and re-try this function if it wishes to make
- * offline removals work.
- *
- * @param persona the {@link Persona} to remove
- * @throws PersonaStoreError if removing the persona failed
- *
- * @since 0.1.11
- */
- public abstract async void remove_persona (Persona persona)
- throws Folks.PersonaStoreError;
-
- /**
- * Whether this {@link PersonaStore} is the primary store to be used for
- * linking {@link Persona}s.
- *
- * @since 0.6.3
- */
- public bool is_primary_store { get; internal set; default = false; }
-
- /* The setter folks_persona_store_set_is_user_set_default() is redeclared
- * in folks/redeclare-internal-api.h so that libfolks-eds can use it.
- * If you alter this property, check the generated C and update that
- * header if necessary. https://bugzilla.gnome.org/show_bug.cgi?id=697354 */
- /**
- * Whether this {@link PersonaStore} is marked as the default in its backend
- * by the user.
- *
- * i.e. A {@link PersonaStore} for the EDS backend would set this to ``true``
- * if it represents the user’s default address book.
- *
- * @since 0.6.3
- */
- public bool is_user_set_default { get; internal set; default = false; }
-}
diff --git a/folks/persona.vala b/folks/persona.vala
deleted file mode 100644
index add9be12..00000000
--- a/folks/persona.vala
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- * Copyright (C) 2013 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using GLib;
-
-/**
- * Errors which can be thrown when asynchronously setting a property of a
- * {@link Persona} using a setter method defined on an interface such as
- * {@link AliasDetails}.
- *
- * @since 0.6.2
- */
-public errordomain Folks.PropertyError
-{
- /**
- * Property is not writeable for this particular object.
- *
- * @since 0.6.2
- */
- NOT_WRITEABLE,
-
- /**
- * Value was invalid for the property.
- *
- * @since 0.6.2
- */
- INVALID_VALUE,
-
- /**
- * Unknown error when setting the property.
- *
- * @since 0.6.2
- */
- UNKNOWN_ERROR,
-
- /**
- * The backing store is offline or otherwise unavailable.
- *
- * This is a temporary error which should be retifiable by going online or
- * ensuring the backing store is logged in.
- *
- * @since 0.7.4
- */
- UNAVAILABLE
-}
-
-/**
- * Represents a "shard" of a person from a single source (a single
- * {@link Backend}), such as an XMPP contact from Telepathy or a vCard contact
- * from evolution-data-server.
- *
- * All the personas belonging to one physical person are aggregated to form a
- * single {@link Individual} representing that person.
- *
- * Properties of a persona are provided by implementing "details" interfaces,
- * such as {@link NameDetails} or {@link EmailDetails}. They must be accessed
- * through these interfaces. Different backends' subclasses of {@link Persona}
- * may implement different sets of interfaces. The set of interfaces implemented
- * by a given persona is guaranteed not to change over the lifetime of that
- * persona.
- */
-public abstract class Folks.Persona : Object
-{
- /**
- * The internal ID used to represent the Persona for linking.
- *
- * This is opaque, and shouldn't be parsed or considered meaningful by
- * clients.
- *
- * The internal ID should be unique within a backend, but may not be unique
- * across backends, so that links can be made between Personas with similar
- * internal IDs.
- */
- /* For example: jabber:foo@xmpp.example.org or joe@example.org */
- public string iid { get; construct; }
-
- /**
- * The universal ID used to represent the Persona outside its {@link Backend}.
- *
- * This is opaque, and should only be parsed by clients using
- * {@link Persona.split_uid}.
- *
- * This is the canonical way to refer to any Persona. It is guaranteed to be
- * unique within the Persona's {@link PersonaStore}.
- *
- * A Persona's UID is immutable over the life of the Persona in the backing
- * store, so a given UID is guaranteed to refer to the same Persona each time
- * libfolks is used, until the Persona is permanently removed from its backing
- * store.
- *
- * @see Persona.build_uid
- * @see Persona.split_uid
- */
- /* For example: telepathy:jabber:foo@xmpp.example.org or
- * key-file:relationships.ini:joe@example.org
- *
- * It comprises three components, separated by colons:
- * # {@link Backend.name}
- * # {@link PersonaStore.id}
- * # Persona identifier
- * Each component is escaped by replacing all colons with double underscores
- * before building the UID.*/
- public string uid { get; construct; }
-
- /**
- * The human-readable, service-specific universal ID used to represent the
- * Persona.
- *
- * For example: ``foo@@xmpp.example.org``.
- *
- * This should be used whenever the user needs to be presented with a
- * familiar, service-specific ID. For instance, in a prompt for the user to
- * select a specific IM contact within an {@link Individual} to begin a chat
- * with.
- *
- * This is not guaranteed to be unique outside of the Persona's
- * {@link PersonaStore}, but is guaranteed to be unique within it. If a
- * suitable human-readable ID isn’t available from the backend, the display ID
- * will be equal to the {@link Persona.iid}.
- *
- * @since 0.1.13
- */
- public string display_id { get; construct; }
-
- /**
- * Whether the Persona is the user.
- *
- * Iff the Persona represents the user (the person who owns the account in
- * the respective backend) this is ``true``.
- *
- * @since 0.3.0
- */
- public bool is_user { get; construct; }
-
- /**
- * The {@link PersonaStore} which contains this Persona.
- */
- public weak PersonaStore store { get; construct; }
-
- private weak Individual? _individual = null;
-
- private void _individual_weak_notify_cb (Object obj)
- {
- debug ("Individual %p has been destroyed; resetting the Individual of %s",
- obj, this.iid);
- this._individual = null;
- this.notify_property ("individual");
- }
-
- /**
- * The {@link Individual} which contains this Persona.
- *
- * This may be ``null``, but should only ever be so when the Persona has just
- * been created, when its {@link PersonaStore} is being destroyed, or when
- * it's moving between {@link Individual}s.
- *
- * @since 0.6.0
- */
- public weak Individual? individual
- {
- get
- {
- assert (this._individual == null ||
- ((!) this._individual).personas.contains (this));
-
- return this._individual;
- }
-
- internal set
- {
- assert (value == null || ((!) value).personas.contains (this));
-
- if (this._individual != null)
- {
- this._individual.weak_unref (this._individual_weak_notify_cb);
- }
-
- if (value != null)
- {
- value.weak_ref (this._individual_weak_notify_cb);
- }
-
- this._individual = value;
- }
- }
-
- /**
- * The names of the properties of this Persona which are linkable.
- *
- * If a property name is in this list, and the Persona is from a
- * {@link PersonaStore} whose trust level is {@link PersonaStoreTrust.FULL},
- * the {@link IndividualAggregator} should be able to reliably use the value
- * of the property from a given Persona instance to link the Persona with
- * other Personas and form {@link Individual}s.
- *
- * Note that {@link Persona.uid} is always implicitly a member of this list,
- * and doesn't need to be added explicitly.
- *
- * This list will have no effect if the Persona's {@link PersonaStore} trust
- * level is not {@link PersonaStoreTrust.FULL}.
- *
- * This property value is guaranteed to be constant for a given persona,
- * but may vary between personas in the same store.
- *
- * @since 0.1.13
- */
- public abstract string[] linkable_properties { get; }
-
- /**
- * The names of the properties of this Persona which are writeable.
- *
- * If a property name is in this list, setting the property should result in
- * the updated value being stored in the backend's permanent storage (unless
- * it gets rejected due to being invalid, or a different error occurs).
- *
- * It's intended that this property value will be constant for a given Persona
- * subclass, but this isn't guaranteed; it's possible that Persona subclasses
- * may vary the value of this property at run time.
- *
- * @since 0.6.0
- */
- public abstract string[] writeable_properties { get; }
-
- /**
- * Callback into the aggregator to manipulate a link mapping.
- *
- * This is a callback provided by the {@link IndividualAggregator} whenever
- * a {@link Persona.linkable_property_to_links} method is called, which should
- * be called by the ``linkable_property_to_links`` implementation for each
- * linkable-property-to-individual mapping it wants to add or remove in the
- * aggregator.
- *
- * @param link the mapping string to be added to the
- * {@link IndividualAggregator}
- * @since 0.1.13
- */
- public delegate void LinkablePropertyCallback (string link);
-
- /* FIXME: This code should move to the ImDetails interface as a concrete
- * method of the interface. However, that depends on bgo#624842 */
- /**
- * Produce one or more mapping strings for the given property's value.
- *
- * This is a virtual method, to be overridden by subclasses of {@link Persona}
- * who have linkable properties. Each of their linkable properties should be
- * handled by their implementation of this function, examining the current
- * value of the property and calling ``callback`` with one or more mapping
- * strings for the property's value. Each of these mapping strings will be
- * added to the {@link IndividualAggregator}'s link map, related to the
- * {@link Individual} instance which contains this {@link Persona}.
- *
- * @param prop_name the name of the linkable property to use, which must be
- * listed in {@link Persona.linkable_properties}
- * @param callback a callback to execute for each of the mapping strings
- * generated by this property
- * @see Persona.linkable_properties
- * @since 0.1.13
- */
- public virtual void linkable_property_to_links (string prop_name,
- LinkablePropertyCallback callback)
- {
- /* Backend-specific Persona subclasses should override this if they have
- * any linkable properties */
- assert_not_reached ();
- }
-
- private static void _add_escaped_uid_component (StringBuilder uid, string component)
- {
- /* Escape colons with backslashes */
- for (int i = 0; i < component.length; i++)
- {
- char c = component[i];
- if (c == ':' || c == '\\')
- {
- uid.append_c ('\\');
- }
- uid.append_c (c);
- }
- }
-
- private static string _unescape_uid_component (string component)
- {
- /* Unescape colons and backslashes */
- string unescaped = component.replace ("\\:", ":");
- return unescaped.replace ("\\", "\\\\");
- }
-
- /**
- * Build a UID from the given components.
- *
- * Each component is escaped before the UID is built. All components must be
- * non-empty strings.
- *
- * @param backend_name the {@link Backend.name}
- * @param persona_store_id the {@link PersonaStore.id}
- * @param persona_id the Persona identifier (backend-specific)
- * @return a valid UID
- * @see Persona.split_uid
- * @since 0.1.13
- */
- public static string build_uid (string backend_name,
- string persona_store_id, string persona_id)
- requires (backend_name != "")
- requires (persona_store_id != "")
- requires (persona_id != "")
- {
- long min_total_length = backend_name.length
- + persona_store_id.length
- + persona_id.length
- + 2 // 2 colons
- + 1; // terminator
-
- StringBuilder uid = new StringBuilder.sized (min_total_length);
- Persona._add_escaped_uid_component (uid, backend_name);
- uid.append_c (':');
- Persona._add_escaped_uid_component (uid, persona_store_id);
- uid.append_c (':');
- Persona._add_escaped_uid_component (uid, persona_id);
-
- return (owned) uid.str;
- }
-
- /**
- * Split a UID into its component parts.
- *
- * Each component is unescaped before being returned. The UID //must// be
- * correctly formed.
- *
- * @param uid a valid UID
- * @param backend_name the {@link Backend.name}
- * @param persona_store_id the {@link PersonaStore.id}
- * @param persona_id the Persona identifier (backend-specific)
- * @see Persona.build_uid
- * @since 0.1.13
- */
- public static void split_uid (string uid, out string backend_name,
- out string persona_store_id, out string persona_id)
- {
- assert (uid.validate ());
-
- size_t backend_name_length = 0, persona_store_id_length = 0;
- var escaped = false;
- for (unowned string i = uid; i.get_char () != '\0'; i = i.next_char ())
- {
- if (i.get_char () == '\\')
- escaped = !escaped;
- else if (escaped == false && i.get_char () == ':')
- {
- if (backend_name_length == 0)
- backend_name_length = ((char*) i) - ((char*) uid);
- else
- persona_store_id_length =
- (((char*) i) - ((char*) uid)) - backend_name_length - 1;
- }
- }
-
- assert (backend_name_length != 0 && persona_store_id_length != 0);
-
- backend_name = Persona._unescape_uid_component (
- uid.substring (0, (long) backend_name_length));
- persona_store_id = Persona._unescape_uid_component (
- ((string) ((char*) uid + backend_name_length + 1)).substring (0,
- (long) persona_store_id_length));
- persona_id = Persona._unescape_uid_component (
- ((string) ((char*) uid + backend_name_length +
- persona_store_id_length + 2)));
- }
-
- ~Persona ()
- {
- this.individual = null;
- }
-}
diff --git a/folks/phone-details.vala b/folks/phone-details.vala
deleted file mode 100644
index 141ac073..00000000
--- a/folks/phone-details.vala
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- * Copyright (C) 2011, 2013 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Marco Barisione <marco.barisione@collabora.co.uk>
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using GLib;
-using Gee;
-
-/**
- * Object representing a phone number that can have some parameters associated
- * with it.
- *
- * See {@link Folks.AbstractFieldDetails} for details on common parameter names
- * and values.
- *
- * @since 0.6.0
- */
-public class Folks.PhoneFieldDetails : AbstractFieldDetails<string>
-{
- private const char[] _extension_chars = { 'p', 'P', 'w', 'W', 'x', 'X' };
- private const char[] _common_delimiters = { ',', '.', '(', ')', '-', ' ',
- '\t', '/' };
- private const char[] _valid_digits = { '#', '*', '0', '1', '2', '3', '4',
- '5', '6', '7', '8', '9' };
-
- private string _id;
- /**
- * {@inheritDoc}
- */
- public override string id
- {
- get { return this._id; }
- set { this._id = (value != null ? value : ""); }
- }
-
- /**
- * Create a new PhoneFieldDetails.
- *
- * @param value the value of the field, which should be a non-empty phone
- * number (no particular format is mandated)
- * @param parameters initial parameters. See
- * {@link AbstractFieldDetails.parameters}. A ``null`` value is equivalent to
- * an empty map of parameters.
- *
- * @return a new PhoneFieldDetails
- *
- * @since 0.6.0
- */
- public PhoneFieldDetails (string value,
- MultiMap<string, string>? parameters = null)
- {
- if (value == "")
- {
- warning ("Empty phone number passed to PhoneFieldDetails.");
- }
-
- Object (value: value,
- parameters: parameters);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override bool equal (AbstractFieldDetails<string> that)
- {
- return base.equal (that);
- }
-
- /**
- * {@inheritDoc}
- */
- public override bool values_equal (AbstractFieldDetails<string> that)
- {
- var _that_fd = that as PhoneFieldDetails;
- if (_that_fd == null)
- return false;
- PhoneFieldDetails that_fd = (!) _that_fd;
-
- var n1 = PhoneFieldDetails._drop_extension (this.get_normalised ());
- var n2 = PhoneFieldDetails._drop_extension (that_fd.get_normalised ());
-
- /* Based on http://blog.barisione.org/2010-06/handling-phone-numbers/ */
- if (n1.length >= 7 && n2.length >= 7)
- {
- var n1_reduced = n1.slice (-7, n1.length);
- var n2_reduced = n2.slice (-7, n2.length);
-
- debug ("[PhoneDetails.equal] Comparing %s with %s",
- n1_reduced, n2_reduced);
-
- return n1_reduced == n2_reduced;
- }
-
- return n1 == n2;
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override uint hash ()
- {
- return base.hash ();
- }
-
- /**
- * Return this object's normalised phone number.
- *
- * Typical normalisations:
- *
- * - ``1-800-123-4567`` → ``18001234567``
- * - ``+1-800-123-4567`` → ``+18001234567``
- * - ``+1-800-123-4567P123`` → ``+18001234567P123``
- *
- * @return the normalised form of ``number``
- *
- * @since 0.6.0
- */
- public string get_normalised ()
- {
- var builder = new StringBuilder ();
-
- /* Based on http://blog.barisione.org/2010-06/handling-phone-numbers/ */
- for (uint i = 0; i < this.value.length; i++)
- {
- var digit = this.value[i];
-
- if (digit in PhoneFieldDetails._extension_chars)
- {
- /* Keep the extension characters P, W and X, but be sure they are
- * upper case. */
- digit = digit.toupper ();
- }
- else if (digit == '+')
- {
- /* "+" is valid only at the beginning of phone numbers or after
- * the number suppression prefix. */
- if (builder.str != "" &&
- builder.str != "*31#" &&
- builder.str != "#31#")
- {
- /* Skip this "+". */
- debug ("[PhoneDetails.get_normalised] Wrong '+' in %s",
- this.value);
- continue;
- }
- }
- else if (digit in PhoneFieldDetails._common_delimiters)
- {
- /* Skip this delimiter. */
- continue;
- }
- else if (digit in PhoneFieldDetails._valid_digits)
- {
- /* Ok, let's keep it. */
- }
- else
- {
- /* What is this? It doesn't seem valid but we just keep it */
- debug ("[PhoneDetails.get_normalised] Unknown character '%c' in '%s'",
- digit, this.value);
- }
-
- builder.append_c (digit);
- }
-
- return builder.str;
- }
-
- /**
- * Returns the given number without its extension (if any).
- *
- * @param number the phone number to process
- * @return the number without its extension; if the number didn't have an
- * extension in the first place, the number is returned unmodified
- *
- * @since 0.6.0
- */
- internal static string _drop_extension (string number)
- {
- /* Based on http://blog.barisione.org/2010-06/handling-phone-numbers/ */
- var builder = new StringBuilder ();
-
- for (uint i = 0; i < number.length; i++)
- {
- var digit = number[i];
- if (digit in PhoneFieldDetails._extension_chars)
- {
- /* Extension character, drop this character and the rest of the
- * string. */
- break;
- }
- builder.append_c (digit);
- }
-
- return builder.str;
- }
-}
-
-/**
- * Interface for classes that can provide a phone number, such as
- * {@link Persona} and {@link Individual}.
- *
- * @since 0.3.5
- */
-public interface Folks.PhoneDetails : Object
-{
- /**
- * The phone numbers of the contact.
- *
- * A list of phone numbers associated to the contact.
- *
- * @since 0.6.0
- */
- public abstract Set<PhoneFieldDetails> phone_numbers { get; set; }
-
- /**
- * Change the contact's phone numbers.
- *
- * It's preferred to call this rather than setting
- * {@link PhoneDetails.phone_numbers} directly, as this method gives error
- * notification and will only return once the phone numbers have been written
- * to the relevant backing store (or the operation's failed).
- *
- * @param phone_numbers the set of phone numbers
- * @throws PropertyError if setting the phone numbers failed
- * @since 0.6.2
- */
- public virtual async void change_phone_numbers (
- Set<PhoneFieldDetails> phone_numbers) throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Phone numbers are not writeable on this contact."));
- }
-}
diff --git a/folks/postal-address-details.vala b/folks/postal-address-details.vala
deleted file mode 100644
index f08d874e..00000000
--- a/folks/postal-address-details.vala
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- * Copyright (C) 2011 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Marco Barisione <marco.barisione@collabora.co.uk>
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using GLib;
-using Gee;
-
-/**
- * Object representing a postal mail address.
- *
- * The components of the address are never ``null``: an empty string
- * indicates that a property is not set.
- */
-public class Folks.PostalAddress : Object
-{
- private string _po_box = "";
- /**
- * The PO Box.
- *
- * The PO Box (also known as Postal office box or Postal box).
- */
- public string po_box
- {
- get { return _po_box; }
- construct set { _po_box = (value != null ? value : ""); }
- }
-
- private string _extension = "";
- /**
- * The address extension.
- *
- * Any additional part of the address, for instance a flat number.
- */
- public string extension
- {
- get { return _extension; }
- construct set { _extension = (value != null ? value : ""); }
- }
-
- private string _street = "";
- /**
- * The street name and number.
- *
- * The street name including the optional building number.
- * The number can be before or after the street name based on the
- * language and country.
- */
- public string street
- {
- get { return _street; }
- construct set { _street = (value != null ? value : ""); }
- }
-
- private string _locality = "";
- /**
- * The locality.
- *
- * The locality, for instance the city name.
- */
- public string locality
- {
- get { return _locality; }
- construct set { _locality = (value != null ? value : ""); }
- }
-
- private string _region = "";
- /**
- * The region.
- *
- * The region, for instance the name of the state or province.
- */
- public string region
- {
- get { return _region; }
- construct set { _region = (value != null ? value : ""); }
- }
-
- private string _postal_code = "";
- /**
- * The postal code.
- *
- * The postal code (also known as post code, postcode or ZIP code).
- */
- public string postal_code
- {
- get { return _postal_code; }
- construct set { _postal_code = (value != null ? value : ""); }
- }
-
- private string _country = "";
- /**
- * The country.
- *
- * The name of the country.
- */
- public string country
- {
- get { return _country; }
- construct set { _country = (value != null ? value : ""); }
- }
-
- private string _address_format = "";
- /**
- * The address format.
- *
- * The two letter country code that determines the format or exact
- * meaning of the other fields.
- */
- public string address_format
- {
- get { return _address_format; }
- construct set { _address_format = (value != null ? value : ""); }
- }
-
- private string _uid = "";
- /**
- * The UID of the Postal Address (if any).
- */
- [Version (deprecated = true, deprecated_since = "0.6.5",
- replacement = "AbstractFieldDetails.id")]
- public string uid
- {
- get { return _uid; }
- construct set { _uid = (value != null ? value : ""); }
- }
-
- /**
- * Create a PostalAddress.
- *
- * You can pass ``null`` if a component is not set.
- *
- * @param po_box the PO Box
- * @param extension the address extension
- * @param street the street name and number
- * @param locality the locality (city, town or village) name
- * @param region the region (state or province) name
- * @param postal_code the postal code
- * @param country the country name
- * @param address_format the address format
- * @param uid external UID for the address instance
- * @since 0.5.1
- */
- public PostalAddress (string? po_box, string? extension, string? street,
- string? locality, string? region, string? postal_code, string? country,
- string? address_format, string? uid)
- {
- Object (po_box: po_box,
- extension: extension,
- street: street,
- locality: locality,
- region: region,
- postal_code: postal_code,
- country: country,
- address_format: address_format,
- uid: uid);
- }
-
- /**
- * Whether none of the components is set.
- *
- * @return ``true`` if all the components are the empty string, ``false``
- * otherwise.
- *
- * @since 0.6.7
- */
- public bool is_empty ()
- {
- return this.po_box == "" &&
- this.extension == "" &&
- this.street == "" &&
- this.locality == "" &&
- this.region == "" &&
- this.postal_code == "" &&
- this.country == "" &&
- this.address_format == "";
- }
-
- /**
- * Compare if two postal addresses are equal. Addresses are equal if all their
- * components are equal (where ``null`` compares equal only with ``null``) and
- * they have the same set of types (or both have no types).
- *
- * This does not factor in the {@link PostalAddress.uid}.
- *
- * @param with another postal address to compare with
- * @return ``true`` if the addresses are equal, ``false`` otherwise
- */
- public bool equal (PostalAddress with)
- {
- if (this.po_box != with.po_box ||
- this.extension != with.extension ||
- this.street != with.street ||
- this.locality != with.locality ||
- this.region != with.region ||
- this.postal_code != with.postal_code ||
- this.country != with.country ||
- this.address_format != with.address_format)
- return false;
-
- return true;
- }
-
- /**
- * Get a formatted version of the address. The format is localised, and by
- * default is comma-separated.
- *
- * @return a formatted address.
- *
- * @since 0.4.0
- */
- public string to_string ()
- {
- var str = _("%s, %s, %s, %s, %s, %s, %s");
- return str.printf (this.po_box, this.extension, this.street,
- this.locality, this.region, this.postal_code, this.country);
- }
-}
-
-/**
- * Object representing a PostalAddress value that can have some parameters
- * associated with it.
- *
- * See {@link Folks.AbstractFieldDetails} for details on common parameter names
- * and values.
- *
- * @since 0.6.0
- */
-public class Folks.PostalAddressFieldDetails :
- AbstractFieldDetails<PostalAddress>
-{
- private string _id;
- /**
- * {@inheritDoc}
- */
- public override string id
- {
- get { return this._id; }
- set
- {
- this._id = (value != null ? value : "");
-
- /* Keep the PostalAddress.uid sync'd from our id */
- if (this._id != this.value.uid)
- this.value.uid = this._id;
- }
- }
-
- /**
- * Create a new PostalAddressFieldDetails.
- *
- * @param value the value of the field, a non-empty {@link PostalAddress}
- * @param parameters initial parameters. See
- * {@link AbstractFieldDetails.parameters}. A ``null`` value is equivalent to
- * an empty map of parameters.
- *
- *
- * @return a new PostalAddressFieldDetails
- *
- * @since 0.6.0
- */
- public PostalAddressFieldDetails (PostalAddress value,
- MultiMap<string, string>? parameters = null)
- {
- if (value.is_empty ())
- {
- warning ("Empty postal address passed to PostalAddressFieldDetails.");
- }
-
- /* We keep id and value.uid synchronised in both directions. */
- Object (value: value,
- parameters: parameters,
- id: value.uid);
- }
-
- construct
- {
- /* Keep the PostalAddress.uid sync'd to our id */
- this.value.notify["uid"].connect ((s, p) =>
- {
- if (this.id != this.value.uid)
- this.id = this.value.uid;
- });
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override bool equal (AbstractFieldDetails<PostalAddress> that)
- {
- if (!base.parameters_equal (that))
- return false;
-
- /* This is fairly-dumb but smart matching is an i10n nightmare. */
- return this.value.to_string () == that.value.to_string ();
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override uint hash ()
- {
- /* This is basic because smart matching is very hard (see equal()). */
- return str_hash (this.value.to_string ());
- }
-}
-
-/**
- * Interface for classes that can provide postal addresses, such as
- * {@link Persona} and {@link Individual}.
- */
-public interface Folks.PostalAddressDetails : Object
-{
- /**
- * The postal addresses of the contact.
- *
- * A list of postal addresses associated to the contact.
- *
- * @since 0.5.1
- */
- public abstract Set<PostalAddressFieldDetails> postal_addresses { get; set; }
-
- /**
- * Change the contact's postal addresses.
- *
- * It's preferred to call this rather than setting
- * {@link PostalAddressDetails.postal_addresses} directly, as this method
- * gives error notification and will only return once the addresses have been
- * written to the relevant backing store (or the operation's failed).
- *
- * @param postal_addresses the set of postal addresses
- * @throws PropertyError if setting the addresses failed
- * @since 0.6.2
- */
- public virtual async void change_postal_addresses (
- Set<PostalAddressFieldDetails> postal_addresses) throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Postal addresses are not writeable on this contact."));
- }
-}
diff --git a/folks/potential-match.vala b/folks/potential-match.vala
deleted file mode 100644
index 3ddf976c..00000000
--- a/folks/potential-match.vala
+++ /dev/null
@@ -1,686 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- */
-
-using Gee;
-
-/**
- * Likely-ness of a potential match.
- *
- * Note that the order should be maintained.
- *
- * @since 0.5.0
- */
-public enum Folks.MatchResult
-{
- /**
- * Zero likelihood of a match.
- *
- * This is used in situations where two individuals should never be linked,
- * such as when one of them has a {@link Individual.trust_level} of
- * {@link TrustLevel.NONE}, or when the individuals are explicitly
- * anti-linked.
- *
- * @since 0.6.8
- */
- NONE = -1,
-
- /**
- * Very low likelihood of a match.
- */
- VERY_LOW = 0,
-
- /**
- * Low likelihood of a match.
- */
- LOW = 1,
-
- /**
- * Medium likelihood of a match.
- */
- MEDIUM = 2,
-
- /**
- * High likelihood of a match.
- */
- HIGH = 3,
-
- /**
- * Very high likelihood of a match.
- */
- VERY_HIGH = 4,
-
- /**
- * Minimum likelihood of a match.
- */
- MIN = NONE,
-
- /**
- * Maximum likelihood of a match.
- */
- MAX = VERY_HIGH
-}
-
-/**
- * Match calculator for pairs of individuals.
- *
- * This provides functionality to explore the degree of a potential match
- * between two individuals. It compares the similarity of the individuals'
- * properties to determine how likely it is that the individuals represent the
- * same physical person.
- *
- * This can be used by folks clients to, for example, present suggestions of
- * pairs of individuals which should be linked by the user.
- *
- * @since 0.5.0
- */
-public class Folks.PotentialMatch : Object
-{
- private Folks.Individual _individual_a;
- private Folks.Individual _individual_b;
-
- /**
- * A set of e-mail addresses known to be aliases of each other, such as
- * various forms of administrator address.
- *
- * @since 0.5.1
- */
- public static Set<string> known_email_aliases = new SmallSet<string> ();
-
- private static double _DIST_THRESHOLD = 0.70;
- private const string _SEPARATORS = "._-+";
-
- static construct
- {
- PotentialMatch.known_email_aliases.add ("admin");
- PotentialMatch.known_email_aliases.add ("abuse");
- PotentialMatch.known_email_aliases.add ("webmaster");
- }
-
- /**
- * Create a new PotentialMatch.
- *
- * @return a new PotentialMatch
- *
- * @since 0.5.0
- */
- public PotentialMatch ()
- {
- base ();
- }
-
- /**
- * Whether two individuals are likely to be the same person.
- *
- * @param a an individual to compare
- * @param b another individual to compare
- *
- * @since 0.5.0
- */
- public MatchResult potential_match (Individual a, Individual b)
- {
- this._individual_a = a;
- this._individual_b = b;
- MatchResult result = MatchResult.MIN;
-
- /* Immediately discount a match if either of the individuals can't be
- * trusted (e.g. due to containing link-local XMPP personas, which can be
- * spoofed). */
- if (a.trust_level == TrustLevel.NONE || b.trust_level == TrustLevel.NONE)
- {
- return result;
- }
-
- /* Similarly, immediately discount a match if the individuals have been
- * anti-linked by the user. */
- if (a.has_anti_link_with_individual (b))
- {
- return result;
- }
-
- result = MatchResult.VERY_LOW;
-
- /* If individuals share gender. */
- if (this._individual_a.gender != Gender.UNSPECIFIED &&
- this._individual_b.gender != Gender.UNSPECIFIED &&
- this._individual_a.gender != this._individual_b.gender)
- {
- return result;
- }
-
- /* If individuals share common im-addresses */
- result = this._inspect_im_addresses (result);
- if (result == MatchResult.MAX)
- return result;
-
- /* If individuals share common e-mails */
- result = this._inspect_emails (result);
- if (result == MatchResult.MAX)
- return result;
-
- /* If individuals share common phone numbers */
- result = this._inspect_phone_numbers (result);
- if (result == MatchResult.MAX)
- return result;
-
- /* they have the same (normalised) name? */
- result = this._name_similarity (result);
- if (result == MatchResult.MAX)
- return result;
-
- return result;
- }
-
- private MatchResult _inspect_phone_numbers (MatchResult old_result)
- {
- var set_a = this._individual_a.phone_numbers;
- var set_b = this._individual_b.phone_numbers;
-
- foreach (var phone_fd_a in set_a)
- {
- foreach (var phone_fd_b in set_b)
- {
- if (phone_fd_a.values_equal (phone_fd_b))
- {
- return MatchResult.HIGH;
- }
- }
- }
-
- return old_result;
- }
-
- /* Approach:
- * - taking in account family, given, prefix, suffix and additional names
- * we give some points for each non-empty match
- *
- * @since 0.5.0
- */
- private MatchResult _name_similarity (MatchResult old_result)
- {
- double similarity = 0.0;
- bool exact_match = false;
-
- if (this._look_alike (this._individual_a.nickname,
- this._individual_b.nickname))
- {
- similarity += 0.20;
- }
-
- if (this._look_alike_or_identical (this._individual_a.full_name,
- this._individual_b.full_name,
- out exact_match) ||
- this._look_alike_or_identical (this._individual_a.alias,
- this._individual_b.full_name,
- out exact_match) ||
- this._look_alike_or_identical (this._individual_a.full_name,
- this._individual_b.alias,
- out exact_match) ||
- this._look_alike_or_identical (this._individual_a.alias,
- this._individual_b.alias,
- out exact_match))
- {
- similarity += 0.70;
- }
-
- var _a = this._individual_a.structured_name;
- var _b = this._individual_b.structured_name;
-
- if (_a != null && _b != null)
- {
- var a = (!) _a;
- var b = (!) _b;
-
- if (a.is_empty () == false && a.equal (b))
- {
- return MatchResult.HIGH;
- }
-
- if (Folks.Utils._str_equal_safe (a.given_name, b.given_name))
- similarity += 0.20;
-
- if (this._look_alike (a.family_name, b.family_name) &&
- this._look_alike (a.given_name, b.given_name))
- {
- similarity += 0.40;
- }
-
- if (Folks.Utils._str_equal_safe (a.additional_names,
- b.additional_names))
- similarity += 0.5;
-
- if (Folks.Utils._str_equal_safe (a.prefixes, b.prefixes))
- similarity += 0.5;
-
- if (Folks.Utils._str_equal_safe (a.suffixes, b.suffixes))
- similarity += 0.5;
- }
-
- debug ("[name_similarity] Got %f\n", similarity);
-
- if (similarity >= PotentialMatch._DIST_THRESHOLD)
- {
- int inc = 2;
- /* We need exact matches to go to at least HIGH, or otherwise its
- not possible to get a HIGH match for e.g. a facebook telepathy
- persona, where alias is the only piece of information
- available */
- if (exact_match)
- inc += 1;
- return this._inc_match_level (old_result, inc);
- }
-
- return old_result;
- }
-
- /**
- * Number of equal IM addresses between two individuals.
- *
- * This compares the addresses without comparing their associated protocols.
- *
- * @since 0.5.0
- */
- private MatchResult _inspect_im_addresses (MatchResult old_result)
- {
- var addrs = new HashSet<string> ();
-
- foreach (var im_a in this._individual_a.im_addresses.get_values ())
- {
- addrs.add (im_a.value);
- }
-
- foreach (var im_b in this._individual_b.im_addresses.get_values ())
- {
- if (addrs.contains (im_b.value) == true)
- {
- return MatchResult.HIGH;
- }
- }
-
- return old_result;
- }
-
- /**
- * Inspect email addresses.
- *
- * @since 0.5.0
- */
- private MatchResult _inspect_emails (MatchResult old_result)
- {
- var set_a = this._individual_a.email_addresses;
- var set_b = this._individual_b.email_addresses;
- MatchResult result = old_result;
-
- foreach (var fd_a in set_a)
- {
- string[] email_split_a = fd_a.value.split ("@");
-
- /* Sanity check for valid e-mail addresses. */
- if (email_split_a.length < 2)
- {
- warning ("Invalid e-mail address when looking for potential " +
- "match: %s", fd_a.value);
- continue;
- }
-
- string[] tokens_a =
- email_split_a[0].split_set (PotentialMatch._SEPARATORS);
-
- foreach (var fd_b in set_b)
- {
- string[] email_split_b = fd_b.value.split ("@");
-
- /* Sanity check for valid e-mail addresses. */
- if (email_split_b.length < 2)
- {
- warning ("Invalid e-mail address when looking for " +
- "potential match: %s", fd_b.value);
- continue;
- }
-
- if (fd_a.value == fd_b.value)
- {
- if (PotentialMatch.known_email_aliases.contains
- (email_split_a[0]) == true)
- {
- if (result < MatchResult.HIGH)
- {
- result = MatchResult.LOW;
- }
- }
- else
- {
- return MatchResult.HIGH;
- }
- }
- else
- {
- string[] tokens_b =
- email_split_b[0].split_set (PotentialMatch._SEPARATORS);
-
- /* Do we have: first.middle.last@ ~= fml@ ? */
- if (this._check_initials_expansion (tokens_a, tokens_b))
- {
- result = MatchResult.MEDIUM;
- }
- /* So we have split the user part of the e-mail
- * address into tokens. Lets see if there is some
- * matches between tokens.
- * As in: first.middle.last@ ~= [first,middle,..]@ */
- else if (this._match_tokens (tokens_a, tokens_b))
- {
- result = MatchResult.MEDIUM;
- }
- }
- }
- }
-
- return result;
- }
-
- /* We are after:
- * you.are.someone@ =~ yas@
- */
- private bool _check_initials_expansion (string[] tokens_a, string[] tokens_b)
- {
- if (tokens_a.length > tokens_b.length &&
- tokens_b.length == 1)
- {
- return this._do_check_initials_expansion (tokens_a, tokens_b[0]);
- }
- else if (tokens_b.length > tokens_a.length &&
- tokens_a.length == 1)
- {
- return this._do_check_initials_expansion (tokens_b, tokens_a[0]);
- }
- return false;
- }
-
- private bool _do_check_initials_expansion (string[] expanded_name,
- string initials)
- {
- if (expanded_name.length != initials.length)
- return false;
-
- for (int i=0; i<expanded_name.length; i++)
- {
- if (expanded_name[i][0] != initials[i])
- return false;
- }
-
- return true;
- }
-
- /*
- * We should probably count how many tokens matched?
- */
- private bool _match_tokens (string[] tokens_a, string[] tokens_b)
- {
- /* To find matching items from 2 sets its more efficient
- * to make the outer loop go with the smaller set. */
- if (tokens_a.length > tokens_b.length)
- return this._do_match_tokens (tokens_a, tokens_b);
- else
- return this._do_match_tokens (tokens_b, tokens_a);
- }
-
- private bool _do_match_tokens (string[] bigger_set, string[] smaller_set)
- {
- for (var i=0; i < smaller_set.length; i++)
- {
- for (var j=0; j < bigger_set.length; j++)
- {
- if (smaller_set[i] == bigger_set[j])
- return true;
- }
- }
-
- return false;
- }
-
- private MatchResult _inc_match_level (
- MatchResult current_level, int times = 1)
- {
- MatchResult ret = current_level + times;
- if (ret > MatchResult.MAX)
- ret = MatchResult.MAX;
-
- return ret;
- }
-
- private bool _look_alike_or_identical (string? a, string? b, out bool exact)
- {
- exact = false;
- if (a == null || a == "" || b == null || b == "")
- {
- return false;
- }
-
- return_val_if_fail (a.validate (), false);
- return_val_if_fail (b.validate (), false);
-
- var a_stripped = this._strip_string ((!) a);
- var b_stripped = this._strip_string ((!) b);
-
- var jaro_dist = this._jaro_dist (a_stripped, b_stripped);
-
- // a and b match exactly iff their Jaro distance is 1.
- if (jaro_dist == 1.0)
- {
- exact = true;
- return true;
- }
-
- // a and b look alike if their Jaro distance is over the threshold.
- return (jaro_dist >= PotentialMatch._DIST_THRESHOLD);
- }
-
- private bool _look_alike (string? a, string? b)
- {
- if (a == null || a == "" || b == null || b == "")
- {
- return false;
- }
-
- return_val_if_fail (a.validate (), false);
- return_val_if_fail (b.validate (), false);
-
- var a_stripped = this._strip_string ((!) a);
- var b_stripped = this._strip_string ((!) b);
-
- // a and b look alike if their Jaro distance is over the threshold.
- return (this._jaro_dist (a_stripped, b_stripped) >= PotentialMatch._DIST_THRESHOLD);
- }
-
- /* Based on:
- * http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance
- *
- * d = 1/3 * ( m/|s1| + m/|s2| + (m - t)/m )
- *
- * where
- *
- * m = matching characters
- * t = number of transpositions
- */
- private double _jaro_dist (unichar[] s1, unichar[] s2)
- {
- double distance;
- int max = s1.length > s2.length ? s1.length : s2.length;
- int max_dist = (max / 2) - 1;
- double t;
- double m = (double) this._matches (s1, s2, max_dist, out t);
- double len_s1 = (double) s1.length;
- double len_s2 = (double) s2.length;
- double a = m / len_s1;
- double b = m / len_s2;
- double c = 0;
-
- if ((int) m > 0)
- c = (m - t) / m;
-
- distance = (1.0/3.0) * (a + b + c);
-
- debug ("Jaro distance: %f (a = %f, b = %f, c = %f)", distance, a, b, c);
-
- return distance;
- }
-
- /**
- * stripped_char:
- *
- * Returns a stripped version of @ch, removing any case, accentuation
- * mark, or any special mark on it.
- *
- * Copied from Empathy's libempathy-gtk/empathy-live-search.c.
- *
- * Copyright (C) 2010 Collabora Ltd.
- * Copyright (C) 2007-2010 Nokia Corporation.
- *
- * Authors: Felix Kaser <felix.kaser@collabora.co.uk>
- * Xavier Claessens <xavier.claessens@collabora.co.uk>
- * Claudio Saavedra <csaavedra@igalia.com>
- */
- private unichar _stripped_char (unichar ch)
- {
- unichar retval[1] = { 0 };
- var utype = ch.type ();
-
- switch (utype)
- {
- case UnicodeType.CONTROL:
- case UnicodeType.FORMAT:
- case UnicodeType.UNASSIGNED:
- case UnicodeType.NON_SPACING_MARK:
- case UnicodeType.COMBINING_MARK:
- case UnicodeType.ENCLOSING_MARK:
- /* Ignore those */
- break;
- case UnicodeType.DECIMAL_NUMBER:
- case UnicodeType.LETTER_NUMBER:
- case UnicodeType.OTHER_NUMBER:
- case UnicodeType.CONNECT_PUNCTUATION:
- case UnicodeType.DASH_PUNCTUATION:
- case UnicodeType.CLOSE_PUNCTUATION:
- case UnicodeType.FINAL_PUNCTUATION:
- case UnicodeType.INITIAL_PUNCTUATION:
- case UnicodeType.OTHER_PUNCTUATION:
- case UnicodeType.OPEN_PUNCTUATION:
- case UnicodeType.CURRENCY_SYMBOL:
- case UnicodeType.MODIFIER_SYMBOL:
- case UnicodeType.MATH_SYMBOL:
- case UnicodeType.OTHER_SYMBOL:
- case UnicodeType.LINE_SEPARATOR:
- case UnicodeType.PARAGRAPH_SEPARATOR:
- case UnicodeType.SPACE_SEPARATOR:
- /* Replace punctuation with spaces. */
- retval[0] = ' ';
- break;
- case UnicodeType.PRIVATE_USE:
- case UnicodeType.SURROGATE:
- case UnicodeType.LOWERCASE_LETTER:
- case UnicodeType.MODIFIER_LETTER:
- case UnicodeType.OTHER_LETTER:
- case UnicodeType.TITLECASE_LETTER:
- case UnicodeType.UPPERCASE_LETTER:
- default:
- ch = ch.tolower ();
- ch.fully_decompose (false, retval);
- break;
- }
-
- return retval[0];
- }
-
- private unichar[] _strip_string (string s)
- {
- int next_idx = 0;
- uint write_idx = 0;
- unichar ch = 0;
- unichar[] output = new unichar[s.length]; // this is a safe overestimate
-
- while (s.get_next_char (ref next_idx, out ch))
- {
- ch = this._stripped_char (ch);
- if (ch != 0)
- {
- output[write_idx++] = ch;
- }
- }
-
- output.length = (int) write_idx;
- return output;
- }
-
- /* Calculate matches and transpositions as defined by the Jaro distance.
- */
- private int _matches (unichar[] s1, unichar[] s2, int max_dist, out double t)
- {
- int matches = 0;
- t = 0.0;
- var len_s1 = s1.length;
-
- unichar look_for = 0;
-
- for (uint idx = 0; idx < len_s1 && (look_for = s1[idx]) != 0; idx++)
- {
- int contains = this._contains (s2, look_for, idx, max_dist);
- if (contains >= 0)
- {
- matches++;
- if (contains > 0)
- t += 1.0;
- }
- }
-
- debug ("%d matches and %f / 2 transpositions", matches, t);
-
- t = t / 2.0;
- return matches;
- }
-
- /* If haystack contains c in pos return 0, if it contains
- * it within the bounds of max_dist return abs(pos-pos_found).
- * If its not found, return -1.
- *
- * pos and max_dist are both in unichars.
- *
- * Note: haystack must have been validated using haystack.validate() before
- * being passed to this method. */
- private int _contains (unichar[] haystack, unichar c, uint pos, uint max_dist)
- {
- var haystack_len = haystack.length; /* in unichars */
-
- if (pos < haystack_len && haystack[pos] == c)
- return 0;
-
- uint idx = ((int) pos - (int) max_dist).clamp (0, haystack_len - 1);
- unichar ch = 0;
-
- while (idx < pos + max_dist && idx < haystack_len &&
- (ch = haystack[idx]) != 0)
- {
- if (ch == c)
- return ((int) pos - (int) idx).abs ();
-
- idx++;
- }
-
- return -1;
- }
-}
diff --git a/folks/presence-details.vala b/folks/presence-details.vala
deleted file mode 100644
index 138c8f93..00000000
--- a/folks/presence-details.vala
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (C) 2010-2011 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- */
-
-using GLib;
-
-/**
- * The possible presence states an object implementing {@link PresenceDetails}
- * could be in.
- *
- * These closely follow the
- * [[http://telepathy.freedesktop.org/spec/Connection_Interface_Simple_Presence.html#Connection_Presence_Type|SimplePresence]]
- * interface in the Telepathy specification.
- */
-public enum Folks.PresenceType
-{
- /**
- * never set
- */
- UNSET,
- /**
- * offline
- */
- OFFLINE,
- /**
- * available
- */
- AVAILABLE,
- /**
- * away from keyboard
- */
- AWAY,
- /**
- * away from keyboard for an extended period of time
- */
- EXTENDED_AWAY,
- /**
- * also known as "invisible" or "appear offline"
- */
- HIDDEN,
- /**
- * at keyboard, but too busy to chat
- */
- BUSY,
- /**
- * presence not received from server
- */
- UNKNOWN,
- /**
- * an error occurred with fetching the presence
- */
- ERROR
-}
-
-/**
- * Interface exposing a {@link Persona}'s or {@link Individual}'s presence;
- * their current availability, such as for chatting.
- *
- * If the {@link Backend} providing the {@link Persona} doesn't support
- * presence, the {@link Persona}'s ``presence_type`` will be set to
- * {@link PresenceType.UNSET} and their ``presence_message`` will be an empty
- * string.
- */
-public interface Folks.PresenceDetails : Object
-{
- /**
- * The contact's presence type.
- *
- * Each contact can have one and only one presence type at any one time,
- * representing their availability for communication. The default presence
- * type is {@link PresenceType.UNSET}.
- */
- public abstract Folks.PresenceType presence_type
- {
- get; set; default = Folks.PresenceType.UNSET;
- }
-
- /**
- * The contact's presence message.
- *
- * This is a short message written by the contact to add detail to their
- * presence type ({@link Folks.PresenceDetails.presence_type}). If the contact
- * hasn't set a message, it will be an empty string.
- */
- public abstract string presence_message { get; set; default = ""; }
-
- /**
- * The contact's client types.
- *
- * One can connect to instant messaging networks on a huge variety of devices,
- * from PCs, to phones to consoles.
- * The client types are represented in strings, using the values
- * [[http://xmpp.org/registrar/disco-categories.html#client|documented by the XMPP registrar]]
- *
- * @since 0.9.5
- */
- public abstract string[] client_types { get; set; }
-
- /**
- * The contact's detailed presence status.
- *
- * This is a more detailed representation of the contact's presence than
- * {@link PresenceDetails.presence_type}. It may be empty, or one of a
- * well-known set of strings, as defined in the Telepathy specification:
- * [[http://telepathy.freedesktop.org/spec/Connection_Interface_Simple_Presence.html#description|Telepathy Specification]]
- *
- * @since 0.6.0
- */
- public abstract string presence_status { get; set; default = ""; }
-
- /* Rank the presence types for comparison purposes, with higher numbers
- * meaning more available */
- private static int _type_availability (PresenceType type)
- {
- switch (type)
- {
- case PresenceType.UNSET:
- return 0;
- case PresenceType.UNKNOWN:
- return 1;
- case PresenceType.ERROR:
- return 2;
- case PresenceType.OFFLINE:
- return 3;
- case PresenceType.HIDDEN:
- return 4;
- case PresenceType.EXTENDED_AWAY:
- return 5;
- case PresenceType.AWAY:
- return 6;
- case PresenceType.BUSY:
- return 7;
- case PresenceType.AVAILABLE:
- return 8;
- default:
- return 1;
- }
- }
-
- /**
- * The default message for a presence type.
- *
- * @param type a {@link PresenceType} for which to retrieve a translated
- * display string
- * @return a default translated display string for the given
- * {@link PresenceType}
- * @since 0.7.1
- */
- public static unowned string get_default_message_from_type (PresenceType type)
- {
- switch (type)
- {
- default:
- case PresenceType.UNKNOWN:
- return _("Unknown status");
- case PresenceType.OFFLINE:
- return _("Offline");
- case PresenceType.UNSET:
- return "";
- case PresenceType.ERROR:
- return _("Error");
- case PresenceType.AVAILABLE:
- return _("Available");
- case PresenceType.AWAY:
- return _("Away");
- case PresenceType.EXTENDED_AWAY:
- return _("Extended away");
- case PresenceType.BUSY:
- return _("Busy");
- case PresenceType.HIDDEN:
- return _("Hidden");
- }
- }
-
- /**
- * Compare two {@link PresenceType}s.
- *
- * ``0`` will be returned if the types are equal, a positive number will be
- * returned if ``type_a`` is more available than ``type_b``, and a negative
- * number will be returned if the opposite is true.
- *
- * @param type_a the first {@link PresenceType} to compare
- * @param type_b the second {@link PresenceType} to compare
- * @return a number representing the similarity of the two types
- * @since 0.1.11
- */
- public static int typecmp (PresenceType type_a, PresenceType type_b)
- {
- return (PresenceDetails._type_availability (type_a) -
- PresenceDetails._type_availability (type_b));
- }
-
- /**
- * Whether the contact is online.
- *
- * This will be ``true`` if the contact's presence type is higher than
- * {@link PresenceType.OFFLINE}, as determined by
- * {@link PresenceDetails.typecmp}.
- *
- * @return ``true`` if the contact is online, ``false`` otherwise
- */
- public bool is_online ()
- {
- return (typecmp (this.presence_type, PresenceType.OFFLINE) > 0);
- }
-}
diff --git a/folks/query.vala b/folks/query.vala
deleted file mode 100644
index 0ea1dda9..00000000
--- a/folks/query.vala
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2011, 2015 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using Gee;
-using GLib;
-
-/**
- * A contact query.
- *
- * If any properties of the query change such that matches may change, the
- * {@link GLib.Object.notify} signal will be emitted, potentially without a
- * detail string. Views which are using this query should re-evaluate their
- * matches on receiving this signal.
- *
- * @see SearchView
- * @since 0.11.0
- */
-public abstract class Folks.Query : Object
-{
- /* FIXME: make PersonaStore._PERSONA_DETAIL internal and use it here once
- * bgo#663886 is fixed */
- /**
- * Set of name match fields.
- *
- * These are ordered approximately by descending match likeliness to speed up
- * calls to {@link is_match} when used as-is.
- *
- * @since 0.11.0
- */
- public const string MATCH_FIELDS_NAMES[] =
- {
- "alias",
- "full-name",
- "nickname",
- "structured-name"
- };
-
- /* FIXME: make PersonaStore._PERSONA_DETAIL internal and use it here once
- * bgo#663886 is fixed */
- /**
- * Set of address (email, IM, postal, phone number, etc.) match fields.
- *
- * These are ordered approximately by descending match likeliness to speed up
- * calls to {@link is_match} when used as-is.
- *
- * @since 0.11.0
- */
- public const string MATCH_FIELDS_ADDRESSES[] =
- {
- "email-addresses",
- "im-addresses",
- "phone-numbers",
- "postal-addresses",
- "web-service-addresses",
- "urls"
- };
-
- /* FIXME: make PersonaStore._PERSONA_DETAIL internal and use it here once
- * bgo#663886 is fixed */
- /**
- * Set of miscellaneous match fields.
- *
- * These are ordered approximately by descending match likeliness to speed up
- * calls to {@link is_match} when used as-is.
- *
- * @since 0.11.0
- */
- public const string MATCH_FIELDS_MISC[] =
- {
- "groups",
- "roles",
- "notes"
- };
-
- private string[] _match_fields = MATCH_FIELDS_NAMES;
- /**
- * The names of the fields to match within
- *
- * The names of valid fields are available via
- * {@link PersonaStore.detail_key}.
- *
- * The ordering of the fields determines the order they are checked for
- * matches, which can have performance implications (these should ideally be
- * ordered from most- to least-likely to match).
- *
- * Also note that more fields (particularly rarely-matched fields) will
- * negatively impact performance, so only include important fields.
- *
- * Default value is {@link Query.MATCH_FIELDS_NAMES}.
- *
- * @since 0.11.0
- * @see PersonaDetail
- * @see PersonaStore.detail_key
- * @see Query.MATCH_FIELDS_NAMES
- * @see Query.MATCH_FIELDS_ADDRESSES
- * @see Query.MATCH_FIELDS_MISC
- */
- public virtual string[] match_fields
- {
- get { return this._match_fields; }
- protected construct { this._match_fields = value; }
- }
-
- /**
- * Determines whether a given {@link Individual} matches this query.
- *
- * This returns a match strength, which is on an arbitrary scale which is not
- * part of libfolks’ public API. These strengths should not be stored by user
- * applications, or examined numerically — they should only be used for
- * pairwise strength comparisons.
- *
- * This function is intended to be used in the {@link SearchView}
- * implementation only. Use {@link SearchView.individuals} to retrieve search
- * results.
- *
- * @param individual an {@link Individual} to match against
- * @return a positive integer if the individual matches this query, or zero
- * if they do not match; higher numbers indicate a better match
- * @since 0.11.0
- */
- public abstract uint is_match (Individual individual);
-}
diff --git a/folks/redeclare-internal-api.h b/folks/redeclare-internal-api.h
deleted file mode 100644
index 1a94f4dd..00000000
--- a/folks/redeclare-internal-api.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright © 2013 Intel Corporation
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef FOLKS_REDECLARE_INTERNAL_API_H
-#define FOLKS_REDECLARE_INTERNAL_API_H
-
-#include <folks/folks.h>
-
-/* These functions are marked 'internal', which means Vala makes them ABI
- * but omits them from header files.
- *
- * We can't just tell valac to generate an "internal" VAPI and header
- * via -h and --internal-vapi, because the "internal" header redefines
- * things like "typedef struct _FolksPersonaStore FolksPersonaStore"
- * which are an error if redefined; so you can only include the "internal"
- * header or the "public" one, never both. If we use the "internal"
- * VAPI then libfolks-eds' "public" header ends up trying to include
- * the "internal" header of libfolks, which is unacceptable.
- *
- * Redundant declarations of functions and macros, unlike typedefs, are
- * allowed by C as long as they have identical content. We ought to be able
- * to check that these declarations match what Vala is currently generating
- * by including this header when compiling libfolks. Unfortunately,
- * if we do that we can't include <folks/folks.h>, because Vala-generated
- * C code redeclares local Vala-generated types, functions, etc. rather than
- * just including <folks/folks.h>, and then the typedefs conflict.
- */
-
-void folks_persona_store_set_is_user_set_default (FolksPersonaStore* self,
- gboolean value);
-
-#endif
diff --git a/folks/role-details.vala b/folks/role-details.vala
deleted file mode 100644
index dc232178..00000000
--- a/folks/role-details.vala
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- * Copyright (C) 2011 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using Gee;
-using GLib;
-
-/**
- * Role a contact has in an organisation.
- *
- * This represents the role a {@link Persona} or {@link Individual} has in a
- * single given organisation, such as a company.
- *
- * @since 0.4.0
- */
-public class Folks.Role : Object
-{
- private string _organisation_name = "";
- /**
- * The name of the organisation in which the role is held.
- */
- public string organisation_name
- {
- get { return this._organisation_name; }
- set { this._organisation_name = (value != null ? value : ""); }
- }
-
- private string _title = "";
- /**
- * The title of the position held.
- *
- * For example: “Director, Ministry of Silly Walks”
- */
- public string title
- {
- get { return this._title; }
- set { this._title = (value != null ? value : ""); }
- }
-
- private string _role = "";
- /**
- * The role of the position.
- *
- * For example: “Programmer”
- *
- * @since 0.6.0
- */
- public string role
- {
- get { return this._role; }
- set { this._role = (value != null ? value : ""); }
- }
-
- private string _uid = "";
- /**
- * The UID that distinguishes this role.
- */
- [Version (deprecated = true, deprecated_since = "0.6.5",
- replacement = "AbstractFieldDetails.id")]
- public string uid
- {
- get { return _uid; }
- set { _uid = (value != null ? value : ""); }
- }
-
- /**
- * Default constructor.
- *
- * @param title title of the position
- * @param organisation_name organisation where the role is hold
- * @param uid a Unique ID associated to this Role
- * @return a new Role
- *
- * @since 0.4.0
- */
- public Role (string? title = null,
- string? organisation_name = null, string? uid = null)
- {
- Object (uid: uid,
- title: title,
- organisation_name: organisation_name);
- }
-
- /**
- * Whether none of the components is set.
- *
- * @return ``true`` if all the components are the empty string, ``false``
- * otherwise.
- *
- * @since 0.6.7
- */
- public bool is_empty ()
- {
- return this.organisation_name == "" &&
- this.title == "" &&
- this.role == "";
- }
-
- /**
- * Compare if two roles are equal. Roles are equal if their titles and
- * organisation names are equal.
- *
- * @param a a role to compare
- * @param b another role to compare
- * @return ``true`` if the roles are equal, ``false`` otherwise
- */
- public static bool equal (Role a, Role b)
- {
- return (a.title == b.title) &&
- (a.role == b.role) &&
- (a.organisation_name == b.organisation_name);
- }
-
- /**
- * Hash function for the class. Suitable for use as a hash table key.
- *
- * @param r a role to hash
- * @return hash value for the role instance
- */
- public static uint hash (Role r)
- {
- return r.organisation_name.hash () ^ r.title.hash () ^ r.role.hash ();
- }
-
- /**
- * Formatted version of this role.
- *
- * @since 0.4.0
- */
- public string to_string ()
- {
- var str = _("Title: %s, Organisation: %s, Role: %s");
- return str.printf (this.title, this.organisation_name, this.role);
- }
-}
-
-/**
- * Object representing details of a contact in an organisation which can have
- * some parameters associated with it.
- *
- * See {@link Folks.AbstractFieldDetails}.
- *
- * @since 0.6.0
- */
-public class Folks.RoleFieldDetails : AbstractFieldDetails<Role>
-{
- private string _id = "";
- /**
- * {@inheritDoc}
- */
- public override string id
- {
- get { return this._id; }
- set
- {
- this._id = (value != null ? value : "");
-
- /* Keep the Role.uid sync'd from our id */
- if (this._id != this.value.uid)
- this.value.uid = this._id;
- }
- }
-
- /**
- * Create a new RoleFieldDetails.
- *
- * @param value the non-empty {@link Role} of the field
- * @param parameters initial parameters. See
- * {@link AbstractFieldDetails.parameters}. A ``null`` value is equivalent to an
- * empty map of parameters.
- *
- * @return a new RoleFieldDetails
- *
- * @since 0.6.0
- */
- public RoleFieldDetails (Role value,
- MultiMap<string, string>? parameters = null)
- {
- if (value.is_empty ())
- {
- warning ("Empty role passed to RoleFieldDetails.");
- }
-
- /* We keep id and value.uid synchronised in both directions. */
- Object (value: value,
- parameters: parameters,
- id: value.uid);
- }
-
- construct
- {
- /* Keep the Role.uid sync'd to our id */
- this.value.notify["uid"].connect ((s, p) =>
- {
- if (this.id != this.value.uid)
- this.id = this.value.uid;
- });
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override bool equal (AbstractFieldDetails<Role> that)
- {
- var _that_fd = that as RoleFieldDetails;
- if (_that_fd == null)
- return false;
- RoleFieldDetails that_fd = (!) _that_fd;
-
- if (!base.parameters_equal (that))
- return false;
-
- return Role.equal (this.value, that_fd.value);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override uint hash ()
- {
- return str_hash (this.value.to_string ());
- }
-}
-
-/**
- * This interfaces represents the list of roles a {@link Persona} and
- * {@link Individual} might have.
- *
- * @since 0.4.0
- */
-public interface Folks.RoleDetails : Object
-{
- /**
- * The roles of the contact.
- *
- * @since 0.6.0
- */
- public abstract Set<RoleFieldDetails> roles { get; set; }
-
- /**
- * Change the contact's roles.
- *
- * It's preferred to call this rather than setting {@link RoleDetails.roles}
- * directly, as this method gives error notification and will only return once
- * the roles have been written to the relevant backing store (or the
- * operation's failed).
- *
- * @param roles the set of roles
- * @throws PropertyError if setting the roles failed
- * @since 0.6.2
- */
- public virtual async void change_roles (Set<RoleFieldDetails> roles)
- throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Roles are not writeable on this contact."));
- }
-}
diff --git a/folks/search-view.vala b/folks/search-view.vala
deleted file mode 100644
index b8af5289..00000000
--- a/folks/search-view.vala
+++ /dev/null
@@ -1,538 +0,0 @@
-/*
- * Copyright (C) 2011, 2015 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using Gee;
-using GLib;
-
-/**
- * A view of {@link Individual}s which match a given {@link Query}.
- *
- * The search view supports ‘live’ and ‘snapshot’ search results. Live results
- * will continue to update over a long period of time as persona stores go
- * online and offline or individuals are edited so they start or stop matching
- * the {@link Query}.
- *
- * For a shell search provider, for example, snapshot results are appropriate.
- * For a search in a contacts UI, live results are more appropriate as they will
- * update over time as other edits are made in the application.
- *
- * In both cases, {@link SearchView.individuals} is guaranteed to be correct
- * after {@link SearchView.prepare} finishes.
- *
- * For live results, continue listening to the
- * {@link SearchView.individuals_changed_detailed} signal.
- *
- * @since 0.11.0
- */
-public class Folks.SearchView : Object
-{
- private bool _prepare_pending = false;
-
- private IndividualAggregator _aggregator;
- /**
- * The {@link IndividualAggregator} that this view is based upon.
- *
- * @since 0.11.0
- */
- public IndividualAggregator aggregator
- {
- get { return this._aggregator; }
- }
-
- private Query _query;
- /**
- * The {@link Query} that this view is based upon.
- *
- * If this {@link SearchView} has already been prepared, setting this will
- * force a re-evaluation of all {@link Individual}s in the
- * {@link IndividualAggregator} which can be an expensive operation.
- *
- * This re-evaluates the query immediately, so most clients should implement
- * de-bouncing to ensure re-evaluation only happens when (for example) the
- * user has stopped typing a new query.
- *
- * @since 0.11.0
- */
- public Query query
- {
- get { return this._query; }
- set
- {
- if (this._query == value)
- return;
-
- if (this._query != null)
- {
- debug ("SearchView's query replaced, forcing re-evaluation of " +
- "all Individuals.");
- }
-
- this._query.notify.disconnect (this._query_notify_cb);
- this._query = value;
- this._query.notify.connect (this._query_notify_cb);
-
- /* Re-evaluate all Individuals (only if necessary) */
- this.refresh.begin ();
- }
- }
-
- private SortedSet<Individual> _individuals;
- private SortedSet<Individual> _individuals_ro;
- /**
- * A sorted set of {@link Individual}s which match the search query.
- *
- * This is the canonical set of {@link Individual}s provided by this
- * view. It is sorted by match strength, with the individual who is the best
- * match to the search query as the {@link Gee.SortedSet.first} element of
- * the set.
- *
- * Match strengths are not publicly exposed, as they are on an arbitrary
- * scale. To compare two matching individuals for match strength, check for
- * membership of one of them in the {@link Gee.SortedSet.head_set} of the
- * other.
- *
- * For clients who only wish to have a snapshot of search results, this
- * property is valid once {@link SearchView.prepare} is finished and this
- * {@link SearchView} may be unreferenced and ignored afterward.
- *
- * @since 0.11.0
- */
- public SortedSet<Individual> individuals
- {
- get { return this._individuals_ro; }
- }
-
- private bool _is_prepared = false;
- /**
- * Whether {@link IndividualAggregator.prepare} has successfully completed for
- * this view's aggregator.
- *
- * @since 0.11.0
- */
- public bool is_prepared
- {
- get { return this._is_prepared; }
- }
-
- /**
- * Whether the search view has reached a quiescent state. This will happen at
- * some point after {@link IndividualAggregator.prepare} has successfully
- * completed for its aggregator.
- *
- * It's guaranteed that this property's value will only ever change after
- * {@link SearchView.is_prepared} has changed to ``true``.
- *
- * @since 0.11.0
- */
- public bool is_quiescent
- {
- /* Just proxy the aggregator’s quiescence. If we implement anything fancy
- * and async in our matching in future, this can change. */
- get { return this.aggregator.is_quiescent; }
- }
-
- private void _aggregator_is_quiescent_cb ()
- {
- this.notify_property ("is-quiescent");
- }
-
- /**
- * Emitted when one or more {@link Individual}s are added to or removed from
- * the view.
- *
- * The sets of `added` and `removed` individuals are sorted by descending
- * match strength. Using the {@link Gee.SortedSet.lower} and
- * {@link Gee.SortedSet.higher} APIs with {@link SearchView.individuals}, the
- * `added` individuals can be inserted at the correct positions in a UI
- * representation of the search view.
- *
- * The match strengths are on the same scale as in
- * {@link SearchView.individuals}, so orderings between the two sorted sets
- * are valid. See {@link SearchView.individuals} for more information about
- * match strengths.
- *
- * @param added a set of {@link Individual}s added to the search view
- * @param removed a set of {@link Individual}s removed from the search view
- *
- * @see IndividualAggregator.individuals_changed_detailed
- * @since 0.11.0
- */
- public signal void individuals_changed_detailed (SortedSet<Individual> added,
- SortedSet<Individual> removed);
-
- /**
- * Create a new view of Individuals matching a given query.
- *
- * This view will be kept up-to-date as individuals change (which may change
- * their membership in the results).
- *
- * @param query query to match upon
- * @param aggregator the {@link IndividualAggregator} to match within
- *
- * @since 0.11.0
- */
- public SearchView (IndividualAggregator aggregator, Query query)
- {
- debug ("Constructing SearchView %p", this);
-
- this._aggregator = aggregator;
- this._aggregator.notify["is-quiescent"].connect (
- this._aggregator_is_quiescent_cb);
- this._individuals = this._create_empty_sorted_set ();
- this._individuals_ro = this._individuals.read_only_view;
- this._is_prepared = false;
- this._prepare_pending = false;
- this._query = query;
- }
-
- ~SearchView ()
- {
- debug ("Destroying SearchView %p", this);
-
- this._aggregator.notify["is-quiescent"].disconnect (
- this._aggregator_is_quiescent_cb);
- }
-
- /**
- * Prepare the view for use.
- *
- * This calls {@link IndividualAggregator.prepare} as necessary to start
- * aggregating all {@link Individual}s.
- *
- * This function is guaranteed to be idempotent, so multiple search views may
- * share a single aggregator; {@link SearchView.prepare} must be called on all
- * of the views.
- *
- * For any clients only interested in a snapshot of search results,
- * {@link SearchView.individuals} is valid once this async function is
- * finished.
- *
- * @throws GLib.Error if preparation failed
- *
- * @since 0.11.0
- */
- public async void prepare () throws GLib.Error
- {
- if (!this._is_prepared && !this._prepare_pending)
- {
- this._prepare_pending = true;
- this._aggregator.individuals_changed_detailed.connect (
- this._aggregator_individuals_changed_detailed_cb);
- try
- {
- yield this._aggregator.prepare ();
- }
- catch (GLib.Error e)
- {
- this._prepare_pending = false;
- this._aggregator.individuals_changed_detailed.disconnect (
- this._aggregator_individuals_changed_detailed_cb);
-
- throw e;
- }
-
- this._is_prepared = true;
- this._prepare_pending = false;
- this.notify_property ("is-prepared");
-
- yield this.refresh ();
- }
- }
-
- /**
- * Clean up and release resources used by the search view.
- *
- * This will disconnect the aggregator cleanly from any resources it is using.
- * It is recommended to call this method before finalising the search view,
- * but calling it is not required.
- *
- * Note that this will not unprepare the underlying aggregator: call
- * {@link IndividualAggregator.unprepare} to do that. This allows multiple
- * search views to use a single aggregator and unprepare at different times.
- *
- * Concurrent calls to this function from different threads will block until
- * preparation has completed. However, concurrent calls to this function from
- * a single thread might not, i.e. the first call will block but subsequent
- * calls might return before the first one. (Though they will be safe in every
- * other respect.)
- *
- * @since 0.11.0
- * @throws GLib.Error if unpreparing the backend-specific services failed —
- * this will be a backend-specific error
- */
- public async void unprepare () throws GLib.Error
- {
- if (!this._is_prepared || this._prepare_pending)
- {
- return;
- }
-
- this._prepare_pending = false;
- }
-
- /**
- * Refresh the view’s results.
- *
- * Explicitly re-match all the view’s results to ensure matches are up to
- * date. For a normal {@link IndividualAggregator}, this is explicitly not
- * necessary, as the view will watch signal emissions from the aggregator and
- * keep itself up to date.
- *
- * However, for search-only persona stores, which do not support notification
- * of changes to personas, this method is the only way to update the set of
- * matches against the store.
- *
- * This method should be called whenever an explicit update is needed to the
- * search results, e.g. if the user requests a refresh.
- *
- * @throws GLib.Error if matching failed
- * @since 0.11.0
- */
- public async void refresh () throws GLib.Error
- {
- if (this._is_prepared)
- this._evaluate_all_aggregator_individuals ();
- }
-
- private void _aggregator_individuals_changed_detailed_cb (
- MultiMap<Individual?, Individual?> changes)
- {
- this._evaluate_individuals (changes, null);
- }
-
- private string _build_match_strength_key ()
- {
- /* FIXME: This is a pretty big hack. Ideally, we would use a custom
- * SortedSmallSet implementation, written in C and using a GPtrArray,
- * instead of TreeSet and this GObject data hackery.
- *
- * However, since we’re dealing with small result sets, this is good
- * enough for a first implementation. */
- return "folks-match-strength-%p".printf (this);
- }
-
- private int _compare_individual_matches (Individual a, Individual b)
- {
- /* Zero must only be returned if the individuals are equal, not in terms
- * of their match strength, but in terms of their content. */
- if (a == b)
- return 0;
-
- var key = this._build_match_strength_key ();
-
- /* If either of these are unset, they will be zero, meaning they don’t
- * match the query. Normal match strengths are positive, so that works out
- * fine. */
- var match_strength_a = a.get_data<uint> (key);
- var match_strength_b = b.get_data<uint> (key);
-
- if (match_strength_a != match_strength_b)
- return ((int) match_strength_b - (int) match_strength_a);
-
- /* Break match strength ties by display name. */
- var display_name = a.display_name.collate (b.display_name);
- if (display_name != 0)
- return display_name;
-
- /* Break display name ties by ID (which will be stable). */
- return a.id.collate (b.id);
- }
-
- private SortedSet<Individual> _create_empty_sorted_set ()
- {
- return new TreeSet<Individual> (this._compare_individual_matches);
- }
-
- private void _evaluate_individuals (
- MultiMap<Individual?, Individual?>? changes,
- Set<Individual?>? evaluates)
- {
- var view_added = this._create_empty_sorted_set ();
- var view_removed = this._create_empty_sorted_set ();
-
- /* Determine whether each evaluate should be added or removed (note that
- * pure adds from 'changes' may only be added, never removed) */
- if (evaluates != null)
- {
- foreach (var evaluate in evaluates)
- {
- if (evaluate == null)
- continue;
-
- if (this._check_match (evaluate))
- view_added.add (evaluate);
- else
- view_removed.add (evaluate);
- }
- }
-
- /* Determine which adds and removals make sense for the given query */
- if (changes != null)
- {
- /* Determine whether given adds should actually be added (they mostly
- * come from the Aggregator, so we need to filter out non-matches) */
- var iter = changes.map_iterator ();
-
- while (iter.next ())
- {
- var individual_old = iter.get_key ();
- var individual_new = iter.get_value ();
-
- if (individual_new != null && this._check_match (individual_new))
- {
- /* @individual_new is being added (if @individual_old is
- * `null`) or replacing @individual_old. */
- view_added.add (individual_new);
- }
-
- if (individual_old != null)
- {
- /* If @individual_new doesn’t match, or if @individual_old is
- * simply being removed, ensure there’s an entry in the change
- * set to remove @individual_old. */
- view_removed.add (individual_old);
- }
- }
- }
-
- /* Perform all removals. Update the @view_removed set if we haven’t ever
- * exposed the individual in {@link SearchView.individuals}. */
- var iter = view_removed.iterator ();
-
- while (iter.next ())
- {
- var individual_old = iter.get ();
-
- if (individual_old != null &&
- !this._remove_individual (individual_old))
- {
- iter.remove ();
- }
- }
-
- /* Perform all additions. Update the @view_added set if we haven’t ever
- * exposed the individual in {@link SearchView.individuals}. */
- iter = view_added.iterator ();
-
- while (iter.next ())
- {
- var individual_new = iter.get ();
-
- if (individual_new != null && !this._add_individual (individual_new))
- {
- iter.remove ();
- }
- }
-
- /* Notify of changes. */
- if (view_added.size > 0 || view_removed.size > 0)
- this.individuals_changed_detailed (view_added, view_removed);
- }
-
- private inline bool _add_individual (Individual individual)
- {
- if (this._individuals.add (individual))
- {
- individual.notify.connect (this._individual_notify_cb);
- return true;
- }
-
- return false;
- }
-
- private inline bool _remove_individual (Individual individual)
- {
- if (this._individuals.remove (individual))
- {
- individual.notify.disconnect (this._individual_notify_cb);
- return true;
- }
-
- return false;
- }
-
- private void _evaluate_all_aggregator_individuals ()
- {
- var individuals = new HashSet<Individual?> ();
- individuals.add_all (this._aggregator.individuals.values);
- this._evaluate_individuals (null, individuals);
- }
-
- /* Returns whether the individual matches the current query. */
- private bool _check_match (Individual individual)
- {
- uint match_score = this._query.is_match (individual);
-
- var key = this._build_match_strength_key ();
- individual.set_data (key, match_score);
-
- return (match_score != 0);
- }
-
- /* Returns true if individual matches (regardless of whether we already knew
- * about it) */
- private bool _evaluate_match (Individual individual)
- {
- var match = this._check_match (individual);
-
- if (match)
- {
- this._add_individual (individual);
- }
- else
- {
- this._remove_individual (individual);
- }
-
- return match;
- }
-
- private void _individual_notify_cb (Object obj, ParamSpec ps)
- {
- var individual = obj as Individual;
-
- if (individual == null)
- return;
-
- var had_individual = this._individuals.contains (individual);
- var have_individual = this._evaluate_match (individual);
-
- var added = (!had_individual && have_individual);
- var removed = (had_individual && !have_individual);
- var view_added = this._create_empty_sorted_set ();
- var view_removed = this._create_empty_sorted_set ();
-
- if (added)
- view_added.add (individual);
- else if (removed)
- view_removed.add (individual);
-
- if (view_added.size > 0 || view_removed.size > 0)
- this.individuals_changed_detailed (view_added, view_removed);
- }
-
- private void _query_notify_cb (Object obj, ParamSpec ps)
- {
- debug ("SearchView's Query changed, forcing re-evaluation of all " +
- "Individuals");
- this.refresh.begin ();
- }
-}
diff --git a/folks/simple-query.vala b/folks/simple-query.vala
deleted file mode 100644
index 2b3f1885..00000000
--- a/folks/simple-query.vala
+++ /dev/null
@@ -1,522 +0,0 @@
-/*
- * Copyright (C) 2011, 2015 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using Gee;
-using GLib;
-
-/**
- * A simple text-based contact query.
- *
- * This is a generic implementation of the {@link Query} interface which
- * supports general UI-style search use cases. It implements case-insensitive
- * prefix matching, with transliteration of accents and other non-ASCII
- * characters to improve matching against accented characters. It also
- * normalises phone numbers to make matches invariant to hyphenation and spacing
- * in phone numbers.
- *
- * @see SearchView
- * @since 0.11.0
- */
-public class Folks.SimpleQuery : Folks.Query
-{
- /* These are guaranteed to be non-null */
- private string _query_string;
- private string[] _query_tokens;
- /**
- * The text query string.
- *
- * This re-evaluates the query immediately, so most clients should implement
- * de-bouncing to ensure re-evaluation only happens when (for example) the
- * user has stopped typing a new query.
- *
- * @since 0.11.0
- */
- public string query_string
- {
- get { return this._query_string; }
- set
- {
- if (value == null)
- value = "";
-
- if (this._query_string == value)
- return;
-
- this._update_query_string (value, this._query_locale);
- }
- }
-
- private string? _query_locale = null;
- /**
- * Locale to interpret the {@link SimpleQuery.query_string} in.
- *
- * If possible, locale-specific query string transliteration is done to
- * increase the number of matches. Set this property to a POSIX locale name
- * (e.g. ‘en’, ‘de_DE’, ‘de_DE@euro’ or ‘C’) to potentially improve the
- * transliteration performed.
- *
- * This may be `null` if the locale is unknown, in which case the current
- * locale will be used. To perform transliteration for no specific locale,
- * use `C`.
- *
- * @since 0.11.0
- */
- public string? query_locale
- {
- get { return this._query_locale; }
- set
- {
- if (this._query_locale == value)
- return;
-
- this._update_query_string (this._query_string, value);
- }
- }
-
- private void _update_query_string (string query_string,
- string? query_locale)
- {
- this._query_string = query_string;
- this._query_locale = query_locale;
- this._query_tokens =
- this._query_string.tokenize_and_fold (this.query_locale, null);
-
- debug ("Created simple query with tokens:");
- foreach (var token in this._query_tokens)
- debug ("\t%s", token);
-
- /* Notify of the need to re-evaluate matches. */
- this.freeze_notify ();
- this.notify_property ("query-string");
- this.notify_property ("query-locale");
- this.thaw_notify ();
- }
-
- /**
- * Create a simple text query.
- *
- * @param query_string text to match contacts against. Results will match all
- * tokens within the whitespace-delimited string (logical-ANDing the tokens).
- * A value of "" will match all contacts. However, it is recommended to not
- * use a query at all if filtering is not required.
- * @param match_fields the field names to apply this query to. See
- * {@link Query.match_fields} for more details. An empty array will match all
- * contacts. However, it is recommended to use the
- * {@link IndividualAggregator} directly if filtering is not required.
- * {@link PersonaDetail} and {@link PersonaStore.detail_key} for pre-defined
- * field names.
- *
- * @since 0.11.0
- */
- public SimpleQuery (
- string query_string,
- string[] match_fields)
- {
- /* Elements of match_fields should be unique, but it's up to the caller
- * to not repeat themselves */
-
- /* The given match_fields isn't null-terminated by default in
- * code that uses our predefined match_fields vectors (like
- * Query.MATCH_FIELDS_NAMES), so we need to create a twin array that is;
- * see bgo#659305 */
- var match_fields_safe = match_fields;
-
- Object (query_string: query_string,
- match_fields: match_fields_safe,
- query_locale: Intl.setlocale (LocaleCategory.ALL, null));
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.11.0
- */
- public override uint is_match (Individual individual)
- {
- /* Treat an empty query string or empty set of fields as "match all" */
- if (this._query_tokens.length < 1 || this.match_fields.length < 1)
- return 1;
-
- /* Only check for matches in tokens not yet found to minimize our work */
- var tokens_remaining = new HashSet<string> ();
-
- foreach (var t in this._query_tokens)
- tokens_remaining.add (t);
-
- /* FIXME: In the future, we should find a way to know this Individual’s
- * locale, and hence hook up translit_locale to improve matches. */
- string? individual_translit_locale = null;
-
- /* Check for all tokens within a given field before moving on to the next
- * field on the assumption that the vast majority of searches will have
- * all tokens within the same field (eg, both tokens in "Jane Doe" will
- * match in one of the name fields).
- *
- * Track the match score as we go. */
- uint match_score = 0;
-
- foreach (var prop_name in this.match_fields)
- {
- unowned ObjectClass iclass = individual.get_class ();
- var prop_spec = iclass.find_property (prop_name);
- if (prop_spec == null)
- {
- warning ("Folks.Individual does not contain property '%s'",
- prop_name);
- }
- else
- {
- var iter = tokens_remaining.iterator ();
- while (iter.next ())
- {
- var token = iter.get ();
- var inc = this._prop_contains_token (individual,
- individual_translit_locale, prop_name, prop_spec, token);
- match_score += inc;
-
- if (inc > 0)
- {
- iter.remove ();
- if (tokens_remaining.size == 0)
- return match_score;
- }
- }
- }
- }
-
- /* Not all of the tokens matched. We do a boolean-and match, so fail. */
- assert (tokens_remaining.size > 0);
- return 0;
- }
-
- /* Return a match score: a positive integer on a match, zero on no match.
- *
- * The match score weightings in this function are fairly arbitrary and can
- * be tweaked. They were generally chosen to prefer names. */
- private uint _prop_contains_token (
- Individual individual,
- string? individual_translit_locale,
- string prop_name,
- ParamSpec prop_spec,
- string token)
- {
- /* It's safe to assume that this._query_tokens.length >= 1 */
-
- /* All properties ordered from most-likely-match to least-likely-match to
- * return as early as possible */
- if (false) {}
- else if (prop_spec.value_type == typeof (string))
- {
- string prop_value;
- individual.get (prop_name, out prop_value);
-
- if (prop_value == null || prop_value == "")
- return 0;
-
- var score = this._string_matches_token (prop_value, token,
- individual_translit_locale);
- if (score > 0)
- {
- /* Weight names more highly. */
- if (prop_name == "full-name" || prop_name == "nickname")
- return score * 10;
- else
- return score * 2;
- }
- }
- else if (prop_spec.value_type == typeof (StructuredName))
- {
- StructuredName prop_value;
- individual.get (prop_name, out prop_value);
-
- if (prop_value == null)
- return 0;
-
- var score = this._string_matches_token (prop_value.given_name, token,
- individual_translit_locale);
- if (score > 0)
- return score * 10;
-
- score = this._string_matches_token (prop_value.family_name, token,
- individual_translit_locale);
- if (score > 0)
- return score * 10;
-
- score = this._string_matches_token (prop_value.additional_names,
- token, individual_translit_locale);
- if (score > 0)
- return score * 5;
-
- /* Skip prefixes and suffixes because CPUs have better things to do */
- }
- else if (prop_spec.value_type == typeof (Gee.Set))
- {
- Gee.Set prop_value_set;
- individual.get (prop_name, out prop_value_set);
-
- if (prop_value_set == null || prop_value_set.is_empty)
- return 0;
-
- if (prop_value_set.element_type.is_a (typeof (AbstractFieldDetails)))
- {
- var prop_value_afd = prop_value_set
- as Gee.Set<AbstractFieldDetails>;
- foreach (var val in prop_value_afd)
- {
- if (val.value_type == typeof (string))
- {
- /* E-mail addresses, phone numbers, URLs, notes. */
- var score = this._prop_contains_token_fd_string (
- individual, individual_translit_locale, prop_name,
- prop_spec, val, token);
- if (score > 0)
- {
- if (prop_name == "email-addresses")
- return score * 4;
- else
- return score * 2;
- }
- }
- else if (val.value_type == typeof (Role))
- {
- /* Roles. */
- var score = this._prop_contains_token_fd_role (individual,
- individual_translit_locale, prop_name, prop_spec, val,
- token);
- if (score > 0)
- {
- return score * 1;
- }
- }
- else if (val.value_type == typeof (PostalAddress))
- {
- /* Postal addresses. */
- var score = this._prop_contains_token_fd_postal_address (
- individual, individual_translit_locale, prop_name,
- prop_spec, val, token);
- if (score > 0)
- {
- return score * 3;
- }
- }
- else
- {
- warning ("Cannot check for match in detail type " +
- "Gee.Set<AbstractFieldDetails<%s>>",
- val.value_type.name ());
- return 0;
- }
- }
- }
- else if (prop_value_set.element_type == typeof (string))
- {
- /* Groups and local IDs. */
- var prop_value_string = prop_value_set as Gee.Set<string>;
- foreach (var val in prop_value_string)
- {
- if (val == null || val == "")
- continue;
-
- var score = this._string_matches_token (val, token,
- individual_translit_locale);
- if (score > 0)
- return score * 1;
- }
- }
- else
- {
- warning ("Cannot check for match in property ‘%s’, detail type " +
- "Gee.Set<%s>", prop_name,
- prop_value_set.element_type.name ());
- return 0;
- }
-
- }
- else if (prop_spec.value_type == typeof (Gee.MultiMap))
- {
- Gee.MultiMap prop_value_multi_map;
- individual.get (prop_name, out prop_value_multi_map);
-
- if (prop_value_multi_map == null || prop_value_multi_map.size < 1)
- return 0;
-
- var key_type = prop_value_multi_map.key_type;
- var value_type = prop_value_multi_map.value_type;
-
- if (key_type.is_a (typeof (string)) &&
- value_type.is_a (typeof (AbstractFieldDetails)))
- {
- var prop_value_multi_map_afd = prop_value_multi_map
- as Gee.MultiMap<string, AbstractFieldDetails>;
- var iter = prop_value_multi_map_afd.map_iterator ();
-
- while (iter.next ())
- {
- var val = iter.get_value ();
-
- /* IM addresses, web service addresses. */
- if (val.value_type == typeof (string))
- {
- var score = this._prop_contains_token_fd_string (
- individual, individual_translit_locale, prop_name,
- prop_spec, val, token);
- if (score > 0)
- {
- return score * 2;
- }
- }
- }
- }
- else
- {
- warning ("Cannot check for match in detail type " +
- "Gee.MultiMap<%s, %s>",
- key_type.name (), value_type.name ());
- return 0;
- }
- }
- else
- {
- warning ("Cannot check for match in detail type %s",
- prop_spec.value_type.name ());
- }
-
- return 0;
- }
-
- private uint _prop_contains_token_fd_string (
- Individual individual,
- string? individual_translit_locale,
- string prop_name,
- ParamSpec prop_spec,
- AbstractFieldDetails<string> val,
- string token)
- {
- if (val.get_type () == typeof (PhoneFieldDetails))
- {
- /* If this doesn’t match, fall through and try and normal string
- * match. This allows for the case of, e.g. matching query ‘123-4567’
- * against ‘+01234567890’. The query string is tokenised to ‘123’ and
- * ‘4567’, neither of which would normally match against the full
- * phone number. */
- if (val.values_equal (new PhoneFieldDetails (token)))
- return 2;
- }
-
- return this._string_matches_token (val.value, token,
- individual_translit_locale);
-
- /* Intentionally ignore the params; they're not interesting */
- }
-
- private uint _prop_contains_token_fd_postal_address (
- Individual individual,
- string? individual_translit_locale,
- string prop_name,
- ParamSpec prop_spec,
- AbstractFieldDetails<PostalAddress> val,
- string token)
- {
- var score = this._string_matches_token (val.value.street, token,
- individual_translit_locale);
- if (score > 0)
- return score;
-
- score = this._string_matches_token (val.value.locality, token,
- individual_translit_locale);
- if (score > 0)
- return score;
-
- score = this._string_matches_token (val.value.region, token,
- individual_translit_locale);
- if (score > 0)
- return score;
-
- score = this._string_matches_token (val.value.country, token,
- individual_translit_locale);
- if (score > 0)
- return score;
-
- /* All other fields intentionally ignored due to general irrelevance */
- return 0;
- }
-
- private uint _prop_contains_token_fd_role (
- Individual individual,
- string? individual_translit_locale,
- string prop_name,
- ParamSpec prop_spec,
- AbstractFieldDetails<Role> val,
- string token)
- {
- var score = this._string_matches_token (val.value.organisation_name,
- token, individual_translit_locale);
- if (score > 0)
- return score;
-
- score = this._string_matches_token (val.value.title, token,
- individual_translit_locale);
- if (score > 0)
- return score;
-
- score = this._string_matches_token (val.value.role, token,
- individual_translit_locale);
- if (score > 0)
- return score;
-
- /* Intentionally ignore the params; they're not interesting */
- return 0;
- }
-
- private inline uint _string_matches_token (string str, string token,
- string? str_translit_locale = null)
- {
- debug ("Matching string ‘%s’ against token ‘%s’.", str, token);
-
- string[] alternates;
- var str_tokens =
- str.tokenize_and_fold (str_translit_locale, out alternates);
-
- /* FIXME: We have to use for() rather than foreach() because of:
- * https://bugzilla.gnome.org/show_bug.cgi?id=743877 */
- for (var i = 0; str_tokens[i] != null; i++)
- {
- var str_token = str_tokens[i];
-
- if (str_token == token)
- return 3;
- else if (str_token.has_prefix (token))
- return 2;
- }
-
- for (var i = 0; alternates[i] != null; i++)
- {
- var str_token = alternates[i];
-
- if (str_token == token)
- return 2;
- else if (str_token.has_prefix (token))
- return 1;
- }
-
- return 0;
- }
-}
diff --git a/folks/small-set-internal.h b/folks/small-set-internal.h
deleted file mode 100644
index d18bd8f6..00000000
--- a/folks/small-set-internal.h
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * small-set - a set optimized for fast iteration when there are few items
- *
- * Copyright © 2013 Intel Corporation
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
-
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
-
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301 USA
- *
- * Authors:
- * Simon McVittie <simon.mcvittie@collabora.co.uk>
- */
-
-#ifndef FOLKS_SMALL_SET_INTERNAL_H
-#define FOLKS_SMALL_SET_INTERNAL_H
-
-#include <folks/small-set.h>
-
-G_BEGIN_DECLS
-
-typedef struct _FolksSmallSetIterator FolksSmallSetIterator;
-typedef struct _FolksSmallSetIteratorClass FolksSmallSetIteratorClass;
-
-GType folks_small_set_iterator_get_type (void);
-
-#define FOLKS_TYPE_SMALL_SET_ITERATOR \
- (folks_small_set_iterator_get_type ())
-#define FOLKS_SMALL_SET_ITERATOR(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), FOLKS_TYPE_SMALL_SET_ITERATOR, \
- FolksSmallSetIterator))
-#define FOLKS_SMALL_SET_ITERATOR_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST ((klass), FOLKS_TYPE_SMALL_SET_ITERATOR, \
- FolksSmallSetIteratorClass))
-#define FOLKS_IS_SMALL_SET_ITERATOR(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FOLKS_TYPE_SMALL_SET_ITERATOR))
-#define FOLKS_IS_SMALL_SET_ITERATOR_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE ((klass), FOLKS_TYPE_SMALL_SET_ITERATOR))
-#define FOLKS_SMALL_SET_ITERATOR_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), FOLKS_TYPE_SMALL_SET_ITERATOR, \
- FolksSmallSetIteratorClass))
-
-typedef enum {
- FOLKS_SMALL_SET_FLAG_READ_ONLY = (1 << 0),
-} FolksSmallSetFlags;
-
-/* This is in the (internal) header to allow inlining. */
-struct _FolksSmallSet {
- /*<private>*/
- GeeAbstractSet parent_instance;
-
- GPtrArray *items;
- GType item_type;
- GBoxedCopyFunc item_dup;
- GDestroyNotify item_free;
- GeeHashDataFunc item_hash;
- gpointer item_hash_data;
- GDestroyNotify item_hash_data_free;
- GeeEqualDataFunc item_equals;
- gpointer item_equals_data;
- GDestroyNotify item_equals_data_free;
-
- FolksSmallSetFlags flags;
- FolksSmallSet *rw_version;
-};
-
-/* Syntactic sugar for iteration. The type must match the type
- * of the size property, which is signed, because Vala. */
-static inline gconstpointer
-folks_small_set_get (FolksSmallSet *self,
- gint i)
-{
- g_return_val_if_fail (self != NULL, NULL);
- g_return_val_if_fail (i >= 0, NULL);
- g_return_val_if_fail ((guint) i < self->items->len, NULL);
-
- return g_ptr_array_index (self->items, i);
-}
-
-FolksSmallSet *
-_folks_small_set_new_take_array (GPtrArray *arr,
- GType item_type,
- GBoxedCopyFunc item_dup,
- GDestroyNotify item_free);
-
-G_END_DECLS
-
-#endif
diff --git a/folks/small-set.c b/folks/small-set.c
deleted file mode 100644
index 39790755..00000000
--- a/folks/small-set.c
+++ /dev/null
@@ -1,973 +0,0 @@
-/*
- * small-set - a set optimized for fast iteration when there are few items
- *
- * Copyright © 2013 Intel Corporation
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301 USA
- *
- * Authors:
- * Simon McVittie <simon.mcvittie@collabora.co.uk>
- */
-
-#include "folks/small-set.h"
-#include "folks/small-set-internal.h"
-
-/*
- * FolksSmallSet:
- *
- * FolksSmallSet is a wrapper around an array, designed to be used for
- * sets with zero or few items. If necessary, it can be used
- * as a read-only, Gee-compatible wrapper around a separately-maintained
- * GPtrArray.
- *
- * Memory efficiency: this is about as small as you're going to get, given
- * the constraints of libgee.
- *
- * Performance: iteration is very fast, iterating over a read-only view is
- * very fast, copying an existing set is quite fast, foreach is quite fast.
- * Everything else scales up poorly with large numbers of elements:
- * adding, removing and testing membership are all O(n). The intention is
- * that if n can be large, you should use HashSet or something.
- *
- * The constructor takes a hash function but does not use it, making it
- * a drop-in replacement for HashSet. In theory we could use the hash function
- * for faster de-duplication when taking a union of sets, if that would be
- * helpful.
- */
-
-struct _FolksSmallSetClass {
- /*<private>*/
- GeeAbstractSetClass parent_class;
-};
-
-typedef enum {
- /* Iteration has started (we called next() at least once).
- * get() is valid, unless REMOVED is also set. */
- ITER_STARTED = (1 << 0),
- /* The item pointed to has been removed. get() is invalid
- * until the next call to next(). */
- ITER_REMOVED = (1 << 1),
-} IterFlags;
-
-struct _FolksSmallSetIterator {
- GObject parent_instance;
- FolksSmallSet *set;
- guint i;
- IterFlags flags;
-};
-
-struct _FolksSmallSetIteratorClass {
- GObjectClass parent_class;
-};
-
-static void traversable_iface_init (GeeTraversableIface *iface);
-
-G_DEFINE_TYPE_WITH_CODE (FolksSmallSet, folks_small_set,
- GEE_TYPE_ABSTRACT_SET,
- G_IMPLEMENT_INTERFACE (GEE_TYPE_TRAVERSABLE, traversable_iface_init);
- G_IMPLEMENT_INTERFACE (GEE_TYPE_ITERABLE, NULL);
- G_IMPLEMENT_INTERFACE (GEE_TYPE_COLLECTION, NULL);
- G_IMPLEMENT_INTERFACE (GEE_TYPE_SET, NULL))
-
-/*
- * Returns: (transfer none): self[i]
- */
-static inline gconstpointer
-_get (FolksSmallSet *self,
- guint i)
-{
- return g_ptr_array_index (self->items, i);
-}
-
-/*
- * Returns: (transfer full): self[i]
- */
-static inline gpointer
-_dup (FolksSmallSet *self,
- guint i)
-{
- if (self->item_dup == NULL)
- return (gpointer) _get (self, i);
-
- return self->item_dup ((gpointer) _get (self, i));
-}
-
-/*
- * @position: (out): i such that item_equals (self[i], item)
- * Returns: %FALSE if there is no such i
- */
-static inline gboolean
-_find (FolksSmallSet *self,
- gconstpointer item,
- guint *position)
-{
- guint i;
-
- /* If we're a read-only view of something with complicated comparator
- * functions, we need to use that version's comparators, because we
- * can't copy delegates */
- if (self->rw_version != NULL)
- {
- g_assert (self->items == self->rw_version->items);
- self = self->rw_version;
- }
-
- for (i = 0; i < self->items->len; i++)
- {
- gconstpointer candidate = _get (self, i);
- gboolean equal;
-
- if (self->item_equals == NULL ||
- self->item_equals == (GeeEqualDataFunc) g_direct_equal)
- equal = (candidate == item);
- else
- equal = self->item_equals (candidate, item, self->item_equals_data);
-
- if (equal)
- {
- if (position != NULL)
- *position = i;
-
- return TRUE;
- }
- }
-
- return FALSE;
-}
-
-static void
-folks_small_set_configure (FolksSmallSet *self,
- GType item_type,
- GBoxedCopyFunc item_dup,
- GDestroyNotify item_free,
- GeeHashDataFunc item_hash,
- gpointer item_hash_data,
- GDestroyNotify item_hash_data_free,
- GeeEqualDataFunc item_equals,
- gpointer item_equals_data,
- GDestroyNotify item_equals_data_free)
-{
- /* We bypass properties because this entire class exists for performance
- * reasons, and it isn't intended to be subclassed or anything. */
- self->item_type = item_type;
- self->item_dup = item_dup;
- self->item_free = item_free;
-
- if (item_hash == NULL)
- {
- self->item_hash = gee_functions_get_hash_func_for (self->item_type,
- &self->item_hash_data, &self->item_hash_data_free);
- }
- else
- {
- self->item_hash = item_hash;
- self->item_hash_data = item_hash_data;
- self->item_hash_data_free = item_hash_data_free;
- }
-
- if (item_equals == NULL)
- {
- self->item_equals = gee_functions_get_equal_func_for (self->item_type,
- &self->item_equals_data, &self->item_equals_data_free);
- }
- else
- {
- self->item_equals = item_equals;
- self->item_equals_data = item_equals_data;
- self->item_equals_data_free = item_equals_data_free;
- }
-}
-
-/*
- * Returns: (transfer full): a new read-only view
- */
-static FolksSmallSet *
-_read_only_view (FolksSmallSet *self)
-{
- FolksSmallSet *other;
-
- g_return_val_if_fail (FOLKS_IS_SMALL_SET (self), NULL);
-
- /* if we're already read-only, we are our own read-only view */
- if (self->flags & FOLKS_SMALL_SET_FLAG_READ_ONLY)
- return g_object_ref (self);
-
- /* if we're not, make a new one */
- other = g_object_new (FOLKS_TYPE_SMALL_SET,
- NULL);
- other->items = g_ptr_array_ref (self->items);
- other->flags = FOLKS_SMALL_SET_FLAG_READ_ONLY;
-
- folks_small_set_configure (other, self->item_type, self->item_dup,
- self->item_free, NULL, NULL, NULL, NULL, NULL, NULL);
-
- if (self->item_hash_data == NULL &&
- self->item_hash_data_free == NULL &&
- self->item_equals_data == NULL &&
- self->item_equals_data_free == NULL)
- {
- /* they're simple enough functions to be copied */
- other->item_hash = self->item_hash;
- other->item_equals = self->item_equals;
- }
- else
- {
- /* we need to use this one's comparator functions */
- other->rw_version = g_object_ref (self);
- }
-
- /* FIXME: benchmark whether giving self a weak ref to other is a
- * performance win or not */
-
- return other;
-}
-
-/* Covariance? We've heard of it */
-#define abstract_set_get_read_only_view \
- ((GeeSet * (*) (GeeAbstractSet *)) _read_only_view)
-#define abstract_collection_get_read_only_view \
- ((GeeCollection * (*) (GeeAbstractCollection *)) _read_only_view)
-
-static gint
-folks_small_set_get_size (GeeAbstractCollection *collection)
-{
- FolksSmallSet *self = FOLKS_SMALL_SET (collection);
-
- g_return_val_if_fail (self != NULL, 0);
- g_return_val_if_fail (self->items->len <= G_MAXINT, G_MAXINT);
-
- return (gint) self->items->len;
-}
-
-static gboolean
-folks_small_set_get_read_only (GeeAbstractCollection *collection)
-{
- FolksSmallSet *self = FOLKS_SMALL_SET (collection);
-
- g_return_val_if_fail (self != NULL, TRUE);
-
- return ((self->flags & FOLKS_SMALL_SET_FLAG_READ_ONLY) != 0);
-}
-
-/*
- * This is deliberately the same signature as HashSet(), even though
- * we don't (currently) use the hashing function.
- *
- * Returns: (transfer full):
- */
-FolksSmallSet *
-folks_small_set_new (GType item_type,
- GBoxedCopyFunc item_dup,
- GDestroyNotify item_free,
- GeeHashDataFunc item_hash,
- gpointer item_hash_data,
- GDestroyNotify item_hash_data_free,
- GeeEqualDataFunc item_equals,
- gpointer item_equals_data,
- GDestroyNotify item_equals_data_free)
-{
- FolksSmallSet *self = g_object_new (FOLKS_TYPE_SMALL_SET,
- NULL);
-
- /* We bypass properties because this entire class exists for performance
- * reasons, and it isn't intended to be subclassed or anything. */
- folks_small_set_configure (self, item_type, item_dup, item_free,
- item_hash, item_hash_data, item_hash_data_free,
- item_equals, item_equals_data, item_equals_data_free);
- self->items = g_ptr_array_new_full (0, item_free);
- self->flags = 0;
-
- return self;
-}
-
-/*
- * Returns: (transfer full):
- */
-FolksSmallSet *
-folks_small_set_empty (GType item_type,
- GBoxedCopyFunc item_dup,
- GDestroyNotify item_free)
-{
- FolksSmallSet *self = g_object_new (FOLKS_TYPE_SMALL_SET,
- NULL);
-
- self->items = g_ptr_array_new_full (0, item_free);
- self->item_type = item_type;
- self->flags = FOLKS_SMALL_SET_FLAG_READ_ONLY;
-
- return self;
-}
-
-/*
- * @arr: (transfer container): must have @item_free as its free-function
- * Returns: (transfer full):
- */
-FolksSmallSet *
-_folks_small_set_new_take_array (GPtrArray *arr,
- GType item_type,
- GBoxedCopyFunc item_dup,
- GDestroyNotify item_free)
-{
- FolksSmallSet *self = g_object_new (FOLKS_TYPE_SMALL_SET,
- NULL);
-
- folks_small_set_configure (self, item_type, item_dup, item_free,
- NULL, NULL, NULL,
- NULL, NULL, NULL);
- self->items = arr;
- self->flags = FOLKS_SMALL_SET_FLAG_READ_ONLY;
-
- return self;
-}
-
-/*
- * Returns: (transfer full):
- */
-FolksSmallSet *
-folks_small_set_copy (GType item_type,
- GBoxedCopyFunc item_dup,
- GDestroyNotify item_free,
- GeeIterable *iterable,
- GeeHashDataFunc item_hash,
- gpointer item_hash_data,
- GDestroyNotify item_hash_data_free,
- GeeEqualDataFunc item_equals,
- gpointer item_equals_data,
- GDestroyNotify item_equals_data_free)
-{
- FolksSmallSet *self;
- GeeIterator *iter;
-
- /* Deliberately not allowing for subclasses here: this class is not
- * subclassable, and it's slower if we do check for subclasses. */
- if (G_OBJECT_TYPE (iterable) == FOLKS_TYPE_SMALL_SET)
- {
- /* Fast path: copy the items directly from the other one. */
- FolksSmallSet *other = (FolksSmallSet *) iterable;
- guint i;
-
- self = g_object_new (FOLKS_TYPE_SMALL_SET,
- NULL);
- folks_small_set_configure (self,
- other->item_type, other->item_dup, other->item_free,
- item_hash, item_hash_data, item_hash_data_free,
- item_equals, item_equals_data, item_equals_data_free);
- self->items = g_ptr_array_new_full (other->items->len,
- other->item_free);
- self->flags = 0;
-
- for (i = 0; i < other->items->len; i++)
- g_ptr_array_add (self->items, _dup (other, i));
-
- return self;
- }
-
- self = folks_small_set_new (item_type, item_dup, item_free,
- item_hash, item_hash_data, item_hash_data_free,
- item_equals, item_equals_data, item_equals_data_free);
- iter = gee_iterable_iterator (iterable);
-
- if (GEE_IS_SET (iterable))
- {
- /* If it's a set, then we don't need to worry about de-duplicating
- * the items. Just copy them in. */
- while (gee_iterator_next (iter))
- {
- g_ptr_array_add (self->items, gee_iterator_get (iter));
- }
- }
- else
- {
- /* Do it the hard way: there might be duplicates. */
- while (gee_iterator_next (iter))
- {
- gpointer item = gee_iterator_get (iter);
-
- if (_find (self, item, NULL))
- {
- if (item_free != NULL)
- item_free (item);
- }
- else
- {
- g_ptr_array_add (self->items, item);
- }
- }
- }
- return self;
-}
-
-enum {
- PROP_0,
- PROP_G_TYPE,
- PROP_G_DUP_FUNC,
- PROP_G_DESTROY_FUNC,
- N_PROPERTIES
-};
-
-static void
-folks_small_set_init (FolksSmallSet *self)
-{
-}
-
-static void
-folks_small_set_dispose (GObject *obj)
-{
- FolksSmallSet *self = FOLKS_SMALL_SET (obj);
-
- g_clear_object (&self->rw_version);
-
- if ((self->flags & FOLKS_SMALL_SET_FLAG_READ_ONLY) == 0)
- g_ptr_array_set_size (self->items, 0);
-
- ((GObjectClass *) folks_small_set_parent_class)->dispose (obj);
-}
-
-static void
-folks_small_set_finalize (GObject *obj)
-{
- FolksSmallSet *self = FOLKS_SMALL_SET (obj);
-
- g_ptr_array_unref (self->items);
-
- if (self->item_hash_data_free != NULL)
- self->item_hash_data_free (self->item_hash_data);
-
- if (self->item_equals_data_free != NULL)
- self->item_equals_data_free (self->item_equals_data);
-
- ((GObjectClass *) folks_small_set_parent_class)->finalize (obj);
-}
-
-static GType
-folks_small_set_get_g_type (GeeTraversable *traversable)
-{
- FolksSmallSet *self = FOLKS_SMALL_SET (traversable);
-
- return self->item_type;
-}
-
-static GBoxedCopyFunc
-folks_small_set_get_g_dup_func (GeeTraversable *traversable)
-{
- FolksSmallSet *self = FOLKS_SMALL_SET (traversable);
-
- return self->item_dup;
-}
-
-static GDestroyNotify
-folks_small_set_get_g_destroy_func (GeeTraversable *traversable)
-{
- FolksSmallSet *self = FOLKS_SMALL_SET (traversable);
-
- return self->item_free;
-}
-
-/*
- * Call @f for each element, until it returns %FALSE.
- *
- * Overridden because we can do better than allocating a new GObject,
- * which is what Gee would do.
- *
- * Returns: %FALSE if @f returns %FALSE, or %TRUE if we reached the
- * end of the set.
- */
-static gboolean
-folks_small_set_foreach (GeeTraversable *traversable,
- GeeForallFunc f,
- gpointer user_data)
-{
- FolksSmallSet *self = FOLKS_SMALL_SET (traversable);
- guint i;
-
- g_return_val_if_fail (self != NULL, FALSE);
-
- for (i = 0; i < self->items->len; i++)
- {
- /* Yes, GeeForallFunc receives a new copy/ref, astonishing though
- * that may seem to C programmers. */
- if (!f (_dup (self, i), user_data))
- return FALSE;
- }
-
- return TRUE;
-}
-
-static void
-traversable_iface_init (GeeTraversableIface *iface)
-{
- iface->get_g_type = folks_small_set_get_g_type;
- iface->get_g_dup_func = folks_small_set_get_g_dup_func;
- iface->get_g_destroy_func = folks_small_set_get_g_destroy_func;
- iface->foreach = folks_small_set_foreach;
-}
-
-static GeeIterator *
-folks_small_set_iterator (GeeAbstractCollection *collection)
-{
- FolksSmallSet *self = FOLKS_SMALL_SET (collection);
- FolksSmallSetIterator *iter;
-
- g_return_val_if_fail (self != NULL, NULL);
-
- iter = g_object_new (FOLKS_TYPE_SMALL_SET_ITERATOR,
- NULL);
-
- iter->set = g_object_ref (self);
- iter->flags = 0;
- return (GeeIterator *) iter;
-}
-
-static gboolean
-folks_small_set_contains (GeeAbstractCollection *collection,
- gconstpointer item)
-{
- FolksSmallSet *self = FOLKS_SMALL_SET (collection);
-
- g_return_val_if_fail (self != NULL, FALSE);
-
- return _find (self, item, NULL);
-}
-
-/*
- * Add @item.
- *
- * Returns: %TRUE if it was not already there.
- */
-static gboolean
-folks_small_set_add (GeeAbstractCollection *collection,
- gconstpointer item)
-{
- FolksSmallSet *self = FOLKS_SMALL_SET (collection);
- gpointer copy;
-
- g_return_val_if_fail (self != NULL, FALSE);
- g_return_val_if_fail ((self->flags & FOLKS_SMALL_SET_FLAG_READ_ONLY) == 0, FALSE);
-
- if (_find (self, item, NULL))
- return FALSE;
-
- if (self->item_dup == NULL)
- copy = (gpointer) item;
- else
- copy = self->item_dup ((gpointer) item);
-
- g_ptr_array_add (self->items, copy);
- return TRUE;
-}
-
-/*
- * Remove @item.
- *
- * Returns: %TRUE if it was previously there.
- */
-static gboolean
-folks_small_set_remove (GeeAbstractCollection *collection,
- gconstpointer item)
-{
- FolksSmallSet *self = FOLKS_SMALL_SET (collection);
-
- g_return_val_if_fail (self != NULL, FALSE);
- g_return_val_if_fail ((self->flags & FOLKS_SMALL_SET_FLAG_READ_ONLY) == 0, FALSE);
-
- if (self->item_equals == NULL ||
- self->item_equals == (GeeEqualDataFunc) g_direct_equal)
- {
- if (g_ptr_array_remove_fast (self->items, (gpointer) item))
- return TRUE;
- }
- else
- {
- guint pos;
-
- if (_find (self, item, &pos))
- {
- g_ptr_array_remove_index_fast (self->items, pos);
- return TRUE;
- }
- }
-
- return FALSE;
-}
-
-/*
- * Remove all items.
- */
-static void
-folks_small_set_clear (GeeAbstractCollection *collection)
-{
- FolksSmallSet *self = FOLKS_SMALL_SET (collection);
-
- g_return_if_fail (self != NULL);
- g_return_if_fail ((self->flags & FOLKS_SMALL_SET_FLAG_READ_ONLY) == 0);
-
- g_ptr_array_set_size (self->items, 0);
-}
-
-static void
-folks_small_set_class_init (FolksSmallSetClass *cls)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (cls);
- GeeAbstractSetClass *as_class = GEE_ABSTRACT_SET_CLASS (cls);
- GeeAbstractCollectionClass *ac_class = GEE_ABSTRACT_COLLECTION_CLASS (cls);
-
- object_class->dispose = folks_small_set_dispose;
- object_class->finalize = folks_small_set_finalize;
-
- ac_class->contains = folks_small_set_contains;
- ac_class->add = folks_small_set_add;
- ac_class->remove = folks_small_set_remove;
- ac_class->clear = folks_small_set_clear;
- ac_class->iterator = folks_small_set_iterator;
- ac_class->get_size = folks_small_set_get_size;
- ac_class->get_read_only = folks_small_set_get_read_only;
- ac_class->get_read_only_view = abstract_collection_get_read_only_view;
-
- as_class->get_read_only_view = abstract_set_get_read_only_view;
-}
-
-/* ==== The iterator ==== */
-
-static void iterator_iface_init (GeeIteratorIface *iface);
-static void iterator_traversable_iface_init (GeeTraversableIface *iface);
-
-G_DEFINE_TYPE_WITH_CODE (FolksSmallSetIterator, folks_small_set_iterator,
- G_TYPE_OBJECT,
- G_IMPLEMENT_INTERFACE (GEE_TYPE_TRAVERSABLE,
- iterator_traversable_iface_init);
- G_IMPLEMENT_INTERFACE (GEE_TYPE_ITERATOR, iterator_iface_init))
-
-enum {
- ITER_PROP_0,
- ITER_PROP_VALID,
- ITER_PROP_READ_ONLY,
- ITER_PROP_G_TYPE,
- ITER_PROP_G_DUP_FUNC,
- ITER_PROP_G_DESTROY_FUNC,
- N_ITER_PROPERTIES
-};
-
-/*
- * Returns: (transfer full): self.get()
- */
-static inline gpointer
-_iterator_dup (FolksSmallSetIterator *self)
-{
- return _dup (self->set, self->i);
-}
-
-static inline gboolean
-_iterator_flag (FolksSmallSetIterator *self,
- IterFlags flag)
-{
- return ((self->flags & flag) != 0);
-}
-
-static inline gboolean
-_iterator_is_valid (FolksSmallSetIterator *self)
-{
- return (_iterator_flag (self, ITER_STARTED) &&
- !_iterator_flag (self, ITER_REMOVED) &&
- self->i < self->set->items->len);
-}
-
-static inline gboolean
-_iterator_has_next (FolksSmallSetIterator *self)
-{
- if (_iterator_flag (self, ITER_STARTED))
- return ((self->i + 1) < self->set->items->len);
- else
- return (self->set->items->len > 0);
-}
-
-static void
-folks_small_set_iterator_init (FolksSmallSetIterator *self)
-{
- self->set = NULL; /* fixed up by FolksSmallSet */
- self->flags = 0;
- self->i = G_MAXUINT;
-}
-
-static void
-folks_small_set_iterator_get_property (GObject *obj,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
-{
- FolksSmallSetIterator *self = FOLKS_SMALL_SET_ITERATOR (obj);
-
- switch (prop_id)
- {
- case ITER_PROP_VALID:
- g_value_set_boolean (value, _iterator_is_valid (self));
- break;
-
- case ITER_PROP_READ_ONLY:
- g_value_set_boolean (value, ((self->set->flags & FOLKS_SMALL_SET_FLAG_READ_ONLY) != 0));
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
- }
-}
-
-static void
-folks_small_set_iterator_set_property (GObject *obj,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- switch (prop_id)
- {
- /* ignore useless construct-only property - we always use the set's */
- case ITER_PROP_G_TYPE:
- case ITER_PROP_G_DUP_FUNC:
- case ITER_PROP_G_DESTROY_FUNC:
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
- }
-}
-
-static void
-folks_small_set_iterator_finalize (GObject *obj)
-{
- FolksSmallSetIterator *self = FOLKS_SMALL_SET_ITERATOR (obj);
-
- g_object_unref (self->set);
-
- ((GObjectClass *) folks_small_set_iterator_parent_class)->finalize (obj);
-}
-
-static void
-folks_small_set_iterator_class_init (FolksSmallSetIteratorClass *cls)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (cls);
-
- object_class->get_property = folks_small_set_iterator_get_property;
- object_class->set_property = folks_small_set_iterator_set_property;
- object_class->finalize = folks_small_set_iterator_finalize;
-
- g_object_class_install_property (object_class, ITER_PROP_VALID,
- g_param_spec_boolean ("valid", "Valid?", "TRUE if get() is valid",
- FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE));
-
- g_object_class_install_property (object_class, ITER_PROP_READ_ONLY,
- g_param_spec_boolean ("read-only", "Read-only?", "TRUE if read-only",
- FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE));
-
- g_object_class_install_property (object_class, ITER_PROP_G_TYPE,
- g_param_spec_gtype ("g-type", "Item type", "GType of items", G_TYPE_NONE,
- G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
-
- g_object_class_install_property (object_class, ITER_PROP_G_DUP_FUNC,
- g_param_spec_pointer ("g-dup-func", "Item copy function",
- "Copies or refs an item",
- G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
-
- g_object_class_install_property (object_class, ITER_PROP_G_DESTROY_FUNC,
- g_param_spec_pointer ("g-destroy-func", "Item free function",
- "Frees or unrefs item",
- G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
-}
-
-/* ---- ... as Traversable ---- */
-
-static gboolean
-folks_small_set_iterator_foreach (GeeTraversable *traversable,
- GeeForallFunc f,
- gpointer user_data)
-{
- FolksSmallSetIterator *self = FOLKS_SMALL_SET_ITERATOR (traversable);
-
- g_return_val_if_fail (self != NULL, FALSE);
- g_return_val_if_fail (self->set != NULL, FALSE);
-
- if (!_iterator_flag (self, ITER_STARTED))
- {
- self->flags = ITER_STARTED;
- /* we will wrap around to 0 when we advance (ISO C guarantees that
- * unsigned arithmetic wraps around) */
- self->i = G_MAXUINT;
- }
- else if (!_iterator_flag (self, ITER_REMOVED))
- {
- /* Look at the current item before we advance.
- * Yes, GeeForallFunc receives a new copy/ref. */
- if (!f (_iterator_dup (self), user_data))
- return FALSE;
- }
-
- for (self->i++; self->i < self->set->items->len; self->i++)
- {
- /* back onto track, even if an item was removed */
- self->flags &= ~ITER_REMOVED;
-
- /* Yes, GeeForallFunc receives a new copy/ref. */
- if (!f (_iterator_dup (self), user_data))
- return FALSE;
- }
-
- return TRUE;
-}
-
-static GType
-folks_small_set_iterator_get_g_type (GeeTraversable *traversable)
-{
- FolksSmallSetIterator *self = FOLKS_SMALL_SET_ITERATOR (traversable);
-
- g_return_val_if_fail (self != NULL, G_TYPE_INVALID);
-
- return self->set->item_type;
-}
-
-static GBoxedCopyFunc
-folks_small_set_iterator_get_g_dup_func (GeeTraversable *traversable)
-{
- FolksSmallSetIterator *self = FOLKS_SMALL_SET_ITERATOR (traversable);
-
- g_return_val_if_fail (self != NULL, NULL);
-
- return self->set->item_dup;
-}
-
-static GDestroyNotify
-folks_small_set_iterator_get_g_destroy_func (GeeTraversable *traversable)
-{
- FolksSmallSetIterator *self = FOLKS_SMALL_SET_ITERATOR (traversable);
-
- g_return_val_if_fail (self != NULL, NULL);
-
- return self->set->item_free;
-}
-
-static void
-iterator_traversable_iface_init (GeeTraversableIface *iface)
-{
- iface->foreach = folks_small_set_iterator_foreach;
- iface->get_g_type = folks_small_set_iterator_get_g_type;
- iface->get_g_dup_func = folks_small_set_iterator_get_g_dup_func;
- iface->get_g_destroy_func = folks_small_set_iterator_get_g_destroy_func;
-}
-
-/* ---- ... as Iterator ---- */
-
-static gboolean
-folks_small_set_iterator_next (GeeIterator *iter)
-{
- FolksSmallSetIterator *self = FOLKS_SMALL_SET_ITERATOR (iter);
-
- g_return_val_if_fail (self != NULL, FALSE);
-
- if (!_iterator_has_next (self))
- {
- return FALSE;
- }
- if (_iterator_flag (self, ITER_STARTED))
- {
- /* back onto track, even if an item was removed */
- self->flags &= ~ITER_REMOVED;
- self->i++;
- }
- else
- {
- self->flags = ITER_STARTED;
- self->i = 0;
- }
-
- g_assert (_iterator_is_valid (self));
- return TRUE;
-}
-
-static gboolean
-folks_small_set_iterator_has_next (GeeIterator *iter)
-{
- FolksSmallSetIterator *self = FOLKS_SMALL_SET_ITERATOR (iter);
-
- g_return_val_if_fail (self != NULL, FALSE);
- return _iterator_has_next (self);
-}
-
-static gpointer
-folks_small_set_iterator_get (GeeIterator *iter)
-{
- FolksSmallSetIterator *self = FOLKS_SMALL_SET_ITERATOR (iter);
-
- g_return_val_if_fail (self != NULL, NULL);
- g_return_val_if_fail (_iterator_flag (self, ITER_STARTED), NULL);
- g_return_val_if_fail (!_iterator_flag (self, ITER_REMOVED), NULL);
-
- return _iterator_dup (self);
-}
-
-static void
-folks_small_set_iterator_remove (GeeIterator *iter)
-{
- FolksSmallSetIterator *self = FOLKS_SMALL_SET_ITERATOR (iter);
-
- g_return_if_fail (self != NULL);
- g_return_if_fail ((self->set->flags & FOLKS_SMALL_SET_FLAG_READ_ONLY) == 0);
- g_return_if_fail (_iterator_flag (self, ITER_STARTED));
- g_return_if_fail (!_iterator_flag (self, ITER_REMOVED));
-
- /* Suppose self->i == 5, i.e. we are pointing at pdata[5] in a list
- * of length 10. */
-
- /* Move pdata[9] to overwrite pdata[5] */
- g_ptr_array_remove_index_fast (self->set->items, self->i);
-
- /* Next time we advance the iterator, we want it to move to pdata[5];
- * so we need to move back one. i is unsigned, so it's OK if it underflows
- * to all-ones - ISO C guarantees that unsigned arithmetic wraps around. */
- self->i--;
-
- /* We're not allowed to get() until we're back on track, though. */
- self->flags |= ITER_REMOVED;
-}
-
-static gboolean
-folks_small_set_iterator_get_valid (GeeIterator *iter)
-{
- FolksSmallSetIterator *self = FOLKS_SMALL_SET_ITERATOR (iter);
-
- g_return_val_if_fail (self != NULL, FALSE);
-
- return _iterator_is_valid (self);
-}
-
-static gboolean
-folks_small_set_iterator_get_read_only (GeeIterator *iter)
-{
- FolksSmallSetIterator *self = FOLKS_SMALL_SET_ITERATOR (iter);
-
- g_return_val_if_fail (self != NULL, TRUE);
-
- return ((self->set->flags & FOLKS_SMALL_SET_FLAG_READ_ONLY) != 0);
-}
-
-/* unfold, concat are inherited */
-
-static void
-iterator_iface_init (GeeIteratorIface *iface)
-{
- iface->next = folks_small_set_iterator_next;
- iface->has_next = folks_small_set_iterator_has_next;
- iface->get = folks_small_set_iterator_get;
- iface->remove = folks_small_set_iterator_remove;
- iface->get_valid = folks_small_set_iterator_get_valid;
- iface->get_read_only = folks_small_set_iterator_get_read_only;
-}
diff --git a/folks/small-set.h b/folks/small-set.h
deleted file mode 100644
index 5df54ce3..00000000
--- a/folks/small-set.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * small-set - a set optimized for fast iteration when there are few items
- *
- * Copyright © 2013 Intel Corporation
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
-
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
-
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301 USA
- *
- * Authors:
- * Simon McVittie <simon.mcvittie@collabora.co.uk>
- */
-
-#ifndef FOLKS_SMALL_SET_H
-#define FOLKS_SMALL_SET_H
-
-#include <glib.h>
-#include <glib-object.h>
-#include <gee.h>
-
-G_BEGIN_DECLS
-
-typedef struct _FolksSmallSet FolksSmallSet;
-typedef struct _FolksSmallSetClass FolksSmallSetClass;
-
-GType folks_small_set_get_type (void);
-
-#define FOLKS_TYPE_SMALL_SET \
- (folks_small_set_get_type ())
-#define FOLKS_SMALL_SET(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), FOLKS_TYPE_SMALL_SET, \
- FolksSmallSet))
-#define FOLKS_SMALL_SET_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST ((klass), FOLKS_TYPE_SMALL_SET, \
- FolksSmallSetClass))
-#define FOLKS_IS_SMALL_SET(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FOLKS_TYPE_SMALL_SET))
-#define FOLKS_IS_SMALL_SET_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE ((klass), FOLKS_TYPE_SMALL_SET))
-#define FOLKS_SMALL_SET_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), FOLKS_TYPE_SMALL_SET, \
- FolksSmallSetClass))
-
-FolksSmallSet *
-folks_small_set_new (GType item_type,
- GBoxedCopyFunc item_dup,
- GDestroyNotify item_free,
- GeeHashDataFunc item_hash,
- gpointer item_hash_data,
- GDestroyNotify item_hash_data_free,
- GeeEqualDataFunc item_equals,
- gpointer item_equals_data,
- GDestroyNotify item_equals_data_free);
-
-FolksSmallSet *
-folks_small_set_empty (GType item_type,
- GBoxedCopyFunc item_dup,
- GDestroyNotify item_free);
-
-FolksSmallSet *folks_small_set_copy (GType item_type,
- GBoxedCopyFunc item_dup,
- GDestroyNotify item_free,
- GeeIterable *iterable,
- GeeHashDataFunc item_hash,
- gpointer item_hash_data,
- GDestroyNotify item_hash_data_free,
- GeeEqualDataFunc item_equals,
- gpointer item_equals_data,
- GDestroyNotify item_equals_data_free);
-
-G_END_DECLS
-
-#endif
diff --git a/folks/url-details.vala b/folks/url-details.vala
deleted file mode 100644
index 117fec2d..00000000
--- a/folks/url-details.vala
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- * Copyright (C) 2011 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Marco Barisione <marco.barisione@collabora.co.uk>
- * Travis Reitter <travis.reitter@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using GLib;
-using Gee;
-
-/**
- * Object representing a URL that can have some parameters associated with it.
- *
- * See {@link Folks.AbstractFieldDetails} for details on common parameter names
- * and values.
- *
- * @since 0.6.0
- */
-public class Folks.UrlFieldDetails : AbstractFieldDetails<string>
-{
- /**
- * Parameter value for URLs for the contact's home page.
- *
- * Value for a parameter with name {@link AbstractFieldDetails.PARAM_TYPE}.
- *
- * @since 0.6.3
- */
- public const string PARAM_TYPE_HOME_PAGE = "x-home-page";
-
- /**
- * Parameter value for URLs for the contact's personal or professional blog.
- *
- * Value for a parameter with name {@link AbstractFieldDetails.PARAM_TYPE}.
- *
- * @since 0.6.3
- */
- public const string PARAM_TYPE_BLOG = "x-blog";
-
- /**
- * Parameter value for URLs for the contact's social networking profile.
- *
- * Value for a parameter with name {@link AbstractFieldDetails.PARAM_TYPE}.
- *
- * @since 0.6.3
- */
- public const string PARAM_TYPE_PROFILE = "x-profile";
-
- /**
- * Parameter value for URLs for the contact's personal or professional FTP
- * server.
- *
- * Value for a parameter with name {@link AbstractFieldDetails.PARAM_TYPE}.
- *
- * @since 0.6.3
- */
- public const string PARAM_TYPE_FTP = "x-ftp";
-
- /**
- * Create a new UrlFieldDetails.
- *
- * @param value the value of the field, a non-empty URI
- * @param parameters initial parameters. See
- * {@link AbstractFieldDetails.parameters}. A ``null`` value is equivalent to
- * an empty map of parameters.
- *
- * @return a new UrlFieldDetails
- *
- * @since 0.6.0
- */
- public UrlFieldDetails (string value,
- MultiMap<string, string>? parameters = null)
- {
- if (value == "")
- {
- warning ("Empty URI passed to UrlFieldDetails.");
- }
-
- Object (value: value,
- parameters: parameters);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override bool equal (AbstractFieldDetails<string> that)
- {
- return base.equal (that);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override uint hash ()
- {
- return base.hash ();
- }
-}
-
-/**
- * Associates a list of URLs with a contact.
- *
- * @since 0.3.5
- */
-public interface Folks.UrlDetails : Object
-{
- /**
- * The websites of the contact.
- *
- * A list or websites associated to the contact.
- *
- * @since 0.5.1
- */
- public abstract Set<UrlFieldDetails> urls { get; set; }
-
- /**
- * Change the contact's URLs.
- *
- * It's preferred to call this rather than setting {@link UrlDetails.urls}
- * directly, as this method gives error notification and will only return once
- * the URLs have been written to the relevant backing store (or the
- * operation's failed).
- *
- * @param urls the set of URLs
- * @throws PropertyError if setting the URLs failed
- * @since 0.6.2
- */
- public virtual async void change_urls (Set<UrlFieldDetails> urls)
- throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("URLs are not writeable on this contact."));
- }
-}
diff --git a/folks/utils.vala b/folks/utils.vala
deleted file mode 100644
index 2339e273..00000000
--- a/folks/utils.vala
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
- * Travis Reitter <travis.reitter@collabora.co.uk>
- */
-
-using Gee;
-
-/* TODO: This should be converted to a nested namespace, rather than a class,
- * when folks next breaks API. Having it as a class means that a GType is always
- * registered for it, and a C constructor function created, even though
- * instantiating it is pointless as all the methods are static (and should
- * remain so). */
-/**
- * Utility functions to simplify common patterns in Folks client code.
- *
- * These may be used by folks clients as well, and are part of folks' supported
- * stable API.
- *
- * @since 0.6.0
- */
-public class Folks.Utils : Object
-{
- internal static bool _str_equal_safe (string a, string b)
- {
- return (a != "" && b != "" && a.down () == b.down ());
- }
-
- /**
- * Create a new utilities object.
- *
- * This method is useless and should never be used. It will be removed in a
- * future version in favour of making the Utils class into a nested namespace.
- *
- * @return a new utilities object
- * @since 0.6.0
- */
- [Version (deprecated = true, deprecated_since = "0.7.4",
- replacement = "Folks.Utils")]
- public Utils ()
- {
- base ();
- }
-
- /**
- * Check whether two multi-maps of strings to strings are equal. This performs
- * a deep check for equality, checking whether both maps are of the same size,
- * and that each key maps to the same set of values in both maps.
- *
- * @param a a multi-map to compare
- * @param b another multi-map to compare
- * @return ``true`` if the multi-maps are equal, ``false`` otherwise
- *
- * @since 0.6.0
- */
- public static bool multi_map_str_str_equal (
- MultiMap<string, string> a,
- MultiMap<string, string> b)
- {
- if (a == b)
- return true;
-
- var a_size = a.size;
- var b_size = b.size;
-
- if (a_size == 0 && b_size == 0)
- {
- /* fast path: avoid the actual iteration, which creates GObjects */
- return true;
- }
- else if (a_size == b_size)
- {
- foreach (var key in a.get_keys ())
- {
- if (b.contains (key))
- {
- var a_values = a.get (key);
- var b_values = b.get (key);
- if (a_values.size != b_values.size)
- return false;
-
- foreach (var a_value in a_values)
- {
- if (!b_values.contains (a_value))
- return false;
- }
- }
- else
- {
- return false;
- }
- }
- }
- else
- {
- return false;
- }
-
- return true;
- }
-
- /**
- * Check whether two multi-maps of strings to AbstractFieldDetails are equal.
- *
- * This performs a deep check for equality, checking whether both maps are of
- * the same size, and that each key maps to the same set of values in both
- * maps.
- *
- * @param a a multi-map to compare
- * @param b another multi-map to compare
- * @return ``true`` if the multi-maps are equal, ``false`` otherwise
- *
- * @since 0.6.0
- */
- public static bool multi_map_str_afd_equal (
- MultiMap<string, AbstractFieldDetails> a,
- MultiMap<string, AbstractFieldDetails> b)
- {
- if (a == b)
- return true;
-
- var a_size = a.size;
- var b_size = b.size;
-
- if (a_size == 0 && b_size == 0)
- {
- /* fast path: avoid the actual iteration, which creates GObjects */
- return true;
- }
- else if (a_size == b_size)
- {
- foreach (var key in a.get_keys ())
- {
- if (b.contains (key))
- {
- var a_values = a.get (key);
- var b_values = b.get (key);
- if (a_values.size != b_values.size)
- return false;
-
- foreach (var a_value in a_values)
- {
- if (!b_values.contains (a_value))
- return false;
- }
- }
- else
- {
- return false;
- }
- }
- }
- else
- {
- return false;
- }
-
- return true;
- }
-
- /**
- * Check whether a set of strings to AbstractFieldDetails are equal.
- *
- * This performs a deep check for equality, checking whether both sets are of
- * the same size, and that each key maps to the same set of values in both
- * maps.
- *
- * @param a a set to compare
- * @param b another set to compare
- * @return ``true`` if the sets are equal, ``false`` otherwise
- *
- * @since 0.6.0
- */
- public static bool set_afd_equal (
- Set<AbstractFieldDetails> a,
- Set<AbstractFieldDetails> b)
- {
- if (a == b)
- return true;
-
- var a_size = a.size;
- var b_size = b.size;
-
- if (a_size == 0 && b_size == 0)
- {
- /* fast path: avoid creating the iterator, which is a GObject */
- return true;
- }
- else if (a_size == b_size)
- {
- foreach (var val in a)
- {
- if (!b.contains (val))
- {
- return false;
- }
- }
- }
- else
- {
- return false;
- }
-
- return true;
- }
-
- /**
- * Check whether a set of AbstractFieldDetails with string values are equal.
- *
- * This performs a deep check for equality, checking whether both sets are of
- * the same size, and that each set has the same values using string compaction
- * instead of AbstractFieldDetails equal function
- *
- * @param a a set to compare
- * @param b another set to compare
- * @return ``true`` if the sets are equal, ``false`` otherwise
- *
- * @since 0.9.7
- */
- public static bool set_string_afd_equal (
- Set<AbstractFieldDetails<string> > a,
- Set<AbstractFieldDetails<string> > b)
- {
- if (a == b)
- return true;
-
- var a_size = a.size;
- var b_size = b.size;
-
- if (a_size == 0 && b_size == 0)
- {
- /* fast path: avoid creating the iterator, which is a GObject */
- return true;
- }
- else if (a_size == b_size)
- {
- foreach (var a_val in a)
- {
- bool found = false;
- foreach (var b_val in b)
- {
- if (a_val.parameters_equal (b_val) &&
- str_equal(a_val.value, b_val.value))
- {
- found = true;
- }
- }
- if (!found)
- {
- return false;
- }
- }
- }
- else
- {
- return false;
- }
-
- return true;
- }
-}
diff --git a/folks/warnings.h b/folks/warnings.h
deleted file mode 100644
index 15a922d8..00000000
--- a/folks/warnings.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * warnings - a set of gcc pragmas to disable warnings in Vala-generated C code
- *
- * Copyright © 2013 Collabora Ltd.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
-
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
-
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301 USA
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-#ifndef FOLKS_WARNINGS_H
-#define FOLKS_WARNINGS_H
-
-/* The following pragmas disable various GCC warnings which frequently arise
- * from C code generated by Vala. Fixing the code generator to eliminate these
- * warnings (especially the first few) is very low on the Vala developers'
- * priority list, so for now we have to live with them disabled.
- *
- * The warnings nearer the end of the list are a little more specific and
- * important, and bugs have been filed (and referenced below) to try and get
- * the code generator fixed so we can remove the pragmas.
- *
- * This file should be included using
- * *_CPPFLAGS += -include $(top_srcdir)/folks/warnings.h
- * in every build target which compiles only Vala source files. Targets which
- * compile both Vala and (non-generated) C should *not* include this file, so
- * that genuine warnings in the C code are caught. */
-
-/* Vala likes to duplicate function declarations at the top of every C file.
- * They're all identical, so not a problem, but GCC should shut up about
- * them. */
-#pragma GCC diagnostic ignored "-Wredundant-decls"
-
-/* Vala continually sets variabeles which it never later uses. */
-#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
-
-/* Vala continually generates unused temporary variables. */
-#pragma GCC diagnostic ignored "-Wunused-variable"
-
-/* Vala sometimes generates unused utility functions. */
-#pragma GCC diagnostic ignored "-Wunused-function"
-
-/* Vala sometimes generates labels it doesn't use when doing async state
- * machines and error handling. */
-#pragma GCC diagnostic ignored "-Wunused-label"
-
-/* Vala misses off the braces in nested static struct initialisations */
-#pragma GCC diagnostic ignored "-Wmissing-braces"
-
-/* Vala warns us about using deprecated functions from other namespaces, but
- * handily doesn't warn about using deprecated functions internally. */
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-
-/* Vala likes putting format literals through temporary variables. */
-#pragma GCC diagnostic ignored "-Wformat-nonliteral"
-
-/* Vala often doesn't cast to the right parent class or interface for method
- * calls. Disabling -Wcast-qual doesn't seem to achieve anything, but we might
- * as well try.
- *
- * See: https://bugzilla.gnome.org/show_bug.cgi?id=652553
- * and: https://bugzilla.gnome.org/show_bug.cgi?id=710863
- * and: https://bugzilla.gnome.org/show_bug.cgi?id=710865 */
-#pragma GCC diagnostic ignored "-Wcast-qual"
-
-/* Vala can't emit the G_GNUC_PRINTF function attribute.
- *
- * See: https://bugzilla.gnome.org/show_bug.cgi?id=710862 */
-#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
-
-#endif
diff --git a/folks/web-service-details.vala b/folks/web-service-details.vala
deleted file mode 100644
index 2ed13602..00000000
--- a/folks/web-service-details.vala
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2011 Collabora Ltd.
- * Copyright (C) 2011 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Alban Crequy <alban.crequy@collabora.co.uk>
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using Gee;
-
-/**
- * Object representing a web service contact that can have some parameters
- * associated with it.
- *
- * See {@link Folks.AbstractFieldDetails}.
- *
- * @since 0.6.0
- */
-public class Folks.WebServiceFieldDetails : AbstractFieldDetails<string>
-{
- /**
- * Create a new WebServiceFieldDetails.
- *
- * @param value the value of the field, a non-empty web service address
- * @param parameters initial parameters. See
- * {@link AbstractFieldDetails.parameters}. A ``null`` value is equivalent to
- * an empty map of parameters.
- *
- * @return a new WebServiceFieldDetails
- *
- * @since 0.6.0
- */
- public WebServiceFieldDetails (string value,
- MultiMap<string, string>? parameters = null)
- {
- if (value == "")
- {
- warning ("Empty web service address passed to " +
- "WebServiceFieldDetails.");
- }
-
- Object (value: value,
- parameters: parameters);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override bool equal (AbstractFieldDetails<string> that)
- {
- return base.equal (that);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 0.6.0
- */
- public override uint hash ()
- {
- return base.hash ();
- }
-}
-
-/**
- * Web service contact details.
- *
- * @since 0.5.0
- */
-public interface Folks.WebServiceDetails : Object
-{
- /**
- * A mapping of web service to an (unordered) set of web service addresses.
- *
- * Each mapping is from an arbitrary web service identifier to a set of web
- * service addresses for the contact, listed in no particular order.
- *
- * Web service addresses are guaranteed to be unique per web service, but
- * not necessarily unique amongst all web services.
- *
- * @since 0.6.0
- */
- public abstract
- Gee.MultiMap<string, WebServiceFieldDetails> web_service_addresses
- {
- get; set;
- }
-
- /**
- * Change the contact's web service addresses.
- *
- * It's preferred to call this rather than setting
- * {@link WebServiceDetails.web_service_addresses} directly, as this method
- * gives error notification and will only return once the addresses have been
- * written to the relevant backing store (or the operation's failed).
- *
- * @param web_service_addresses the set of addresses
- * @throws PropertyError if setting the addresses failed
- * @since 0.6.2
- */
- public virtual async void change_web_service_addresses (
- MultiMap<string, WebServiceFieldDetails> web_service_addresses)
- throws PropertyError
- {
- /* Default implementation. */
- throw new PropertyError.NOT_WRITEABLE (
- _("Web service addresses are not writeable on this contact."));
- }
-}
diff --git a/meson.build b/meson.build
index 6bdaae27..9669746c 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
project('folks', [ 'vala', 'c' ],
- version: '0.15.7',
+ version: '1.0.alpha',
license: 'LGPL-2.1-or-later',
meson_version: '>= 0.57',
)
@@ -11,8 +11,22 @@ pkgconfig = import('pkgconfig')
# Versions
#-------------------------------------------------
+version_arr = meson.project_version().split('-')[0].split('.')
+folks_version_major = version_arr[0].to_int()
+folks_version_minor = version_arr[1].to_int()
+
+if version_arr[2].startswith('alpha') or version_arr[2].startswith('beta') or version_arr[2].startswith('rc')
+ folks_version_micro = 0
+else
+ folks_version_micro = version_arr[2].to_int()
+endif
+
+# The so major version of the library
+folks_soversion = '1'
+folks_library_version = '1.@0@.@1@'.format(folks_version_minor, folks_version_micro)
+
# Core library API version (potentially used for parallel installation)
-folks_api_version = '0.7'
+folks_api_version = folks_version_major
# Core library version. When updating this, don't forget to bump the backend
# library versions too, below.
@@ -55,7 +69,6 @@ docs_enabled = get_option('docs')
eds_backend_enabled = get_option('eds_backend')
tests_enabled = get_option('tests')
installed_tests_enabled = get_option('installed_tests')
-ofono_backend_enabled = get_option('ofono_backend')
telepathy_backend_enabled = get_option('telepathy_backend')
zeitgeist_enabled = get_option('zeitgeist')
import_tool_enabled = get_option('import_tool')
@@ -105,10 +118,6 @@ if eds_backend_enabled
eds_sources_service_name, eds_address_book_service_name))
endif
-if ofono_backend_enabled
- libebook_dep = dependency('libebook-1.2', version: '>=' + min_eds_version)
-endif
-
if bluez_backend_enabled
libebook_dep = dependency('libebook-1.2', version: '>=' + min_eds_version)
if tests_enabled
@@ -140,31 +149,7 @@ if get_option('profiling')
add_project_arguments(['--vapidir', meson.current_source_dir() / 'vapi'], language: 'vala')
endif
-# Configuration
-#-------------------------------------------------
-conf = configuration_data()
-conf.set_quoted('GETTEXT_PACKAGE', meson.project_name())
-conf.set_quoted('LOCALE_DIR', folks_prefix / get_option('localedir'))
-conf.set_quoted('PACKAGE_NAME', meson.project_name())
-conf.set_quoted('PACKAGE_STRING', meson.project_name())
-conf.set_quoted('PACKAGE_VERSION', meson.project_version())
-conf.set_quoted('ABS_TOP_SRCDIR', meson.project_source_root())
-conf.set_quoted('ABS_TOP_BUILDDIR', meson.project_build_root())
-conf.set_quoted('INSTALLED_TESTS_DIR', installed_tests_dir)
-conf.set_quoted('INSTALLED_TESTS_META_DIR', folks_prefix / installed_tests_meta_dir)
-conf.set_quoted('BACKEND_DIR', folks_prefix / folks_backend_dir)
-conf.set10('HAVE_BLUEZ', bluez_backend_enabled)
-conf.set10('HAVE_EDS', eds_backend_enabled)
-conf.set10('HAVE_OFONO', ofono_backend_enabled)
-conf.set10('HAVE_TELEPATHY', telepathy_backend_enabled)
-if eds_backend_enabled
- conf.set_quoted('EDS_SOURCES_SERVICE_NAME', eds_sources_service_name)
- conf.set_quoted('EDS_ADDRESS_BOOK_SERVICE_NAME', eds_address_book_service_name)
-endif
-configure_file(output: 'config.h', configuration: conf)
-config_h_dir = include_directories('.')
-
-build_conf_dep = valac.find_library('build-conf', dirs: meson.current_source_dir() / 'folks')
+root_dir = include_directories('.')
# Vala args
add_project_arguments([
@@ -185,12 +170,6 @@ common_pkgconf_variables = [
'vapidir=${datarootdir}' / 'vala' / 'vapi',
]
-# Post-install scripts
-#-------------------------------------------------
-gnome.post_install(
- glib_compile_schemas: true,
-)
-
# Subdirectories
#-------------------------------------------------
@@ -203,7 +182,7 @@ subdir('folks')
# This will be filed with the backends' build targets (_not_ the libraries)
folks_backends = []
-subdir('backends')
+#subdir('backends')
folks_backend_paths = [] # Add the backend's paths
foreach backend : folks_backends
@@ -220,9 +199,11 @@ subdir('po')
# Tests
if tests_enabled
- subdir('tests')
+# subdir('tests')
endif
if docs_enabled
subdir('docs')
endif
+
+subproject('folks-daemon') \ No newline at end of file
diff --git a/meson_options.txt b/meson_options.txt
index 361f30cf..f4382fa9 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,7 +1,6 @@
# Backends
option('bluez_backend', type: 'boolean', value: true, description: 'build the Bluez backend')
option('eds_backend', type: 'boolean', value: true, description: 'build the E-D-S backend')
-option('ofono_backend', type: 'boolean', value: true, description: 'build the oFono backend')
option('telepathy_backend', type: 'boolean', value: true, description: 'build the Telepathy backend')
option('zeitgeist', type: 'boolean', value: false, description: 'build Zeitgeist support in the Telepathy backend')
# Tools
diff --git a/subprojects/folks-daemon/meson.build b/subprojects/folks-daemon/meson.build
new file mode 100644
index 00000000..551c5b65
--- /dev/null
+++ b/subprojects/folks-daemon/meson.build
@@ -0,0 +1,23 @@
+project('folks-daemon', 'c',
+ version: '1.0.alpha',
+ meson_version: '>= 0.59.0',
+ default_options: [ 'warning_level=2', 'c_std=gnu11' ],
+)
+
+gnome = import('gnome')
+
+config_dic = configuration_data()
+config_dic.set_quoted('PACKAGE_VERSION', meson.project_version())
+config_dic.set10('HAS_BLUEZ', 'bluez' in get_option('backends'))
+config_dic.set10('HAS_EDS', 'eds' in get_option('backends'))
+config_h = configure_file(
+ output: 'folksd-config.h',
+ configuration: config_dic,
+)
+config_dep = declare_dependency(
+ sources: config_h,
+ include_directories: include_directories('.'),
+)
+
+subdir('src')
+
diff --git a/subprojects/folks-daemon/meson_options.txt b/subprojects/folks-daemon/meson_options.txt
new file mode 100644
index 00000000..51ce3cb2
--- /dev/null
+++ b/subprojects/folks-daemon/meson_options.txt
@@ -0,0 +1,3 @@
+option('backends', type: 'array', choices : ['bluez', 'eds'], value : ['bluez', 'eds'], description: 'list of backends to build')
+# Profiling
+option('profiling', type: 'boolean', value: false, description: 'Enable profiling using sysprof') \ No newline at end of file
diff --git a/subprojects/folks-daemon/src/bluez/folksd-bluez-device-handler.c b/subprojects/folks-daemon/src/bluez/folksd-bluez-device-handler.c
new file mode 100644
index 00000000..ecaf9825
--- /dev/null
+++ b/subprojects/folks-daemon/src/bluez/folksd-bluez-device-handler.c
@@ -0,0 +1,309 @@
+#include "folksd-bluez-device-handler.h"
+
+#include <gio/gio.h>
+
+#include "folksd-bluez-obex-transfer.h"
+#include "folksd-bluez-obex-phonebook-access.h"
+
+struct _FolksdBluezDeviceHandler
+{
+ GObject parent_instance;
+
+ FolksdBluezDevice1 *device;
+ FolksdBluezObexClient1 *obex_client;
+ GCancellable *cancellable;
+};
+
+G_DEFINE_FINAL_TYPE (FolksdBluezDeviceHandler, folksd_bluez_device_handler, G_TYPE_OBJECT)
+
+enum {
+ PROP_DEVICE = 1,
+ PROP_OBEX_CLIENT,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+FolksdBluezDeviceHandler *
+folksd_bluez_device_handler_new (FolksdBluezDevice1 *dev,
+ FolksdBluezObexClient1 *obex_client)
+{
+ return g_object_new (FOLKSD_TYPE_BLUEZ_DEVICE_HANDLER,
+ "device", dev,
+ "obex-client", obex_client,
+ NULL);
+}
+
+static void
+folksd_bluez_device_handler_dispose (GObject *object)
+{
+ FolksdBluezDeviceHandler *self = (FolksdBluezDeviceHandler *)object;
+
+ if (self->cancellable)
+ g_cancellable_cancel (self->cancellable);
+
+ g_clear_object (&self->cancellable);
+
+ G_OBJECT_CLASS (folksd_bluez_device_handler_parent_class)->dispose (object);
+}
+
+static void
+folksd_bluez_device_handler_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ FolksdBluezDeviceHandler *self = FOLKSD_BLUEZ_DEVICE_HANDLER (object);
+
+ switch (prop_id)
+ {
+ case PROP_DEVICE:
+ g_value_set_object (value, self->device);
+ break;
+ case PROP_OBEX_CLIENT:
+ g_value_set_object (value, self->obex_client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+folksd_bluez_device_handler_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ FolksdBluezDeviceHandler *self = FOLKSD_BLUEZ_DEVICE_HANDLER (object);
+
+ switch (prop_id)
+ {
+ case PROP_DEVICE:
+ g_assert (!self->device);
+ self->device = g_value_dup_object (value);
+ break;
+ case PROP_OBEX_CLIENT:
+ g_assert (!self->obex_client);
+ self->obex_client = g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+folksd_bluez_device_handler_class_init (FolksdBluezDeviceHandlerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = folksd_bluez_device_handler_dispose;
+ object_class->get_property = folksd_bluez_device_handler_get_property;
+ object_class->set_property = folksd_bluez_device_handler_set_property;
+
+ properties[PROP_DEVICE] =
+ g_param_spec_object ("device",
+ "Device",
+ "Bluez Device",
+ FOLKSD_BLUEZ_TYPE_DEVICE1,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_OBEX_CLIENT] =
+ g_param_spec_object ("obex-client",
+ "Obex Client",
+ "Bluez Obex Client",
+ FOLKSD_BLUEZ_OBEX_TYPE_CLIENT1,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class,
+ N_PROPS,
+ properties);
+}
+
+static void
+folksd_bluez_device_handler_init (G_GNUC_UNUSED FolksdBluezDeviceHandler *self)
+{
+
+}
+
+void
+on_transfer_properties_changed (GDBusProxy *proxy,
+ G_GNUC_UNUSED GVariant *changed_properties,
+ G_GNUC_UNUSED GStrv invalidated_properties,
+ gpointer user_data)
+{
+ GTask *task = user_data;
+ FolksdBluezDeviceHandler *self = g_task_get_source_object (task);
+ FolksdBluezObexTransfer1 *obex_transfer = FOLKSD_BLUEZ_OBEX_TRANSFER1 (proxy);
+ const char *status;
+
+ status = folksd_bluez_obex_transfer1_get_status (obex_transfer);
+ g_critical ("CHANGED: %s", folksd_bluez_obex_transfer1_get_status (obex_transfer));
+ if (g_task_return_error_if_cancelled (task)) {
+ g_signal_handlers_disconnect_by_func (obex_transfer, G_CALLBACK (on_transfer_properties_changed), user_data);
+ g_task_set_task_data (task, NULL, NULL);
+ }
+
+ if (!g_strcmp0 (status, "error")) {
+
+ g_signal_handlers_disconnect_by_func (obex_transfer, G_CALLBACK (on_transfer_properties_changed), user_data);
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Error during transfer of the address book ‘%s’ from Bluetooth device ‘%s’.",
+ folksd_bluez_obex_transfer1_get_name (obex_transfer),
+ folksd_bluez_device1_get_alias (self->device));
+ g_task_set_task_data (task, NULL, NULL);
+ } else if (!g_strcmp0 (status, "complete")) {
+ const char *filename;
+
+ g_signal_handlers_disconnect_by_func (obex_transfer, G_CALLBACK (on_transfer_properties_changed), user_data);
+
+ filename = folksd_bluez_obex_transfer1_get_filename (obex_transfer);
+ if (!filename) {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Error during transfer of the address book ‘%s’ from Bluetooth device ‘%s’.",
+ folksd_bluez_obex_transfer1_get_name (obex_transfer),
+ folksd_bluez_device1_get_alias (self->device));
+ } else {
+ g_task_return_pointer (task, g_file_new_for_path (filename), g_object_unref);
+ }
+
+ g_task_set_task_data (task, NULL, NULL);
+ }
+}
+
+static void
+update_contacts_thread (GTask *task,
+ gpointer source_object,
+ G_GNUC_UNUSED gpointer task_data,
+ GCancellable *cancellable)
+{
+ FolksdBluezDeviceHandler *self = source_object;
+ GVariantDict args;
+ g_autofree char *session_path = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(FolksdBluezObexPhonebookAccess1) obex_pbap = NULL;
+ g_autofree char *transfer_path = NULL;
+ g_autoptr(GVariant) properties = NULL;
+ g_autoptr(FolksdBluezObexTransfer1) obex_transfer = NULL;
+ g_autoptr(GMainLoop) main_loop = NULL;
+
+ g_assert (FOLKSD_IS_BLUEZ_DEVICE_HANDLER (self));
+
+ g_debug ("Creating a new OBEX session.");
+
+ g_variant_dict_init (&args, NULL);
+ g_variant_dict_insert (&args, "Target", "s", "PBAP");
+
+ if (!folksd_bluez_obex_client1_call_create_session_sync (self->obex_client,
+ folksd_bluez_device1_get_address (self->device),
+ g_variant_dict_end (&args),
+ &session_path,
+ cancellable,
+ &error)) {
+ g_prefix_error (&error, "Failed to create session: ");
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ g_debug (" Got OBEX session path: %s", session_path);
+ obex_pbap = folksd_bluez_obex_phonebook_access1_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ "org.bluez.obex",
+ session_path,
+ cancellable,
+ &error);
+ if (!obex_pbap) {
+ g_prefix_error (&error, "Failed to get OBEX PBAB: ");
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ g_debug (" Got OBEX PBAP proxy");
+ /* Select the phonebook object we want to download ie:
+ * PB: phonebook for the saved contacts */
+ if (!folksd_bluez_obex_phonebook_access1_call_select_sync (obex_pbap, "int", "PB", cancellable, &error)){
+ g_prefix_error (&error, "Failed to call select: ");
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ /* Initiate a phone book transfer from the PSE server using a
+ * plain string vCard format, transferring to a temporary file. */
+ g_variant_dict_init (&args, NULL);
+ g_variant_dict_insert (&args, "Format", "s", "Vcard30");
+ const char *fields[] = { "UID", "N", "FN", "NICKNAME", "TEL", "URL", "EMAIL", "PHOTO", NULL };
+ g_variant_dict_insert (&args, "Fields", "^as", fields);
+ if (!folksd_bluez_obex_phonebook_access1_call_pull_all_sync (obex_pbap, "",
+ g_variant_dict_end (&args),
+ &transfer_path,
+ &properties,
+ cancellable,
+ &error)) {
+ g_prefix_error (&error, "Failed to call pull all: ");
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ obex_transfer = folksd_bluez_obex_transfer1_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES,
+ "org.bluez.obex",
+ transfer_path,
+ cancellable,
+ &error);
+ if (!obex_transfer) {
+ g_prefix_error (&error, "Failed to get OBEX Transfer: ");
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ main_loop = g_main_loop_new (NULL, TRUE);
+ g_signal_connect (obex_transfer, "g-properties-changed", G_CALLBACK (on_transfer_properties_changed), task);
+ g_task_set_task_data (task, main_loop, (GDestroyNotify) g_main_loop_quit);
+ g_main_loop_run (main_loop);
+}
+
+void
+folksd_bluez_device_handler_update_contacts_async (FolksdBluezDeviceHandler *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (FOLKSD_IS_BLUEZ_DEVICE_HANDLER (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, folksd_bluez_device_handler_update_contacts_async);
+ g_task_run_in_thread (task, update_contacts_thread);
+}
+
+GFile *
+folksd_bluez_device_handler_update_contacts_finish (FolksdBluezDeviceHandler *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (FOLKSD_IS_BLUEZ_DEVICE_HANDLER (self), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+GCancellable *
+folksd_bluez_device_handler_get_cancellable (FolksdBluezDeviceHandler *self)
+{
+ g_return_val_if_fail (FOLKSD_IS_BLUEZ_DEVICE_HANDLER (self), NULL);
+
+ return self->cancellable;
+}
+
+FolksdBluezDevice1 *
+folksd_bluez_device_handler_get_device (FolksdBluezDeviceHandler *self)
+{
+ g_return_val_if_fail (FOLKSD_IS_BLUEZ_DEVICE_HANDLER (self), NULL);
+
+ return self->device;
+}
diff --git a/subprojects/folks-daemon/src/bluez/folksd-bluez-device-handler.h b/subprojects/folks-daemon/src/bluez/folksd-bluez-device-handler.h
new file mode 100644
index 00000000..33c13f07
--- /dev/null
+++ b/subprojects/folks-daemon/src/bluez/folksd-bluez-device-handler.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "folksd-bluez-device.h"
+#include "folksd-bluez-obex-client.h"
+
+G_BEGIN_DECLS
+
+#define FOLKSD_TYPE_BLUEZ_DEVICE_HANDLER (folksd_bluez_device_handler_get_type())
+
+G_DECLARE_FINAL_TYPE (FolksdBluezDeviceHandler, folksd_bluez_device_handler, FOLKSD, BLUEZ_DEVICE_HANDLER, GObject)
+
+FolksdBluezDeviceHandler *folksd_bluez_device_handler_new (FolksdBluezDevice1 *dev,
+ FolksdBluezObexClient1 *obex_client);
+
+GCancellable *folksd_bluez_device_handler_get_cancellable (FolksdBluezDeviceHandler *self);
+
+FolksdBluezDevice1 *folksd_bluez_device_handler_get_device (FolksdBluezDeviceHandler *self);
+
+void folksd_bluez_device_handler_update_contacts_async (FolksdBluezDeviceHandler *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GFile *folksd_bluez_device_handler_update_contacts_finish (FolksdBluezDeviceHandler *self,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
diff --git a/subprojects/folks-daemon/src/bluez/folksd-bluez-miner.c b/subprojects/folks-daemon/src/bluez/folksd-bluez-miner.c
new file mode 100644
index 00000000..b042a5c8
--- /dev/null
+++ b/subprojects/folks-daemon/src/bluez/folksd-bluez-miner.c
@@ -0,0 +1,325 @@
+#include "folksd-bluez-miner.h"
+
+#include <libebook-contacts/libebook-contacts.h>
+
+#include "folksd-bluez-device.h"
+#include "folksd-bluez-obex-client.h"
+#include "folksd-bluez-device-handler.h"
+#include "folksd-utils.h"
+
+struct _FolksdBluezMiner
+{
+ GObject parent_instance;
+
+ TrackerEndpointDBus *endpoint;
+ GCancellable *cancellable;
+ GPtrArray *handlers;
+ GDBusObjectManager *manager;
+ FolksdBluezObexClient1 *obex_client;
+};
+
+G_DEFINE_FINAL_TYPE (FolksdBluezMiner, folksd_bluez_miner, G_TYPE_OBJECT)
+
+enum {
+ PROP_TRACKER_ENDPOINT = 1,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+FolksdBluezMiner *
+folksd_bluez_miner_new (TrackerEndpointDBus *endpoint)
+{
+ return g_object_new (FOLKSD_TYPE_BLUEZ_MINER, "endpoint", endpoint, NULL);
+}
+
+static void
+folksd_bluez_miner_finalize (GObject *object)
+{
+ FolksdBluezMiner *self = (FolksdBluezMiner *)object;
+
+ if (self->cancellable)
+ g_cancellable_cancel (self->cancellable);
+
+ g_clear_object (&self->cancellable);
+ g_clear_object (&self->endpoint);
+ g_clear_object (&self->obex_client);
+ g_clear_object (&self->manager);
+
+ G_OBJECT_CLASS (folksd_bluez_miner_parent_class)->finalize (object);
+}
+
+static void
+folksd_bluez_miner_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ FolksdBluezMiner *self = FOLKSD_BLUEZ_MINER (object);
+
+ switch (prop_id)
+ {
+ case PROP_TRACKER_ENDPOINT:
+ g_value_set_object (value, self->endpoint);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+folksd_bluez_miner_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ FolksdBluezMiner *self = FOLKSD_BLUEZ_MINER (object);
+
+ switch (prop_id)
+ {
+ case PROP_TRACKER_ENDPOINT:
+ g_assert (!self->endpoint);
+ self->endpoint = g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+folksd_bluez_miner_class_init (FolksdBluezMinerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = folksd_bluez_miner_finalize;
+ object_class->get_property = folksd_bluez_miner_get_property;
+ object_class->set_property = folksd_bluez_miner_set_property;
+
+ properties[PROP_TRACKER_ENDPOINT] =
+ g_param_spec_object ("endpoint",
+ "Endpoint",
+ "Tracker DBus Endpoint",
+ TRACKER_TYPE_ENDPOINT_DBUS,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class,
+ N_PROPS,
+ properties);
+}
+
+static gboolean
+device_supports_pbap_pse (FolksdBluezDevice1 *device)
+{
+ g_auto(GStrv) uuids = NULL;
+
+ g_assert (FOLKSD_BLUEZ_IS_DEVICE1 (device));
+
+ uuids = folksd_bluez_device1_dup_uuids (device);
+ if (!uuids)
+ return FALSE;
+
+ for (int i = 0; uuids[i] != NULL; i++) {
+ /* Phonebook Access - PSE (Phone Book Server Equipment).
+ * 0x112F is the pse part. */
+ if (!g_strcmp0 (uuids[i], "0000112f-0000-1000-8000-00805f9b34fb")) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+on_device_contact_updated (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ FolksdBluezMiner *self = user_data;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GFile) file = NULL;
+ g_autoptr(GFileInputStream) istream = NULL;
+ g_autoptr(GDataInputStream) dataistream = NULL;
+ g_autoptr(GString) vcard_str = NULL;
+ g_autoptr(TrackerResource) addressbook_resource = NULL;
+ g_autoptr(TrackerBatch) batch = NULL;
+ TrackerSparqlConnection *connection;
+
+ file = folksd_bluez_device_handler_update_contacts_finish (FOLKSD_BLUEZ_DEVICE_HANDLER (source_object), res, &error);
+ if (!file) {
+ g_critical ("Error getting contacts: %s", error->message);
+ return;
+ }
+
+ istream = g_file_read (file, NULL, &error);
+ if (!istream) {
+ g_critical ("Error reading file: %s", error->message);
+ return;
+ }
+
+ addressbook_resource = folksd_utils_create_addressbook ("phone", "Phone Contacts");
+ dataistream = g_data_input_stream_new (G_INPUT_STREAM (istream));
+ vcard_str = g_string_new (NULL);
+ while (TRUE) {
+ g_autofree char *line = NULL;
+
+ line = g_data_input_stream_read_line (dataistream, NULL, NULL, &error);
+ if (line)
+ g_string_append_printf (vcard_str, "%s\n", line);
+
+ if ((!line || !g_ascii_strncasecmp (line, "END:VCARD", sizeof("END:VCARD") - 1)) && vcard_str->len > 0) {
+ EVCard *vcard;
+ vcard = e_vcard_new_from_string (vcard_str->str);
+ e_vcard_dump_structure (vcard);
+ g_object_unref (vcard);
+ g_string_truncate (vcard_str, 0);
+ }
+
+ if (!line)
+ break;
+ }
+
+ connection = tracker_endpoint_get_sparql_connection (TRACKER_ENDPOINT (self->endpoint));
+ batch = tracker_sparql_connection_create_batch (connection);
+ tracker_batch_add_resource (batch, NULL, addressbook_resource);
+
+ if (!tracker_batch_execute (batch, NULL, &error)) {
+ g_printerr ("Couldn't insert batch of resources: %s", error->message);
+ return;
+ }
+
+}
+
+static void
+on_dbus_object_added (FolksdBluezMiner *self,
+ GDBusObject *object,
+ G_GNUC_UNUSED GDBusObjectManager *manager)
+{
+ g_autoptr(FolksdBluezDevice1) dev = NULL;
+ g_autoptr(FolksdBluezDeviceHandler) handler = NULL;
+ const gchar *path;
+
+ dev = folksd_bluez_object_get_device1 (FOLKSD_BLUEZ_OBJECT (object));
+ if (!dev)
+ return;
+
+ path = g_dbus_object_get_object_path (object);
+ g_debug ("Adding device at path ‘%s’.", path);
+
+ if (!folksd_bluez_device1_get_paired (dev)) {
+ g_debug (" Device isn’t paired. Ignoring. Manually pair the device to start downloading contacts.");
+ return;
+ }
+
+ if (!folksd_bluez_device1_get_connected (dev)) {
+ g_debug (" Device is disconnected. Ignoring.");
+ return;
+ }
+
+ if (folksd_bluez_device1_get_blocked (dev)) {
+ g_debug (" Device is blocked. Ignoring.");
+ return;
+ }
+
+ if (!device_supports_pbap_pse (dev)) {
+ g_debug (" Doesn’t support PBAP PSE. Ignoring.");
+ return;
+ }
+
+ handler = folksd_bluez_device_handler_new (dev, self->obex_client);
+ folksd_bluez_device_handler_update_contacts_async (handler,
+ folksd_bluez_device_handler_get_cancellable (handler),
+ on_device_contact_updated,
+ self);
+
+
+ g_ptr_array_add (self->handlers, g_steal_pointer (&handler));
+}
+
+gboolean
+hander_matches_device (FolksdBluezDeviceHandler *a,
+ FolksdBluezDevice1 *b)
+{
+ return folksd_bluez_device_handler_get_device (a) == b;
+}
+
+static void
+on_dbus_object_removed (FolksdBluezMiner *self,
+ G_GNUC_UNUSED GDBusObject *object,
+ G_GNUC_UNUSED GDBusObjectManager *manager)
+{
+ g_autoptr(FolksdBluezDevice1) dev = NULL;
+ guint index_;
+
+ dev = folksd_bluez_object_get_device1 (FOLKSD_BLUEZ_OBJECT (object));
+ if (!dev)
+ return;
+
+ if (g_ptr_array_find_with_equal_func (self->handlers,
+ object,
+ (GEqualFunc) hander_matches_device,
+ &index_)) {
+ FolksdBluezDeviceHandler *handler = g_ptr_array_index (self->handlers, index_);
+ g_cancellable_cancel (folksd_bluez_device_handler_get_cancellable (handler));
+ g_ptr_array_remove_index_fast (self->handlers, index_);
+ }
+}
+
+static void
+on_bluez_client_proxy (G_GNUC_UNUSED GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ FolksdBluezMiner *self = user_data;
+ g_autoptr(GError) error = NULL;
+
+ self->obex_client = folksd_bluez_obex_client1_proxy_new_for_bus_finish (res, &error);
+ if (!self->obex_client) {
+ g_critical ("Error connecting to OBEX transfer daemon over D-Bus. Ensure BlueZ and obexd are installed. (%s)", error->message);
+ return;
+ }
+
+ g_signal_connect_swapped (self->manager, "object-added" , G_CALLBACK(on_dbus_object_added), self);
+ g_signal_connect_swapped (self->manager, "object-removed" , G_CALLBACK(on_dbus_object_removed), self);
+ g_autolist(GDBusObject) object_list = g_dbus_object_manager_get_objects (self->manager);
+ for (GList *l = object_list; l != NULL; l = l->next)
+ {
+ on_dbus_object_added (self, l->data, self->manager);
+ }
+}
+
+static void
+on_bluez_object_manager (G_GNUC_UNUSED GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ FolksdBluezMiner *self = user_data;
+ g_autoptr(GError) error = NULL;
+
+ self->manager = folksd_bluez_object_manager_client_new_for_bus_finish (res, &error);
+ if (!self->manager) {
+ g_critical ("No BlueZ 5 object manager running, so the BlueZ backend will be inactive. Either your BlueZ installation is too old (only version 5 is supported) or the service can’t be started. (%s)", error->message);
+ return;
+ }
+
+ folksd_bluez_obex_client1_proxy_new_for_bus (
+ G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION,
+ "org.bluez.obex", "/org/bluez/obex",
+ self->cancellable,
+ on_bluez_client_proxy,
+ self);
+}
+
+static void
+folksd_bluez_miner_init (FolksdBluezMiner *self)
+{
+ self->cancellable = g_cancellable_new ();
+ self->handlers = g_ptr_array_new_with_free_func (g_object_unref);
+ folksd_bluez_object_manager_client_new_for_bus (
+ G_BUS_TYPE_SYSTEM,
+ G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
+ "org.bluez", "/",
+ self->cancellable,
+ on_bluez_object_manager,
+ self);
+}
diff --git a/subprojects/folks-daemon/src/bluez/folksd-bluez-miner.h b/subprojects/folks-daemon/src/bluez/folksd-bluez-miner.h
new file mode 100644
index 00000000..3b48a9f5
--- /dev/null
+++ b/subprojects/folks-daemon/src/bluez/folksd-bluez-miner.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <glib-object.h>
+#include <libtracker-sparql/tracker-sparql.h>
+
+G_BEGIN_DECLS
+
+#define FOLKSD_TYPE_BLUEZ_MINER (folksd_bluez_miner_get_type())
+
+G_DECLARE_FINAL_TYPE (FolksdBluezMiner, folksd_bluez_miner, FOLKSD, BLUEZ_MINER, GObject)
+
+FolksdBluezMiner *folksd_bluez_miner_new (TrackerEndpointDBus *endpoint);
+
+G_END_DECLS
diff --git a/subprojects/folks-daemon/src/bluez/org.bluez.Device1.xml b/subprojects/folks-daemon/src/bluez/org.bluez.Device1.xml
new file mode 100644
index 00000000..2b8db69a
--- /dev/null
+++ b/subprojects/folks-daemon/src/bluez/org.bluez.Device1.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.bluez.Device1">
+ <property name="Address" type="s" access="read"/>
+ <property name="Name" type="s" access="read"/>
+ <property name="Alias" type="s" access="readwrite"/>
+ <property name="Class" type="u" access="read"/>
+ <property name="Appearance" type="q" access="read"/>
+ <property name="Icon" type="s" access="read"/>
+ <property name="Paired" type="b" access="read"/>
+ <property name="Trusted" type="b" access="readwrite"/>
+ <property name="Blocked" type="b" access="readwrite"/>
+ <property name="LegacyPairing" type="b" access="read"/>
+ <property name="RSSI" type="n" access="read"/>
+ <property name="Connected" type="b" access="read"/>
+ <property name="UUIDs" type="as" access="read"/>
+ <property name="Modalias" type="s" access="read"/>
+ <property name="Adapter" type="o" access="read"/>
+ <method name="Disconnect"/>
+ <method name="Connect"/>
+ <method name="ConnectProfile">
+ <arg name="UUID" type="s" direction="in"/>
+ </method>
+ <method name="DisconnectProfile">
+ <arg name="UUID" type="s" direction="in"/>
+ </method>
+ <method name="Pair"/>
+ <method name="CancelPairing"/>
+ </interface>
+</node>
diff --git a/subprojects/folks-daemon/src/bluez/org.bluez.obex.Client1.xml b/subprojects/folks-daemon/src/bluez/org.bluez.obex.Client1.xml
new file mode 100644
index 00000000..586ad903
--- /dev/null
+++ b/subprojects/folks-daemon/src/bluez/org.bluez.obex.Client1.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <!--
+ org.bluez.obex.Client1:
+ @short_description: Database to store permissions
+
+ The permission store can be used by portals to store permissions
+ that sandboxed applications have to various resources, such as
+ files outside the sandbox.
+
+ Since the resources managed by portals can be varied, the permission
+ store is fairly free-form: there can be multiple tables; resources are
+ identified by an ID, as are applications, and permissions are stored as
+ string arrays. None of these strings are interpreted by the permission
+ store in any way.
+
+ In addition, the permission store allows to associate extra data
+ (in the form of a GVariant) with each resource.
+
+ This document describes version 2 of the permission store interface.
+ -->
+
+ <interface name="org.bluez.obex.Client1">
+ <!--
+ CreateSession:
+ @destination: map from application ID to permissions
+ @args: A dictionary to hold optional or type-specific parameters.
+
+ Create a new OBEX session for the given remote address.
+
+ Typical parameters for @args that can be set in include the following:
+ * string "Target" : type of session to be created
+ * string "Source" : local address to be used
+ * byte "Channel"
+
+ The currently supported targets are the following:
+ "ftp"
+ "map"
+ "opp"
+ "pbap"
+ "sync"
+ -->
+ <method name="CreateSession">
+ <arg name="destination" type="s" direction="in"/>
+ <arg name="args" type="a{sv}" direction="in"/>
+ <arg name="session" type="o" direction="out"/>
+ </method>
+
+ <!--
+ RemoveSession:
+ @session: the path of the session
+
+ Unregister session and abort pending transfers.
+ -->
+ <method name="RemoveSession">
+ <arg name="session" type="o" direction="in"/>
+ </method>
+ </interface>
+
+</node>
diff --git a/subprojects/folks-daemon/src/bluez/org.bluez.obex.PhonebookAccess1.xml b/subprojects/folks-daemon/src/bluez/org.bluez.obex.PhonebookAccess1.xml
new file mode 100644
index 00000000..124e272d
--- /dev/null
+++ b/subprojects/folks-daemon/src/bluez/org.bluez.obex.PhonebookAccess1.xml
@@ -0,0 +1,45 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node>
+ <interface name="org.bluez.obex.PhonebookAccess1">
+ <property name="Folder" type="s" access="read"></property>
+ <property name="DatabaseIdentifier" type="s" access="read"></property>
+ <property name="PrimaryCounter" type="s" access="read"></property>
+ <property name="SecondaryCounter" type="s" access="read"></property>
+ <property name="FixedImageSize" type="b" access="read"></property>
+ <method name="Select">
+ <arg name="location" type="s" direction="in"/>
+ <arg name="phonebook" type="s" direction="in"/>
+ </method>
+ <method name="PullAll">
+ <arg name="targetfile" type="s" direction="in"/>
+ <arg name="filters" type="a{sv}" direction="in"/>
+ <arg name="transfer" type="o" direction="out"/>
+ <arg name="properties" type="a{sv}" direction="out"/>
+ </method>
+ <method name="Pull">
+ <arg name="vcard" type="s" direction="in"/>
+ <arg name="targetfile" type="s" direction="in"/>
+ <arg name="filters" type="a{sv}" direction="in"/>
+ <arg name="transfer" type="o" direction="out"/>
+ <arg name="properties" type="a{sv}" direction="out"/>
+ </method>
+ <method name="List">
+ <arg name="filters" type="a{sv}" direction="in"/>
+ <arg name="vcard_listing" type="a(ss)" direction="out"/>
+ </method>
+ <method name="Search">
+ <arg name="field" type="s" direction="in"/>
+ <arg name="value" type="s" direction="in"/>
+ <arg name="filters" type="a{sv}" direction="in"/>
+ <arg name="vcard_listing" type="a(ss)" direction="out"/>
+ </method>
+ <method name="GetSize">
+ <arg name="size" type="q" direction="out"/>
+ </method>
+ <method name="ListFilterFields">
+ <arg name="fields" type="as" direction="out"/>
+ </method>
+ <method name="UpdateVersion"></method>
+ </interface>
+</node>
diff --git a/subprojects/folks-daemon/src/bluez/org.bluez.obex.Transfer1.xml b/subprojects/folks-daemon/src/bluez/org.bluez.obex.Transfer1.xml
new file mode 100644
index 00000000..16fb322f
--- /dev/null
+++ b/subprojects/folks-daemon/src/bluez/org.bluez.obex.Transfer1.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.bluez.obex.Transfer1">
+ <property name="Status" type="s" access="read" />
+ <property name="Session" type="o" access="read" />
+ <property name="Name" type="s" access="read" />
+ <property name="Type" type="s" access="read" />
+ <property name="Time" type="t" access="read" />
+ <property name="Size" type="t" access="read" />
+ <property name="Transferred" type="t" access="read" />
+ <property name="Filename" type="s" access="read" />
+ <method name="Cancel" />
+ <method name="Suspend" />
+ <method name="Resume" />
+ </interface>
+</node>
diff --git a/subprojects/folks-daemon/src/eds/folksd-eds-miner.c b/subprojects/folks-daemon/src/eds/folksd-eds-miner.c
new file mode 100644
index 00000000..f91d4e11
--- /dev/null
+++ b/subprojects/folks-daemon/src/eds/folksd-eds-miner.c
@@ -0,0 +1,316 @@
+#include "folksd-eds-miner.h"
+
+#include <gio/gio.h>
+#include <libedataserver/libedataserver.h>
+#include <libebook/libebook.h>
+
+#include "missing-autocleanups/missing-autocleanups.h"
+#include "folksd-utils.h"
+
+struct _FolksdEdsMiner
+{
+ GObject parent_instance;
+
+ TrackerEndpointDBus *endpoint;
+ GCancellable *cancellable;
+ ESourceRegistry *source_registry;
+ ESourceRegistryWatcher *watcher;
+ GPtrArray *opened_client_views;
+};
+
+G_DEFINE_FINAL_TYPE (FolksdEdsMiner, folksd_eds_miner, G_TYPE_OBJECT)
+
+enum {
+ PROP_TRACKER_ENDPOINT = 1,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+FolksdEdsMiner *
+folksd_eds_miner_new (TrackerEndpointDBus *endpoint)
+{
+ return g_object_new (FOLKSD_TYPE_EDS_MINER, "endpoint", endpoint, NULL);
+}
+
+static void
+folksd_create_contact_resource (TrackerResource *addressbook,
+ EContact *contact)
+{
+ g_autoptr(EContactName) name = NULL;
+ g_autofree char *full_name = NULL;
+ g_autofree char *uri = NULL;
+ TrackerResource *resource;
+ const char *uid;
+ g_autofree char *affiliation_uri = NULL;
+ TrackerResource *affiliation;
+ GList *emails;
+ GList *tels;
+
+ g_assert(addressbook != NULL);
+ g_assert(contact != NULL);
+
+ name = e_contact_get (contact, E_CONTACT_NAME);
+ if (!name) {
+ g_warning ("No name field!");
+ return;
+ }
+
+ full_name = e_contact_name_to_string (name);
+ uri = tracker_sparql_escape_uri_printf ("urn:contact:%s", full_name);
+
+ resource = tracker_resource_new (uri);
+ tracker_resource_set_uri (resource, "rdf:type", "nco:PersonContact");
+
+ uid = e_contact_get_const (contact, E_CONTACT_UID);
+ tracker_resource_set_string (resource, "nco:contactUID", uid);
+
+ tracker_resource_set_string (resource, "nco:fullname", full_name);
+ if (name->given)
+ tracker_resource_set_string (resource, "nco:nameGiven", name->given);
+ if (name->family)
+ tracker_resource_set_string (resource, "nco:nameFamily", name->family);
+ if (name->additional)
+ tracker_resource_set_string (resource, "nco:nameAdditional", name->additional);
+ if (name->prefixes)
+ tracker_resource_set_string (resource, "nco:nameHonorificPrefix", name->prefixes);
+ if (name->suffixes)
+ tracker_resource_set_string (resource, "nco:nameHonorificSuffix", name->suffixes);
+
+ affiliation_uri = g_strdup_printf ("%s:affiliation", uri);
+ affiliation = tracker_resource_new (affiliation_uri);
+ tracker_resource_set_uri (affiliation, "rdf:type", "nco:Role");
+ emails = e_contact_get (contact, E_CONTACT_EMAIL);
+ for (GList *l = emails; l != NULL; l = l->next) {
+ g_autofree char *email = g_strdup_printf ("<mailto:%s>", (const char*) l->data);
+ tracker_resource_set_string (affiliation, "nco:hasEmailAddress", email);
+ }
+
+ g_clear_pointer (&emails, e_contact_attr_list_free);
+ tels = e_contact_get (contact, E_CONTACT_TEL);
+ for (GList *l = tels; l != NULL; l = l->next) {
+ g_autofree char *tel = g_strdup_printf ("<tel:%s>", (const char*) l->data);
+ tracker_resource_set_string (affiliation, "nco:hasPhoneNumber", tel);
+ }
+
+ g_clear_pointer (&tels, e_contact_attr_list_free);
+ tracker_resource_add_take_relation(resource, "nco:hasAffiliation", g_steal_pointer (&affiliation));
+
+ tracker_resource_add_take_relation(addressbook, "nco:containsContact", g_steal_pointer (&resource));
+}
+
+static void
+on_book_objects_added (FolksdEdsMiner *self,
+ GSList *contacts,
+ EBookClientView *client_view)
+{
+ g_autoptr(EBookClient) client = e_book_client_view_ref_client (client_view);
+ ESource *source = e_client_get_source (E_CLIENT(client));
+ g_autoptr(TrackerResource) addressbook_resource = folksd_utils_create_addressbook (e_source_get_uid (source), e_source_get_display_name (source));
+ TrackerSparqlConnection *connection;
+ g_autoptr(TrackerBatch) batch = NULL;
+ g_autoptr(GError) error = NULL;
+
+ for (GSList *l = contacts; l != NULL; l = l->next)
+ {
+ EContact* contact = l->data;
+ folksd_create_contact_resource (addressbook_resource, contact);
+ }
+
+ connection = tracker_endpoint_get_sparql_connection (TRACKER_ENDPOINT (self->endpoint));
+ batch = tracker_sparql_connection_create_batch (connection);
+ tracker_batch_add_resource (batch, NULL, addressbook_resource);
+
+ if (!tracker_batch_execute (batch, NULL, &error)) {
+ g_printerr ("Couldn't insert batch of resources: %s", error->message);
+ return;
+ }
+}
+
+static void
+on_book_objects_removed (G_GNUC_UNUSED FolksdEdsMiner *self,
+ GSList *contact_ids,
+ G_GNUC_UNUSED EBookClientView *client_view)
+{
+ for (GSList *l = contact_ids; l != NULL; l = l->next)
+ {
+ G_GNUC_UNUSED char* contact_id = l->data;
+ }
+}
+
+static void
+on_book_objects_modified (G_GNUC_UNUSED FolksdEdsMiner *self,
+ GSList *contacts,
+ G_GNUC_UNUSED EBookClientView *client_view)
+{
+ for (GSList *l = contacts; l != NULL; l = l->next)
+ {
+ G_GNUC_UNUSED EContact* contact = l->data;
+ }
+}
+
+static void
+on_book_client_view (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ FolksdEdsMiner *self = user_data;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(EBookClientView) view = NULL;
+
+ if (!e_book_client_get_view_finish (E_BOOK_CLIENT (source_object),
+ res,
+ &view,
+ &error))
+ {
+ g_critical ("Unable to query book client view: %s", error->message);
+ return;
+ }
+
+ g_signal_connect_object (view, "objects-added", G_CALLBACK (on_book_objects_added), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (view, "objects-removed", G_CALLBACK (on_book_objects_removed), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (view, "objects-modified", G_CALLBACK (on_book_objects_modified), self, G_CONNECT_SWAPPED);
+
+ e_book_client_view_start (view, &error);
+ g_ptr_array_add (self->opened_client_views, g_steal_pointer (&view));
+}
+
+static void
+on_book_client_connected (G_GNUC_UNUSED GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ FolksdEdsMiner *self = user_data;
+ g_autoptr(EClient) book_client = NULL;
+ g_autoptr(GError) error = NULL;
+
+ book_client = e_book_client_connect_finish (res, &error);
+ if (!book_client) {
+ g_critical ("Unable to open Book client: %s", error->message);
+ return;
+ }
+
+ e_book_client_get_view (E_BOOK_CLIENT(book_client),
+ "(contains \"x-evolution-any-field\" \"\")",
+ self->cancellable,
+ on_book_client_view,
+ self);
+}
+
+static void
+on_address_book_appeared (FolksdEdsMiner *self,
+ ESource *source,
+ G_GNUC_UNUSED ESourceRegistryWatcher *watcher)
+{
+ e_book_client_connect (source,
+ -1,
+ self->cancellable,
+ on_book_client_connected,
+ self);
+}
+
+static void
+on_address_book_disappeared (G_GNUC_UNUSED FolksdEdsMiner *self,
+ G_GNUC_UNUSED ESource *source,
+ G_GNUC_UNUSED ESourceRegistryWatcher *watcher)
+{
+}
+
+static void
+folksd_eds_miner_finalize (GObject *object)
+{
+ FolksdEdsMiner *self = (FolksdEdsMiner *)object;
+
+ if (self->cancellable)
+ g_cancellable_cancel (self->cancellable);
+
+ if (self->watcher) {
+ g_signal_handlers_disconnect_by_func (self->watcher, on_address_book_appeared, self);
+ g_signal_handlers_disconnect_by_func (self->watcher, on_address_book_disappeared, self);
+ }
+
+ g_clear_object (&self->cancellable);
+ g_clear_pointer (&self->opened_client_views, g_ptr_array_unref);
+ g_clear_object (&self->watcher);
+ g_clear_object (&self->source_registry);
+ g_clear_object (&self->endpoint);
+
+ G_OBJECT_CLASS (folksd_eds_miner_parent_class)->finalize (object);
+}
+
+static void
+folksd_eds_miner_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ FolksdEdsMiner *self = FOLKSD_EDS_MINER (object);
+
+ switch (prop_id)
+ {
+ case PROP_TRACKER_ENDPOINT:
+ g_value_set_object (value, self->endpoint);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+folksd_eds_miner_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ FolksdEdsMiner *self = FOLKSD_EDS_MINER (object);
+
+ switch (prop_id)
+ {
+ case PROP_TRACKER_ENDPOINT:
+ g_assert (!self->endpoint);
+ self->endpoint = g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+folksd_eds_miner_class_init (FolksdEdsMinerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = folksd_eds_miner_finalize;
+ object_class->get_property = folksd_eds_miner_get_property;
+ object_class->set_property = folksd_eds_miner_set_property;
+
+ properties[PROP_TRACKER_ENDPOINT] =
+ g_param_spec_object ("endpoint",
+ "Endpoint",
+ "Tracker DBus Endpoint",
+ TRACKER_TYPE_ENDPOINT_DBUS,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class,
+ N_PROPS,
+ properties);
+}
+
+static void
+folksd_eds_miner_init (FolksdEdsMiner *self)
+{
+ g_autoptr(GError) error = NULL;
+
+ self->cancellable = g_cancellable_new ();
+ self->opened_client_views = g_ptr_array_new_with_free_func (g_object_unref);
+ self->source_registry = e_source_registry_new_sync (self->cancellable, &error);
+ if (G_UNLIKELY (!self->source_registry)) {
+ g_debug ("Unable to contact Evolution Data Server: %s", error->message);
+ return;
+ }
+
+ self->watcher = e_source_registry_watcher_new (self->source_registry, E_SOURCE_EXTENSION_ADDRESS_BOOK);
+ g_signal_connect_swapped (self->watcher, "appeared", G_CALLBACK (on_address_book_appeared), self);
+ g_signal_connect_swapped (self->watcher, "disappeared", G_CALLBACK (on_address_book_disappeared), self);
+ e_source_registry_watcher_reclaim (self->watcher);
+}
diff --git a/subprojects/folks-daemon/src/eds/folksd-eds-miner.h b/subprojects/folks-daemon/src/eds/folksd-eds-miner.h
new file mode 100644
index 00000000..6618b46a
--- /dev/null
+++ b/subprojects/folks-daemon/src/eds/folksd-eds-miner.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <glib-object.h>
+#include <libtracker-sparql/tracker-sparql.h>
+
+G_BEGIN_DECLS
+
+#define FOLKSD_TYPE_EDS_MINER (folksd_eds_miner_get_type())
+
+G_DECLARE_FINAL_TYPE (FolksdEdsMiner, folksd_eds_miner, FOLKSD, EDS_MINER, GObject)
+
+FolksdEdsMiner *folksd_eds_miner_new (TrackerEndpointDBus *endpoint);
+
+G_END_DECLS
diff --git a/subprojects/folks-daemon/src/eds/missing-autocleanups/e-book-autocleanups.h b/subprojects/folks-daemon/src/eds/missing-autocleanups/e-book-autocleanups.h
new file mode 100644
index 00000000..a121b299
--- /dev/null
+++ b/subprojects/folks-daemon/src/eds/missing-autocleanups/e-book-autocleanups.h
@@ -0,0 +1,31 @@
+/*
+ * e-book-autocleanups.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __E_BOOK_AUTOCLEANUPS_H__
+#define __E_BOOK_AUTOCLEANUPS_H__
+
+#ifndef __GI_SCANNER__
+#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EBookClient, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EBookClientCursor, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EBookClientView, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EDestination, g_object_unref)
+
+#endif /* G_DEFINE_AUTOPTR_CLEANUP_FUNC */
+#endif /* !__GI_SCANNER__ */
+#endif /* __E_BOOK_AUTOCLEANUPS_H__ */
diff --git a/subprojects/folks-daemon/src/eds/missing-autocleanups/e-book-contacts-autocleanups.h b/subprojects/folks-daemon/src/eds/missing-autocleanups/e-book-contacts-autocleanups.h
new file mode 100644
index 00000000..6751cc1e
--- /dev/null
+++ b/subprojects/folks-daemon/src/eds/missing-autocleanups/e-book-contacts-autocleanups.h
@@ -0,0 +1,42 @@
+/*
+ * e-book-contacts-autocleanups.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __E_BOOK_CONTACTS_AUTOCLEANUPS_H__
+#define __E_BOOK_CONTACTS_AUTOCLEANUPS_H__
+
+#ifndef __GI_SCANNER__
+#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EAddressWestern, e_address_western_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EBookQuery, e_book_query_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EContact, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EContactAddress, e_contact_address_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EContactCert, e_contact_cert_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EContactDate, e_contact_date_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EContactGeo, e_contact_geo_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EContactName, e_contact_name_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EContactPhoto, e_contact_photo_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(ENameWestern, e_name_western_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EPhoneNumber, e_phone_number_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(ESourceBackendSummarySetup, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVCard, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVCardAttribute, e_vcard_attribute_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVCardAttributeParam, e_vcard_attribute_param_free)
+
+#endif /* G_DEFINE_AUTOPTR_CLEANUP_FUNC */
+#endif /* !__GI_SCANNER__ */
+#endif /* __E_BOOK_CONTACTS_AUTOCLEANUPS_H__ */
diff --git a/subprojects/folks-daemon/src/eds/missing-autocleanups/missing-autocleanups.h b/subprojects/folks-daemon/src/eds/missing-autocleanups/missing-autocleanups.h
new file mode 100644
index 00000000..7b66f242
--- /dev/null
+++ b/subprojects/folks-daemon/src/eds/missing-autocleanups/missing-autocleanups.h
@@ -0,0 +1,7 @@
+#ifdef LIBEBOOK_CONTACTS_H
+#include "missing-autocleanups/e-book-contacts-autocleanups.h"
+#endif
+
+#ifdef LIBEBOOK_H
+#include "missing-autocleanups/e-book-autocleanups.h"
+#endif
diff --git a/subprojects/folks-daemon/src/folksd-contacts-miner.c b/subprojects/folks-daemon/src/folksd-contacts-miner.c
new file mode 100644
index 00000000..99b63dc5
--- /dev/null
+++ b/subprojects/folks-daemon/src/folksd-contacts-miner.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2022 Corentin Noël <corentin.noel@collabora.com>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "folksd-contacts-miner.h"
+#include "folksd-utils.h"
+#include "folksd-config.h"
+#if HAS_BLUEZ
+#include "folksd-bluez-miner.h"
+#endif
+#if HAS_EDS
+#include "folksd-eds-miner.h"
+#endif
+
+struct _EdmContactsMiner
+{
+ GObject parent_instance;
+
+ GDBusConnection *connection;
+ TrackerEndpointDBus *endpoint;
+ GCancellable *cancellable;
+#if HAS_BLUEZ
+ FolksdBluezMiner *bluez_miner;
+#endif
+#if HAS_EDS
+ FolksdEdsMiner *eds_miner;
+#endif
+};
+
+G_DEFINE_FINAL_TYPE (EdmContactsMiner, edm_contacts_miner, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+EdmContactsMiner *
+edm_contacts_miner_new (void)
+{
+ return g_object_new (EDM_TYPE_CONTACTS_MINER, NULL);
+}
+
+static void
+edm_contacts_miner_dispose (GObject *object)
+{
+ EdmContactsMiner *self = (EdmContactsMiner *)object;
+
+#if HAS_BLUEZ
+ g_clear_object (&self->bluez_miner);
+#endif
+#if HAS_EDS
+ g_clear_object (&self->eds_miner);
+#endif
+
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ g_clear_object (&self->endpoint);
+ g_clear_object (&self->connection);
+
+ G_OBJECT_CLASS (edm_contacts_miner_parent_class)->dispose (object);
+}
+
+static void
+edm_contacts_miner_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EdmContactsMiner *self = EDM_CONTACTS_MINER (object);
+
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+edm_contacts_miner_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EdmContactsMiner *self = EDM_CONTACTS_MINER (object);
+
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+edm_contacts_miner_class_init (EdmContactsMinerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = edm_contacts_miner_dispose;
+ object_class->get_property = edm_contacts_miner_get_property;
+ object_class->set_property = edm_contacts_miner_set_property;
+}
+
+
+gboolean
+tracker_dbus_request_name (GDBusConnection *connection,
+ const gchar *name,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ GVariant *reply;
+ gint rval;
+
+ reply = g_dbus_connection_call_sync (connection,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "RequestName",
+ g_variant_new ("(su)",
+ name,
+ 0x4 /* DBUS_NAME_FLAG_DO_NOT_QUEUE */),
+ G_VARIANT_TYPE ("(u)"),
+ 0, -1, NULL, &inner_error);
+ if (inner_error) {
+ g_propagate_prefixed_error (error, inner_error,
+ "Could not acquire name:'%s'. ",
+ name);
+ return FALSE;
+ }
+
+ g_variant_get (reply, "(u)", &rval);
+ g_variant_unref (reply);
+
+ if (rval != 1 /* DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER */) {
+ g_set_error (error,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_ADDRESS_IN_USE,
+ "D-Bus service name:'%s' is already taken, "
+ "perhaps the application is already running?",
+ name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+static void
+edm_contacts_miner_init (EdmContactsMiner *self)
+{
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GFile) store = NULL;
+ g_autoptr(GFile) ontology = NULL;
+ g_autoptr(TrackerSparqlConnection) sparql_conn = NULL;
+
+ self->cancellable = g_cancellable_new ();
+
+ self->connection = g_bus_get_sync (TRACKER_IPC_BUS, self->cancellable, &error);
+ if (error) {
+ g_critical ("Could not create DBus connection: %s", error->message);
+ return;
+ }
+
+ store = g_file_new_build_filename (g_get_user_cache_dir (), "tracker3", "folks", NULL);
+ ontology = tracker_sparql_get_ontology_nepomuk ();
+ sparql_conn = tracker_sparql_connection_new (TRACKER_SPARQL_CONNECTION_FLAGS_NONE,
+ store,
+ ontology,
+ self->cancellable,
+ &error);
+ if (!sparql_conn) {
+ g_critical ("Unable to create SPARQL connection: %s", error->message);
+ return;
+ }
+
+ self->endpoint = tracker_endpoint_dbus_new (sparql_conn,
+ self->connection,
+ NULL,
+ self->cancellable,
+ &error);
+ if (!self->endpoint) {
+ g_critical ("Unable to create endpoint: %s", error->message);
+ return;
+ }
+
+ if (!tracker_dbus_request_name (self->connection, "org.freedesktop.Tracker3.Miner.Folks", &error)) {
+ g_critical ("Unable to own name: %s", error->message);
+ return;
+ }
+
+#if HAS_BLUEZ
+ self->bluez_miner = folksd_bluez_miner_new (self->endpoint);
+#endif
+#if HAS_EDS
+ self->eds_miner = folksd_eds_miner_new (self->endpoint);
+#endif
+}
+
diff --git a/subprojects/folks-daemon/src/folksd-contacts-miner.h b/subprojects/folks-daemon/src/folksd-contacts-miner.h
new file mode 100644
index 00000000..b8015d27
--- /dev/null
+++ b/subprojects/folks-daemon/src/folksd-contacts-miner.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2022 Corentin Noël <corentin.noel@collabora.com>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EDM_TYPE_CONTACTS_MINER (edm_contacts_miner_get_type())
+
+G_DECLARE_FINAL_TYPE (EdmContactsMiner, edm_contacts_miner, EDM, CONTACTS_MINER, GObject)
+
+EdmContactsMiner *edm_contacts_miner_new (void);
+
+G_END_DECLS
diff --git a/subprojects/folks-daemon/src/folksd-utils.c b/subprojects/folks-daemon/src/folksd-utils.c
new file mode 100644
index 00000000..5a4be02e
--- /dev/null
+++ b/subprojects/folks-daemon/src/folksd-utils.c
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2022 Corentin Noël <corentin.noel@collabora.com>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "folksd-utils.h"
+
+/**
+ * folksd_utils_create_addressbook:
+ * @uid: Unique ID
+ * @display_name: (nullable): Display name of the Addressbook
+ *
+ * Creates a new addressbook with the given @uid and @display_name.
+ *
+ * Returns: (transfer full): a #TrackerResource
+ */
+TrackerResource *
+folksd_utils_create_addressbook (const char *uid,
+ const char *display_name)
+{
+ g_autofree char *uri = NULL;
+ TrackerResource *resource;
+
+ g_return_val_if_fail (uid != NULL, NULL);
+
+ uri = tracker_sparql_escape_uri_printf ("urn:addressbook:%s", uid);
+ resource = tracker_resource_new (uri);
+ tracker_resource_set_uri (resource, "rdf:type", "nco:ContactList");
+ if (display_name)
+ tracker_resource_set_string (resource, "nie:title", display_name);
+
+ return resource;
+}
diff --git a/subprojects/folks-daemon/src/folksd-utils.h b/subprojects/folks-daemon/src/folksd-utils.h
new file mode 100644
index 00000000..e1eed072
--- /dev/null
+++ b/subprojects/folks-daemon/src/folksd-utils.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2022 Corentin Noël <corentin.noel@collabora.com>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <libtracker-sparql/tracker-sparql.h>
+
+#define TRACKER_IPC_BUS tracker_ipc_bus()
+
+static inline GBusType
+tracker_ipc_bus (void)
+{
+ const char *bus = g_getenv ("TRACKER_BUS_TYPE");
+
+ if (G_UNLIKELY (bus != NULL &&
+ g_ascii_strcasecmp (bus, "system") == 0)) {
+ return G_BUS_TYPE_SYSTEM;
+ }
+
+ return G_BUS_TYPE_SESSION;
+}
+
+TrackerResource *
+folksd_utils_create_addressbook (const char *uid,
+ const char *display_name);
diff --git a/subprojects/folks-daemon/src/main.c b/subprojects/folks-daemon/src/main.c
new file mode 100644
index 00000000..334f8b90
--- /dev/null
+++ b/subprojects/folks-daemon/src/main.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2022 Corentin Noël <corentin.noel@collabora.com>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "folksd-config.h"
+
+#include <glib.h>
+#include <stdlib.h>
+
+#include "folksd-contacts-miner.h"
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_autoptr(GOptionContext) context = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(EdmContactsMiner) contacts_miner = NULL;
+ gboolean version = FALSE;
+ GOptionEntry main_entries[] = {
+ { "version", 0, 0, G_OPTION_ARG_NONE, &version, "Show program version", NULL },
+ G_OPTION_ENTRY_NULL
+ };
+
+ context = g_option_context_new ("- my command line tool");
+ g_option_context_add_main_entries (context, main_entries, NULL);
+
+ if (!g_option_context_parse (context, &argc, &argv, &error))
+ {
+ g_printerr ("%s\n", error->message);
+ return EXIT_FAILURE;
+ }
+
+ if (version)
+ {
+ g_printerr ("%s\n", PACKAGE_VERSION);
+ return EXIT_SUCCESS;
+ }
+
+ g_autoptr(GMainLoop) main_loop = g_main_loop_new (NULL, TRUE);
+ contacts_miner = edm_contacts_miner_new ();
+ g_main_loop_run (main_loop);
+ return EXIT_SUCCESS;
+}
diff --git a/subprojects/folks-daemon/src/meson.build b/subprojects/folks-daemon/src/meson.build
new file mode 100644
index 00000000..7dcd8629
--- /dev/null
+++ b/subprojects/folks-daemon/src/meson.build
@@ -0,0 +1,67 @@
+folks_daemon_dirs = [
+ include_directories('.'),
+]
+
+folks_daemon_sources = [
+ 'main.c',
+ 'folksd-contacts-miner.c',
+ 'folksd-utils.c',
+]
+
+folks_daemon_deps = [
+ dependency('glib-2.0', version: '>=2.70'),
+ dependency('gobject-2.0'),
+ dependency('gio-2.0'),
+ dependency('tracker-sparql-3.0'),
+]
+
+if 'bluez' in get_option('backends')
+ folks_daemon_sources += [
+ 'bluez/folksd-bluez-miner.c',
+ 'bluez/folksd-bluez-device-handler.c',
+ ]
+ folks_daemon_sources += gnome.gdbus_codegen('folksd-bluez-device',
+ sources: 'bluez/org.bluez.Device1.xml',
+ interface_prefix : 'org.bluez.',
+ namespace : 'FolksdBluez',
+ object_manager: true,
+ )
+ folks_daemon_sources += gnome.gdbus_codegen('folksd-bluez-obex-client',
+ sources: 'bluez/org.bluez.obex.Client1.xml',
+ interface_prefix : 'org.bluez.obex.',
+ namespace : 'FolksdBluezObex',
+ )
+ folks_daemon_sources += gnome.gdbus_codegen('folksd-bluez-obex-transfer',
+ sources: 'bluez/org.bluez.obex.Transfer1.xml',
+ interface_prefix : 'org.bluez.obex.',
+ namespace : 'FolksdBluezObex',
+ )
+ folks_daemon_sources += gnome.gdbus_codegen('folksd-bluez-obex-phonebook-access',
+ sources: 'bluez/org.bluez.obex.PhonebookAccess1.xml',
+ interface_prefix : 'org.bluez.obex.',
+ namespace : 'FolksdBluezObex',
+ )
+ folks_daemon_deps += [
+ dependency('libebook-contacts-1.2'),
+ ]
+ folks_daemon_dirs += include_directories('./bluez')
+endif
+
+if 'eds' in get_option('backends')
+ folks_daemon_sources += [
+ 'eds/folksd-eds-miner.c',
+ ]
+ folks_daemon_deps += [
+ dependency('libedataserver-1.2'),
+ dependency('libebook-contacts-1.2'),
+ dependency('libebook-1.2'),
+ ]
+ folks_daemon_dirs += include_directories('./eds')
+endif
+
+executable(meson.project_name(),
+ folks_daemon_sources,
+ dependencies: [ folks_daemon_deps, config_dep ],
+ include_directories: folks_daemon_dirs,
+ install: true,
+)
diff --git a/tools/import-pidgin.vala b/tools/import-pidgin.vala
deleted file mode 100644
index 0ea729b5..00000000
--- a/tools/import-pidgin.vala
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- * Copyright (C) 2013 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-using Xml;
-using Folks;
-
-public class Folks.Importers.Pidgin : Folks.Importer
-{
- private PersonaStore destination_store;
- private uint persona_count = 0;
-
- public override async uint import (PersonaStore destination_store,
- string? source_filename) throws ImportError
- {
- this.destination_store = destination_store;
- string filename = source_filename;
-
- /* Default filename */
- if (filename == null || filename.strip () == "")
- {
- filename = Path.build_filename (Environment.get_home_dir (),
- ".purple", "blist.xml", null);
- }
-
- var file = File.new_for_path (filename);
- if (!file.query_exists ())
- {
- /* Translators: the parameter is a filename. */
- throw new ImportError.MALFORMED_INPUT (_("File %s does not exist."),
- filename);
- }
-
- FileInfo file_info;
- try
- {
- file_info = yield file.query_info_async (
- FileAttribute.ACCESS_CAN_READ, FileQueryInfoFlags.NONE,
- Priority.DEFAULT);
- }
- catch (GLib.Error e)
- {
- throw new ImportError.MALFORMED_INPUT (
- /* Translators: the first parameter is a filename, and the second
- * is an error message. */
- _("Failed to get information about file %s: %s"), filename,
- e.message);
- }
-
- if (!file_info.get_attribute_boolean (FileAttribute.ACCESS_CAN_READ))
- {
- /* Translators: the parameter is a filename. */
- throw new ImportError.MALFORMED_INPUT (_("File %s is not readable."),
- filename);
- }
-
- Xml.Doc* xml_doc = Parser.parse_file (filename);
-
- if (xml_doc == null)
- {
- throw new ImportError.MALFORMED_INPUT (
- /* Translators: the parameter is a filename. */
- _("The Pidgin buddy list file ‘%s’ could not be loaded."),
- filename);
- }
-
- /* Check the root node */
- Xml.Node *root_node = xml_doc->get_root_element ();
-
- if (root_node == null || root_node->name != "purple" ||
- root_node->get_prop ("version") != "1.0")
- {
- /* Free the document manually before throwing because the garbage
- * collector can't work on pointers. */
- delete xml_doc;
- throw new ImportError.MALFORMED_INPUT (
- /* Translators: the parameter is a filename. */
- _("The Pidgin buddy list file ‘%s’ could not be loaded: the root element could not be found or was not recognized."),
- filename);
- }
-
- /* Parse each <blist> child element */
- for (Xml.Node *iter = root_node->children; iter != null;
- iter = iter->next)
- {
- if (iter->type != ElementType.ELEMENT_NODE || iter->name != "blist")
- continue;
-
- yield this.parse_blist (iter);
- }
-
- /* Tidy up */
- delete xml_doc;
-
- stdout.printf (
- /* Translators: the first parameter is the number of buddies which
- * were successfully imported, and the second is a filename. */
- ngettext ("Imported %u buddy from ‘%s’.",
- "Imported %u buddies from ‘%s’.", this.persona_count) + "\n",
- this.persona_count, filename);
-
- /* Return the number of Personas we imported */
- return this.persona_count;
- }
-
- private async void parse_blist (Xml.Node *blist_node)
- {
- for (Xml.Node *iter = blist_node->children; iter != null;
- iter = iter->next)
- {
- if (iter->type != ElementType.ELEMENT_NODE || iter->name != "group")
- continue;
-
- yield this.parse_group (iter);
- }
- }
-
- private async void parse_group (Xml.Node *group_node)
- {
- string group_name = group_node->get_prop ("name");
-
- for (Xml.Node *iter = group_node->children; iter != null;
- iter = iter->next)
- {
- if (iter->type != ElementType.ELEMENT_NODE || iter->name != "contact")
- continue;
-
- Persona persona = yield this.parse_contact (iter);
-
- /* Skip the persona if creating them failed or if they don't support
- * groups. */
- if (persona == null || !(persona is GroupDetails))
- continue;
-
- try
- {
- GroupDetails group_details = (GroupDetails) persona;
- yield group_details.change_group (group_name, true);
- }
- catch (GLib.Error e)
- {
- stderr.printf (
- /* Translators: the first parameter is a persona identifier,
- * and the second is an error message. */
- _("Error changing group of contact ‘%s’: %s") + "\n",
- persona.iid, e.message);
- }
- }
- }
-
- private async Persona? parse_contact (Xml.Node *contact_node)
- {
- string alias = null;
- var im_addresses = new HashMultiMap<string, ImFieldDetails> ();
- string im_address_string = "";
-
- /* Parse the <buddy> elements beneath <contact> */
- for (Xml.Node *iter = contact_node->children; iter != null;
- iter = iter->next)
- {
- if (iter->type != ElementType.ELEMENT_NODE || iter->name != "buddy")
- continue;
-
- string blist_protocol = iter->get_prop ("proto");
- if (blist_protocol == null)
- continue;
-
- string tp_protocol =
- this.blist_protocol_to_tp_protocol (blist_protocol);
- if (tp_protocol == null)
- continue;
-
- /* Parse the <name> and <alias> elements beneath <buddy> */
- for (Xml.Node *subiter = iter->children; subiter != null;
- subiter = subiter->next)
- {
- if (subiter->type != ElementType.ELEMENT_NODE)
- continue;
-
- if (subiter->name == "alias")
- alias = subiter->get_content ();
- else if (subiter->name == "name")
- {
- /* The <name> element seems to give the contact ID, which
- * we need to insert into the Persona's im-addresses property
- * for the linking to work. */
- string im_address = subiter->get_content ();
- im_addresses.set (tp_protocol,
- new ImFieldDetails (im_address));
- im_address_string += " %s\n".printf (im_address);
- }
- }
- }
-
- /* Don't bother if there's no alias and only one IM address */
- if (im_addresses.size < 2 &&
- (alias == null || alias.strip () == "" ||
- alias.strip () == im_address_string.strip ()))
- {
- stdout.printf (
- /* Translators: the parameter is the buddy's IM address. */
- _("Ignoring buddy with no alias and only one IM address:\n%s"),
- im_address_string);
- return null;
- }
-
- /* Create or update the relevant Persona */
- var details = new GLib.HashTable<string, Value?> (str_hash, str_equal);
- Value im_addresses_value = Value (typeof (MultiMap));
- im_addresses_value.set_object (im_addresses);
- details.insert ("im-addresses", im_addresses_value);
-
- Persona persona;
- try
- {
- persona =
- yield this.destination_store.add_persona_from_details (details);
- }
- catch (PersonaStoreError e)
- {
- /* Translators: the first parameter is an alias, the second is a set
- * of IM addresses each on a new line, and the third is an error
- * message. */
- stderr.printf (
- _("Failed to create new contact for buddy with alias ‘%s’ and IM addresses:\n%s\nError: %s\n"),
- alias, im_address_string, e.message);
- return null;
- }
-
- /* Set the Persona's details */
- if (alias != null && persona is AliasDetails)
- ((AliasDetails) persona).alias = alias;
-
- /* Print progress */
- stdout.printf (
- /* Translators: the first parameter is a persona identifier, the
- * second is an alias for the persona, and the third is a set of IM
- * addresses each on a new line. */
- _("Created contact ‘%s’ for buddy with alias ‘%s’ and IM addresses:\n%s"),
- persona.uid, alias, im_address_string);
- this.persona_count++;
-
- return persona;
- }
-
- private string? blist_protocol_to_tp_protocol (string blist_protocol)
- {
- string tp_protocol = blist_protocol;
- if (blist_protocol.has_prefix ("prpl-"))
- tp_protocol = blist_protocol.substring (5);
-
- /* Convert protocol names from Pidgin to Telepathy. Other protocol names
- * should be OK now that we've taken off the "prpl-" prefix. See:
- * http://telepathy.freedesktop.org/spec/Connection_Manager.html#Protocol
- * and http://developer.pidgin.im/wiki/prpl_id. */
- if (tp_protocol == "bonjour")
- tp_protocol = "local-xmpp";
- else if (tp_protocol == "novell")
- tp_protocol = "groupwise";
- else if (tp_protocol == "gg")
- tp_protocol = "gadugadu";
- else if (tp_protocol == "meanwhile")
- tp_protocol = "sametime";
- else if (tp_protocol == "simple")
- tp_protocol = "sip";
-
- return tp_protocol;
- }
-}
diff --git a/tools/import.vala b/tools/import.vala
deleted file mode 100644
index 400dc539..00000000
--- a/tools/import.vala
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using GLib;
-using Gee;
-using Xml;
-using Folks;
-
-/*
- * Command line application to import meta-contact information from various
- * places into libfolks' key file backend.
- *
- * Used as follows:
- * folks-import [--source=pidgin] [--source-filename=~/.purple/blist.xml]
- */
-
-public class Folks.ImportTool : Object
-{
- private static string source;
- private static string source_filename;
-
- private const string DEFAULT_SOURCE = "pidgin";
-
- private const OptionEntry[] options =
- {
- { "source", 's', 0, OptionArg.STRING, ref ImportTool.source,
- N_("Source backend name (default: ‘pidgin’)"), "name" },
- { "source-filename", 0, 0, OptionArg.FILENAME,
- ref ImportTool.source_filename,
- N_("Source filename (default: specific to source backend)"), null },
- { null }
- };
-
- public static int main (string[] args)
- {
- Intl.setlocale (LocaleCategory.ALL, "");
- Intl.bindtextdomain (BuildConf.GETTEXT_PACKAGE, BuildConf.LOCALE_DIR);
- Intl.textdomain (BuildConf.GETTEXT_PACKAGE);
-
- OptionContext context = new OptionContext (
- _("— import meta-contact information to libfolks"));
- context.add_main_entries (ImportTool.options, "folks");
-
- try
- {
- context.parse (ref args);
- }
- catch (OptionError e)
- {
- /* Translators: the parameter is an error message. */
- stderr.printf (_("Couldn’t parse command line options: %s") + "\n",
- e.message);
- return 1;
- }
-
- /* We only support importing from Pidgin at the moment */
- if (source == null || source.strip () == "")
- source = ImportTool.DEFAULT_SOURCE;
-
- /* FIXME: We need to create this, even though we don't use it, to prevent
- * debug message spew, as its constructor initialises the log handling.
- * bgo#629096 */
- IndividualAggregator aggregator = IndividualAggregator.dup ();
- aggregator = null;
-
- /* Create a main loop and start importing */
- MainLoop main_loop = new MainLoop ();
-
- bool success = false;
- ImportTool.import.begin ((o, r) =>
- {
- success = ImportTool.import.end (r);
- main_loop.quit ();
- });
-
- main_loop.run ();
-
- return success ? 0 : 1;
- }
-
- private static async bool import ()
- {
- BackendStore backend_store = BackendStore.dup ();
-
- try
- {
- yield backend_store.load_backends ();
- }
- catch (GLib.Error e1)
- {
- /* Translators: the parameter is an error message. */
- stderr.printf (_("Couldn’t load the backends: %s") + "\n",
- e1.message);
- return false;
- }
-
- /* Get the key-file backend */
- Backend kf_backend = backend_store.dup_backend_by_name ("key-file");
-
- if (kf_backend == null)
- {
- /* Translators: the parameter is a backend identifier. */
- stderr.printf (_("Couldn’t load the ‘%s’ backend.") + "\n",
- "key-file");
- return false;
- }
-
- try
- {
- yield kf_backend.prepare ();
- }
- catch (GLib.Error e2)
- {
- /* Translators: the first parameter is a backend identifier and the
- * second parameter is an error message. */
- stderr.printf (_("Couldn’t prepare the ‘%s’ backend: %s") + "\n",
- "key-file", e2.message);
- return false;
- }
-
- /* Get its only PersonaStore */
- PersonaStore destination_store = null;
- var stores = kf_backend.persona_stores.values;
-
- if (stores.size == 0)
- {
- stderr.printf (
- /* Translators: the parameter is a backend identifier. */
- _("Couldn’t load the ‘%s’ backend’s persona store.") + "\n",
- "key-file");
- return false;
- }
-
- try
- {
- /* Get the first persona store */
- foreach (var persona_store in stores)
- {
- destination_store = persona_store;
- break;
- }
-
- yield destination_store.prepare ();
- }
- catch (GLib.Error e3)
- {
- stderr.printf (
- /* Translators: the first parameter is a backend identifier and the
- * second parameter is an error message. */
- _("Couldn’t prepare the ‘%s’ backend’s persona store: %s") + "\n",
- e3.message);
- return false;
- }
-
- if (source == "pidgin")
- {
- Importer importer = new Importers.Pidgin ();
-
- try
- {
- /* Import! */
- yield importer.import (destination_store,
- ImportTool.source_filename);
- }
- catch (ImportError e)
- {
- /* Translators: the parameter is an error message. */
- stderr.printf (_("Error importing contacts: %s") + "\n",
- e.message);
- return false;
- }
-
- /* Wait for the PersonaStore to finish writing its changes to disk */
- yield destination_store.flush ();
-
- return true;
- }
- else
- {
- stderr.printf (
- /* Translators: both parameters are identifiers for backends. */
- _("Unrecognized source backend name ‘%s’. ‘%s’ is currently the only supported source backend.") + "\n",
- source, "pidgin");
- return false;
- }
- }
-}
-
-public errordomain Folks.ImportError
-{
- MALFORMED_INPUT,
-}
-
-public abstract class Folks.Importer : Object
-{
- public abstract async uint import (PersonaStore destination_store,
- string? source_filename) throws ImportError;
-}
diff --git a/tools/inspect.vala b/tools/inspect.vala
new file mode 100644
index 00000000..321172df
--- /dev/null
+++ b/tools/inspect.vala
@@ -0,0 +1,42 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Copyright 2023 Collabora, Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+public class FolksInspect.Application : GLib.Application {
+ private Application () {
+ Object (application_id: "org.gnome.Folks.Inspect", flags: ApplicationFlags.NON_UNIQUE);
+ }
+
+ public override void activate () {
+ try {
+ var manager = new Folks.Manager.sync ();
+ print ("Found %u addressbooks:\n", manager.get_n_items ());
+ for (uint i = 0; i < manager.get_n_items (); i++) {
+ var store = (Folks.PersonaStore) manager.get_object (i);
+ print (" - %s\n", store.title);
+ }
+ } catch (Error e) {
+ error ("Unable to connect to Folks service: %s", e.message);
+ }
+ }
+
+ public static int main (string[] args) {
+ Application app = new Application ();
+ return app.run (args);
+ }
+}
diff --git a/tools/inspect/command-backends.vala b/tools/inspect/command-backends.vala
deleted file mode 100644
index d2418902..00000000
--- a/tools/inspect/command-backends.vala
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using Folks;
-using Gee;
-using GLib;
-
-private class Folks.Inspect.Commands.Backends : Folks.Inspect.Command
-{
- public override string name
- {
- get { return "backends"; }
- }
-
- public override string description
- {
- get { return "Inspect the backends loaded by the aggregator."; }
- }
-
- public override string help
- {
- get
- {
- return "backends List all known backends.\n" +
- "backends [backend name] Display the details of the " +
- "specified backend and list its persona stores.";
- }
- }
-
- public Backends (Client client)
- {
- base (client);
- }
-
- public override async int run (string? command_string)
- {
- if (command_string == null)
- {
- /* List all the backends */
- Collection<Backend> backends =
- this.client.backend_store.list_backends ();
-
- Utils.print_line ("%u backends:", backends.size);
-
- Utils.indent ();
- foreach (Backend backend in backends)
- Utils.print_line ("%s", backend.name);
- Utils.unindent ();
- }
- else
- {
- /* Show the details of a particular backend */
- Backend backend =
- this.client.backend_store.dup_backend_by_name (command_string);
-
- if (backend == null)
- {
- Utils.print_line ("Unrecognised backend name '%s'.",
- command_string);
- return 1;
- }
-
- Utils.print_line ("Backend '%s' with %u persona stores " +
- "(type ID, ID ('display name')):",
- backend.name, backend.persona_stores.size);
-
- /* List the backend's persona stores */
- Utils.indent ();
- foreach (var store in backend.persona_stores.values)
- {
- Utils.print_line ("%s, %s ('%s')", store.type_id, store.id,
- store.display_name);
- }
- Utils.unindent ();
- }
-
- return 0;
- }
-
- public override string[]? complete_subcommand (string subcommand)
- {
- /* @subcommand should be a backend name */
- return Readline.completion_matches (subcommand,
- Utils.backend_name_completion_cb);
- }
-}
diff --git a/tools/inspect/command-debug.vala b/tools/inspect/command-debug.vala
deleted file mode 100644
index 08946c06..00000000
--- a/tools/inspect/command-debug.vala
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2011 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using Folks;
-using GLib;
-
-private class Folks.Inspect.Commands.Debug : Folks.Inspect.Command
-{
- public override string name
- {
- get { return "debug"; }
- }
-
- public override string description
- {
- get { return "Print debugging output from libfolks."; }
- }
-
- public override string help
- {
- get
- {
- return "debug Print status information from libfolks.";
- }
- }
-
- public Debug (Client client)
- {
- base (client);
- }
-
- public override async int run (string? command_string)
- {
- var debug = Folks.Debug.dup ();
- debug.emit_print_status ();
- return 0;
- }
-}
diff --git a/tools/inspect/command-help.vala b/tools/inspect/command-help.vala
deleted file mode 100644
index f5e1e4f6..00000000
--- a/tools/inspect/command-help.vala
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using Folks;
-using Gee;
-using GLib;
-
-private class Folks.Inspect.Commands.Help : Folks.Inspect.Command
-{
- public override string name
- {
- get { return "help"; }
- }
-
- public override string description
- {
- get { return "Get help on using the program."; }
- }
-
- public override string help
- {
- get
- {
- return "help Describe all the available " +
- "commands.\n" +
- "help [command name] Give more detailed help on the " +
- "specified command.";
- }
- }
-
- public Help (Client client)
- {
- base (client);
- }
-
- public override async int run (string? command_string)
- {
- if (command_string == null)
- {
- /* Help index */
- Utils.print_line ("Type 'help <command>' for more information " +
- "about a particular command.");
-
- MapIterator<string, Command> iter =
- this.client.commands.map_iterator ();
-
- Utils.indent ();
- while (iter.next () == true)
- {
- Utils.print_line ("%-20s %s", iter.get_key (),
- iter.get_value ().description);
- }
- Utils.unindent ();
- }
- else
- {
- /* Help for a given command */
- Command command = this.client.commands.get (command_string);
- if (command == null)
- {
- Utils.print_line ("Unrecognised command '%s'.", command_string);
- return 1;
- }
- else
- {
- Utils.print_line ("%s", command.help);
- }
- }
-
- return 0;
- }
-
- public override string[]? complete_subcommand (string subcommand)
- {
- /* @subcommand should be a command name */
- return Readline.completion_matches (subcommand,
- Utils.command_name_completion_cb);
- }
-}
diff --git a/tools/inspect/command-individuals.vala b/tools/inspect/command-individuals.vala
deleted file mode 100644
index db486e20..00000000
--- a/tools/inspect/command-individuals.vala
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using Folks;
-using Gee;
-using GLib;
-
-private class Folks.Inspect.Commands.Individuals : Folks.Inspect.Command
-{
- public override string name
- {
- get { return "individuals"; }
- }
-
- public override string description
- {
- get
- {
- return "Inspect the individuals currently present in the aggregator";
- }
- }
-
- public override string help
- {
- get
- {
- return "individuals List all known " +
- "individuals.\n" +
- "individuals [individual ID] Display the details of the " +
- "specified individual and list its personas.";
- }
- }
-
- public Individuals (Client client)
- {
- base (client);
- }
-
- public override async int run (string? command_string)
- {
- if (command_string == null)
- {
- /* List all the individuals */
- foreach (var individual in this.client.aggregator.individuals.values)
- {
- Utils.print_individual (individual, false);
- Utils.print_blank_line ();
- }
- }
- else
- {
- /* Display the details of a single individual */
- var individual =
- this.client.aggregator.individuals.get (command_string);
-
- if (individual == null)
- {
- Utils.print_line ("Unrecognised individual ID '%s'.",
- command_string);
- return 1;
- }
-
- Utils.print_individual (individual, true);
- }
-
- return 0;
- }
-
- public override string[]? complete_subcommand (string subcommand)
- {
- /* @subcommand should be an individual ID */
- return Readline.completion_matches (subcommand,
- Utils.individual_id_completion_cb);
- }
-}
diff --git a/tools/inspect/command-linking.vala b/tools/inspect/command-linking.vala
deleted file mode 100644
index f3429609..00000000
--- a/tools/inspect/command-linking.vala
+++ /dev/null
@@ -1,347 +0,0 @@
-/*
- * Copyright (C) 2011 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip@tecnocode.co.uk>
- */
-
-using Folks;
-using Gee;
-using GLib;
-
-private class Folks.Inspect.Commands.Linking : Folks.Inspect.Command
-{
- private const string[] _valid_subcommands =
- {
- "link-personas",
- "link-individuals",
- "unlink-individual",
- };
-
- public override string name
- {
- get { return "linking"; }
- }
-
- public override string description
- {
- get
- {
- return "Link and unlink personas";
- }
- }
-
- public override string help
- {
- get
- {
- return "linking link-personas [persona 1 UID] " +
- "[persona 2 UID] … " +
- "Link the given personas.\n" +
- "linking link-individuals [individual 1 ID] " +
- "[individual 2 ID] … " +
- "Link the personas in the given individuals.\n" +
- "linking unlink-individual " +
- "[individual ID] " +
- "Unlink the given individual.";
- }
- }
-
- public Linking (Client client)
- {
- base (client);
- }
-
- public override async int run (string? command_string)
- {
- string[] parts = {};
-
- if (command_string != null)
- {
- /* Parse subcommands */
- parts = command_string.split (" ");
- }
-
- if (!Utils.validate_subcommand (this.name, command_string, parts[0],
- Linking._valid_subcommands))
- return 1;
-
- if (parts[0] == "link-personas" || parts[0] == "link-individuals")
- {
- var personas = new HashSet<Persona> (); /* set of personas to link */
-
- if (parts.length < 2)
- {
- if (parts[0] == "link-personas")
- {
- Utils.print_line ("Must pass at least one persona to a " +
- "'link-personas' subcommand.");
- }
- else
- {
- Utils.print_line ("Must pass at least one individual to a " +
- "'link-individuals' subcommand.");
- }
-
- return 1;
- }
-
- /* Link the personas in the given individuals. We must have at least
- * one. */
- for (uint i = 1; i < parts.length; i++)
- {
- if (parts[i] == null || parts[i].strip () == "")
- {
- if (parts[0] == "link-personas")
- {
- Utils.print_line ("Unrecognised persona UID '%s'.",
- parts[i]);
- }
- else
- {
- Utils.print_line ("Unrecognised individual ID '%s'.",
- parts[i]);
- }
-
- return 1;
- }
-
- var found = false;
-
- if (parts[0] == "link-personas")
- {
- var uid = parts[i].strip ();
-
- foreach (var individual in
- this.client.aggregator.individuals.values)
- {
- foreach (Persona persona in individual.personas)
- {
- if (persona.uid == uid)
- {
- personas.add (persona);
- found = true;
- break;
- }
- }
-
- if (found == true)
- {
- break;
- }
- }
-
- if (found == false)
- {
- Utils.print_line ("Unrecognised persona UID '%s'.",
- parts[i]);
- return 1;
- }
- }
- else
- {
- var id = parts[i].strip ();
-
- foreach (var individual in
- this.client.aggregator.individuals.values)
- {
- if (individual.id == id)
- {
- foreach (Persona persona in individual.personas)
- {
- personas.add (persona);
- }
-
- found = true;
- break;
- }
- }
-
- if (found == false)
- {
- Utils.print_line ("Unrecognised individual ID '%s'.",
- parts[i]);
- return 1;
- }
- }
- }
-
- /* Link the personas */
- try
- {
- yield this.client.aggregator.link_personas (personas);
- }
- catch (IndividualAggregatorError e)
- {
- Utils.print_line ("Error (domain: %u, code: %u) linking %u " +
- "personas: %s",
- e.domain, e.code, personas.size, e.message);
- return 1;
- }
-
- /* We can't print out the individual which was produced, as
- * more than one may have been produced (due to anti-links)
- * or several others may have been consumed in the process.
- *
- * Chaos, really. */
- Utils.print_line ("Linking of %u personas was successful.",
- personas.size);
- }
- else if (parts[0] == "unlink-individual")
- {
- if (parts.length != 2)
- {
- Utils.print_line ("Must pass exactly one individual ID to an " +
- "'unlink-individual' subcommand.");
- return 1;
- }
-
- var ind = this.client.aggregator.individuals.get (parts[1]);
-
- if (ind == null)
- {
- Utils.print_line ("Unrecognised individual ID '%s'.", parts[1]);
- return 1;
- }
-
- /* Unlink the individual. */
- try
- {
- yield this.client.aggregator.unlink_individual (ind);
- }
- catch (Error e)
- {
- Utils.print_line ("Error (domain: %u, code: %u) unlinking " +
- "individual '%s': %s",
- e.domain, e.code, ind.id, e.message);
- return 1;
- }
-
- /* Success! */
- Utils.print_line ("Unlinking of individual '%s' was successful.",
- ind.id);
- }
- else
- {
- assert_not_reached ();
- }
-
- return 0;
- }
-
- /* FIXME: These can't be in the subcommand_name_completion_cb() function
- * because Vala doesn't allow static local variables. Erk. */
- [CCode (array_length = false, array_null_terminated = true)]
- private static string[] subcommand_completions;
- private static uint completion_count;
- private static string prefix;
-
- /* Complete a subcommand name (either “link-personas”, “link-individuals”
- * or “unlink-individual”), starting with @word. */
- public static string? subcommand_name_completion_cb (string word,
- int state)
- {
- /* Initialise state. I may have said this before, but whoever wrote the
- * readline API should be shot. */
- if (state == 0)
- {
- string[] parts = word.split (" ");
-
- if (parts.length > 0 &&
- (parts[0] == "link-personas" || parts[0] == "link-individuals"))
- {
- var last_part = parts[parts.length - 1];
-
- if (parts[0] == "link-personas")
- {
- subcommand_completions =
- Readline.completion_matches (last_part,
- Utils.persona_uid_completion_cb);
- }
- else
- {
- subcommand_completions =
- Readline.completion_matches (last_part,
- Utils.individual_id_completion_cb);
- }
-
- if (last_part == "")
- {
- prefix = word;
- }
- else
- {
- prefix = word[0:-last_part.length];
- }
- }
- else if (parts.length > 0 && parts[0] == "unlink-individual")
- {
- /* Only accepts one argument */
- if (parts.length != 2)
- {
- /* Clean up */
- subcommand_completions = null;
- completion_count = 0;
- prefix = "";
-
- return null;
- }
-
- subcommand_completions =
- Readline.completion_matches (parts[1],
- Utils.individual_id_completion_cb);
- prefix = "unlink-individual ";
- }
- else
- {
- subcommand_completions = Linking._valid_subcommands;
- prefix = "";
- }
-
- completion_count = 0;
- }
-
- while (completion_count < subcommand_completions.length)
- {
- var completion = subcommand_completions[completion_count];
- var candidate = prefix + completion;
- completion_count++;
-
- if (completion != null && completion != "" &&
- candidate.has_prefix (word))
- {
- return completion;
- }
- }
-
- /* Clean up */
- subcommand_completions = null;
- completion_count = 0;
- prefix = "";
-
- return null;
- }
-
- public override string[]? complete_subcommand (string subcommand)
- {
- /* @subcommand should be either “link-personas”, “link-individuals”
- * or “unlink-individual” */
- return Readline.completion_matches (subcommand,
- Linking.subcommand_name_completion_cb);
- }
-}
-
-/* vim: filetype=vala textwidth=80 tabstop=2 expandtab: */
diff --git a/tools/inspect/command-persona-stores.vala b/tools/inspect/command-persona-stores.vala
deleted file mode 100644
index 994e49be..00000000
--- a/tools/inspect/command-persona-stores.vala
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using Folks;
-using Gee;
-using GLib;
-
-private class Folks.Inspect.Commands.PersonaStores : Folks.Inspect.Command
-{
- public override string name
- {
- get { return "persona-stores"; }
- }
-
- public override string description
- {
- get
- {
- return "Inspect the persona stores loaded by the aggregator";
- }
- }
-
- public override string help
- {
- get
- {
- return "persona-stores List all known " +
- "persona stores.\n" +
- "persona-stores [persona store ID] Display the details of " +
- "the specified persona store and list its personas.";
- }
- }
-
- public PersonaStores (Client client)
- {
- base (client);
- }
-
- public override async int run (string? command_string)
- {
- if (command_string == null)
- {
- /* List all the persona stores */
- Collection<Backend> backends =
- this.client.backend_store.list_backends ();
-
- foreach (Backend backend in backends)
- {
- var stores = backend.persona_stores;
-
- foreach (var persona_store in stores.values)
- {
- Utils.print_persona_store (persona_store, false);
- Utils.print_blank_line ();
- }
- }
- }
- else
- {
- /* Show the details of a particular persona store */
- Collection<Backend> backends =
- this.client.backend_store.list_backends ();
- PersonaStore store = null;
-
- foreach (Backend backend in backends)
- {
- var stores = backend.persona_stores;
- store = stores.get (command_string);
- if (store != null)
- break;
- }
-
- if (store == null)
- {
- Utils.print_line ("Unrecognised persona store ID '%s'.",
- command_string);
- return 1;
- }
-
- Utils.print_persona_store (store, true);
- }
-
- return 0;
- }
-
- public override string[]? complete_subcommand (string subcommand)
- {
- /* @subcommand should be a persona store ID */
- return Readline.completion_matches (subcommand,
- Utils.persona_store_id_completion_cb);
- }
-}
diff --git a/tools/inspect/command-personas.vala b/tools/inspect/command-personas.vala
deleted file mode 100644
index f7f87c01..00000000
--- a/tools/inspect/command-personas.vala
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using Folks;
-using Gee;
-using GLib;
-
-private class Folks.Inspect.Commands.Personas : Folks.Inspect.Command
-{
- public override string name
- {
- get { return "personas"; }
- }
-
- public override string description
- {
- get
- {
- return "Inspect the personas currently present in the aggregator";
- }
- }
-
- public override string help
- {
- get
- {
- return "personas List all known personas.\n" +
- "personas [persona UID] Display the details of the " +
- "specified persona.";
- }
- }
-
- public Personas (Client client)
- {
- base (client);
- }
-
- public override async int run (string? command_string)
- {
- bool found_persona = false;
-
- foreach (var individual in this.client.aggregator.individuals.values)
- {
- foreach (Persona persona in individual.personas)
- {
- /* Either list all personas, or only list the one specified */
- if (command_string != null && persona.uid != command_string)
- continue;
-
- Utils.print_persona (persona);
-
- if (command_string == null)
- Utils.print_blank_line ();
- else
- found_persona = true;
- }
- }
-
- /* Return an error if the persona wasn’t found. */
- if (!found_persona && command_string != null)
- {
- Utils.print_line ("Unrecognised persona UID '%s'.", command_string);
- return 1;
- }
-
- return 0;
- }
-
- public override string[]? complete_subcommand (string subcommand)
- {
- /* @subcommand should be a persona UID */
- return Readline.completion_matches (subcommand,
- Utils.persona_uid_completion_cb);
- }
-}
diff --git a/tools/inspect/command-quit.vala b/tools/inspect/command-quit.vala
deleted file mode 100644
index 4a92ff69..00000000
--- a/tools/inspect/command-quit.vala
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using Folks;
-using GLib;
-
-private class Folks.Inspect.Commands.Quit : Folks.Inspect.Command
-{
- public override string name
- {
- get { return "quit"; }
- }
-
- public override string description
- {
- get
- {
- return "Quit the program.";
- }
- }
-
- public override string help
- {
- get
- {
- return "quit Quit the program gracefully, like a cow lolloping " +
- "across a green field.";
- }
- }
-
- public Quit (Client client)
- {
- base (client);
- }
-
- public override async int run (string? command_string)
- {
- Process.exit (0);
- }
-}
diff --git a/tools/inspect/command-search.vala b/tools/inspect/command-search.vala
deleted file mode 100644
index 4c8d644b..00000000
--- a/tools/inspect/command-search.vala
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Alvaro Soliverez <alvaro.soliverez@collabora.co.uk>
- */
-
-using Folks;
-using Gee;
-using GLib;
-
-private class Folks.Inspect.Commands.Search : Folks.Inspect.Command
-{
- public override string name
- {
- get { return "search"; }
- }
-
- public override string description
- {
- get
- {
- return "Search the individuals currently present in the aggregator";
- }
- }
-
- public override string help
- {
- get
- {
- return "search [string] Search the name fields of " +
- "the known individuals for the given string";
- }
- }
-
- public Search (Client client)
- {
- base (client);
- }
-
- public override async int run (string? command_string)
- {
- if (command_string == null)
- {
- /* A search string is required */
- Utils.print_line ("Please enter a search string");
- }
- else
- {
- var query = new SimpleQuery (
- command_string, Query.MATCH_FIELDS_NAMES);
- var search_view = new SearchView (this.client.aggregator, query);
-
- try
- {
- yield search_view.prepare ();
- }
- catch (GLib.Error e)
- {
- GLib.warning ("Error when calling prepare: %s", e.message);
- }
-
- foreach (var individual in search_view.individuals)
- {
- Utils.print_line ("%s %s", individual.id,
- individual.display_name);
- }
- }
-
- return 0;
- }
-}
diff --git a/tools/inspect/command-set.vala b/tools/inspect/command-set.vala
deleted file mode 100644
index 846ab605..00000000
--- a/tools/inspect/command-set.vala
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2012 Jeremy Whiting <jeremy.whiting@collabora.com>
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Jeremy Whiting <jeremy.whiting@collabora.com>
- */
-
-using Folks;
-using Gee;
-using GLib;
-
-private class Folks.Inspect.Commands.Set : Folks.Inspect.Command
-{
- private const string[] _valid_subcommands =
- {
- "alias",
- };
-
- public override string name
- {
- get { return "set"; }
- }
-
- public override string description
- {
- get
- {
- return "Set an individual's properties";
- }
- }
-
- public override string help
- {
- get
- {
- return "set alias [individual UID] [new alias]" +
- " Set the alias of the given individual.";
- }
- }
-
- public Set (Client client)
- {
- base (client);
- }
-
- public override async int run (string? command_string)
- {
- string[] parts = {};
-
- if (command_string != null)
- {
- /* Parse subcommands */
- parts = command_string.split (" ");
- }
-
- if (!Utils.validate_subcommand (this.name, command_string, parts[0],
- Set._valid_subcommands))
- return 1;
-
- if (parts[0] == "alias")
- {
- if (parts.length < 3)
- {
- Utils.print_line ("Must pass at least one individual ID and a new alias to an " +
- "'alias' subcommand.");
-
- return 1;
- }
-
- /* To set an attribute on an individual, we must have at least one. */
- if (parts[1] == null || parts[1].strip () == "")
- {
- Utils.print_line ("Unrecognised individual ID '%s'.",
- parts[1]);
-
- return 1;
- }
-
- var id = parts[1].strip ();
-
- var individual = this.client.aggregator.individuals.get (id);
- if (individual == null)
- {
- Utils.print_line ("Unrecognized individual ID '%s'.", id);
- return 1;
- }
-
- try
- {
- var persona = yield this.client.aggregator.ensure_individual_property_writeable (individual, "alias");
-
- /* Since the individual may have changed, use the individual from the new persona. */
- persona.individual.alias = parts[2];
- Utils.print_line ("Setting of individual's alias to '%s' was successful.",
- parts[2]);
- }
- catch (Folks.IndividualAggregatorError e)
- {
- Utils.print_line ("Setting of individual's alias to '%s' failed.",
- parts[2]);
- return 1;
- }
- }
- else
- {
- assert_not_reached ();
- }
-
- return 0;
- }
-
- /* FIXME: These can't be in the subcommand_name_completion_cb() function
- * because Vala doesn't allow static local variables. Erk. */
- [CCode (array_length = false, array_null_terminated = true)]
- private static string[] subcommand_completions;
- private static uint completion_count;
- private static string prefix;
-
- /* Complete a subcommand name (“alias”), starting with @word. */
- public static string? subcommand_name_completion_cb (string word,
- int state)
- {
- if (state == 0)
- {
- string[] parts = word.split (" ");
-
- if (parts.length > 0 &&
- (parts[0] == "alias"))
- {
- var last_part = parts[parts.length - 1];
-
- if (parts[0] == "alias")
- {
- subcommand_completions =
- Readline.completion_matches (last_part,
- Utils.individual_id_completion_cb);
- }
-
- if (last_part == "")
- {
- prefix = word;
- }
- else
- {
- prefix = word[0:-last_part.length];
- }
- }
- else
- {
- subcommand_completions = Set._valid_subcommands;
- prefix = "";
- }
-
- completion_count = 0;
- }
-
- while (completion_count < subcommand_completions.length)
- {
- var completion = subcommand_completions[completion_count];
- var candidate = prefix + completion;
- completion_count++;
-
- if (completion != null && completion != "" &&
- candidate.has_prefix (word))
- {
- return completion;
- }
- }
-
- /* Clean up */
- subcommand_completions = null;
- completion_count = 0;
- prefix = "";
-
- return null;
- }
-
- public override string[]? complete_subcommand (string subcommand)
- {
- /* @subcommand should be “alias” */
- return Readline.completion_matches (subcommand,
- subcommand_name_completion_cb);
- }
-}
-
-/* vim: filetype=vala textwidth=80 tabstop=2 expandtab: */
diff --git a/tools/inspect/command-signals.vala b/tools/inspect/command-signals.vala
deleted file mode 100644
index 2bb54ef0..00000000
--- a/tools/inspect/command-signals.vala
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using Folks;
-using Gee;
-using GLib;
-
-/*
- * signals — list signals we're currently connected to
- * signals connect ClassName — connect to all the signals on all the instances
- * of that class
- * signals connect ClassName::signal — connect to the given signal on all the
- * instances of that class
- * signals connect 0xdeadbeef — connect to all the signals on a particular class
- * instance
- * signals connect 0xdeadbeef::signal — connect to the given signal on a
- * particular class instance
- * signals disconnect (as above)
- * signals disconnect — signal handler ID
- * signals ClassName — list all the signals on all the instances of that class,
- * highlighting the ones we're currently connected to
- * signals 0xdeadbeef — list all the signals on a particular class instance,
- * highlighting the ones we're currently connected to
- * signals ClassName::signal — show the details of this signal
- * signals 0xdeadbeef::signal — show the details of this signal
- */
-
-private class Folks.Inspect.Commands.Signals : Folks.Inspect.Command
-{
- private const string[] _valid_subcommands =
- {
- "connect",
- "disconnect",
- };
-
- public override string name
- {
- get { return "signals"; }
- }
-
- public override string description
- {
- get
- {
- return "Allow connection to and display of signals emitted by " +
- "libfolks.";
- }
- }
-
- public override string help
- {
- get
- {
- return "signals " +
- "List signals we're currently connected to.\n" +
- "signals connect [class name] " +
- "Connect to all the signals on all the instances of that " +
- "class.\n" +
- "signals connect [class name]::[signal name] " +
- "Connect to the given signal on all the instances of that " +
- "class.\n" +
- "signals connect [object pointer] " +
- "Connect to all the signals on a particular class instance.\n" +
- "signals connect [object pointer]::[signal name] " +
- "Connect to the given signal on a particular class instance.\n" +
- "signals disconnect " +
- "(As for 'connect'.)\n" +
- "signals [class name] " +
- "List all the signals on all the instances of that class, " +
- "highlighting the ones we're currently connected to.\n" +
- "signals [object pointer] " +
- "List all the signals on a particular class instance, " +
- "highlighting the ones we're currently connected to.\n" +
- "signals [class name]::[signal name] " +
- "Show the details of this signal.\n" +
- "signals [object pointer]::[signal name] " +
- "Show the details of this signal.";
- }
- }
-
- public Signals (Client client)
- {
- base (client);
- }
-
- public override async int run (string? command_string)
- {
- if (command_string == null)
- {
- /* List all the signals we're connected to */
- this.client.signal_manager.list_signals (Type.INVALID, null);
- }
- else
- {
- /* Parse subcommands */
- string[] parts = command_string.split (" ", 2);
-
- if (!Utils.validate_subcommand (this.name, command_string, parts[0],
- Signals._valid_subcommands))
- return 1;
-
- Type class_type;
- Object class_instance;
- string signal_name;
- string detail_string;
-
- if (parts[0] == "connect" || parts[0] == "disconnect")
- {
- /* Connect to or disconnect from a signal */
- if (parts[1] == null || parts[1].strip () == "")
- {
- Utils.print_line ("Unrecognised signal identifier '%s'.",
- parts[1]);
- return 1;
- }
-
- if (this.parse_signal_id (parts[1].strip (), out class_type,
- out class_instance, out signal_name,
- out detail_string) == false)
- {
- return 1;
- }
-
- /* FIXME: Handle "disconnect <signal ID>" */
- if (parts[0] == "connect")
- {
- uint signal_count =
- this.client.signal_manager.connect_to_signal (class_type,
- class_instance, signal_name, detail_string);
- Utils.print_line ("Connected to %u signals.", signal_count);
- }
- else
- {
- uint signal_count =
- this.client.signal_manager.disconnect_from_signal (
- class_type, class_instance, signal_name,
- detail_string);
- Utils.print_line ("Disconnected from %u signals.",
- signal_count);
- }
- }
- else
- {
- /* List some of the signals we're connected to, or display
- * their details. */
- if (this.parse_signal_id (parts[0].strip (), out class_type,
- out class_instance, out signal_name,
- out detail_string) == false)
- {
- return 1;
- }
-
- if (signal_name == null)
- {
- this.client.signal_manager.list_signals (class_type,
- class_instance);
- }
- else
- {
- /* Get the class type from the instance */
- if (class_type == Type.INVALID)
- class_type = class_instance.get_type ();
-
- this.client.signal_manager.show_signal_details (class_type,
- signal_name, detail_string);
- }
- }
- }
-
- return 0;
- }
-
- public override string[]? complete_subcommand (string subcommand)
- {
- /* @subcommand should be a backend name */
- /* TODO */
- return Readline.completion_matches (subcommand,
- Utils.backend_name_completion_cb);
- }
-
- private bool parse_signal_id (string input,
- out Type class_type,
- out Object? class_instance,
- out string? signal_name,
- out string? detail_string)
- {
- /* We accept any of the following formats:
- * ClassName::signal-name
- * ClassName::signal-name::detail
- * 0xdeadbeef::signal-name
- * 0xdeadbeef::signal-name::detail
- * ClassName
- * 0xdeadbeef
- *
- * We output exactly one of class_type and class_instance, and optionally
- * output signal_name and/or detail_string as appropriate.
- */
- assert (input != null && input != "");
-
- /* Default output */
- class_type = Type.INVALID;
- class_instance = null;
- signal_name = null;
- detail_string = null;
-
- string[] parts = input.split ("::", 3);
- string class_name_or_instance = parts[0];
- string signal_name_inner = (parts.length > 1) ? parts[1] : null;
- string detail_string_inner = (parts.length > 2) ? parts[2] : null;
-
- if (signal_name_inner == "" || detail_string_inner == "")
- {
- Utils.print_line ("Invalid signal identifier '%s'.", input);
- return false;
- }
-
- if (class_name_or_instance.length > 2 &&
- class_name_or_instance[0] == '0' && class_name_or_instance[1] == 'x')
- {
- /* We have a class instance. The ‘0x’ prefix ensures it will be
- * parsed in base 16. */
- var address = uint64.parse (class_name_or_instance);
- class_instance = (Object) address;
- assert (class_instance.get_type ().is_object ());
- }
- else
- {
- /* We have a class name */
- class_type = Type.from_name (class_name_or_instance);
- if (class_type == Type.INVALID ||
- (class_type.is_instantiatable () == false &&
- class_type.is_interface () == false))
- {
- Utils.print_line ("Unrecognised class name '%s'.",
- class_name_or_instance);
- return false;
- }
- }
-
- signal_name = signal_name_inner;
- detail_string = detail_string_inner;
-
- return true;
- }
-}
diff --git a/tools/inspect/inspect.vala b/tools/inspect/inspect.vala
deleted file mode 100644
index c4860d76..00000000
--- a/tools/inspect/inspect.vala
+++ /dev/null
@@ -1,620 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- * Copyright (C) 2012 Philip Withnall
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using Folks.Inspect.Commands;
-using Folks;
-using Readline;
-using Gee;
-using GLib;
-using Posix;
-
-/* We have to have a static global instance so that the readline callbacks can
- * access its data, since they don't pass closures around. */
-static Inspect.Client main_client = null;
-
-public class Folks.Inspect.Client : Object
-{
- public HashMap<string, Command> commands;
- private static bool _is_readline_installed;
- private MainLoop main_loop;
- public IndividualAggregator aggregator { get; private set; }
- public BackendStore backend_store { get; private set; }
- public SignalManager signal_manager { get; private set; }
-
- /* To page or not to page? */
- private termios _original_termios_p;
- private bool _original_termios_p_valid = false;
- private bool _quit_after_pager_dies = false;
- private static Pid _pager_pid = 0;
- private IOChannel? _stdin_channel = null;
- private static uint _stdin_watch_id = 0;
- private FileStream? _pager_channel = null;
- private uint _pager_child_watch_id = 0;
-
- public static int main (string[] args)
- {
- int retval = 0;
-
- Intl.setlocale (LocaleCategory.ALL, "");
- Intl.bindtextdomain (BuildConf.GETTEXT_PACKAGE, BuildConf.LOCALE_DIR);
- Intl.textdomain (BuildConf.GETTEXT_PACKAGE);
-
- /* Parse command line options. */
- OptionContext context = new OptionContext ("[COMMAND]");
- context.set_summary ("Inspect meta-contact information in libfolks.");
-
- try
- {
- context.parse (ref args);
- }
- catch (OptionError e1)
- {
- GLib.stderr.printf ("Couldn’t parse command line options: %s\n",
- e1.message);
- return 1;
- }
-
- /* Create the client. */
- main_client = new Client ();
-
- /* Set up signal handling. */
-#if VALA_0_40
- Unix.signal_add (Posix.Signal.TERM, () =>
-#else
- Unix.signal_add (Posix.SIGTERM, () =>
-#endif
- {
- /* Propagate the signal to our pager process, if it's running. */
- if (Client._pager_pid != 0)
- {
- main_client._quit_after_pager_dies = true;
-#if VALA_0_40
- kill (Client._pager_pid, Posix.Signal.TERM);
-#else
- kill (Client._pager_pid, Posix.SIGTERM);
-#endif
- }
- else
- {
- /* Quit the client and let that exit the process. */
- main_client.quit ();
- }
-
- return false;
- });
-
- /* Run the command. */
- if (args.length == 1)
- {
- main_client.run_interactive.begin ();
- retval = 0;
- }
- else
- {
- GLib.assert (args.length > 1);
-
- /* Drop the first argument and parse the rest as a command line. If
- * the first argument is ‘--’ then the command was passed after some
- * flags. */
- string command_line;
- if (args[1] == "--")
- {
- command_line = string.joinv (" ", args[2:0]);
- }
- else
- {
- command_line = string.joinv (" ", args[1:0]);
- }
-
- main_client.run_non_interactive.begin (command_line, (obj, res) =>
- {
- retval = main_client.run_non_interactive.end (res);
- main_client.quit ();
- });
- }
-
- main_client.main_loop.run ();
-
- return retval;
- }
-
- public Client ()
- {
- Utils.init ();
-
- this.commands = new HashMap<string, Command> ();
-
- /* Register the commands we support */
- /* FIXME: This should be automatic */
- this.commands.set ("quit", new Commands.Quit (this));
- this.commands.set ("help", new Commands.Help (this));
- this.commands.set ("individuals", new Commands.Individuals (this));
- this.commands.set ("linking", new Commands.Linking (this));
- this.commands.set ("personas", new Commands.Personas (this));
- this.commands.set ("backends", new Commands.Backends (this));
- this.commands.set ("persona-stores", new Commands.PersonaStores (this));
- this.commands.set ("set", new Commands.Set (this));
- this.commands.set ("signals", new Commands.Signals (this));
- this.commands.set ("debug", new Commands.Debug (this));
- this.commands.set ("search", new Commands.Search (this));
-
- /* Create various bits of folks machinery. */
- this.main_loop = new MainLoop ();
- this.signal_manager = new SignalManager ();
- this.backend_store = BackendStore.dup ();
- this.aggregator = IndividualAggregator.dup ();
- }
-
- public void quit ()
- {
- /* Stop paging. */
- this._stop_paged_output ();
-
- /* Uninstall readline, if it's installed. */
- if (Client._is_readline_installed)
- {
- this._uninstall_readline_and_stdin ();
- }
-
- /* Restore the user's original terminal settings, since the pager might've
- * fiddled with them. */
- if (this._original_termios_p_valid)
- {
- tcsetattr (Posix.STDIN_FILENO, Posix.TCSADRAIN,
- this._original_termios_p);
- }
-
- /* Kill the main loop. */
- this.main_loop.quit ();
- }
-
- private async void _wait_for_quiescence () throws GLib.Error
- {
- var has_yielded = false;
- var signal_id = this.aggregator.notify["is-quiescent"].connect (
- (obj, pspec) =>
- {
- if (has_yielded == true)
- {
- this._wait_for_quiescence.callback ();
- }
- });
-
- try
- {
- yield this.aggregator.prepare ();
-
- if (this.aggregator.is_quiescent == false)
- {
- has_yielded = true;
- yield;
- }
- }
- finally
- {
- this.aggregator.disconnect (signal_id);
- GLib.assert (this.aggregator.is_quiescent == true);
- }
- }
-
- public async int run_non_interactive (string command_line)
- {
- /* Non-interactive mode: run a single command and output the results.
- * We do this all from the main thread, in a main loop, waiting for
- * quiescence before running the command. */
-
- /* Check we can parse the command first. */
- string subcommand;
- string command_name;
- var command = Client.parse_command_line (command_line, out command_name,
- out subcommand);
-
- if (command == null)
- {
- GLib.stdout.printf ("Unrecognised command ‘%s’.\n", command_name);
- return 1;
- }
-
- /* Wait until we reach quiescence, or the results will probably be
- * useless. */
- try
- {
- yield this._wait_for_quiescence ();
- }
- catch (GLib.Error e1)
- {
- GLib.stderr.printf ("Error preparing aggregator: %s\n", e1.message);
- return 1;
- }
-
- /* Run the command */
- int retval = yield command.run (subcommand);
- this.quit ();
-
- return retval;
- }
-
- public async int run_interactive ()
- {
- /* Interactive mode: have a little shell which allows the data from
- * libfolks to be browsed and edited in real time. We do this by watching
- * stdin in our main loop, and passing character notifications to
- * readline. The main loop also processes all the folks events, thus
- * preventing us having to run a second thread. */
-
- /* Copy the user's original terminal settings. */
- if (tcgetattr (Posix.STDIN_FILENO, out this._original_termios_p) == 0)
- {
- this._original_termios_p_valid = true;
- }
-
- /* Handle SIGINT. */
-#if VALA_0_40
- Unix.signal_add (Posix.Signal.INT, () =>
-#else
- Unix.signal_add (Posix.SIGINT, () =>
-#endif
- {
- if (Client._is_readline_installed == false)
- {
- return true;
- }
-
- /* Tidy up. */
- Readline.free_line_state ();
- Readline.cleanup_after_signal ();
- Readline.reset_after_signal ();
-
- /* Display a fresh prompt. */
- GLib.stdout.printf ("^C");
- Readline.crlf ();
- Readline.reset_line_state ();
- Readline.replace_line ("", 0);
- Readline.redisplay ();
-
- return true;
- });
-
- /* Allow things to be set for folks-inspect in ~/.inputrc, and install our
- * own completion function. */
- Readline.readline_name = "folks-inspect";
- Readline.attempted_completion_function = Client.completion_cb;
- Readline.catch_signals = 0; /* go away, readline */
-
- /* Install readline and the stdin handler. */
- this._stdin_channel = new IOChannel.unix_new (GLib.stdin.fileno ());
- this._install_readline_and_stdin ();
-
- /* Run the aggregator and the main loop. */
- this.aggregator.prepare.begin ();
-
- return 0;
- }
-
- private void _install_readline_and_stdin ()
- {
- /* stdin handler. */
- Client._stdin_watch_id = this._stdin_channel.add_watch (IOCondition.IN,
- this._stdin_handler_cb);
-
- /* Callback for each character appearing on stdin. */
- Readline.callback_handler_install ("> ", Client._readline_handler_cb);
- Client._is_readline_installed = true;
- }
-
- private void _uninstall_readline_and_stdin ()
- {
- Readline.callback_handler_remove ();
- Client._is_readline_installed = false;
-
- Source.remove (Client._stdin_watch_id);
- Client._stdin_watch_id = 0;
- }
-
- /* This should only ever be called while readline is installed. */
- private bool _stdin_handler_cb (IOChannel source, IOCondition cond)
- {
- /* At least a single character is available on stdin, so let readline
- * consume it. */
- if ((cond & IOCondition.IN) != 0)
- {
- Readline.callback_read_char ();
- return true;
- }
-
- assert_not_reached ();
- }
-
- private static void _readline_handler_cb (string? _command_line)
- {
- if (_command_line == null)
- {
- /* EOF. If we've entered some text, don't do anything. Otherwise,
- * quit. */
- if (Readline.line_buffer != "")
- {
- Readline.ding ();
- return;
- }
-
- /* Quit. */
- main_client.quit ();
-
- return;
- }
-
- var command_line = (!) _command_line;
-
- command_line = command_line.strip ();
- if (command_line == "")
- {
- /* If the user's entered a blank line, just display a new prompt
- * without doing anything else. */
- return;
- }
-
- string subcommand;
- string command_name;
- Command command = Client.parse_command_line (command_line,
- out command_name, out subcommand);
-
- /* Run the command */
- if (command != null)
- {
- if (command_name != "quit")
- {
- /* Start paging output. This is stopped when the pager dies. */
- main_client._start_paged_output ();
- }
-
- command.run.begin (subcommand, (obj, res) =>
- {
- command.run.end (res);
-
- if (main_client._pager_channel != null)
- {
- /* Close the stream to the pager so it knows it's
- * reached EOF. */
- main_client._pager_channel = null;
- Utils.output_filestream = GLib.stdout;
- }
- else
- {
- /* Failed to start the pager in the first place. */
- Readline.reset_line_state ();
- Readline.replace_line ("", 0);
- Readline.redisplay ();
- }
- });
-
- }
- else
- {
- GLib.stdout.printf ("Unrecognised command ‘%s’.\n", command_name);
- }
-
- /* Store the command in the history, even if it failed */
- Readline.History.add (command_line);
- }
-
- private void _start_paged_output ()
- {
- /* If the output is not a TTY (because it's a pipe or a file or a
- * toaster) we don't page. */
- if (!isatty (1))
- {
- return;
- }
-
- var pager = Environment.get_variable ("PAGER");
- if (pager != null && pager == "")
- {
- return;
- }
-
- if (pager == null)
- {
- pager = "less -FRSX";
- }
-
- /* Convert command to null terminated array */
- string[] args;
- try
- {
- GLib.Shell.parse_argv (pager, out args);
- }
- catch (GLib.ShellError e)
- {
- warning ("Error parsing pager arguments: %s", e.message);
- return;
- }
-
- /* Remove the readline and stdin handlers while the pager is running. */
- this._uninstall_readline_and_stdin ();
-
- /* Store the readline terminal state so that we can restore them
- * after the pager has exited. */
- Readline.prep_terminal (1);
-
- /* Spawn the pager. */
- int pager_fd = 0;
-
- try
- {
- GLib.Process.spawn_async_with_pipes (null,
- args,
- null,
- GLib.SpawnFlags.LEAVE_DESCRIPTORS_OPEN |
- GLib.SpawnFlags.SEARCH_PATH |
- GLib.SpawnFlags.DO_NOT_REAP_CHILD /* we use a ChildWatch */,
- null,
- out Client._pager_pid,
- out pager_fd, // Std input
- null, // Std out
- null); // Std error
- }
- catch (SpawnError e2)
- {
- warning ("Error spawning pager: %s", e2.message);
-
- /* Reinstall the readline handler and stdin handler. */
- this._install_readline_and_stdin ();
-
- return;
- }
-
- /* Redirect our output to the pager. */
- this._pager_channel = FileStream.fdopen (pager_fd, "w");
- Utils.output_filestream = this._pager_channel;
-
- /* Watch for when the pager exits. */
- this._pager_child_watch_id = ChildWatch.add (Client._pager_pid,
- (pid, status) =>
- {
- /* $PAGER died or was killed. */
- this._stop_paged_output ();
-
- /* Reset the readline state ready to display a new prompt. If the
- * pager exited as the result of a signal, it probably didn't
- * tidy up after itself (e.g. ``less`` leaves a colon prompt
- * behind on the current line), so move to a new line. Doing this
- * normally just looks a bit weird. */
- if (Process.if_signaled (status))
- {
- Readline.crlf ();
- }
-
- Readline.reset_line_state ();
- Readline.replace_line ("", 0);
-
- /* Are we supposed to quit (e.g. due to receiving a SIGTERM)? */
- if (this._quit_after_pager_dies)
- {
- main_client.quit ();
- return;
- }
-
- /* Reinstall the readline handler and stdin handler. */
- this._install_readline_and_stdin ();
- });
- }
-
- private void _stop_paged_output ()
- {
- if (Client._pager_pid == 0)
- {
- return;
- }
-
- Process.close_pid (Client._pager_pid);
- Source.remove (this._pager_child_watch_id);
-
- this._pager_channel = null;
- Utils.output_filestream = GLib.stdout;
- Client._pager_pid = 0;
- this._pager_child_watch_id = 0;
-
- /* Reset the terminal state (e.g. ECHO, which can get left turned
- * off if the pager was killed uncleanly). */
- Readline.deprep_terminal ();
- Readline.free_line_state ();
- Readline.cleanup_after_signal ();
- Readline.reset_after_signal ();
- }
-
- private static Command? parse_command_line (string command_line,
- out string command_name,
- out string? subcommand)
- {
- /* Default output */
- command_name = "";
- subcommand = null;
-
- string[] parts = command_line.split (" ", 2);
-
- if (parts.length < 1)
- return null;
-
- command_name = parts[0];
- if (parts.length == 2 && parts[1] != "")
- subcommand = parts[1];
- else
- subcommand = null;
-
- /* Extract the first part of the command and see if it matches anything in
- * this.commands */
- return main_client.commands.get (parts[0]);
- }
-
- [CCode (array_length = false, array_null_terminated = true)]
- private static string[]? completion_cb (string word,
- int start,
- int end)
- {
- /* word is the word to complete, and start and end are its bounds inside
- * Readline.line_buffer, which contains the entire current line. */
-
- /* Command name completion */
- if (start == 0)
- {
- return Readline.completion_matches (word,
- Utils.command_name_completion_cb);
- }
-
- /* Command parameter completion is passed off to the Command objects */
- string command_name;
- string subcommand;
- Command command = Client.parse_command_line (Readline.line_buffer,
- out command_name,
- out subcommand);
-
- if (command != null)
- {
- if (subcommand == null)
- subcommand = "";
- return command.complete_subcommand (subcommand);
- }
-
- return null;
- }
-}
-
-public abstract class Folks.Inspect.Command
-{
- protected Client client;
-
- protected Command (Client client)
- {
- this.client = client;
- }
-
- public abstract string name { get; }
- public abstract string description { get; }
- public abstract string help { get; }
-
- public abstract async int run (string? command_string);
-
- public virtual string[]? complete_subcommand (string subcommand)
- {
- /* Default implementation */
- return null;
- }
-}
diff --git a/tools/inspect/meson.build b/tools/inspect/meson.build
deleted file mode 100644
index d9203291..00000000
--- a/tools/inspect/meson.build
+++ /dev/null
@@ -1,35 +0,0 @@
-folks_inspect_sources = [
- 'command-backends.vala',
- 'command-debug.vala',
- 'command-help.vala',
- 'command-individuals.vala',
- 'command-linking.vala',
- 'command-persona-stores.vala',
- 'command-personas.vala',
- 'command-quit.vala',
- 'command-search.vala',
- 'command-set.vala',
- 'command-signals.vala',
- 'inspect.vala',
- 'signal-manager.vala',
- 'utils.vala',
-]
-
-folks_inspect_deps = [
- build_conf_dep,
- libfolks_dep,
- posix_dep,
- readline_dep,
-]
-
-folks_inspect_c_flags = [
- '-include', 'config.h',
-]
-
-folks_inspect = executable('folks-inspect',
- folks_inspect_sources,
- dependencies: folks_inspect_deps,
- c_args: folks_inspect_c_flags,
- include_directories: config_h_dir,
- install: true,
-)
diff --git a/tools/inspect/signal-manager.vala b/tools/inspect/signal-manager.vala
deleted file mode 100644
index 7c5b17a0..00000000
--- a/tools/inspect/signal-manager.vala
+++ /dev/null
@@ -1,509 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using Folks;
-using Gee;
-using GLib;
-
-public class Folks.Inspect.SignalManager : Object
-{
- /* Map from class type → map from signal ID to hook ID */
- private HashMap<Type, HashMap<uint, ulong>> signals_by_class_type;
- /* Map from class instance → map from signal ID to hook ID */
- private HashMap<Object, HashMap<uint, ulong>> signals_by_class_instance;
-
- public SignalManager ()
- {
- this.signals_by_class_type =
- new HashMap<Type, HashMap<uint, ulong>> ();
- this.signals_by_class_instance =
- new HashMap<Object, HashMap<uint, ulong>> ();
- }
-
- public void list_signals (Type class_type,
- Object? class_instance)
- {
- if (class_type != Type.INVALID)
- {
- /* List the signals we're connected to via emission hooks on this
- * class type */
- HashMap<uint, ulong> hook_ids =
- this.signals_by_class_type.get (class_type);
-
- Utils.print_line ("Signals on all instances of class type '%s':",
- class_type.name ());
- Utils.indent ();
- this.list_signals_for_type (class_type, hook_ids);
- Utils.unindent ();
- }
- else if (class_instance != null)
- {
- /* List the signals we're connected to on this class instance */
- HashMap<uint, ulong> signal_handler_ids =
- this.signals_by_class_instance.get (class_instance);
-
- Utils.print_line ("Signals on instance %p of class type '%s':",
- class_instance, class_instance.get_type ().name ());
- Utils.indent ();
- this.list_signals_for_type (class_instance.get_type (),
- signal_handler_ids);
- Utils.unindent ();
- }
- else
- {
- /* List all the signals we're connected to on everything */
- MapIterator<Type, HashMap<uint, ulong>> class_type_iter =
- this.signals_by_class_type.map_iterator ();
-
- Utils.print_line ("Connected signals on all instances of classes:");
-
- Utils.indent ();
- while (class_type_iter.next () == true)
- {
- HashMap<uint, ulong> hook_ids = class_type_iter.get_value ();
- MapIterator<uint, ulong> hook_iter = hook_ids.map_iterator ();
-
- string class_name = class_type_iter.get_key ().name ();
- while (hook_iter.next () == true)
- {
- Utils.print_line ("%s::%s — connected", class_name,
- Signal.name (hook_iter.get_key ()));
- }
- }
- Utils.unindent ();
-
- MapIterator<Object, HashMap<uint, ulong>> class_instance_iter =
- this.signals_by_class_instance.map_iterator ();
-
- Utils.print_line ("Connected signals on specific instances of " +
- "classes:");
-
- Utils.indent ();
- while (class_instance_iter.next () == true)
- {
- HashMap<uint, ulong> signal_handler_ids =
- class_instance_iter.get_value ();
- MapIterator<uint, ulong> signal_handler_iter =
- signal_handler_ids.map_iterator ();
-
- string class_name =
- class_instance_iter.get_key ().get_type ().name ();
- while (signal_handler_iter.next () == true)
- {
- Utils.print_line ("%s::%s — connected", class_name,
- Signal.name (signal_handler_iter.get_key ()));
- }
- }
- Utils.unindent ();
- }
- }
-
- public void show_signal_details (Type class_type,
- string? signal_name,
- string? detail_string)
- {
- uint signal_id = Signal.lookup (signal_name, class_type);
- if (signal_id == 0)
- {
- Utils.print_line ("Unrecognised signal name '%s' on class '%s'.",
- signal_name, class_type.name ());
- return;
- }
-
- /* Query the signal's information */
- SignalQuery query_info;
- Signal.query (signal_id, out query_info);
-
- /* Print the query response */
- Utils.print_line ("Signal ID %u", query_info.signal_id);
- Utils.print_line ("Signal name %s", query_info.signal_name);
- Utils.print_line ("Emitting type %s", query_info.itype.name ());
- Utils.print_line ("Signal flags %s",
- SignalManager.signal_flags_to_string (query_info.signal_flags));
- Utils.print_line ("Return type %s", query_info.return_type.name ());
- Utils.print_line ("Parameter types:");
- Utils.indent ();
- for (uint i = 0; i < query_info.n_params; i++)
- Utils.print_line ("%-4u %s", i, query_info.param_types[i].name ());
- Utils.unindent ();
- }
-
- public uint connect_to_signal (Type class_type,
- Object? class_instance,
- string? signal_name,
- string? detail_string)
- {
- /* We return the number of signals we connected to */
- if (class_type != Type.INVALID && signal_name != null)
- {
- /* Connecting to a given signal on all instances of a class */
- uint signal_id = Signal.lookup (signal_name, class_type);
- if (signal_id == 0)
- {
- Utils.print_line ("Unrecognised signal name '%s' on class '%s'.",
- signal_name, class_type.name ());
- return 0;
- }
-
- if (this.add_emission_hook (class_type, signal_id,
- detail_string) == false)
- {
- Utils.print_line ("Not allowed to connect to signal '%s' on " +
- "class '%s'.", signal_name, class_type.name ());
- return 0;
- }
-
- return 1;
- }
- else if (class_type != Type.INVALID && signal_name == null)
- {
- /* Connecting to all signals on all instances of a class */
- uint[] signal_ids = Signal.list_ids (class_type);
- uint signal_count = 0;
-
- foreach (uint signal_id in signal_ids)
- {
- if (this.add_emission_hook (class_type, signal_id, null) == true)
- signal_count++;
- }
-
- return signal_count;
- }
- else if (class_instance != null && signal_name != null)
- {
- /* Connecting to a given signal on a given class instance */
- uint signal_id =
- Signal.lookup (signal_name, class_instance.get_type ());
- if (signal_id == 0)
- {
- Utils.print_line ("Unrecognised signal name '%s' on instance " +
- "%p of class '%s'.", signal_name, class_instance,
- class_instance.get_type ().name ());
- return 0;
- }
-
- this.add_signal_handler (class_instance, signal_id, detail_string);
-
- return 1;
- }
- else if (class_instance != null && signal_name == null)
- {
- /* Connecting to all signals on a given class instance */
- uint[] signal_ids = Signal.list_ids (class_instance.get_type ());
- uint signal_count = 0;
-
- foreach (uint signal_id in signal_ids)
- {
- signal_count++;
- this.add_signal_handler (class_instance, signal_id, null);
- }
-
- return signal_count;
- }
-
- assert_not_reached ();
- }
-
- public uint disconnect_from_signal (Type class_type,
- Object? class_instance,
- string? signal_name,
- string? detail_string)
- {
- /* We return the number of signals we disconnected from */
- if (class_type != Type.INVALID && signal_name != null)
- {
- /* Disconnecting from a given signal on all instances of a class */
- uint signal_id = Signal.lookup (signal_name, class_type);
- if (signal_id == 0)
- {
- Utils.print_line ("Unrecognised signal name '%s' on class '%s'.",
- signal_name, class_type.name ());
- return 0;
- }
-
- if (this.remove_emission_hook (class_type, signal_id) == false)
- {
- Utils.print_line ("Could not remove hook for signal '%s' on " +
- "class '%s'.", signal_name, class_type.name ());
- return 0;
- }
-
- return 1;
- }
- else if (class_type != Type.INVALID && signal_name == null)
- {
- /* Disconnecting from all signals on all instances of a class */
- uint[] signal_ids = Signal.list_ids (class_type);
- uint signal_count = 0;
-
- foreach (uint signal_id in signal_ids)
- {
- if (this.remove_emission_hook (class_type, signal_id) == true)
- signal_count--;
- }
-
- return signal_count;
- }
- else if (class_instance != null && signal_name != null)
- {
- /* Disconnecting from a given signal on a given class instance */
- uint signal_id =
- Signal.lookup (signal_name, class_instance.get_type ());
- if (signal_id == 0)
- {
- Utils.print_line ("Unrecognised signal name '%s' on instance " +
- "%p of class '%s'.", signal_name, class_instance,
- class_instance.get_type ().name ());
- return 0;
- }
-
- this.remove_signal_handler (class_instance, signal_id);
-
- return 1;
- }
- else if (class_instance != null && signal_name == null)
- {
- /* Disconnecting from all signals on a given class instance */
- uint[] signal_ids = Signal.list_ids (class_instance.get_type ());
- uint signal_count = 0;
-
- foreach (uint signal_id in signal_ids)
- {
- if (this.remove_signal_handler (class_instance, signal_id))
- signal_count--;
- }
-
- return signal_count;
- }
-
- assert_not_reached ();
- }
-
- private void list_signals_for_type (Type type,
- HashMap<uint, ulong>? signal_id_map)
- {
- uint[] signal_ids = Signal.list_ids (type);
-
- /* Print information about the signals on this type */
- if (signal_ids != null)
- {
- string type_name = type.name ();
- foreach (uint signal_id in signal_ids)
- {
- unowned string signal_name = Signal.name (signal_id);
-
- if (signal_id_map != null &&
- signal_id_map.has_key (signal_id) == true)
- {
- Utils.print_line ("%s::%s — connected",
- type_name, signal_name);
- }
- else
- {
- Utils.print_line ("%s::%s",
- type_name, signal_name);
- }
- }
- }
-
- /* Recurse to the type's interfaces */
- Type[] interfaces = type.interfaces ();
- foreach (Type interface_type in interfaces)
- this.list_signals_for_type (interface_type, signal_id_map);
-
- /* Chain up to the type's parent */
- Type parent_type = type.parent ();
- if (parent_type != Type.INVALID)
- this.list_signals_for_type (parent_type, signal_id_map);
- }
-
- /* FIXME: This is necessary because if we do sizeof(Closure), Vala will
- * generate the following C code: sizeof(GClosure*).
- * This is not what we want. */
- [CCode (cname = "sizeof (GClosure)")] extern const int CLOSURE_STRUCT_SIZE;
-
- private void add_signal_handler (Object class_instance,
- uint signal_id,
- string? detail_string)
- {
- Closure closure = new Closure (SignalManager.CLOSURE_STRUCT_SIZE, this);
- closure.set_meta_marshal (null, SignalManager.signal_meta_marshaller);
-
- Quark detail_quark = 0;
- if (detail_string != null)
- detail_quark = Quark.try_string (detail_string);
-
- ulong signal_handler_id = Signal.connect_closure_by_id (class_instance,
- signal_id, detail_quark, closure, false);
-
- /* Store the signal handler ID so we can list or remove it later */
- HashMap<uint, ulong> signal_handler_ids =
- this.signals_by_class_instance.get (class_instance);
- if (signal_handler_ids == null)
- {
- signal_handler_ids = new HashMap<uint, ulong> ();
- this.signals_by_class_instance.set (class_instance,
- signal_handler_ids);
- }
-
- signal_handler_ids.set (signal_id, signal_handler_id);
- }
-
- private bool remove_signal_handler (Object class_instance,
- uint signal_id)
- {
- HashMap<uint, ulong> signal_handler_ids =
- this.signals_by_class_instance.get (class_instance);
-
- if (signal_handler_ids == null ||
- signal_handler_ids.has_key (signal_id) == false)
- {
- return false;
- }
-
- ulong signal_handler_id = signal_handler_ids.get (signal_id);
- SignalHandler.disconnect (class_instance, signal_handler_id);
- signal_handler_ids.unset (signal_id);
-
- return true;
- }
-
- private static void signal_meta_marshaller (Closure closure,
- out Value? return_value,
- Value[] param_values,
- void *invocation_hint,
- void *marshal_data)
- {
- SignalInvocationHint* hint = (SignalInvocationHint*) invocation_hint;
-
- /* Default output */
- return_value = null;
-
- SignalQuery query_info;
- Signal.query (hint->signal_id, out query_info);
-
- Utils.print_line ("Signal '%s::%s' emitted with parameters:",
- query_info.itype.name (), query_info.signal_name);
-
- Utils.indent ();
- uint i = 0;
- foreach (Value param_value in param_values)
- {
- Utils.print_line ("%-4u %-10s %s", i++, param_value.type ().name (),
- Utils.transform_value_to_string (param_value));
- }
- Utils.unindent ();
- }
-
- private bool add_emission_hook (Type class_type,
- uint signal_id,
- string? detail_string)
- {
- Quark detail_quark = 0;
- if (detail_string != null)
- detail_quark = Quark.try_string (detail_string);
-
- /* Query the signal to check it supports emission hooks */
- SignalQuery query;
- Signal.query (signal_id, out query);
-
- /* FIXME: It would be nice if we could find some way to support NO_HOOKS
- * signals. */
- if ((query.signal_flags & SignalFlags.NO_HOOKS) != 0)
- return false;
-
- ulong hook_id = Signal.add_emission_hook (signal_id,
-#if VALA_0_42
- detail_quark, this.emission_hook_cb);
-#else
- detail_quark, this.emission_hook_cb, null);
-#endif
-
- /* Store the hook ID so we can list or remove it later */
- HashMap<uint, ulong> hook_ids =
- this.signals_by_class_type.get (class_type);
- if (hook_ids == null)
- {
- hook_ids = new HashMap<uint, ulong> ();
- this.signals_by_class_type.set (class_type, hook_ids);
- }
-
- hook_ids.set (signal_id, hook_id);
-
- return true;
- }
-
- private bool remove_emission_hook (Type class_type,
- uint signal_id)
- {
- HashMap<uint, ulong> hook_ids =
- this.signals_by_class_type.get (class_type);
-
- if (hook_ids == null || hook_ids.has_key (signal_id) == false)
- return false;
-
- ulong hook_id = hook_ids.get (signal_id);
- Signal.remove_emission_hook (signal_id, hook_id);
- hook_ids.unset (signal_id);
-
- return true;
- }
-
- private bool emission_hook_cb (SignalInvocationHint hint,
- Value[] param_values)
- {
- SignalQuery query_info;
- Signal.query (hint.signal_id, out query_info);
-
- Utils.print_line ("Signal '%s::%s' emitted with parameters:",
- query_info.itype.name (), query_info.signal_name);
-
- Utils.indent ();
- uint i = 0;
- foreach (Value param_value in param_values)
- {
- Utils.print_line ("%-4u %-10s %s", i++, param_value.type ().name (),
- Utils.transform_value_to_string (param_value));
- }
- Utils.unindent ();
-
- return true;
- }
-
- private static string signal_flags_to_string (SignalFlags flags)
- {
- string output = "";
-
- if ((flags & SignalFlags.RUN_FIRST) != 0)
- output += "G_SIGNAL_RUN_FIRST";
- if ((flags & SignalFlags.RUN_LAST) != 0)
- output += ((output != "") ? " | " : "") + "G_SIGNAL_RUN_LAST";
- if ((flags & SignalFlags.RUN_CLEANUP) != 0)
- output += ((output != "") ? " | " : "") + "G_SIGNAL_RUN_CLEANUP";
- if ((flags & SignalFlags.DETAILED) != 0)
- output += ((output != "") ? " | " : "") + "G_SIGNAL_DETAILED";
- if ((flags & SignalFlags.ACTION) != 0)
- output += ((output != "") ? " | " : "") + "G_SIGNAL_ACTION";
- if ((flags & SignalFlags.NO_HOOKS) != 0)
- output += ((output != "") ? " | " : "") + "G_SIGNAL_NO_HOOKS";
-
- return output;
- }
-}
diff --git a/tools/inspect/utils.vala b/tools/inspect/utils.vala
deleted file mode 100644
index 4bdffbd9..00000000
--- a/tools/inspect/utils.vala
+++ /dev/null
@@ -1,647 +0,0 @@
-/*
- * Copyright (C) 2010 Collabora Ltd.
- *
- * This library is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Philip Withnall <philip.withnall@collabora.co.uk>
- */
-
-using Folks;
-using Gee;
-using GLib;
-
-private class Folks.Inspect.Utils
-{
- /* The current indentation level, in spaces */
- private static uint indentation = 0;
- private static string indentation_string = "";
-
- /* The FILE we're printing output to. */
- public static unowned FileStream output_filestream = GLib.stdout;
-
- public static void init ()
- {
- Utils.indentation_string = "";
- Utils.indentation = 0;
- Utils.output_filestream = GLib.stdout;
-
- /* Register some general transformation functions */
- Value.register_transform_func (typeof (Object), typeof (string),
- Utils.transform_object_to_string);
- Value.register_transform_func (typeof (Folks.PersonaStore),
- typeof (string), Utils.transform_persona_store_to_string);
- Value.register_transform_func (typeof (string[]), typeof (string),
- Utils.transform_string_array_to_string);
- Value.register_transform_func (typeof (DateTime), typeof (string),
- Utils.transform_date_time_to_string);
- }
-
- private static void transform_object_to_string (Value src,
- out Value dest)
- {
- var output = "%p".printf (src.get_object ());
- dest = (owned) output;
- }
-
- private static void transform_persona_store_to_string (Value src,
- out Value dest)
- {
- var store = (Folks.PersonaStore) src;
- var output = "%p: %s, %s (%s)".printf (store, store.type_id,
- store.id, store.display_name);
- dest = (owned) output;
- }
-
- private static void transform_string_array_to_string (Value src,
- out Value dest)
- {
- unowned string[] array = (string[]) src;
- string output = "{ ";
- bool first = true;
- foreach (unowned string element in array)
- {
- if (first == false)
- output += ", ";
- output += "'%s'".printf (element);
- first = false;
- }
- output += " }";
- dest = (owned) output;
- }
-
- private static void transform_date_time_to_string (Value src, out Value dest)
- {
- unowned DateTime? date_time = (DateTime?) src;
- string output = "(null)";
- if (date_time != null)
- {
- output = ((!) date_time).format ("%FT%T%z");
- }
-
- dest = (owned) output;
- }
-
- public static void indent ()
- {
- /* We indent in increments of two spaces */
- Utils.indentation += 2;
- Utils.indentation_string = string.nfill (Utils.indentation, ' ');
- }
-
- public static void unindent ()
- {
- Utils.indentation -= 2;
- Utils.indentation_string = string.nfill (Utils.indentation, ' ');
- }
-
- [PrintfFormat ()]
- public static void print_line (string format, ...)
- {
- /* FIXME: store the va_list temporarily to work around bgo#638308 */
- var valist = va_list ();
- string output = format.vprintf (valist);
- var str = "%s%s".printf (Utils.indentation_string, output);
- Utils.output_filestream.puts (str);
- }
-
- public static void print_blank_line ()
- {
- Utils.output_filestream.puts ("");
- }
-
- public static void print_individual (Individual individual,
- bool show_personas)
- {
- Utils.print_line ("Individual '%s' with %u personas:",
- individual.id, individual.personas.size);
-
- /* List the Individual's properties */
- var properties = individual.get_class ().list_properties ();
-
- Utils.indent ();
- foreach (unowned ParamSpec pspec in properties)
- {
- Value prop_value;
- string output_string;
-
- /* Ignore the personas property if we're printing the personas out */
- if (show_personas == true && pspec.get_name () == "personas")
- continue;
-
- prop_value = Value (pspec.value_type);
- individual.get_property (pspec.get_name (), ref prop_value);
-
- output_string = Utils.property_to_string (individual.get_type (),
- pspec.get_name (), prop_value);
-
- Utils.print_line ("%-20s %s", pspec.get_nick (), output_string);
- }
-
- if (show_personas == true)
- {
- Utils.print_blank_line ();
- Utils.print_line ("Personas:");
-
- Utils.indent ();
- foreach (Persona persona in individual.personas)
- Utils.print_persona (persona);
- Utils.unindent ();
- }
- Utils.unindent ();
- }
-
- public static void print_persona (Persona persona)
- {
- Utils.print_line ("Persona '%s':", persona.uid);
-
- /* List the Persona's properties */
- var properties = persona.get_class ().list_properties ();
-
- Utils.indent ();
- foreach (unowned ParamSpec pspec in properties)
- {
- Value prop_value;
- string output_string;
-
- prop_value = Value (pspec.value_type);
- persona.get_property (pspec.get_name (), ref prop_value);
-
- output_string = Utils.property_to_string (persona.get_type (),
- pspec.get_name (), prop_value);
-
- Utils.print_line ("%-20s %s", pspec.get_nick (), output_string);
- }
- Utils.unindent ();
- }
-
- public static void print_persona_store (PersonaStore store,
- bool show_personas)
- {
- if (store.is_prepared == false)
- {
- Utils.print_line ("Persona store '%s':", store.id);
- Utils.indent ();
- Utils.print_line ("Not prepared.");
- Utils.unindent ();
-
- return;
- }
-
- Utils.print_line ("Persona store '%s' with %u personas:",
- store.id, store.personas.size);
-
- /* List the store's properties */
- var properties = store.get_class ().list_properties ();
-
- Utils.indent ();
- foreach (unowned ParamSpec pspec in properties)
- {
- Value prop_value;
- string output_string;
-
- /* Ignore the personas property if we're printing the personas out */
- if (show_personas == true && pspec.get_name () == "personas")
- continue;
-
- prop_value = Value (pspec.value_type);
- store.get_property (pspec.get_name (), ref prop_value);
-
- output_string = Utils.property_to_string (store.get_type (),
- pspec.get_name (), prop_value);
-
- Utils.print_line ("%-20s %s", pspec.get_nick (), output_string);
- }
-
- if (show_personas == true)
- {
- Utils.print_blank_line ();
- Utils.print_line ("Personas:");
-
- Utils.indent ();
- foreach (var persona in store.personas.values)
- {
- Utils.print_persona (persona);
- }
- Utils.unindent ();
- }
- Utils.unindent ();
- }
-
- private static string property_to_string (Type object_type,
- string prop_name,
- Value prop_value)
- {
- string output_string;
-
- /* Overrides for various known properties */
- if (object_type.is_a (typeof (Individual)) && prop_name == "personas")
- {
- unowned var personas = (Set<Persona>) prop_value.get_object ();
- return "List of %u personas".printf (personas.size);
- }
- else if (object_type.is_a (typeof (PersonaStore)) &&
- prop_name == "personas")
- {
- unowned var personas =
- (Map<string, Persona>) prop_value.get_object ();
- return "Set of %u personas".printf (personas.size);
- }
- else if (prop_name == "groups" ||
- prop_name == "local-ids" ||
- prop_name == "supported-fields" ||
- prop_name == "anti-links")
- {
- unowned var groups = (Set<string>) prop_value.get_object ();
- output_string = "{ ";
- bool first = true;
-
- foreach (var group in groups)
- {
- if (first == false)
- output_string += ", ";
- output_string += "'%s'".printf (group);
- first = false;
- }
-
- output_string += " }";
- return output_string;
- }
- else if (prop_name == "avatar")
- {
- string ret = null;
- unowned var avatar = (LoadableIcon) prop_value.get_object ();
-
- if (avatar != null &&
- avatar is FileIcon && ((FileIcon) avatar).get_file () != null)
- {
- ret = "%p (file: %s)".printf (avatar,
- ((FileIcon) avatar).get_file ().get_uri ());
- }
- else if (avatar != null)
- {
- ret = "%p".printf (avatar);
- }
-
- return ret;
- }
- else if (prop_name == "file")
- {
- string ret = null;
- unowned File? file = (File) prop_value.get_object ();
-
- if (file != null)
- {
- ret = "%p (file: %s)".printf (file, file.get_uri ());
- }
-
- return ret;
- }
- else if (prop_name == "im-addresses" ||
- prop_name == "web-service-addresses")
- {
- unowned var prop_list =
- (MultiMap<string, AbstractFieldDetails<string>>)
- prop_value.get_object ();
- output_string = "{ ";
- bool first = true;
-
- foreach (var k in prop_list.get_keys ())
- {
- if (first == false)
- output_string += ", ";
- output_string += "'%s' : { ".printf (k);
- first = false;
-
- var v = prop_list.get (k);
- bool _first = true;
- foreach (var a in v)
- {
- if (_first == false)
- output_string += ", ";
- output_string += "'%s'".printf (a.value);
- _first = false;
- }
-
- output_string += " }";
- }
-
- output_string += " }";
- return output_string;
- }
- else if (prop_name == "email-addresses" ||
- prop_name == "phone-numbers" ||
- prop_name == "urls")
- {
- output_string = "{ ";
- bool first = true;
- unowned var prop_list =
- (Set<AbstractFieldDetails<string>>) prop_value.get_object ();
-
- foreach (var p in prop_list)
- {
- if (!first)
- {
- output_string += ", ";
- }
- output_string += p.value;
- first = false;
- }
- output_string += " }";
-
- return output_string;
- }
- else if (prop_name == "birthday")
- {
- unowned DateTime dobj = (DateTime) prop_value.get_boxed ();
- if (dobj != null)
- return dobj.to_string ();
- else
- return "";
- }
- else if (prop_name == "postal-addresses")
- {
- output_string = "{ ";
- bool first = true;
- unowned var prop_list =
- (Set<PostalAddressFieldDetails>) prop_value.get_object ();
-
- foreach (var p in prop_list)
- {
- if (!first)
- {
- output_string += ". ";
- }
- output_string += p.value.to_string ();
- first = false;
- }
- output_string += " }";
-
- return output_string;
- }
- else if (prop_name == "notes")
- {
- unowned var notes =
- prop_value.get_object () as Set<NoteFieldDetails>;
-
- output_string = "{ ";
- bool first = true;
-
- foreach (var note in notes)
- {
- if (!first)
- {
- output_string += ", ";
- }
- output_string += note.id;
- first = false;
- }
- output_string += " }";
-
- return output_string;
- }
- else if (prop_name == "roles")
- {
- unowned var roles = (Set<RoleFieldDetails>) prop_value.get_object ();
-
- output_string = "{ ";
- bool first = true;
-
- foreach (var role in roles)
- {
- if (!first)
- {
- output_string += ", ";
- }
- output_string += role.value.to_string ();
- first = false;
- }
- output_string += " }";
-
- return output_string;
- }
- else if (prop_name == "structured-name")
- {
- unowned StructuredName sn = (StructuredName) prop_value.get_object ();
- string ret = null;
- if (sn != null)
- ret = sn.to_string ();
- return ret;
- }
-
- return Utils.transform_value_to_string (prop_value);
- }
-
- public static string transform_value_to_string (Value prop_value)
- {
- if (Value.type_transformable (prop_value.type (), typeof (string)))
- {
- /* Convert to a string value */
- Value string_value = Value (typeof (string));
- prop_value.transform (ref string_value);
- return string_value.get_string ();
- }
- else
- {
- /* Can't convert the property value to a string */
- return "Can't convert from type '%s' to '%s'".printf (
- prop_value.type ().name (), typeof (string).name ());
- }
- }
-
- /* FIXME: This can't be in the command_completion_cb() function because Vala
- * doesn't allow static local variables. Erk. */
- private static MapIterator<string, Command>? command_name_iter = null;
-
- /* Complete a command name, starting with @word. */
- public static string? command_name_completion_cb (string word,
- int state)
- {
- /* Initialise state. Whoever wrote the readline API should be shot. */
- if (state == 0)
- Utils.command_name_iter = main_client.commands.map_iterator ();
-
- while (Utils.command_name_iter.next () == true)
- {
- string command_name = Utils.command_name_iter.get_key ();
- if (command_name.has_prefix (word))
- return command_name;
- }
-
- /* Clean up */
- Utils.command_name_iter = null;
- return null;
- }
-
- /* FIXME: This can't be in the individual_id_completion_cb() function because
- * Vala doesn't allow static local variables. Erk. */
- private static MapIterator<string, Individual>? individual_id_iter = null;
-
- /* Complete an individual's ID, starting with @word. */
- public static string? individual_id_completion_cb (string word,
- int state)
- {
- /* Initialise state. Whoever wrote the readline API should be shot. */
- if (state == 0)
- {
- Utils.individual_id_iter =
- main_client.aggregator.individuals.map_iterator ();
- }
-
- while (Utils.individual_id_iter.next () == true)
- {
- var id = Utils.individual_id_iter.get_key ();
- if (id.has_prefix (word))
- return id;
- }
-
- /* Clean up */
- Utils.individual_id_iter = null;
- return null;
- }
-
- /* FIXME: This can't be in the individual_id_completion_cb() function because
- * Vala doesn't allow static local variables. Erk. */
- private static Iterator<Persona>? persona_uid_iter = null;
-
- /* Complete an individual's ID, starting with @word. */
- public static string? persona_uid_completion_cb (string word,
- int state)
- {
- /* Initialise state. Whoever wrote the readline API should be shot. */
- if (state == 0)
- {
- Utils.individual_id_iter =
- main_client.aggregator.individuals.map_iterator ();
- Utils.persona_uid_iter = null;
- }
-
- while (Utils.persona_uid_iter != null ||
- Utils.individual_id_iter.next () == true)
- {
- var individual = Utils.individual_id_iter.get_value ();
-
- if (Utils.persona_uid_iter == null)
- {
- assert (individual != null);
- Utils.persona_uid_iter = individual.personas.iterator ();
- }
-
- while (Utils.persona_uid_iter.next ())
- {
- var persona = Utils.persona_uid_iter.get ();
- if (persona.uid.has_prefix (word))
- return persona.uid;
- }
-
- /* Clean up */
- Utils.persona_uid_iter = null;
- }
-
- /* Clean up */
- Utils.individual_id_iter = null;
- return null;
- }
-
- /* FIXME: This can't be in the backend_name_completion_cb() function because
- * Vala doesn't allow static local variables. Erk. */
- private static Iterator<Backend>? backend_name_iter = null;
-
- /* Complete an individual's ID, starting with @word. */
- public static string? backend_name_completion_cb (string word,
- int state)
- {
- /* Initialise state. Whoever wrote the readline API should be shot. */
- if (state == 0)
- {
- Utils.backend_name_iter =
- main_client.backend_store.list_backends ().iterator ();
- }
-
- while (Utils.backend_name_iter.next () == true)
- {
- Backend backend = Utils.backend_name_iter.get ();
- if (backend.name.has_prefix (word))
- return backend.name;
- }
-
- /* Clean up */
- Utils.backend_name_iter = null;
- return null;
- }
-
- /* FIXME: This can't be in the persona_store_id_completion_cb() function
- * because Vala doesn't allow static local variables. Erk. */
- private static MapIterator<string, PersonaStore>? persona_store_id_iter =
- null;
-
- /* Complete a persona store's ID, starting with @word. */
- public static string? persona_store_id_completion_cb (string word,
- int state)
- {
- /* Initialise state. Whoever wrote the readline API should be shot. */
- if (state == 0)
- {
- Utils.backend_name_iter =
- main_client.backend_store.list_backends ().iterator ();
- Utils.persona_store_id_iter = null;
- }
-
- while (Utils.persona_store_id_iter != null ||
- Utils.backend_name_iter.next () == true)
- {
- if (Utils.persona_store_id_iter == null)
- {
- Backend backend = Utils.backend_name_iter.get ();
- Utils.persona_store_id_iter =
- backend.persona_stores.map_iterator ();
- }
-
- while (Utils.persona_store_id_iter.next () == true)
- {
- var id = Utils.persona_store_id_iter.get_key ();
- if (id.has_prefix (word))
- return id;
- }
-
- /* Clean up */
- Utils.persona_store_id_iter = null;
- }
-
- /* Clean up */
- Utils.backend_name_iter = null;
- return null;
- }
-
- /* Command validation code for commands which take a well-known set of
- * subcommands. */
- public static bool validate_subcommand (string command,
- string? command_string, string? subcommand, string[] valid_subcommands)
- {
- if (subcommand != null && subcommand in valid_subcommands)
- return true;
-
- /* Print an error. */
- Utils.print_line ("Unrecognised '%s' command '%s'.", command,
- (command_string != null) ? command_string : "");
-
- Utils.print_line ("Valid commands:");
- Utils.indent ();
- foreach (var c in valid_subcommands)
- Utils.print_line ("%s", c);
- Utils.unindent ();
-
- return false;
- }
-}
diff --git a/tools/meson.build b/tools/meson.build
index f91ffde0..a9ba4155 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -1,37 +1,11 @@
-# folks-inspect
-if inspect_tool_enabled
- subdir('inspect')
-endif
+add_languages('vala')
-# folks-import
-if import_tool_enabled
- folks_import_sources = [
- 'import.vala',
- 'import-pidgin.vala',
- ]
-
- folks_import_deps = [
- build_conf_dep,
- gee_dep,
- gobject_dep,
+executable(
+ 'folks-inspect',
+ 'inspect.vala',
+ dependencies: [
+ gio_dep,
glib_dep,
- libfolks_dep,
- libxml_dep,
- ]
-
- folks_import_vala_flags = [
- ]
-
- folks_import_c_flags = [
- '-include', 'config.h',
- ]
-
- folks_import = executable('folks-import',
- folks_import_sources,
- dependencies: folks_import_deps,
- vala_args: folks_import_vala_flags,
- c_args: folks_import_c_flags,
- include_directories: config_h_dir,
- install: true,
- )
-endif
+ folks_dep,
+ ],
+) \ No newline at end of file