--- /dev/null
- this.logger = dbus_conn.get_object_for_name_owner (
+ /*
+ * 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 Tp;
+ using Folks;
+
+ private struct AccountFavourites
+ {
+ DBus.ObjectPath account_path;
+ string[] ids;
+ }
+
+ [DBus (name = "org.freedesktop.Telepathy.Logger.DRAFT")]
+ private interface LoggerIface : DBus.Object
+ {
+ public abstract async AccountFavourites[] get_favourite_contacts ()
+ throws DBus.Error;
+ public abstract async void add_favourite_contact (
+ DBus.ObjectPath account_path, string id) throws DBus.Error;
+ public abstract async void remove_favourite_contact (
+ DBus.ObjectPath account_path, string id) throws DBus.Error;
+
+ public abstract signal void favourite_contacts_changed (
+ DBus.ObjectPath account_path, string[] added, string[] removed);
+ }
+
+ internal class Logger : GLib.Object
+ {
+ private static LoggerIface logger;
+ private string account_path;
+
+ public signal void favourite_contacts_changed (string[] added,
+ string[] removed);
+
+ public Logger (string account_path) throws DBus.Error
+ {
+ if (this.logger == null)
+ {
+ /* Create a logger proxy for favourites support */
+ /* FIXME: This should be ported to the Vala GDBus stuff and made
+ * async, but that depends on
+ * https://bugzilla.gnome.org/show_bug.cgi?id=622611 being fixed.
+ * FIXME: If this is made async, race conditions may appear in
+ * TpfPersonaStore, which will need to be prevented. e.g.
+ * change_is_favourite() may get called before logger initialisation
+ * is complete; favourites-change requests should be queued. */
+ var dbus_conn = DBus.Bus.get (DBus.BusType.SESSION);
++ this.logger = dbus_conn.get_object (
+ "org.freedesktop.Telepathy.Logger",
+ "/org/freedesktop/Telepathy/Logger",
+ "org.freedesktop.Telepathy.Logger.DRAFT") as LoggerIface;
+ }
+
+ this.account_path = account_path;
+ this.logger.favourite_contacts_changed.connect ((ap, a, r) =>
+ {
+ if (ap != this.account_path)
+ return;
+
+ this.favourite_contacts_changed (a, r);
+ });
+ }
+
+ public async string[] get_favourite_contacts () throws DBus.Error
+ {
+ AccountFavourites[] favs = yield this.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 DBus.Error
+ {
+ yield this.logger.add_favourite_contact (
+ new DBus.ObjectPath (this.account_path), id);
+ }
+
+ public async void remove_favourite_contact (string id) throws DBus.Error
+ {
+ yield this.logger.remove_favourite_contact (
+ new DBus.ObjectPath (this.account_path), id);
+ }
+ }
this.account_status_changed_cb (Tp.ConnectionStatus.DISCONNECTED,
status, reason, null, null);
}
- debug ("attempting to list favourite contacts...");
+
+ try
+ {
+ this.logger = new Logger (this.id);
+ this.logger.favourite_contacts_changed.connect (
+ this.favourite_contacts_changed_cb);
+ }
+ catch (DBus.Error e)
+ {
+ warning ("couldn't connect to the telepathy-logger service");
+ this.logger = null;
+ }
+ }
+
+ private async void initialise_favourite_contacts ()
+ {
+ /* Get an initial set of favourite contacts */
+ try
+ {
- {
- debug ("adding '%s' as a favourite", ids[i]);
- this.favourite_handles.add (h);
- }
+ string[] contacts = yield this.logger.get_favourite_contacts ();
+
+ if (contacts.length == 0)
+ return;
+
+ /* Note that we don't need to release these handles, as they're
+ * also held by the relevant contact objects, and will be released
+ * as appropriate by those objects (we're circumventing tp-glib's
+ * handle reference counting). */
+ this.conn.request_handles (-1, HandleType.CONTACT, contacts,
+ (c, ht, nh, h, i, e, w) =>
+ {
+ try
+ {
+ this.change_favourites_by_request_handles (nh, h, i, e,
+ true);
+ }
+ catch (GLib.Error e)
+ {
+ warning ("couldn't get list of favourite contacts: %s",
+ e.message);
+ }
+ },
+ this);
+ /* FIXME: Have to pass this as weak_object parameter since Vala
+ * seems to swap the order of user_data and weak_object in the
+ * callback. */
+ }
+ catch (DBus.Error e)
+ {
+ warning ("couldn't get list of favourite contacts: %s", e.message);
+ }
+ }
+
+ private void change_favourites_by_request_handles (uint n_handles,
+ Handle[] handles, string[] ids, GLib.Error? error,
+ bool add) throws GLib.Error
+ {
+ if (error != null)
+ throw error;
+
+ for (var i = 0; i < n_handles; i++)
+ {
+ Handle h = handles[i];
+ Persona p = this.handle_persona_map[h];
+
+ /* Add/Remove the handle to the set of favourite handles, since we
+ * might not have the corresponding contact yet */
+ if (add)
- {
- debug ("removing '%s' as a favourite", ids[i]);
- this.favourite_handles.remove (h);
- }
++ this.favourite_handles.add (h);
+ else
- debug ("favourite_contacts_changed_cb for account '%s'", this.id);
-
++ this.favourite_handles.remove (h);
+
+ if (p == null)
+ {
+ warning ("unknown persona '%s' in favourites list", ids[i]);
+ continue;
+ }
+
+ /* Mark or unmark the persona as a favourite */
+ p.is_favourite = add;
+ }
+ }
+
+ private void favourite_contacts_changed_cb (string[] added, string[] removed)
+ {
+ /* Don't listen to favourites updates if the account is disconnected. */
+ if (this.conn == null)
+ return;
+
+ /* Add favourites */
+ if (added.length > 0)
+ {
+ this.conn.request_handles (-1, HandleType.CONTACT, added,
+ (c, ht, nh, h, i, e, w) =>
+ {
+ try
+ {
+ this.change_favourites_by_request_handles (nh, h, i, e,
+ true);
+ }
+ catch (GLib.Error e)
+ {
+ warning ("couldn't add favourite contacts: %s",
+ e.message);
+ }
+ },
+ this);
+ }
+
+ /* Remove favourites */
+ if (removed.length > 0)
+ {
+ this.conn.request_handles (-1, HandleType.CONTACT, removed,
+ (c, ht, nh, h, i, e, w) =>
+ {
+ try
+ {
+ this.change_favourites_by_request_handles (nh, h, i, e,
+ false);
+ }
+ catch (GLib.Error e)
+ {
+ warning ("couldn't remove favourite contacts: %s",
+ e.message);
+ }
+ },
+ this);
+ }
}
private void account_status_changed_cb (ConnectionStatus old_status,
return null;
}
- {
- debug ("adding '%s' as a favourite contact", id);
- yield this.logger.add_favourite_contact (id);
- }
+
+ public async void change_is_favourite (Folks.Persona persona,
+ bool is_favourite)
+ {
+ /* It's possible for us to not be able to connect to the logger;
+ * see connection_ready_cb() */
+ if (this.logger == null)
+ {
+ warning ("failed to change favourite without connection to the " +
+ "telepathy-logger service");
+ return;
+ }
+
+ try
+ {
+ /* Add or remove the persona to the list of favourites as
+ * appropriate. */
+ var id = ((Tpf.Persona) persona).contact.get_identifier ();
+
+ if (is_favourite)
- {
- debug ("removing '%s' as a favourite contact", id);
- yield this.logger.remove_favourite_contact (id);
- }
++ yield this.logger.add_favourite_contact (id);
+ else
++ yield this.logger.remove_favourite_contact (id);
+ }
+ catch (DBus.Error e)
+ {
+ warning ("failed to change a persona's favourite status");
+ }
+ }
}
public override Folks.PresenceType presence_type { get; private set; }
public override string presence_message { get; private set; }
- debug ("tpf_persona_set_is_favourite called");
+ /* interface Favourite */
+ public override bool is_favourite
+ {
+ get { return this._is_favourite; }
+
+ set
+ {
+ if (this._is_favourite == value)
+ return;
+
+ ((Tpf.PersonaStore) this.store).change_is_favourite (this, value);
+ this._is_favourite = value;
+ }
+ }
+
/* interface Groups */
public HashTable<string, bool> groups
{
m4_define([folks_major_version], [0])
m4_define([folks_minor_version], [1])
- m4_define([folks_micro_version], [5])
-m4_define([folks_micro_version], [3])
++m4_define([folks_micro_version], [6])
m4_define([folks_nano_version], [0])
dnl Display the nano_version only if it's not '0'
--- /dev/null
+ /*
+ * 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;
+
++/**
++ * Interface exposing a {@link Persona}'s or {@link Individual}'s user-defined
++ * status as a favourite.
++ */
+ public interface Folks.Favourite : Object
+ {
++ /**
++ * Whether this contact is a user-defined favourite.
++ */
+ public abstract bool is_favourite { get; set; }
+ }
/* XXX: should setting this push it down into the Persona (to foward along to
* the actual store if possible?) */
+ /**
+ * {@inheritDoc}
+ */
public string alias { get; set; }
+
+ /**
+ * {@inheritDoc}
+ */
public File avatar { get; set; }
+
+ /**
+ * {@inheritDoc}
+ */
public CapabilitiesFlags capabilities { get; private set; }
- public string id { get; private set; }
+
+ /**
+ * {@inheritDoc}
+ */
public Folks.PresenceType presence_type { get; private set; }
+
+ /**
+ * {@inheritDoc}
+ */
public string presence_message { get; private set; }
- /* the last of this individuals personas has been removed, so it is invalid */
+ /**
+ * A unique identifier for the Individual.
+ *
+ * This uniquely identifies the Individual, and persists across
+ * {@link IndividualAggregator} instances.
+ *
+ * FIXME: Will this.id actually be the persistent ID for storage?
+ */
+ public string id { get; private set; }
+
+ /**
+ * 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.
+ */
public signal void removed ();
+ /**
++ * Whether this Individual is a user-defined favourite.
++ *
++ * This property is `true` if any of this Individual's {@link Persona}s are
++ * favourites).
++ *
++ * When set, the value is propagated to all of this Individual's
++ * {@link Persona}s.
++ */
+ public bool is_favourite
+ {
+ get { return this._is_favourite; }
+
+ /* Propagate the new favourite status to every Persona, but only if it's
+ * changed. */
+ set
+ {
+ if (this._is_favourite == value)
+ return;
+
+ this._is_favourite = value;
+ this._personas.foreach ((p) =>
+ {
+ if (p is Favourite)
+ ((Favourite) p).is_favourite = value;
+ });
+ }
+ }
+
++ /**
+ * {@inheritDoc}
+ */
public HashTable<string, bool> groups
{
get { return this._groups; }
this.update_presence ();
}
+ private void notify_is_favourite_cb (Object obj, ParamSpec ps)
+ {
+ this.update_is_favourite ();
+ }
+
+ /**
+ * 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.
+ *
+ * @return a new Individual
+ */
public Individual (GLib.List<Persona>? personas)
{
Object (personas: personas);
public abstract CapabilitiesFlags capabilities { get; set; }
/* interface Presence */
+ /**
+ * {@inheritDoc}
+ */
public abstract Folks.PresenceType presence_type { get; set; }
+
+ /**
+ * {@inheritDoc}
+ */
public abstract string presence_message { get; set; }
- /* interface Favourite */
+ /**
++ * {@inheritDoc}
++ */
+ public abstract bool is_favourite { get; set; }
+
- /* internal ID */
++ /**
+ * The internal ID used to represent the Persona within its {@link Backend}.
+ *
+ * This should not be used by client code.
+ */
public string iid { get; construct; }
- /* universal ID (eg, "foo@xmpp.example.org") */
+
+ /**
+ * The universal ID used to represent the Persona outside its {@link Backend}.
+ *
+ * For example: `foo@@xmpp.example.org`.
+ *
+ * This is the canonical way to refer to any Persona. It is guaranteed to be
+ * unique within the Persona's {@link PersonaStore}.
+ */
public string uid { get; construct; }
+ /**
+ * The {@link PersonaStore} which contains this Persona.
+ */
public PersonaStore store { get; construct; }
}