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 * Philip Withnall <philip.withnall@collabora.co.uk>
24 using Folks.Backends.Kf;
27 * A persona subclass which represents a single persona from a simple key file.
31 public class Folks.Backends.Kf.Persona : Folks.Persona,
37 private HashMultiMap<string, ImFieldDetails> _im_addresses;
38 private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses;
39 private string _alias = ""; /* must not be null */
40 private const string[] _linkable_properties =
43 "web-service-addresses"
45 private const string[] _writeable_properties =
49 "web-service-addresses",
56 public override string[] linkable_properties
58 get { return Kf.Persona._linkable_properties; }
66 public override string[] writeable_properties
68 get { return Kf.Persona._writeable_properties; }
76 [CCode (notify = false)]
79 get { return this._alias; }
80 set { this.change_alias.begin (value); }
88 public async void change_alias (string alias) throws PropertyError
90 /* Deal with badly-behaved callers. */
96 if (this._alias == alias)
101 debug ("Setting alias of Kf.Persona '%s' to '%s'.", this.uid, alias);
103 unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
104 key_file.set_string (this.display_id, "__alias", alias);
105 yield ((Kf.PersonaStore) this.store).save_key_file ();
108 this.notify_property ("alias");
114 [CCode (notify = false)]
115 public MultiMap<string, ImFieldDetails> im_addresses
117 get { return this._im_addresses; }
118 set { this.change_im_addresses.begin (value); }
126 public async void change_im_addresses (
127 MultiMap<string, ImFieldDetails> im_addresses) throws PropertyError
129 unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
131 /* Remove the current IM addresses from the key file */
132 foreach (var protocol1 in this._im_addresses.get_keys ())
136 key_file.remove_key (this.display_id, protocol1);
138 catch (KeyFileError e1)
140 /* Ignore the error, since it's just a group or key not found
145 /* Add the new IM addresses to the key file and build a normalised
146 * table of them to set as the new property value */
147 var new_im_addresses = new HashMultiMap<string, ImFieldDetails> (
149 (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
150 (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
152 foreach (var protocol2 in im_addresses.get_keys ())
154 var addresses = im_addresses.get (protocol2);
155 var normalised_addresses = new HashSet<string> ();
157 foreach (var im_fd in addresses)
159 string normalised_address;
162 normalised_address = ImDetails.normalise_im_address (
163 im_fd.value, protocol2);
165 catch (ImDetailsError e2)
167 throw new PropertyError.INVALID_VALUE (
168 /* Translators: this is an error message for if the user
169 * provides an invalid IM address. The first parameter is
170 * an IM address (e.g. “foo@jabber.org”), the second is
171 * the name of a protocol (e.g. “jabber”) and the third is
172 * an error message. */
173 _("Invalid IM address ‘%s’ for protocol ‘%s’: %s"),
174 im_fd.value, protocol2, e2.message);
177 normalised_addresses.add (normalised_address);
178 var new_im_fd = new ImFieldDetails (normalised_address);
179 new_im_addresses.set (protocol2, new_im_fd);
182 string[] addrs = (string[]) normalised_addresses.to_array ();
183 addrs.length = normalised_addresses.size;
185 key_file.set_string_list (this.display_id, protocol2, addrs);
188 /* Get the PersonaStore to save the key file */
189 yield ((Kf.PersonaStore) this.store).save_key_file ();
191 this._im_addresses = new_im_addresses;
192 this.notify_property ("im-addresses");
198 [CCode (notify = false)]
199 public MultiMap<string, WebServiceFieldDetails> web_service_addresses
201 get { return this._web_service_addresses; }
202 set { this.change_web_service_addresses.begin (value); }
210 public async void change_web_service_addresses (
211 MultiMap<string, WebServiceFieldDetails> web_service_addresses)
214 unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
216 /* Remove the current web service addresses from the key file */
217 foreach (var web_service1 in this._web_service_addresses.get_keys ())
221 key_file.remove_key (this.display_id,
222 "web-service." + web_service1);
224 catch (KeyFileError e)
226 /* Ignore the error, since it's just a group or key not found
231 /* Add the new web service addresses to the key file and build a
232 * table of them to set as the new property value */
233 var new_web_service_addresses =
234 new HashMultiMap<string, WebServiceFieldDetails> (
236 (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
237 (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
239 foreach (var web_service2 in web_service_addresses.get_keys ())
241 var ws_fds = web_service_addresses.get (web_service2);
243 string[] addrs = new string[0];
244 foreach (var ws_fd1 in ws_fds)
245 addrs += ws_fd1.value;
247 key_file.set_string_list (this.display_id,
248 "web-service." + web_service2, addrs);
250 foreach (var ws_fd2 in ws_fds)
251 new_web_service_addresses.set (web_service2, ws_fd2);
254 /* Get the PersonaStore to save the key file */
255 yield ((Kf.PersonaStore) this.store).save_key_file ();
257 this._web_service_addresses = new_web_service_addresses;
258 this.notify_property ("web-service-addresses");
261 private HashSet<string> _anti_links;
262 private Set<string> _anti_links_ro;
269 [CCode (notify = false)]
270 public Set<string> anti_links
272 get { return this._anti_links_ro; }
273 set { this.change_anti_links.begin (value); }
281 public async void change_anti_links (Set<string> anti_links)
284 if (Folks.Internal.equal_sets<string> (anti_links, this.anti_links))
289 unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
291 /* Skip the persona's UID; don't allow reflexive anti-links. */
292 anti_links.remove (this.uid);
294 key_file.set_string_list (this.display_id,
295 Kf.PersonaStore.anti_links_key_name, anti_links.to_array ());
297 /* Get the PersonaStore to save the key file */
298 yield ((Kf.PersonaStore) this.store).save_key_file ();
300 /* Update the stored anti-links. */
301 this._anti_links.clear ();
302 this._anti_links.add_all (anti_links);
303 this.notify_property ("anti-links");
307 * Create a new persona.
309 * Create a new persona for the {@link PersonaStore} ``store``, representing
310 * the Persona given by the group ``uid`` in the key file ``key_file``.
312 public Persona (string id, Folks.PersonaStore store)
314 var iid = store.id + ":" + id;
315 var uid = Folks.Persona.build_uid ("key-file", store.id, id);
317 Object (display_id: id,
326 debug ("Adding key-file Persona '%s' (IID '%s', group '%s')", this.uid,
327 this.iid, this.display_id);
329 this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
331 (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
332 (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
333 this._web_service_addresses =
334 new HashMultiMap<string, WebServiceFieldDetails> (
336 (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
337 (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
338 this._anti_links = new HashSet<string> ();
339 this._anti_links_ro = this._anti_links.read_only_view;
341 /* Load the IM addresses from the key file */
342 unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
346 var keys = key_file.get_keys (this.display_id);
347 foreach (unowned string key in keys)
350 if (key == "__alias")
352 this._alias = key_file.get_string (this.display_id, key);
354 if (this._alias == null)
359 debug (" Loaded alias '%s'.", this._alias);
364 if (key == Kf.PersonaStore.anti_links_key_name)
366 var anti_link_array =
367 key_file.get_string_list (this.display_id, key);
369 if (anti_link_array != null)
371 foreach (var anti_link in anti_link_array)
373 this._anti_links.add (anti_link);
376 debug (" Loaded %u anti-links.",
377 anti_link_array.length);
382 /* Web service addresses */
383 var decomposed_key = key.split(".", 2);
384 if (decomposed_key.length == 2 &&
385 decomposed_key[0] == "web-service")
387 unowned string web_service = decomposed_key[1];
388 var web_service_addresses = key_file.get_string_list (
389 this.display_id, web_service);
391 foreach (var web_service_address in web_service_addresses)
393 this._web_service_addresses.set (web_service,
394 new WebServiceFieldDetails (web_service_address));
401 unowned string protocol = key;
402 var im_addresses = key_file.get_string_list (
403 this.display_id, protocol);
405 foreach (var im_address in im_addresses)
410 address = ImDetails.normalise_im_address (im_address,
413 catch (ImDetailsError e)
415 /* Warn of and ignore any invalid IM addresses */
420 var im_fd = new ImFieldDetails (address);
421 this._im_addresses.set (protocol, im_fd);
425 catch (KeyFileError e)
427 /* We get a GROUP_NOT_FOUND exception if we're creating a new
428 * Persona, since it doesn't yet exist in the key file. We shouldn't
429 * get any other exceptions, since we're iterating through a list of
430 * keys we've just retrieved. */
431 if (!(e is KeyFileError.GROUP_NOT_FOUND))
433 /* Translators: the parameter is an error message. */
434 warning (_("Couldn't load data from key file: %s"), e.message);
442 public override void linkable_property_to_links (string prop_name,
443 Folks.Persona.LinkablePropertyCallback callback)
445 if (prop_name == "im-addresses")
447 foreach (var protocol in this._im_addresses.get_keys ())
449 var im_addresses = this._im_addresses.get (protocol);
451 foreach (var im_fd in im_addresses)
452 callback (protocol + ":" + im_fd.value);
455 else if (prop_name == "web-service-addresses")
457 foreach (var web_service in this.web_service_addresses.get_keys ())
459 var web_service_addresses =
460 this._web_service_addresses.get (web_service);
462 foreach (var ws_fd in web_service_addresses)
463 callback (web_service + ":" + ws_fd.value);
469 base.linkable_property_to_links (prop_name, callback);