/* * Copyright (C) 2010 Collabora Ltd. * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * * Authors: * Travis Reitter */ using GLib; using Gee; /** * Trust level for a {@link PersonaStore}'s {@link Persona}s for linking * purposes. * * @since 0.1.13 */ public enum Folks.PersonaStoreTrust { /** * The {@link Persona}s aren't trusted at all, and cannot be linked. * * This should be used for {@link PersonaStore}s where even the * {@link Persona} UID could be maliciously edited to corrupt {@link Persona} * links, or where the UID changes regularly. * * @since 0.1.13 */ NONE, /** * Only the {@link Persona.uid} property is trusted for linking. * * In practice, this means that {@link Persona}s from this * {@link PersonaStore} will not contribute towards the linking process, but * can be linked together by their UIDs using data from {@link Persona}s from * a fully-trusted {@link PersonaStore}. * * @since 0.1.13 */ PARTIAL, /** * Every property in {@link Persona.linkable_properties} is trusted. * * This should only be used for user-controlled {@link PersonaStore}s, as if a * remote store is compromised, malicious changes could be made to its data * which corrupt the user's {@link Persona} links. * * @since 0.1.13 */ FULL } /** * Errors from {@link PersonaStore}s. */ public errordomain Folks.PersonaStoreError { /** * An argument to the method was invalid. */ INVALID_ARGUMENT, /** * Creation of a {@link Persona} failed. */ CREATE_FAILED, /** * Such an operation may not be performed on a {@link Persona} with * {@link Persona.is_user} set to `true`. * * @since 0.3.0 */ UNSUPPORTED_ON_USER, /** * The {@link PersonaStore} was offline (ie, this is a temporary failure). * * @since 0.3.0 */ STORE_OFFLINE, /** * The {@link PersonaStore} doesn't support write operations. * * @since 0.3.4 */ READ_ONLY, /** * The operation was denied due to not having sufficient permissions. * * @since 0.6.0 */ PERMISSION_DENIED, /** * Removal of a {@link Persona} failed. This is a generic error which is used * if no other error code (such as, e.g., * {@link PersonaStoreError.PERMISSION_DENIED}) is applicable. * * @since 0.6.0 */ REMOVE_FAILED, /** * Such an operation may only be performed on a {@link Persona} with * {@link Persona.is_user} set to `true`. * * @since 0.6.4 */ UNSUPPORTED_ON_NON_USER, } /** * Definition of the available fields to be looked up with * {@link PersonaStore.detail_key}. * * @since 0.5.0 */ /* NOTE: Must be kept in sync with * {@link Folks.PersonaStore._PERSONA_DETAIL}. */ public enum Folks.PersonaDetail { /** * Invalid field for use in error returns. * * @since 0.6.2 */ INVALID = -1, /** * Field for {@link AliasDetails.alias}. * * @since 0.5.0 */ ALIAS = 0, /** * Field for {@link AvatarDetails.avatar}. * * @since 0.5.0 */ AVATAR, /** * Field for {@link BirthdayDetails.birthday}. * * @since 0.5.0 */ BIRTHDAY, /** * Field for {@link EmailDetails.email_addresses}. * * @since 0.5.0 */ EMAIL_ADDRESSES, /** * Field for {@link NameDetails.full_name}. * * @since 0.5.0 */ FULL_NAME, /** * Field for {@link GenderDetails.gender}. * * @since 0.5.0 */ GENDER, /** * Field for {@link ImDetails.im_addresses}. * * @since 0.5.0 */ IM_ADDRESSES, /** * Field for {@link FavouriteDetails.is_favourite}. * * @since 0.5.0 */ IS_FAVOURITE, /** * Field for {@link LocalIdDetails.local_ids}. * * @since 0.5.0 */ LOCAL_IDS, /** * Field for {@link NameDetails.nickname}. * * @since 0.5.0 */ NICKNAME, /** * Field for {@link NoteDetails.notes}. * * @since 0.5.0 */ NOTES, /** * Field for {@link PhoneDetails.phone_numbers}. * * @since 0.5.0 */ PHONE_NUMBERS, /** * Field for {@link PostalAddressDetails.postal_addresses}. * * @since 0.5.0 */ POSTAL_ADDRESSES, /** * Field for {@link RoleDetails.roles}. * * @since 0.5.0 */ ROLES, /** * Field for {@link NameDetails.structured_name}. * * @since 0.5.0 */ STRUCTURED_NAME, /** * Field for {@link UrlDetails.urls}. * * @since 0.5.0 */ URLS, /** * Field for {@link WebServiceDetails.web_service_addresses}. * * @since 0.5.0 */ WEB_SERVICE_ADDRESSES, /** * Field for {@link GroupDetails.groups}. * * @since 0.6.2 */ GROUPS } /** * A store for {@link Persona}s. * * After creating a PersonaStore instance, you must connect to the * {@link PersonaStore.personas_changed} signal, //then// call * {@link PersonaStore.prepare}, otherwise a race condition may occur between * emission of {@link PersonaStore.personas_changed} and your code connecting to * it. */ public abstract class Folks.PersonaStore : Object { construct { debug ("Constructing PersonaStore ā€˜%sā€™ (%p)", this.id, this); } ~PersonaStore () { debug ("Destroying PersonaStore ā€˜%sā€™ (%p)", this.id, this); } /** * The following list of properties are the basic keys * that each PersonaStore with write capabilities should * support for {@link PersonaStore.add_persona_from_details}. * * Note that these aren't the only valid keys; backends are * allowed to support keys beyond the ones defined here * which might be specific to the backend in question. * * NOTE: MUST be kept in sync with {@link Folks.PersonaDetail}. * * @since 0.5.0 */ private static const string _PERSONA_DETAIL[] = { "alias", "avatar", "birthday", "email-addresses", "full-name", "gender", "im-addresses", "is-favourite", "local-ids", "nickname", "notes", "phone-numbers", "postal-addresses", "roles", "structured-name", "urls", "web-service-addresses", "groups" }; /** * Returns the key corresponding to @detail, for use in * the details param of {@link PersonaStore.add_persona_from_details}. * * @param detail the {@link PersonaDetail} to lookup * @return the corresponding property name, or `null` if `detail` is invalid * * @since 0.5.0 */ public static unowned string? detail_key (Folks.PersonaDetail detail) { if (detail == PersonaDetail.INVALID || detail >= PersonaStore._PERSONA_DETAIL.length) { return null; } return PersonaStore._PERSONA_DETAIL[detail]; } /** * Emitted when one or more {@link Persona}s are added to or removed from * the store. * * This will not be emitted until after {@link PersonaStore.prepare} has been * called. * * @param added a set of {@link Persona}s which have been removed * @param removed a set of {@link Persona}s which have been removed * @param message a string message from the backend, if any * @param actor the {@link Persona} who made the change, if known * @param reason the reason for the change * * @since 0.5.1 */ public signal void personas_changed (Set added, Set removed, string? message, Persona? actor, GroupDetails.ChangeReason reason); /* Emit the personas-changed signal, turning null parameters into empty sets * and only passing a read-only view to the signal handlers. */ protected void _emit_personas_changed (Set? added, Set? removed, string? message = null, Persona? actor = null, GroupDetails.ChangeReason reason = GroupDetails.ChangeReason.NONE) { var _added = added; var _removed = removed; if ((added == null || ((!) added).size == 0) && (removed == null || ((!) removed).size == 0)) { /* Don't bother signalling if nothing's changed */ return; } else if (added == null) { _added = new HashSet (); } else if (removed == null) { _removed = new HashSet (); } // We've now guaranteed that both _added and _removed are non-null. this.personas_changed (((!) _added).read_only_view, ((!) _removed).read_only_view, message, actor, reason); } /** * Emitted when the backing store for this PersonaStore has been removed. * * At this point, the PersonaStore and all its {@link Persona}s are invalid, * so any client referencing it should unreference it. * * This will not be emitted until after {@link PersonaStore.prepare} has been * called. */ public abstract signal void removed (); /** * The type of PersonaStore this is. * * This is the same for all PersonaStores provided by a given {@link Backend}. * * This is guaranteed to always be available; even before * {@link PersonaStore.prepare} is called. */ public abstract string type_id { /* Note: the type_id must not contain colons because the primary writeable * store is configured, either via GConf or the FOLKS_PRIMARY_STORE * env variable, with a string of the form 'type_id:store_id'. */ get; } /** * The human-readable, service-specific name used to represent the * PersonaStore to the user. * * For example: `foo@@xmpp.example.org`. * * This should be used whenever the user needs to be presented with a * familiar, service-specific name. For instance, in a prompt for the user to * select a specific IM account from which to initiate a chat. * * This is not guaranteed to be unique even within this PersonaStore's * {@link Backend}. * * @since 0.1.13 */ public string display_name { get; construct; } /** * The instance identifier for this PersonaStore. * * Since each {@link Backend} can provide multiple different PersonaStores * for different accounts or servers (for example), they each need an ID * which is unique within the backend. */ public string id { get; construct; } /** * The {@link Persona}s exposed by this PersonaStore. * * @since 0.5.1 */ public abstract Map personas { get; } /** * Whether this {@link PersonaStore} can add {@link Persona}s. * * @since 0.3.1 */ public abstract MaybeBool can_add_personas { get; default = MaybeBool.UNSET; } /** * Whether this {@link PersonaStore} can set the alias of {@link Persona}s. * * @since 0.3.1 */ [Deprecated (since = "0.6.3.1", replacement = "PersonaStore.always_writeable_properties")] public abstract MaybeBool can_alias_personas { get; default = MaybeBool.UNSET; } /** * Whether this {@link PersonaStore} can set the groups of {@link Persona}s. * * @since 0.3.1 */ [Deprecated (since = "0.6.3.1", replacement = "PersonaStore.always_writeable_properties")] public abstract MaybeBool can_group_personas { get; default = MaybeBool.UNSET; } /** * Whether this {@link PersonaStore} can remove {@link Persona}s. * * @since 0.3.1 */ public abstract MaybeBool can_remove_personas { get; default = MaybeBool.UNSET; } /** * Whether {@link PersonaStore.prepare} has successfully completed for this * store. * * @since 0.3.0 */ public abstract bool is_prepared { get; default = false; } /** * Whether the store has reached a quiescent state. This will happen at some * point after {@link PersonaStore.prepare} has successfully completed for the * store. A store is in a quiescent state when all the {@link Persona}s that * it originally knows about have been loaded. * * It's guaranteed that this property's value will only ever change after * {@link IndividualAggregator.is_prepared} has changed to `true`. * * @since 0.6.2 */ public abstract bool is_quiescent { get; default = false; } /** * Whether the PersonaStore is writeable. * * Only if a PersonaStore is writeable will its {@link Persona}s be updated by * changes to the {@link Individual}s containing them, and those changes then * be written out to the relevant backing store. * * If this property is `false`, it doesn't mean that {@link Persona}s in this * persona store aren't writeable at all. If their properties are updated * through the {@link Persona}, rather than through the {@link Individual} * containing that persona, changes may be propagated to the backing store. * * PersonaStores must not set this property themselves; it will be set as * appropriate by the {@link IndividualAggregator}. * * @since 0.1.13 */ [Deprecated (since = "0.6.3", replacement = "PersonaStore.is_primary_store")] public bool is_writeable { get; set; default = false; } /** * The trust level of the PersonaStore for linking. * * Each {@link PersonaStore} is assigned a trust level by the * IndividualAggregator, designating whether to trust the properties of its * {@link Persona}s for linking to produce {@link Individual}s. * * @see PersonaStoreTrust * @since 0.1.13 */ public PersonaStoreTrust trust_level { get; set; default = PersonaStoreTrust.NONE; } /** * The names of the properties of the {@link Persona}s in this store which are * always writeable. * * If a property name is in this list, setting the property on a persona * 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). * * This property value is guaranteed to be constant for a given persona store, * but may vary between persona stores in the same backend. It's guaranteed * that this will always be a subset of the value of * {@link Persona.writeable_properties} for the personas in this persona * store. * * @since 0.6.2 */ public abstract string[] always_writeable_properties { get; } /** * Prepare the PersonaStore for use. * * This connects the PersonaStore to whichever backend-specific services it * requires to be able to provide {@link Persona}s. This should be called * //after// connecting to the {@link PersonaStore.personas_changed} signal, * or a race condition could occur, with the signal being emitted before your * code has connected to it, and {@link Persona}s getting "lost" as a result. * * This is normally handled transparently by the {@link IndividualAggregator}. * * If this function throws an error, the PersonaStore will not be functional. * * This function is guaranteed to be idempotent (since version 0.3.0). * * Concurrent calls to this function from different threads will block until * preparation has completed. However, concurrent calls to this function from * a single thread might not, i.e. the first call will block but subsequent * calls might return before the first one. (Though they will be safe in every * other respect.) * * @since 0.1.11 */ public abstract async void prepare () throws GLib.Error; /** * Flush any pending changes to the PersonaStore's backing store. * * PersonaStores may (transparently) implement caching or I/O queueing which * means that changes to their {@link Persona}s may not be immediately written * to the PersonaStore's backing store. Calling this function will force all * pending changes to be flushed to the backing store. * * This must not be called before {@link PersonaStore.prepare}. * * @since 0.1.17 */ public virtual async void flush () { /* Default implementation doesn't have to do anything */ } /** * Add a new {@link Persona} to the PersonaStore. * * The {@link Persona} will be created by the PersonaStore backend from the * key-value pairs given in `details`. * * All additions through this function will later be emitted through the * personas-changed signal to be notified of the new {@link Persona}. The * return value is purely for convenience, since it can be complicated to * correlate the provided details with the final Persona. * * If the store is offline (or {@link PersonaStore.prepare()} hasn't yet been * called successfully), this function will throw * {@link PersonaStoreError.STORE_OFFLINE}. It's the responsibility of the * caller to cache details and re-try this function if it wishes to make * offline adds work. * * If the details are not recognised or are invalid, * {@link PersonaStoreError.INVALID_ARGUMENT} will be thrown. A default set * of possible details are defined by {@link Folks.PersonaDetail} but backends * can either support a subset or superset of the suggested defaults. * * If a {@link Persona} with the given details already exists in the store, no * error will be thrown and this function will return `null`. * * @param details a key-value map of details to use in creating the new * {@link Persona} * * @return the new {@link Persona} or `null` if the corresponding Persona * already existed. If non-`null`, the new {@link Persona} will also be * amongst the {@link Persona}(s) in a future emission of * {@link PersonaStore.personas_changed}. */ public abstract async Persona? add_persona_from_details ( HashTable details) throws Folks.PersonaStoreError; /** * Remove a {@link Persona} from the PersonaStore. * * It isn't guaranteed that the Persona will actually be removed by the time * this asynchronous function finishes. The successful removal of the Persona * will be signalled through emission of * {@link PersonaStore.personas_changed}. * * If the store is offline (or {@link PersonaStore.prepare()} hasn't yet been * called successfully), this function will throw * {@link PersonaStoreError.STORE_OFFLINE}. It's the responsibility of the * caller to cache details and re-try this function if it wishes to make * offline removals work. * * @param persona the {@link Persona} to remove * @since 0.1.11 */ public abstract async void remove_persona (Persona persona) throws Folks.PersonaStoreError; /** * Whether this {@link PersonaStore} is the primary store which is * to be used for linking {@link Persona}s and such. * * @since 0.6.3 */ public bool is_primary_store { get; internal set; default = false; } /** * Whether this {@link PersonaStore} has been marked as the default * store (in its backend) by the user. I.e.: a PersonaStore for the e-d-s * backend would set this to true if it represents the default address book. * * @since 0.6.3 */ public bool is_user_set_default { get; internal set; default = false; } }