Merge branch libgeetest which upgrades our libgee dependency.
[platform/upstream/folks.git] / backends / key-file / kf-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  *       Philip Withnall <philip.withnall@collabora.co.uk>
19  */
20
21 using GLib;
22 using Gee;
23 using Folks;
24 using Folks.Backends.Kf;
25
26 /**
27  * A persona subclass which represents a single persona from a simple key file.
28  *
29  * @since 0.1.13
30  */
31 public class Folks.Backends.Kf.Persona : Folks.Persona,
32     AliasDetails,
33     ImDetails,
34     WebServiceDetails
35 {
36   private HashMultiMap<string, ImFieldDetails> _im_addresses;
37   private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses;
38   private string _alias = ""; /* must not be null */
39   private const string[] _linkable_properties =
40     {
41       "im-addresses",
42       "web-service-addresses"
43     };
44   private const string[] _writeable_properties =
45     {
46       "alias",
47       "im-addresses",
48       "web-service-addresses"
49     };
50
51   /**
52    * {@inheritDoc}
53    */
54   public override string[] linkable_properties
55     {
56       get { return this._linkable_properties; }
57     }
58
59   /**
60    * {@inheritDoc}
61    *
62    * @since 0.6.0
63    */
64   public override string[] writeable_properties
65     {
66       get { return this._writeable_properties; }
67     }
68
69   /**
70    * {@inheritDoc}
71    *
72    * @since 0.1.15
73    */
74   [CCode (notify = false)]
75   public string alias
76     {
77       get { return this._alias; }
78       set { this.change_alias.begin (value); }
79     }
80
81   /**
82    * {@inheritDoc}
83    *
84    * @since 0.6.2
85    */
86   public async void change_alias (string alias) throws PropertyError
87     {
88       /* Deal with badly-behaved callers. */
89       if (alias == null)
90         {
91           alias = "";
92         }
93
94       if (this._alias == alias)
95         {
96           return;
97         }
98
99       debug ("Setting alias of Kf.Persona '%s' to '%s'.", this.uid, alias);
100
101       unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
102       key_file.set_string (this.display_id, "__alias", alias);
103       yield ((Kf.PersonaStore) this.store).save_key_file ();
104
105       this._alias = alias;
106       this.notify_property ("alias");
107     }
108
109   /**
110    * {@inheritDoc}
111    */
112   [CCode (notify = false)]
113   public MultiMap<string, ImFieldDetails> im_addresses
114     {
115       get { return this._im_addresses; }
116       set { this.change_im_addresses.begin (value); }
117     }
118
119   /**
120    * {@inheritDoc}
121    *
122    * @since 0.6.2
123    */
124   public async void change_im_addresses (
125       MultiMap<string, ImFieldDetails> im_addresses) throws PropertyError
126     {
127       unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
128
129       /* Remove the current IM addresses from the key file */
130       foreach (var protocol1 in this._im_addresses.get_keys ())
131         {
132           try
133             {
134               key_file.remove_key (this.display_id, protocol1);
135             }
136           catch (KeyFileError e1)
137             {
138               /* Ignore the error, since it's just a group or key not found
139                * error. */
140             }
141         }
142
143       /* Add the new IM addresses to the key file and build a normalised
144        * table of them to set as the new property value */
145       var new_im_addresses = new HashMultiMap<string, ImFieldDetails> (
146           null, null,
147            AbstractFieldDetails<string>.hash_static,
148            AbstractFieldDetails<string>.equal_static);
149
150       foreach (var protocol2 in im_addresses.get_keys ())
151         {
152           var addresses = im_addresses.get (protocol2);
153           var normalised_addresses = new HashSet<string> ();
154
155           foreach (var im_fd in addresses)
156             {
157               string normalised_address;
158               try
159                 {
160                   normalised_address = ImDetails.normalise_im_address (
161                       im_fd.value, protocol2);
162                 }
163                catch (ImDetailsError e2)
164                 {
165                   throw new PropertyError.INVALID_VALUE (
166                       /* Translators: this is an error message for if the user
167                        * provides an invalid IM address. The first parameter is
168                        * an IM address (e.g. “foo@jabber.org”), the second is
169                        * the name of a protocol (e.g. “jabber”) and the third is
170                        * an error message. */
171                       _("Invalid IM address ‘%s’ for protocol ‘%s’: %s"),
172                       im_fd.value, protocol2, e2.message);
173                 }
174
175               normalised_addresses.add (normalised_address);
176               var new_im_fd = new ImFieldDetails (normalised_address);
177               new_im_addresses.set (protocol2, new_im_fd);
178             }
179
180           string[] addrs = (string[]) normalised_addresses.to_array ();
181           addrs.length = normalised_addresses.size;
182
183           key_file.set_string_list (this.display_id, protocol2, addrs);
184         }
185
186       /* Get the PersonaStore to save the key file */
187       yield ((Kf.PersonaStore) this.store).save_key_file ();
188
189       this._im_addresses = new_im_addresses;
190       this.notify_property ("im-addresses");
191     }
192
193   /**
194    * {@inheritDoc}
195    */
196   [CCode (notify = false)]
197   public MultiMap<string, WebServiceFieldDetails> web_service_addresses
198     {
199       get { return this._web_service_addresses; }
200       set { this.change_web_service_addresses.begin (value); }
201     }
202
203   /**
204    * {@inheritDoc}
205    *
206    * @since 0.6.2
207    */
208   public async void change_web_service_addresses (
209       MultiMap<string, WebServiceFieldDetails> web_service_addresses)
210           throws PropertyError
211     {
212       unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
213
214       /* Remove the current web service addresses from the key file */
215       foreach (var web_service1 in this._web_service_addresses.get_keys ())
216         {
217           try
218             {
219               key_file.remove_key (this.display_id,
220                   "web-service." + web_service1);
221             }
222           catch (KeyFileError e)
223             {
224               /* Ignore the error, since it's just a group or key not found
225                * error. */
226             }
227         }
228
229       /* Add the new web service addresses to the key file and build a
230        * table of them to set as the new property value */
231       var new_web_service_addresses =
232         new HashMultiMap<string, WebServiceFieldDetails> (
233             null, null,
234              AbstractFieldDetails<string>.hash_static,
235              AbstractFieldDetails<string>.equal_static);
236
237       foreach (var web_service2 in web_service_addresses.get_keys ())
238         {
239           var ws_fds = web_service_addresses.get (web_service2);
240
241           string[] addrs = new string[0];
242           foreach (var ws_fd1 in ws_fds)
243             addrs += ws_fd1.value;
244
245           key_file.set_string_list (this.display_id,
246               "web-service." + web_service2, addrs);
247
248           foreach (var ws_fd2 in ws_fds)
249             new_web_service_addresses.set (web_service2, ws_fd2);
250         }
251
252       /* Get the PersonaStore to save the key file */
253       yield ((Kf.PersonaStore) this.store).save_key_file ();
254
255       this._web_service_addresses = new_web_service_addresses;
256       this.notify_property ("web-service-addresses");
257     }
258
259   /**
260    * Create a new persona.
261    *
262    * Create a new persona for the {@link PersonaStore} `store`, representing
263    * the Persona given by the group `uid` in the key file `key_file`.
264    */
265   public Persona (string id, Folks.PersonaStore store)
266     {
267       var iid = store.id + ":" + id;
268       var uid = this.build_uid ("key-file", store.id, id);
269
270       Object (display_id: id,
271               iid: iid,
272               uid: uid,
273               store: store,
274               is_user: false);
275     }
276
277   construct
278     {
279       debug ("Adding key-file Persona '%s' (IID '%s', group '%s')", this.uid,
280           this.iid, this.display_id);
281
282       this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
283           null, null,  AbstractFieldDetails<string>.hash_static,
284            AbstractFieldDetails<string>.equal_static);
285       this._web_service_addresses =
286         new HashMultiMap<string, WebServiceFieldDetails> (
287             null, null,
288              AbstractFieldDetails<string>.hash_static,
289              AbstractFieldDetails<string>.equal_static);
290
291       /* Load the IM addresses from the key file */
292       unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
293
294       try
295         {
296           var keys = key_file.get_keys (this.display_id);
297           foreach (unowned string key in keys)
298             {
299               /* Alias */
300               if (key == "__alias")
301                 {
302                   this._alias = key_file.get_string (this.display_id, key);
303
304                   if (this._alias == null)
305                     {
306                       this._alias = "";
307                     }
308
309                   debug ("    Loaded alias '%s'.", this._alias);
310                   continue;
311                 }
312
313               /* Web service addresses */
314               var decomposed_key = key.split(".", 2);
315               if (decomposed_key.length == 2 &&
316                   decomposed_key[0] == "web-service")
317                 {
318                   unowned string web_service = decomposed_key[1];
319                   var web_service_addresses = key_file.get_string_list (
320                       this.display_id, web_service);
321
322                   foreach (var web_service_address in web_service_addresses)
323                     {
324                       this._web_service_addresses.set (web_service,
325                           new WebServiceFieldDetails (web_service_address));
326                     }
327
328                   continue;
329                 }
330
331               /* IM addresses */
332               unowned string protocol = key;
333               var im_addresses = key_file.get_string_list (
334                   this.display_id, protocol);
335
336               foreach (var im_address in im_addresses)
337                 {
338                   string address;
339                   try
340                     {
341                       address = ImDetails.normalise_im_address (im_address,
342                           protocol);
343                     }
344                   catch (ImDetailsError e)
345                     {
346                       /* Warn of and ignore any invalid IM addresses */
347                       warning (e.message);
348                       continue;
349                     }
350
351                   var im_fd = new ImFieldDetails (address);
352                   this._im_addresses.set (protocol, im_fd);
353                 }
354             }
355         }
356       catch (KeyFileError e)
357         {
358           /* We get a GROUP_NOT_FOUND exception if we're creating a new
359            * Persona, since it doesn't yet exist in the key file. We shouldn't
360            * get any other exceptions, since we're iterating through a list of
361            * keys we've just retrieved. */
362           if (!(e is KeyFileError.GROUP_NOT_FOUND))
363             {
364               /* Translators: the parameter is an error message. */
365               warning (_("Couldn't load data from key file: %s"), e.message);
366             }
367         }
368     }
369
370   /**
371    * {@inheritDoc}
372    */
373   public override void linkable_property_to_links (string prop_name,
374       Folks.Persona.LinkablePropertyCallback callback)
375     {
376       if (prop_name == "im-addresses")
377         {
378           foreach (var protocol in this._im_addresses.get_keys ())
379             {
380               var im_addresses = this._im_addresses.get (protocol);
381
382               foreach (var im_fd in im_addresses)
383                   callback (protocol + ":" + im_fd.value);
384             }
385         }
386       else if (prop_name == "web-service-addresses")
387         {
388           foreach (var web_service in this.web_service_addresses.get_keys ())
389             {
390               var web_service_addresses =
391                   this._web_service_addresses.get (web_service);
392
393               foreach (var ws_fd in web_service_addresses)
394                   callback (web_service + ":" + ws_fd.value);
395             }
396         }
397       else
398         {
399           /* Chain up */
400           base.linkable_property_to_links (prop_name, callback);
401         }
402     }
403 }