/backends/telepathy/tp-backend-factory.c
/backends/telepathy/tpf-persona.c
/backends/telepathy/tpf-persona-store.c
+/backends/telepathy/tpf-logger.c
/folks/alias.c
/folks/avatar.c
/folks/backend.c
/folks/persona.c
/folks/persona-store.c
/folks/presence.c
+/folks/favourite.c
/folks/folks.h
/folks/folks.pc
/folks/folks-uninstalled.pc
libfolks_telepathy_la_VALASOURCES = \
tpf-persona.vala \
tpf-persona-store.vala \
+ tpf-logger.vala \
$(NULL)
libfolks_telepathy_la_SOURCES = \
--- /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;
+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);
+ }
+}
*
* Authors:
* Travis Reitter <travis.reitter@collabora.co.uk>
+ * Philip Withnall <philip.withnall@collabora.co.uk>
*/
using GLib;
private HashMap<string, Channel> standard_channels_unready;
private HashMap<string, Channel> group_channels_unready;
private HashMap<string, Channel> groups;
+ /* FIXME: Should be HashSet<Handle> */
+ private HashSet<uint> favourite_handles;
private Channel publish;
private Channel stored;
private Channel subscribe;
private Connection conn;
private TpLowlevel ll;
private AccountManager account_manager;
+ private Logger logger;
[Property(nick = "basis account",
blurb = "Telepathy account this store is based upon")]
this.standard_channels_unready = new HashMap<string, Channel> ();
this.group_channels_unready = new HashMap<string, Channel> ();
this.groups = new HashMap<string, Channel> ();
+ this.favourite_handles = new HashSet<uint> ();
this.ll = new TpLowlevel ();
this.account_manager = AccountManager.dup ();
this.account_status_changed_cb (Tp.ConnectionStatus.DISCONNECTED,
status, reason, null, null);
}
+
+ 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
+ {
+ 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)
+ this.favourite_handles.add (h);
+ else
+ 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,
this.add_standard_channel (conn, "stored");
this.add_standard_channel (conn, "subscribe");
this.conn = conn;
+
+ /* We can only initialise the favourite contacts once we've got conn */
+ this.initialise_favourite_contacts.begin ();
}
private void new_group_channels_cb (void *data)
{
personas_new.insert (persona.iid, persona);
+ var h = contact.get_handle ();
this._personas.insert (persona.iid, persona);
- this.handle_persona_map[contact.get_handle ()] = persona;
+ this.handle_persona_map[h] = persona;
+
+ /* If the handle is a favourite, ensure the persona's marked
+ * as such. This deals with the case where we receive a
+ * contact _after_ we've discovered that they're a
+ * favourite. */
+ persona.is_favourite = this.favourite_handles.contains (h);
}
}
catch (Tp.Error e)
return null;
}
+
+ 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)
+ 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");
+ }
+ }
}
using Folks;
public class Tpf.Persona : Folks.Persona, Alias, Avatar, Folks.Capabilities,
- Groups, Presence
+ Groups, Presence, Favourite
{
private HashTable<string, bool> _groups;
+ private bool _is_favourite;
/* interface Alias */
public override string alias { get; set; }
public override Folks.PresenceType presence_type { get; private set; }
public override string presence_message { get; private set; }
+ /* 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], [6])
m4_define([folks_nano_version], [0])
dnl Display the nano_version only if it's not '0'
backend.vala \
backend-store.vala \
capabilities.vala \
+ favourite.vala \
groups.vala \
individual.vala \
individual-aggregator.vala \
--- /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; }
+}
* might have, such as their different IM addresses or vCard entries.
*/
public class Folks.Individual : Object, Alias, Avatar, Capabilities, Groups,
- Presence
+ Presence, Favourite
{
private HashTable<string, bool> _groups;
private GLib.List<Persona> _personas;
private HashTable<PersonaStore, HashSet<Persona>> stores;
+ private bool _is_favourite;
/* XXX: should setting this push it down into the Persona (to foward along to
* the actual store if possible?) */
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
this.notify_presence_cb);
persona.notify["presence-type"].disconnect (
this.notify_presence_cb);
+ persona.notify["is-favourite"].disconnect (
+ this.notify_is_favourite_cb);
groups.group_changed.disconnect (this.persona_group_changed_cb);
});
persona.notify["presence-message"].connect (
this.notify_presence_cb);
persona.notify["presence-type"].connect (this.notify_presence_cb);
+ persona.notify["is-favourite"].connect (
+ this.notify_is_favourite_cb);
groups.group_changed.connect (this.persona_group_changed_cb);
});
this.update_presence ();
}
+ private void notify_is_favourite_cb (Object obj, ParamSpec ps)
+ {
+ this.update_is_favourite ();
+ }
+
/**
* Create a new Individual.
*
this.update_groups ();
this.update_presence ();
+ this.update_is_favourite ();
this.update_avatar ();
}
this.presence_type = presence_type;
}
+ private void update_is_favourite ()
+ {
+ bool favourite = false;
+
+ this._personas.foreach ((persona) =>
+ {
+ var p = (Persona) persona;
+
+ if (favourite == false)
+ favourite = p.is_favourite;
+ });
+
+ /* Only notify if the value has changed */
+ if (this._is_favourite != favourite)
+ this._is_favourite = favourite;
+ }
+
private void update_avatar ()
{
File avatar = null;
public abstract string presence_message { get; set; }
/**
+ * {@inheritDoc}
+ */
+ public abstract bool is_favourite { get; set; }
+
+ /**
* The internal ID used to represent the Persona within its {@link Backend}.
*
* This should not be used by client code.