*/
using GLib;
-using Folks;
+
+/**
+ * Errors which can be thrown when asynchronously setting a property of a
+ * {@link Persona} using a setter method defined on an interface such as
+ * {@link AliasDetails}.
+ *
+ * @since 0.6.2
+ */
+public errordomain Folks.PropertyError
+{
+ /**
+ * Property is not writeable for this particular object.
+ *
+ * @since 0.6.2
+ */
+ NOT_WRITEABLE,
+
+ /**
+ * Value was invalid for the property.
+ *
+ * @since 0.6.2
+ */
+ INVALID_VALUE,
+
+ /**
+ * Unknown error when setting the property.
+ *
+ * @since 0.6.2
+ */
+ UNKNOWN_ERROR
+}
/**
* Represents a "shard" of a person from a single source (a single
* {@link Backend}), such as an XMPP contact from Telepathy or a vCard contact
- * from evolution-data-server. All the personas belonging to one physical person
- * are aggregated to form a single {@link Individual} representing that person.
+ * from evolution-data-server.
+ *
+ * All the personas belonging to one physical person are aggregated to form a
+ * single {@link Individual} representing that person.
*/
-public abstract class Folks.Persona : Object, Alias, Avatar, Capabilities,
- Presence
+public abstract class Folks.Persona : Object
{
- /* interface Alias */
+ private weak Individual? _individual;
+
/**
- * {@inheritDoc}
+ * The internal ID used to represent the Persona for linking.
+ *
+ * This is opaque, and shouldn't be parsed or considered meaningful by
+ * clients.
+ *
+ * The internal ID should be unique within a backend, but may not be unique
+ * across backends, so that links can be made between Personas with similar
+ * internal IDs.
*/
- public abstract string alias { get; set; }
+ /* For example: jabber:foo@xmpp.example.org or joe@example.org */
+ public string iid { get; construct; }
- /* interface Avatar */
/**
- * {@inheritDoc}
+ * The universal ID used to represent the Persona outside its {@link Backend}.
+ *
+ * This is opaque, and should only be parsed by clients using
+ * {@link Persona.split_uid}.
+ *
+ * This is the canonical way to refer to any Persona. It is guaranteed to be
+ * unique within the Persona's {@link PersonaStore}.
+ *
+ * A Persona's UID is immutable over the life of the Persona in the backing
+ * store, so a given UID is guaranteed to refer to the same Persona each time
+ * libfolks is used, until the Persona is permanently removed from its backing
+ * store.
+ *
+ * @see Persona.build_uid
+ * @see Persona.split_uid
*/
- public abstract File avatar { get; set; }
+ /* For example: telepathy:jabber:foo@xmpp.example.org or
+ * key-file:relationships.ini:joe@example.org
+ *
+ * It comprises three components, separated by colons:
+ * # {@link Backend.name}
+ * # {@link PersonaStore.id}
+ * # Persona identifier
+ * Each component is escaped by replacing all colons with double underscores
+ * before building the UID.*/
+ public string uid { get; construct; }
- /* interface Capabilities */
/**
- * {@inheritDoc}
+ * The human-readable, service-specific universal ID used to represent the
+ * Persona.
+ *
+ * For example: `foo@@xmpp.example.org`.
+ *
+ * This should be used whenever the user needs to be presented with a
+ * familiar, service-specific ID. For instance, in a prompt for the user to
+ * select a specific IM contact within an {@link Individual} to begin a chat
+ * with.
+ *
+ * This is not guaranteed to be unique outside of the Persona's
+ * {@link PersonaStore}.
+ *
+ * @since 0.1.13
*/
- public abstract CapabilitiesFlags capabilities { get; set; }
+ public string display_id { get; construct; }
- /* interface Presence */
/**
- * {@inheritDoc}
+ * Whether the Persona is the user.
+ *
+ * Iff the Persona represents the user (the person who owns the account in
+ * the respective backend) this is `true`.
+ *
+ * @since 0.3.0
*/
- public abstract Folks.PresenceType presence_type { get; set; }
+ public bool is_user { get; construct; }
/**
- * {@inheritDoc}
+ * The {@link PersonaStore} which contains this Persona.
*/
- public abstract string presence_message { get; set; }
+ public weak PersonaStore store { get; construct; }
/**
- * The internal ID used to represent the Persona within its {@link Backend}.
+ * The {@link Individual} which contains this Persona.
*
- * This should not be used by client code.
+ * This may be `null`, but should only ever be so when the Persona has just
+ * been created, when its {@link PersonaStore} is being destroyed, or when
+ * it's moving between {@link Individual}s.
+ *
+ * @since 0.6.0
*/
- public string iid { get; construct; }
+ public weak Individual? individual
+ {
+ get
+ {
+ assert (this._individual == null ||
+ ((!) this._individual).personas.contains (this));
+
+ return this._individual;
+ }
+
+ internal set
+ {
+ assert (value == null || ((!) value).personas.contains (this));
+
+ this._individual = value;
+ }
+ }
/**
- * The universal ID used to represent the Persona outside its {@link Backend}.
+ * The names of the properties of this Persona which are linkable.
*
- * For example: `foo@@xmpp.example.org`.
+ * If a property name is in this list, and the Persona is from a
+ * {@link PersonaStore} whose trust level is {@link PersonaStoreTrust.FULL},
+ * the {@link IndividualAggregator} should be able to reliably use the value
+ * of the property from a given Persona instance to link the Persona with
+ * other Personas and form {@link Individual}s.
*
- * This is the canonical way to refer to any Persona. It is guaranteed to be
- * unique within the Persona's {@link PersonaStore}.
+ * Note that {@link Persona.uid} is always implicitly a member of this list,
+ * and doesn't need to be added explicitly.
+ *
+ * This list will have no effect if the Persona's {@link PersonaStore} trust
+ * level is not {@link PersonaStoreTrust.FULL}.
+ *
+ * This property value is guaranteed to be constant for a given persona,
+ * but may vary between personas in the same store.
+ *
+ * @since 0.1.13
*/
- public string uid { get; construct; }
+ public abstract string[] linkable_properties { get; }
/**
- * The {@link PersonaStore} which contains this Persona.
+ * The names of the properties of this Persona which are writeable.
+ *
+ * If a property name is in this list, setting the property should result in
+ * the updated value being stored in the backend's permanent storage (unless
+ * it gets rejected due to being invalid, or a different error occurs).
+ *
+ * It's intended that this property value will be constant for a given Persona
+ * subclass, but this isn't guaranteed; it's possible that Persona subclasses
+ * may vary the value of this property at run time.
+ *
+ * @since 0.6.0
+ */
+ public abstract string[] writeable_properties { get; }
+
+ /**
+ * Callback into the aggregator to manipulate a link mapping.
+ *
+ * This is a callback provided by the {@link IndividualAggregator} whenever
+ * a {@link Persona.linkable_property_to_links} method is called, which should
+ * be called by the `linkable_property_to_links` implementation for each
+ * linkable-property-to-individual mapping it wants to add or remove in the
+ * aggregator.
+ *
+ * @param link the mapping string to be added to the
+ * {@link IndividualAggregator}
+ * @since 0.1.13
*/
- public PersonaStore store { get; construct; }
+ public delegate void LinkablePropertyCallback (string link);
+
+ /* FIXME: This code should move to the ImDetails interface as a concrete
+ * method of the interface. However, that depends on bgo#624842 */
+ /**
+ * Produce one or more mapping strings for the given property's value.
+ *
+ * This is a virtual method, to be overridden by subclasses of {@link Persona}
+ * who have linkable properties. Each of their linkable properties should be
+ * handled by their implementation of this function, examining the current
+ * value of the property and calling `callback` with one or more mapping
+ * strings for the property's value. Each of these mapping strings will be
+ * added to the {@link IndividualAggregator}'s link map, related to the
+ * {@link Individual} instance which contains this {@link Persona}.
+ *
+ * @param prop_name the name of the linkable property to use, which must be
+ * listed in {@link Persona.linkable_properties}
+ * @param callback a callback to execute for each of the mapping strings
+ * generated by this property
+ * @see Persona.linkable_properties
+ * @since 0.1.13
+ */
+ public virtual void linkable_property_to_links (string prop_name,
+ LinkablePropertyCallback callback)
+ {
+ /* Backend-specific Persona subclasses should override this if they have
+ * any linkable properties */
+ assert_not_reached ();
+ }
+
+ private static string _escape_uid_component (string component)
+ {
+ /* Escape colons with backslashes */
+ string escaped = component.replace ("\\", "\\\\");
+ return escaped.replace (":", "\\:");
+ }
+
+ private static string _unescape_uid_component (string component)
+ {
+ /* Unescape colons and backslashes */
+ string unescaped = component.replace ("\\:", ":");
+ return unescaped.replace ("\\", "\\\\");
+ }
+
+ /**
+ * Build a UID from the given components.
+ *
+ * Each component is escaped before the UID is built.
+ *
+ * @param backend_name the {@link Backend.name}
+ * @param persona_store_id the {@link PersonaStore.id}
+ * @param persona_id the Persona identifier (backend-specific)
+ * @return a valid UID
+ * @see Persona.split_uid
+ * @since 0.1.13
+ */
+ public static string build_uid (string backend_name,
+ string persona_store_id, string persona_id)
+ {
+ return "%s:%s:%s".printf (Persona._escape_uid_component (backend_name),
+ Persona._escape_uid_component (persona_store_id),
+ Persona._escape_uid_component (persona_id));
+ }
+
+ /**
+ * Split a UID into its component parts.
+ *
+ * Each component is unescaped before being returned. The UID //must// be
+ * correctly formed.
+ *
+ * @param uid a valid UID
+ * @param backend_name the {@link Backend.name}
+ * @param persona_store_id the {@link PersonaStore.id}
+ * @param persona_id the Persona identifier (backend-specific)
+ * @see Persona.build_uid
+ * @since 0.1.13
+ */
+ public static void split_uid (string uid, out string backend_name,
+ out string persona_store_id, out string persona_id)
+ {
+ assert (uid.validate ());
+
+ size_t backend_name_length = 0, persona_store_id_length = 0;
+ var escaped = false;
+ for (unowned string i = uid; i.get_char () != '\0'; i = i.next_char ())
+ {
+ if (i.get_char () == '\\')
+ escaped = !escaped;
+ else if (escaped == false && i.get_char () == ':')
+ {
+ if (backend_name_length == 0)
+ backend_name_length = ((char*) i) - ((char*) uid);
+ else
+ persona_store_id_length =
+ (((char*) i) - ((char*) uid)) - backend_name_length - 1;
+ }
+ }
+
+ assert (backend_name_length != 0 && persona_store_id_length != 0);
+
+ backend_name = Persona._unescape_uid_component (
+ uid.substring (0, (long) backend_name_length));
+ persona_store_id = Persona._unescape_uid_component (
+ ((string) ((char*) uid + backend_name_length + 1)).substring (0,
+ (long) persona_store_id_length));
+ persona_id = Persona._unescape_uid_component (
+ ((string) ((char*) uid + backend_name_length +
+ persona_store_id_length + 2)));
+ }
}