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>
26 public errordomain Tpf.PersonaError
32 * A persona subclass which represents a single instant messaging contact from
35 public class Tpf.Persona : Folks.Persona,
43 private HashTable<string, bool> _groups;
44 private bool _is_favourite;
45 private string _alias;
46 private HashTable<string, GenericArray<string>> _im_addresses;
48 /* Whether we've finished being constructed; this is used to prevent
49 * unnecessary trips to the Telepathy service to tell it about properties
50 * being set which are actually just being set from data it's just given us.
52 private bool is_constructed = false;
57 public File avatar { get; set; }
62 public Folks.PresenceType presence_type { get; private set; }
67 public string presence_message { get; private set; }
74 get { return this._alias; }
78 if (this._alias == value)
81 if (this.is_constructed)
82 ((Tpf.PersonaStore) this.store).change_alias (this, value);
90 public bool is_favourite
92 get { return this._is_favourite; }
96 if (this._is_favourite == value)
99 if (this.is_constructed)
100 ((Tpf.PersonaStore) this.store).change_is_favourite (this, value);
101 this._is_favourite = value;
108 public HashTable<string, GenericArray<string>> im_addresses
110 get { return this._im_addresses; }
117 public HashTable<string, bool> groups
119 get { return this._groups; }
123 value.foreach ((k, v) =>
125 var group = (string) k;
126 if (this._groups.lookup (group) == false)
127 this._change_group (group, true);
130 this._groups.foreach ((k, v) =>
132 var group = (string) k;
133 if (value.lookup (group) == false)
134 this._change_group (group, true);
142 public async void change_group (string group, bool is_member)
144 if (_change_group (group, is_member))
146 ((Tpf.PersonaStore) this.store).change_group_membership (this, group,
149 this.group_changed (group, is_member);
153 private bool _change_group (string group, bool is_member)
155 bool changed = false;
159 if (this._groups.lookup (group) != true)
161 this._groups.insert (group, true);
166 changed = this._groups.remove (group);
172 * The Telepathy contact represented by this persona.
174 public Contact contact { get; construct; }
177 * Create a new persona.
179 * Create a new persona for the {@link PersonaStore} `store`, representing
180 * the Telepathy contact given by `contact`.
182 public Persona (Contact contact, PersonaStore store) throws Tpf.PersonaError
184 string[] linkable_properties = { "im-addresses" };
186 /* FIXME: There is the possibility of a crash in the error condition below
187 * due to bgo#604299, where the C self variable isn't initialised until we
188 * chain up to the Object constructor, below. */
189 unowned string id = contact.get_identifier ();
190 if (id == null || id == "")
191 throw new Tpf.PersonaError.INVALID_ARGUMENT ("contact has an " +
194 var account = account_for_connection (contact.get_connection ());
195 string uid = this.build_uid ("telepathy", account.get_protocol (), id);
197 var alias = contact.get_alias ();
200 /* If the alias is empty, fall back to the display ID */
201 if (alias == null || alias.strip () == "")
204 Object (alias: alias,
206 display_id: display_id,
207 /* FIXME: This IID format should be moved out to the IMable
208 * interface along with the code in
209 * Kf.Persona.linkable_property_to_links(), but that depends on
210 * bgo#624842 being fixed. */
211 iid: account.get_protocol () + ":" + id,
214 linkable_properties: linkable_properties);
216 debug ("Creating new Tpf.Persona '%s' for service-specific UID '%s': %p",
218 this.is_constructed = true;
220 /* Set our single IM address */
221 GenericArray<string> im_address_array = new GenericArray<string> ();
222 im_address_array.add (id);
225 new HashTable<string, GenericArray<string>> (str_hash, str_equal);
226 this._im_addresses.insert (account.get_protocol (), im_address_array);
229 this._groups = new HashTable<string, bool> (str_hash, str_equal);
231 contact.notify["avatar-file"].connect ((s, p) =>
233 this.contact_notify_avatar ();
235 this.contact_notify_avatar ();
237 contact.notify["presence-message"].connect ((s, p) =>
239 this.contact_notify_presence_message ();
241 contact.notify["presence-type"].connect ((s, p) =>
243 this.contact_notify_presence_type ();
245 this.contact_notify_presence_message ();
246 this.contact_notify_presence_type ();
248 ((Tpf.PersonaStore) this.store).group_members_changed.connect (
249 (s, group, added, removed) =>
251 if (added.find (this) != null)
252 this._change_group (group, true);
254 if (removed.find (this) != null)
255 this._change_group (group, false);
258 ((Tpf.PersonaStore) this.store).group_removed.connect (
262 * !(error is TelepathyGLib.DBusError.OBJECT_REMOVED) because the
263 * GIR bindings don't annotate errors */
265 (error.domain != TelepathyGLib.dbus_errors_quark () ||
266 error.code != TelepathyGLib.DBusError.OBJECT_REMOVED))
268 debug ("Group invalidated: %s", error.message);
271 this._change_group (group, false);
277 debug ("Destroying Tpf.Persona '%s': %p", this.uid, this);
280 private static Account? account_for_connection (Connection conn)
282 var manager = AccountManager.dup ();
283 GLib.List<unowned Account> accounts = manager.get_valid_accounts ();
285 Account account_found = null;
286 accounts.foreach ((l) =>
288 unowned Account account = (Account) l;
289 if (account.get_connection () == conn)
291 account_found = account;
296 return account_found;
299 private void contact_notify_presence_message ()
301 this.presence_message = this.contact.get_presence_message ();
304 private void contact_notify_presence_type ()
306 this.presence_type = folks_presence_type_from_tp (
307 this.contact.get_presence_type ());
310 private static PresenceType folks_presence_type_from_tp (
311 TelepathyGLib.ConnectionPresenceType type)
315 case TelepathyGLib.ConnectionPresenceType.AVAILABLE:
316 return PresenceType.AVAILABLE;
317 case TelepathyGLib.ConnectionPresenceType.AWAY:
318 return PresenceType.AWAY;
319 case TelepathyGLib.ConnectionPresenceType.BUSY:
320 return PresenceType.BUSY;
321 case TelepathyGLib.ConnectionPresenceType.ERROR:
322 return PresenceType.ERROR;
323 case TelepathyGLib.ConnectionPresenceType.EXTENDED_AWAY:
324 return PresenceType.EXTENDED_AWAY;
325 case TelepathyGLib.ConnectionPresenceType.HIDDEN:
326 return PresenceType.HIDDEN;
327 case TelepathyGLib.ConnectionPresenceType.OFFLINE:
328 return PresenceType.OFFLINE;
329 case TelepathyGLib.ConnectionPresenceType.UNKNOWN:
330 return PresenceType.UNKNOWN;
331 case TelepathyGLib.ConnectionPresenceType.UNSET:
332 return PresenceType.UNSET;
334 return PresenceType.UNKNOWN;
338 private void contact_notify_avatar ()
340 var file = this.contact.get_avatar_file ();
341 if (this.avatar != file)