Merge remote branch 'pwith/favourites'
authorTravis Reitter <travis.reitter@collabora.co.uk>
Tue, 29 Jun 2010 23:50:34 +0000 (16:50 -0700)
committerTravis Reitter <travis.reitter@collabora.co.uk>
Tue, 29 Jun 2010 23:50:34 +0000 (16:50 -0700)
(Fixed)
Conflicts:
folks/individual.vala
folks/persona.vala

1  2 
backends/telepathy/Makefile.am
backends/telepathy/tpf-logger.vala
backends/telepathy/tpf-persona-store.vala
backends/telepathy/tpf-persona.vala
configure.ac
folks/Makefile.am
folks/favourite.vala
folks/individual.vala
folks/persona.vala

Simple merge
index 0000000,8209467..1119379
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,108 +1,108 @@@
 -          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);
+     }
+ }
@@@ -108,6 -115,143 +113,134 @@@ public class Tpf.PersonaStore : Folks.P
            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");
+         }
+     }
  }
@@@ -41,6 -42,22 +42,21 @@@ public class Tpf.Persona : Folks.Person
    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
      {
diff --cc configure.ac
@@@ -3,7 -3,7 +3,7 @@@ m4_define(folks_released, 0
  
  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'
Simple merge
index 0000000,bde3243..cc47b01
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,26 +1,33 @@@
+ /*
+  * 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; }
+ }
@@@ -35,53 -36,36 +36,82 @@@ public class Folks.Individual : Object
  
    /* 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);
@@@ -49,35 -40,16 +49,40 @@@ public abstract class Folks.Persona : O
    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; }
  }