Don't explicitly state the return type of AccountManager.get_valid_accounts().
[platform/upstream/folks.git] / backends / telepathy / tpf-persona.vala
1 /*
2  * Copyright (C) 2010 Collabora Ltd.
3  *
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.
8  *
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.
13  *
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/>.
16  *
17  * Authors:
18  *       Travis Reitter <travis.reitter@collabora.co.uk>
19  */
20
21 using Gee;
22 using GLib;
23 using TelepathyGLib;
24 using Folks;
25
26 public errordomain Tpf.PersonaError
27 {
28   INVALID_ARGUMENT
29 }
30
31 /**
32  * A persona subclass which represents a single instant messaging contact from
33  * Telepathy.
34  */
35 public class Tpf.Persona : Folks.Persona,
36     Alias,
37     Avatar,
38     Folks.Capabilities,
39     Favourite,
40     Groups,
41     Presence
42 {
43   private HashTable<string, bool> _groups;
44   private bool _is_favourite;
45   private string _alias;
46
47   /* Whether we've finished being constructed; this is used to prevent
48    * unnecessary trips to the Telepathy service to tell it about properties
49    * being set which are actually just being set from data it's just given us.
50    */
51   private bool is_constructed = false;
52
53   /**
54    * {@inheritDoc}
55    */
56   public File avatar { get; set; }
57
58   /**
59    * {@inheritDoc}
60    */
61   public CapabilitiesFlags capabilities { get; private set; }
62
63   /**
64    * {@inheritDoc}
65    */
66   public Folks.PresenceType presence_type { get; private set; }
67
68   /**
69    * {@inheritDoc}
70    */
71   public string presence_message { get; private set; }
72
73   /**
74    * {@inheritDoc}
75    */
76   public string alias
77     {
78       get { return this._alias; }
79
80       set
81         {
82           if (this._alias == value)
83             return;
84
85           if (this.is_constructed)
86             ((Tpf.PersonaStore) this.store).change_alias (this, value);
87           this._alias = value;
88         }
89     }
90
91   /**
92    * {@inheritDoc}
93    */
94   public bool is_favourite
95     {
96       get { return this._is_favourite; }
97
98       set
99         {
100           if (this._is_favourite == value)
101             return;
102
103           if (this.is_constructed)
104             ((Tpf.PersonaStore) this.store).change_is_favourite (this, value);
105           this._is_favourite = value;
106         }
107     }
108
109   /**
110    * {@inheritDoc}
111    */
112   public HashTable<string, bool> groups
113     {
114       get { return this._groups; }
115
116       set
117         {
118           value.foreach ((k, v) =>
119             {
120               var group = (string) k;
121               if (this._groups.lookup (group) == false)
122                 this._change_group (group, true);
123             });
124
125           this._groups.foreach ((k, v) =>
126             {
127               var group = (string) k;
128               if (value.lookup (group) == false)
129                 this._change_group (group, true);
130             });
131         }
132     }
133
134   /**
135    * {@inheritDoc}
136    */
137   public void change_group (string group, bool is_member)
138     {
139       if (_change_group (group, is_member))
140         {
141           ((Tpf.PersonaStore) this.store).change_group_membership (this, group,
142             is_member);
143
144           this.group_changed (group, is_member);
145         }
146     }
147
148   private bool _change_group (string group, bool is_member)
149     {
150       bool changed = false;
151
152       if (is_member)
153         {
154           if (this._groups.lookup (group) != true)
155             {
156               this._groups.insert (group, true);
157               changed = true;
158             }
159         }
160       else
161         changed = this._groups.remove (group);
162
163       return changed;
164     }
165
166   /**
167    * The Telepathy contact represented by this persona.
168    */
169   public Contact contact { get; construct; }
170
171   /**
172    * Create a new persona.
173    *
174    * Create a new persona for the {@link PersonaStore} `store`, representing
175    * the Telepathy contact given by `contact`.
176    */
177   public Persona (Contact contact, PersonaStore store) throws Tpf.PersonaError
178     {
179       /* FIXME: There is the possibility of a crash in the error condition below
180        * due to bgo#604299, where the C self variable isn't initialised until we
181        * chain up to the Object constructor, below. */
182       var uid = contact.get_identifier ();
183       if (uid == null || uid == "")
184         throw new Tpf.PersonaError.INVALID_ARGUMENT ("contact has an " +
185             "invalid UID");
186
187       var account = account_for_connection (contact.get_connection ());
188       var account_id = ((Proxy) account).object_path;
189       /* this isn't meant to convey any real information, so no need to escape
190        * existing delimiters */
191       var iid = "telepathy:" + account_id + ":" + uid;
192
193       var alias = contact.get_alias ();
194       if (alias == null || alias.strip () == "")
195         alias = uid;
196
197       Object (alias: alias,
198               contact: contact,
199               iid: iid,
200               uid: uid,
201               store: store);
202
203       this.is_constructed = true;
204
205       this._groups = new HashTable<string, bool> (str_hash, str_equal);
206
207       contact.notify["avatar-file"].connect ((s, p) =>
208         {
209           this.contact_notify_avatar ();
210         });
211       this.contact_notify_avatar ();
212
213       contact.notify["presence-message"].connect ((s, p) =>
214         {
215           this.contact_notify_presence_message ();
216         });
217       contact.notify["presence-type"].connect ((s, p) =>
218         {
219           this.contact_notify_presence_type ();
220         });
221       this.contact_notify_presence_message ();
222       this.contact_notify_presence_type ();
223
224       contact.notify["capabilities"].connect ((s, p) =>
225         {
226           this.contact_notify_capabilities ();
227         });
228       this.contact_notify_capabilities ();
229
230       ((Tpf.PersonaStore) this.store).group_members_changed.connect (
231           (s, group, added, removed) =>
232             {
233               if (added.find (this) != null)
234                 this._change_group (group, true);
235
236               if (removed.find (this) != null)
237                 this._change_group (group, false);
238             });
239
240       ((Tpf.PersonaStore) this.store).group_removed.connect (
241           (s, group, error) =>
242             {
243               if (error != null)
244                 warning ("group invalidated: %s", error.message);
245
246               this._change_group (group, false);
247             });
248     }
249
250   private static Account? account_for_connection (Connection conn)
251     {
252       var manager = AccountManager.dup ();
253       var accounts = manager.get_valid_accounts ();
254
255       Account account_found = null;
256       accounts.foreach ((l) =>
257         {
258           var account = (Account) l;
259           if (account.get_connection () == conn)
260             {
261               account_found = account;
262               return;
263             }
264         });
265
266       return account_found;
267     }
268
269   private void contact_notify_presence_message ()
270     {
271       this.presence_message = this.contact.get_presence_message ();
272     }
273
274   private void contact_notify_presence_type ()
275     {
276       this.presence_type = folks_presence_type_from_tp (
277           this.contact.get_presence_type ());
278     }
279
280   private static PresenceType folks_presence_type_from_tp (
281       TelepathyGLib.ConnectionPresenceType type)
282     {
283       switch (type)
284         {
285           case TelepathyGLib.ConnectionPresenceType.AVAILABLE:
286             return PresenceType.AVAILABLE;
287           case TelepathyGLib.ConnectionPresenceType.AWAY:
288             return PresenceType.AWAY;
289           case TelepathyGLib.ConnectionPresenceType.BUSY:
290             return PresenceType.BUSY;
291           case TelepathyGLib.ConnectionPresenceType.ERROR:
292             return PresenceType.ERROR;
293           case TelepathyGLib.ConnectionPresenceType.EXTENDED_AWAY:
294             return PresenceType.EXTENDED_AWAY;
295           case TelepathyGLib.ConnectionPresenceType.HIDDEN:
296             return PresenceType.HIDDEN;
297           case TelepathyGLib.ConnectionPresenceType.OFFLINE:
298             return PresenceType.OFFLINE;
299           case TelepathyGLib.ConnectionPresenceType.UNKNOWN:
300             return PresenceType.UNKNOWN;
301           case TelepathyGLib.ConnectionPresenceType.UNSET:
302             return PresenceType.UNSET;
303           default:
304             return PresenceType.UNKNOWN;
305         }
306     }
307
308   private void contact_notify_avatar ()
309     {
310       var file = this.contact.get_avatar_file ();
311       if (this.avatar != file)
312         this.avatar = file;
313     }
314
315   private void contact_notify_capabilities ()
316     {
317       var caps = this.contact.get_capabilities ();
318       if (caps != null)
319         this.capabilities = folks_capabilities_flags_from_tp (caps);
320     }
321
322   /* Based off tp_caps_to_capabilities() in empathy-contact.c */
323   private static CapabilitiesFlags folks_capabilities_flags_from_tp (
324       TelepathyGLib.Capabilities caps)
325     {
326       CapabilitiesFlags capabilities = 0;
327       var classes = caps.get_channel_classes ();
328
329       classes.foreach ((m) =>
330         {
331           unowned ValueArray class_struct = (ValueArray) m;
332
333           unowned Value val = class_struct.get_nth (0);
334           unowned HashTable fixed_prop = (HashTable) val.get_boxed ();
335
336           TelepathyGLib.HandleType handle_type =
337               (TelepathyGLib.HandleType) TelepathyGLib.asv_get_uint32 (
338                   fixed_prop, TelepathyGLib.PROP_CHANNEL_TARGET_HANDLE_TYPE,
339                   null);
340           if (handle_type != HandleType.CONTACT)
341             return; /* i.e. continue the loop */
342
343           unowned string chan_type = TelepathyGLib.asv_get_string (fixed_prop,
344               TelepathyGLib.PROP_CHANNEL_CHANNEL_TYPE);
345
346           if (chan_type == TelepathyGLib.IFACE_CHANNEL_TYPE_FILE_TRANSFER)
347             {
348               capabilities |= CapabilitiesFlags.FILE_TRANSFER;
349             }
350           else if (chan_type == TelepathyGLib.IFACE_CHANNEL_TYPE_STREAM_TUBE)
351             {
352               var service = TelepathyGLib.asv_get_string (fixed_prop,
353                   TelepathyGLib.PROP_CHANNEL_TYPE_STREAM_TUBE_SERVICE);
354
355               if (service == "rfb")
356                 capabilities |= CapabilitiesFlags.STREAM_TUBE;
357             }
358           else if (chan_type == TelepathyGLib.IFACE_CHANNEL_TYPE_STREAMED_MEDIA)
359             {
360               val = class_struct.get_nth (1);
361               unowned string[] allowed_prop = (string[]) val.get_boxed ();
362
363               if (allowed_prop != null)
364                 {
365                   for (int i = 0; allowed_prop[i] != null; i++)
366                     {
367                       unowned string prop = allowed_prop[i];
368
369                       if (prop ==
370                           TelepathyGLib.PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_AUDIO)
371                         capabilities |= CapabilitiesFlags.AUDIO;
372                       else if (prop ==
373                           TelepathyGLib.PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_VIDEO)
374                         capabilities |= CapabilitiesFlags.VIDEO;
375                     }
376                 }
377             }
378         });
379
380       return capabilities;
381     }
382 }