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