*/
using GLib;
+using Gee;
using Folks;
using Folks.Backends.Kf;
/**
* A persona subclass which represents a single persona from a simple key file.
+ *
+ * @since 0.1.13
*/
public class Folks.Backends.Kf.Persona : Folks.Persona,
- IMable
+ AliasDetails,
+ AntiLinkable,
+ ImDetails,
+ WebServiceDetails
{
- private unowned GLib.KeyFile key_file;
- /* FIXME: As described in the IMable interface, we have to use
- * GenericArray<string> here rather than just string[], as null-terminated
- * arrays aren't supported as generic types. */
- private HashTable<string, GenericArray<string>> _im_addresses;
+ private HashMultiMap<string, ImFieldDetails> _im_addresses;
+ private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses;
+ private string _alias = ""; /* must not be null */
+ private const string[] _linkable_properties =
+ {
+ "im-addresses",
+ "web-service-addresses"
+ };
+ private const string[] _writeable_properties =
+ {
+ "alias",
+ "im-addresses",
+ "web-service-addresses",
+ "anti-links"
+ };
+
+ /**
+ * {@inheritDoc}
+ */
+ public override string[] linkable_properties
+ {
+ get { return Kf.Persona._linkable_properties; }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since 0.6.0
+ */
+ public override string[] writeable_properties
+ {
+ get { return Kf.Persona._writeable_properties; }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since 0.1.15
+ */
+ [CCode (notify = false)]
+ public string alias
+ {
+ get { return this._alias; }
+ set { this.change_alias.begin (value); }
+ }
/**
* {@inheritDoc}
+ *
+ * @since 0.6.2
*/
- public HashTable<string, GenericArray<string>> im_addresses
+ public async void change_alias (string alias) throws PropertyError
{
- get
- { return this._im_addresses; }
+ /* Deal with badly-behaved callers. */
+ if (alias == null)
+ {
+ alias = "";
+ }
- set
+ if (this._alias == alias)
{
- /* Remove the current IM addresses from the key file */
- this._im_addresses.foreach ((k, v) =>
+ return;
+ }
+
+ debug ("Setting alias of Kf.Persona '%s' to '%s'.", this.uid, alias);
+
+ unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
+ key_file.set_string (this.display_id, "__alias", alias);
+ yield ((Kf.PersonaStore) this.store).save_key_file ();
+
+ this._alias = alias;
+ this.notify_property ("alias");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ [CCode (notify = false)]
+ public MultiMap<string, ImFieldDetails> im_addresses
+ {
+ get { return this._im_addresses; }
+ set { this.change_im_addresses.begin (value); }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since 0.6.2
+ */
+ public async void change_im_addresses (
+ MultiMap<string, ImFieldDetails> im_addresses) throws PropertyError
+ {
+ unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
+
+ /* Remove the current IM addresses from the key file */
+ foreach (var protocol1 in this._im_addresses.get_keys ())
+ {
+ try
+ {
+ key_file.remove_key (this.display_id, protocol1);
+ }
+ catch (KeyFileError e1)
+ {
+ /* Ignore the error, since it's just a group or key not found
+ * error. */
+ }
+ }
+
+ /* Add the new IM addresses to the key file and build a normalised
+ * table of them to set as the new property value */
+ var new_im_addresses = new HashMultiMap<string, ImFieldDetails> (
+ null, null,
+ (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
+ (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
+
+ foreach (var protocol2 in im_addresses.get_keys ())
+ {
+ var addresses = im_addresses.get (protocol2);
+ var normalised_addresses = new HashSet<string> ();
+
+ foreach (var im_fd in addresses)
{
+ string normalised_address;
try
{
- unowned string protocol = (string) k;
- this.key_file.remove_key (this.uid, protocol);
+ normalised_address = ImDetails.normalise_im_address (
+ im_fd.value, protocol2);
}
- catch (KeyFileError e)
+ catch (ImDetailsError e2)
{
- /* Ignore the error, since it's just a group or key not found
- * error. */
+ throw new PropertyError.INVALID_VALUE (
+ /* Translators: this is an error message for if the user
+ * provides an invalid IM address. The first parameter is
+ * an IM address (e.g. “foo@jabber.org”), the second is
+ * the name of a protocol (e.g. “jabber”) and the third is
+ * an error message. */
+ _("Invalid IM address ‘%s’ for protocol ‘%s’: %s"),
+ im_fd.value, protocol2, e2.message);
}
- });
- this._im_addresses = value;
+ normalised_addresses.add (normalised_address);
+ var new_im_fd = new ImFieldDetails (normalised_address);
+ new_im_addresses.set (protocol2, new_im_fd);
+ }
+
+ string[] addrs = (string[]) normalised_addresses.to_array ();
+ addrs.length = normalised_addresses.size;
+
+ key_file.set_string_list (this.display_id, protocol2, addrs);
+ }
+
+ /* Get the PersonaStore to save the key file */
+ yield ((Kf.PersonaStore) this.store).save_key_file ();
+
+ this._im_addresses = new_im_addresses;
+ this.notify_property ("im-addresses");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ [CCode (notify = false)]
+ public MultiMap<string, WebServiceFieldDetails> web_service_addresses
+ {
+ get { return this._web_service_addresses; }
+ set { this.change_web_service_addresses.begin (value); }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since 0.6.2
+ */
+ public async void change_web_service_addresses (
+ MultiMap<string, WebServiceFieldDetails> web_service_addresses)
+ throws PropertyError
+ {
+ unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
- /* Add the new IM addresses to the key file */
- this._im_addresses.foreach ((k, v) =>
+ /* Remove the current web service addresses from the key file */
+ foreach (var web_service1 in this._web_service_addresses.get_keys ())
+ {
+ try
+ {
+ key_file.remove_key (this.display_id,
+ "web-service." + web_service1);
+ }
+ catch (KeyFileError e)
{
- unowned string protocol = (string) k;
- unowned string[] addresses = (string[]) v;
- this.key_file.set_string_list (this.uid, protocol, addresses);
- });
+ /* Ignore the error, since it's just a group or key not found
+ * error. */
+ }
+ }
+
+ /* Add the new web service addresses to the key file and build a
+ * table of them to set as the new property value */
+ var new_web_service_addresses =
+ new HashMultiMap<string, WebServiceFieldDetails> (
+ null, null,
+ (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
+ (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
+
+ foreach (var web_service2 in web_service_addresses.get_keys ())
+ {
+ var ws_fds = web_service_addresses.get (web_service2);
+
+ string[] addrs = new string[0];
+ foreach (var ws_fd1 in ws_fds)
+ addrs += ws_fd1.value;
+
+ key_file.set_string_list (this.display_id,
+ "web-service." + web_service2, addrs);
+
+ foreach (var ws_fd2 in ws_fds)
+ new_web_service_addresses.set (web_service2, ws_fd2);
+ }
+
+ /* Get the PersonaStore to save the key file */
+ yield ((Kf.PersonaStore) this.store).save_key_file ();
+
+ this._web_service_addresses = new_web_service_addresses;
+ this.notify_property ("web-service-addresses");
+ }
+
+ private HashSet<string> _anti_links;
+ private Set<string> _anti_links_ro;
- /* Get the PersonaStore to save the key file */
- ((Kf.PersonaStore) this.store).save_key_file.begin ();
+ /**
+ * {@inheritDoc}
+ *
+ * @since 0.7.3
+ */
+ [CCode (notify = false)]
+ public Set<string> anti_links
+ {
+ get { return this._anti_links_ro; }
+ set { this.change_anti_links.begin (value); }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since 0.7.3
+ */
+ public async void change_anti_links (Set<string> anti_links)
+ throws PropertyError
+ {
+ if (Folks.Internal.equal_sets<string> (anti_links, this.anti_links))
+ {
+ return;
}
+
+ unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
+
+ /* Skip the persona's UID; don't allow reflexive anti-links. */
+ anti_links.remove (this.uid);
+
+ key_file.set_string_list (this.display_id,
+ Kf.PersonaStore.anti_links_key_name, anti_links.to_array ());
+
+ /* Get the PersonaStore to save the key file */
+ yield ((Kf.PersonaStore) this.store).save_key_file ();
+
+ /* Update the stored anti-links. */
+ this._anti_links.clear ();
+ this._anti_links.add_all (anti_links);
+ this.notify_property ("anti-links");
}
/**
* Create a new persona.
*
- * Create a new persona for the {@link PersonaStore} `store`, representing
- * the Persona given by the group `uid` in the key file `key_file`.
+ * Create a new persona for the {@link PersonaStore} ``store``, representing
+ * the Persona given by the group ``uid`` in the key file ``key_file``.
*/
- public Persona (KeyFile key_file, string uid, Folks.PersonaStore store)
+ public Persona (string id, Folks.PersonaStore store)
{
- string iid = "key-file:" + uid;
- string[] linkable_properties = { "im-addresses" };
+ var iid = store.id + ":" + id;
+ var uid = Folks.Persona.build_uid ("key-file", store.id, id);
- Object (iid: iid,
+ Object (display_id: id,
+ iid: iid,
uid: uid,
store: store,
- linkable_properties: linkable_properties);
+ is_user: false);
+ }
+
+ construct
+ {
+ debug ("Adding key-file Persona '%s' (IID '%s', group '%s')", this.uid,
+ this.iid, this.display_id);
- this.key_file = key_file;
- this._im_addresses = new HashTable<string, GenericArray<string>> (
- str_hash, str_equal);
+ this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
+ null, null,
+ (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
+ (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
+ this._web_service_addresses =
+ new HashMultiMap<string, WebServiceFieldDetails> (
+ null, null,
+ (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
+ (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
+ this._anti_links = new HashSet<string> ();
+ this._anti_links_ro = this._anti_links.read_only_view;
/* Load the IM addresses from the key file */
+ unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
+
try
{
- string[] keys = this.key_file.get_keys (uid);
- foreach (string protocol in keys)
+ var keys = key_file.get_keys (this.display_id);
+ foreach (unowned string key in keys)
{
- string[] im_addresses = this.key_file.get_string_list (uid,
- protocol);
-
- /* FIXME: We have to convert our nice efficient string[] to a
- * GenericArray<string> because Vala doesn't like null-terminated
- * arrays as generic types. */
- GenericArray<string> im_address_array =
- new GenericArray<string> ();
- foreach (string address in im_addresses)
- im_address_array.add (address);
-
- this._im_addresses.insert (protocol, im_address_array);
+ /* Alias */
+ if (key == "__alias")
+ {
+ this._alias = key_file.get_string (this.display_id, key);
+
+ if (this._alias == null)
+ {
+ this._alias = "";
+ }
+
+ debug (" Loaded alias '%s'.", this._alias);
+ continue;
+ }
+
+ /* Anti-links. */
+ if (key == Kf.PersonaStore.anti_links_key_name)
+ {
+ var anti_link_array =
+ key_file.get_string_list (this.display_id, key);
+
+ if (anti_link_array != null)
+ {
+ foreach (var anti_link in anti_link_array)
+ {
+ this._anti_links.add (anti_link);
+ }
+
+ debug (" Loaded %u anti-links.",
+ anti_link_array.length);
+ continue;
+ }
+ }
+
+ /* Web service addresses */
+ var decomposed_key = key.split(".", 2);
+ if (decomposed_key.length == 2 &&
+ decomposed_key[0] == "web-service")
+ {
+ unowned string web_service = decomposed_key[1];
+ var web_service_addresses = key_file.get_string_list (
+ this.display_id, web_service);
+
+ foreach (var web_service_address in web_service_addresses)
+ {
+ this._web_service_addresses.set (web_service,
+ new WebServiceFieldDetails (web_service_address));
+ }
+
+ continue;
+ }
+
+ /* IM addresses */
+ unowned string protocol = key;
+ var im_addresses = key_file.get_string_list (
+ this.display_id, protocol);
+
+ foreach (var im_address in im_addresses)
+ {
+ string address;
+ try
+ {
+ address = ImDetails.normalise_im_address (im_address,
+ protocol);
+ }
+ catch (ImDetailsError e)
+ {
+ /* Warn of and ignore any invalid IM addresses */
+ warning (e.message);
+ continue;
+ }
+
+ var im_fd = new ImFieldDetails (address);
+ this._im_addresses.set (protocol, im_fd);
+ }
}
}
catch (KeyFileError e)
{
- /* This should never be reached, as we're listing the keys then
- * iterating through the list. */
- GLib.assert_not_reached ();
+ /* We get a GROUP_NOT_FOUND exception if we're creating a new
+ * Persona, since it doesn't yet exist in the key file. We shouldn't
+ * get any other exceptions, since we're iterating through a list of
+ * keys we've just retrieved. */
+ if (!(e is KeyFileError.GROUP_NOT_FOUND))
+ {
+ /* Translators: the parameter is an error message. */
+ warning (_("Couldn't load data from key file: %s"), e.message);
+ }
}
}
+ /**
+ * {@inheritDoc}
+ */
public override void linkable_property_to_links (string prop_name,
Folks.Persona.LinkablePropertyCallback callback)
{
if (prop_name == "im-addresses")
{
- this.im_addresses.foreach ((k, v) =>
+ foreach (var protocol in this._im_addresses.get_keys ())
{
- unowned string protocol = (string) k;
- unowned GenericArray<string> im_addresses =
- (GenericArray<string>) v;
+ var im_addresses = this._im_addresses.get (protocol);
- im_addresses.foreach ((v) =>
- {
- unowned string address = (string) v;
- callback (protocol + ":" + address);
- });
- });
+ foreach (var im_fd in im_addresses)
+ callback (protocol + ":" + im_fd.value);
+ }
+ }
+ else if (prop_name == "web-service-addresses")
+ {
+ foreach (var web_service in this.web_service_addresses.get_keys ())
+ {
+ var web_service_addresses =
+ this._web_service_addresses.get (web_service);
+
+ foreach (var ws_fd in web_service_addresses)
+ callback (web_service + ":" + ws_fd.value);
+ }
}
else
{