diff options
author | Corentin Noël <tintou@noel.tf> | 2023-04-29 14:59:56 +0200 |
---|---|---|
committer | Corentin Noël <tintou@noel.tf> | 2023-05-15 23:41:01 +0200 |
commit | 0b3c48b6d328789e4d08f74d9b2eecf5cfd967ce (patch) | |
tree | 12ceed2e8198b1aba8d3e6ae401d4c92d15e48b4 | |
parent | 8f8010e4da2fc1d88c71a13330f7aaad7e2ff817 (diff) | |
download | folks-tintou/newlib.tar.gz |
Next libtintou/newlib
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 <foo@bar.com>”). - * - * @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 |