2 * Copyright (C) 2010 Collabora Ltd.
4 * This library is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation, either version 2.1 of the License, or
7 * (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
18 * Travis Reitter <travis.reitter@collabora.co.uk>
25 public class Folks.Individual : Object, Alias, Avatar, Capabilities, Groups,
28 private HashTable<string, bool> _groups;
29 private GLib.List<Persona> _personas;
30 private HashTable<PersonaStore, HashSet<Persona>> stores;
32 /* XXX: should setting this push it down into the Persona (to foward along to
33 * the actual store if possible?) */
34 public string alias { get; set; }
35 public File avatar { get; set; }
36 public CapabilitiesFlags capabilities { get; private set; }
37 public string id { get; private set; }
38 public Folks.PresenceType presence_type { get; private set; }
39 public string presence_message { get; private set; }
41 /* the last of this individuals personas has been removed, so it is invalid */
42 public signal void removed ();
44 public HashTable<string, bool> groups
46 get { return this._groups; }
48 /* Propagate the list of new groups to every Persona in the individual
49 * which implements the Groups interface */
52 this._personas.foreach ((p) =>
55 ((Groups) p).groups = value;
60 public GLib.List<Persona> personas
62 get { return this._personas; }
66 /* Disconnect from all our previous personas */
67 this._personas.foreach ((p) =>
69 var persona = (Persona) p;
70 var groups = (p is Groups) ? (Groups) p : null;
72 persona.notify["avatar"].disconnect (this.notify_avatar_cb);
73 persona.notify["presence-message"].disconnect (
74 this.notify_presence_cb);
75 persona.notify["presence-type"].disconnect (
76 this.notify_presence_cb);
77 groups.group_changed.disconnect (this.persona_group_changed_cb);
80 this._personas = value.copy ();
82 /* TODO: base this upon our ID in permanent storage, once we have that
84 if (this.id == null && this._personas.data != null)
85 this.id = this._personas.data.iid;
87 /* Connect to all the new personas */
88 this._personas.foreach ((p) =>
90 var persona = (Persona) p;
91 var groups = (p is Groups) ? (Groups) p : null;
93 persona.notify["avatar"].connect (this.notify_avatar_cb);
94 persona.notify["presence-message"].connect (
95 this.notify_presence_cb);
96 persona.notify["presence-type"].connect (this.notify_presence_cb);
97 groups.group_changed.connect (this.persona_group_changed_cb);
100 /* Update our aggregated fields and notify the changes */
101 this.update_fields ();
105 private void notify_avatar_cb (Object obj, ParamSpec ps)
107 this.update_avatar ();
110 private void persona_group_changed_cb (string group, bool is_member)
112 this.change_group (group, is_member);
113 this.update_groups ();
116 public void change_group (string group, bool is_member)
118 this._personas.foreach ((p) =>
121 ((Groups) p).change_group (group, is_member);
124 /* don't notify, since it hasn't happened in the persona backing stores
125 * yet; react to that directly */
128 private void notify_presence_cb (Object obj, ParamSpec ps)
130 this.update_presence ();
133 public Individual (GLib.List<Persona>? personas)
135 Object (personas: personas);
137 this.stores = new HashTable<PersonaStore, HashSet<Persona>> (direct_hash,
139 this.stores_update ();
142 private void stores_update ()
144 this._personas.foreach ((p) =>
146 var persona = (Persona) p;
147 var store_is_new = false;
148 var persona_set = this.stores.lookup (persona.store);
149 if (persona_set == null)
151 persona_set = new HashSet<Persona> (direct_hash, direct_equal);
155 persona_set.add (persona);
159 this.stores.insert (persona.store, persona_set);
161 persona.store.removed.connect (this.store_removed_cb);
166 private void store_removed_cb (PersonaStore store)
168 var persona_set = this.stores.lookup (store);
169 if (persona_set != null)
171 foreach (var persona in persona_set)
173 this._personas.remove (persona);
177 this.stores.remove (store);
179 if (this._personas.length () < 1 || this.stores.size () < 1)
185 this.update_fields ();
188 private void update_fields ()
190 /* gather the first occurrence of each field */
192 var caps = CapabilitiesFlags.NONE;
193 this._personas.foreach ((persona) =>
195 var p = (Persona) persona;
197 /* FIXME: also check to see if alias is just whitespace */
201 caps |= p.capabilities;
206 /* FIXME: pick a UID or similar instead */
207 alias = "Name Unknown";
210 /* only notify if the value has changed */
211 if (this.alias != alias)
214 if (this.capabilities != caps)
215 this.capabilities = caps;
217 this.update_groups ();
218 this.update_presence ();
219 this.update_avatar ();
222 private void update_groups ()
224 var new_groups = new HashTable<string, bool> (str_hash, str_equal);
226 /* this._groups is null during initial construction */
227 if (this._groups == null)
228 this._groups = new HashTable<string, bool> (str_hash, str_equal);
230 /* FIXME: this should partition the personas by store (maybe we should
231 * keep that mapping in general in this class), and execute
232 * "groups-changed" on the store (with the set of personas), to allow the
233 * back-end to optimize it (like Telepathy will for MembersChanged for the
234 * groups channel list) */
235 this._personas.foreach ((p) =>
239 var persona = (Groups) p;
241 persona.groups.foreach ((k, v) =>
243 new_groups.insert ((string) k, true);
248 new_groups.foreach ((k, v) =>
250 var group = (string) k;
251 if (this._groups.lookup (group) != true)
253 this._groups.insert (group, true);
254 this._groups.foreach ((k, v) =>
260 this.group_changed (group, true);
264 /* buffer the removals, so we don't remove while iterating */
265 var removes = new GLib.List<string> ();
266 this._groups.foreach ((k, v) =>
268 var group = (string) k;
269 if (new_groups.lookup (group) != true)
270 removes.prepend (group);
273 removes.foreach ((l) =>
275 var group = (string) l;
276 this._groups.remove (group);
277 this.group_changed (group, false);
281 private void update_presence ()
283 var presence_message = "";
284 var presence_type = Folks.PresenceType.UNSET;
286 /* Choose the most available presence from our personas */
287 this._personas.foreach ((p) =>
289 var persona = (Persona) p;
291 if (presence_message == null || presence_message == "")
292 presence_message = persona.presence_message;
294 if (Presence.typecmp (persona.presence_type, presence_type) > 0)
295 presence_type = persona.presence_type;
298 if (presence_message == null)
299 presence_message = "";
301 /* only notify if the value has changed */
302 if (this.presence_message != presence_message)
303 this.presence_message = presence_message;
305 if (this.presence_type != presence_type)
306 this.presence_type = presence_type;
309 private void update_avatar ()
313 this._personas.foreach ((p) =>
315 var persona = (Persona) p;
319 avatar = persona.avatar;
324 /* only notify if the value has changed */
325 if (this.avatar != avatar)
326 this.avatar = avatar;
329 public CapabilitiesFlags get_capabilities ()
331 return this.capabilities;
335 * GLib/C convenience functions (for built-in casting, etc.)
337 public unowned string get_alias ()
342 public HashTable<string, bool> get_groups ()
348 public string get_presence_message ()
350 return this.presence_message;
353 public Folks.PresenceType get_presence_type ()
355 return this.presence_type;
358 public bool is_online ()
361 return p.is_online ();