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 *       Travis Reitter <travis.reitter@collabora.co.uk>
19 */
20
21using GLib;
22using Gee;
23
24/**
25 * Trust level for a {@link PersonaStore}'s {@link Persona}s for linking
26 * purposes.
27 *
28 * Trust levels are set internally by the backends, and must not be modified by
29 * clients.
30 *
31 * @since 0.1.13
32 */
33public enum Folks.PersonaStoreTrust
34{
35  /**
36   * The {@link Persona}s aren't trusted at all, and cannot be linked.
37   *
38   * This should be used for {@link PersonaStore}s where even the
39   * {@link Persona} UID could be maliciously edited to corrupt {@link Persona}
40   * links, or where the UID changes regularly.
41   *
42   * @since 0.1.13
43   */
44  NONE,
45
46  /**
47   * Only the {@link Persona.uid} property is trusted for linking.
48   *
49   * In practice, this means that {@link Persona}s from this
50   * {@link PersonaStore} will not contribute towards the linking process, but
51   * can be linked together by their UIDs using data from {@link Persona}s from
52   * a fully-trusted {@link PersonaStore}.
53   *
54   * @since 0.1.13
55   */
56  PARTIAL,
57
58  /**
59   * Every property in {@link Persona.linkable_properties} is trusted.
60   *
61   * This should only be used for user-controlled {@link PersonaStore}s, as if a
62   * remote store is compromised, malicious changes could be made to its data
63   * which corrupt the user's {@link Persona} links.
64   *
65   * @since 0.1.13
66   */
67  FULL
68}
69/**
70 * Errors from {@link PersonaStore}s.
71 */
72public errordomain Folks.PersonaStoreError
73{
74  /**
75   * An argument to the method was invalid.
76   */
77  INVALID_ARGUMENT,
78
79  /**
80   * Creation of a {@link Persona} failed.
81   */
82  CREATE_FAILED,
83
84  /**
85   * Such an operation may not be performed on a {@link Persona} with
86   * {@link Persona.is_user} set to ``true``.
87   *
88   * @since 0.3.0
89   */
90  UNSUPPORTED_ON_USER,
91
92  /**
93   * The {@link PersonaStore} was offline (ie, this is a temporary failure).
94   *
95   * @since 0.3.0
96   */
97  STORE_OFFLINE,
98
99  /**
100   * The {@link PersonaStore} doesn't support write operations.
101   *
102   * @since 0.3.4
103   */
104  READ_ONLY,
105
106  /**
107   * The operation was denied due to not having sufficient permissions.
108   *
109   * @since 0.6.0
110   */
111  PERMISSION_DENIED,
112
113  /**
114   * Removal of a {@link Persona} failed. This is a generic error which is used
115   * if no other error code (such as, e.g.,
116   * {@link PersonaStoreError.PERMISSION_DENIED}) is applicable.
117   *
118   * @since 0.6.0
119   */
120  REMOVE_FAILED,
121
122  /**
123   * Such an operation may only be performed on a {@link Persona} with
124   * {@link Persona.is_user} set to ``true``.
125   *
126   * @since 0.6.4
127   */
128  UNSUPPORTED_ON_NON_USER,
129}
130
131/**
132 * Definition of the available fields to be looked up with
133 * {@link PersonaStore.detail_key}.
134 *
135 * @since 0.5.0
136 */
137/* NOTE: Must be kept in sync with
138 * {@link Folks.PersonaStore._PERSONA_DETAIL}. */
139public enum Folks.PersonaDetail
140{
141  /**
142   * Invalid field for use in error returns.
143   *
144   * @since 0.6.2
145   */
146  INVALID = -1,
147
148  /**
149   * Field for {@link AliasDetails.alias}.
150   *
151   * @since 0.5.0
152   */
153  ALIAS = 0,
154
155  /**
156   * Field for {@link AvatarDetails.avatar}.
157   *
158   * @since 0.5.0
159   */
160  AVATAR,
161
162  /**
163   * Field for {@link BirthdayDetails.birthday}.
164   *
165   * @since 0.5.0
166   */
167  BIRTHDAY,
168
169  /**
170   * Field for {@link EmailDetails.email_addresses}.
171   *
172   * @since 0.5.0
173   */
174  EMAIL_ADDRESSES,
175
176  /**
177   * Field for {@link NameDetails.full_name}.
178   *
179   * @since 0.5.0
180   */
181  FULL_NAME,
182
183  /**
184   * Field for {@link GenderDetails.gender}.
185   *
186   * @since 0.5.0
187   */
188  GENDER,
189
190  /**
191   * Field for {@link ImDetails.im_addresses}.
192   *
193   * @since 0.5.0
194   */
195  IM_ADDRESSES,
196
197  /**
198   * Field for {@link FavouriteDetails.is_favourite}.
199   *
200   * @since 0.5.0
201   */
202  IS_FAVOURITE,
203
204  /**
205   * Field for {@link LocalIdDetails.local_ids}.
206   *
207   * @since 0.5.0
208   */
209  LOCAL_IDS,
210
211  /**
212   * Field for {@link LocationDetails.location}.
213   *
214   * @since 0.9.2
215   */
216  LOCATION,
217
218  /**
219   * Field for {@link NameDetails.nickname}.
220   *
221   * @since 0.5.0
222   */
223  NICKNAME,
224
225  /**
226   * Field for {@link NoteDetails.notes}.
227   *
228   * @since 0.5.0
229   */
230  NOTES,
231
232  /**
233   * Field for {@link PhoneDetails.phone_numbers}.
234   *
235   * @since 0.5.0
236   */
237  PHONE_NUMBERS,
238
239  /**
240   * Field for {@link PostalAddressDetails.postal_addresses}.
241   *
242   * @since 0.5.0
243   */
244  POSTAL_ADDRESSES,
245
246  /**
247   * Field for {@link RoleDetails.roles}.
248   *
249   * @since 0.5.0
250   */
251  ROLES,
252
253  /**
254   * Field for {@link NameDetails.structured_name}.
255   *
256   * @since 0.5.0
257   */
258  STRUCTURED_NAME,
259
260  /**
261   * Field for {@link UrlDetails.urls}.
262   *
263   * @since 0.5.0
264   */
265  URLS,
266
267  /**
268   * Field for {@link WebServiceDetails.web_service_addresses}.
269   *
270   * @since 0.5.0
271   */
272  WEB_SERVICE_ADDRESSES,
273
274  /**
275   * Field for {@link GroupDetails.groups}.
276   *
277   * @since 0.6.2
278   */
279  GROUPS,
280
281  /**
282   * Field for {@link InteractionDetails.im_interaction_count}.
283   *
284   * @since 0.7.1
285   */
286  IM_INTERACTION_COUNT,
287
288  /**
289   * Field for {@link InteractionDetails.last_im_interaction_datetime}.
290   *
291   * @since 0.7.1
292   */
293  LAST_IM_INTERACTION_DATETIME,
294
295  /**
296   * Field for {@link InteractionDetails.call_interaction_count}.
297   *
298   * @since 0.7.1
299   */
300  CALL_INTERACTION_COUNT,
301
302  /**
303   * Field for {@link InteractionDetails.last_call_interaction_datetime}.
304   *
305   * @since 0.7.1
306   */
307  LAST_CALL_INTERACTION_DATETIME,
308
309  /**
310   * Field for {@link AntiLinkable.anti_links}.
311   *
312   * @since 0.7.3
313   */
314  ANTI_LINKS,
315
316  /**
317   * Field for {@link ExtendedFieldDetails}.
318   *
319   * @since 0.11.0
320   */
321  EXTENDED_INFO,
322}
323
324/**
325 * A store for {@link Persona}s.
326 *
327 * After creating a PersonaStore instance, you must connect to the
328 * {@link PersonaStore.personas_changed} signal, //then// call
329 * {@link PersonaStore.prepare}, otherwise a race condition may occur between
330 * emission of {@link PersonaStore.personas_changed} and your code connecting to
331 * it.
332 */
333public abstract class Folks.PersonaStore : Object
334{
335  construct
336    {
337      debug ("Constructing PersonaStore ‘%s’ (%p)", this.id, this);
338    }
339
340  ~PersonaStore ()
341    {
342      debug ("Destroying PersonaStore ‘%s’ (%p)", this.id, this);
343    }
344
345  /**
346   * The following list of properties are the basic keys
347   * that each PersonaStore with write capabilities should
348   * support for {@link PersonaStore.add_persona_from_details}.
349   *
350   * Note that these aren't the only valid keys; backends are
351   * allowed to support keys beyond the ones defined here
352   * which might be specific to the backend in question.
353   *
354   * NOTE: MUST be kept in sync with {@link Folks.PersonaDetail}.
355   *
356   * @since 0.5.0
357   */
358  private const string _PERSONA_DETAIL[] = {
359    "alias",
360    "avatar",
361    "birthday",
362    "email-addresses",
363    "full-name",
364    "gender",
365    "im-addresses",
366    "is-favourite",
367    "local-ids",
368    "location",
369    "nickname",
370    "notes",
371    "phone-numbers",
372    "postal-addresses",
373    "roles",
374    "structured-name",
375    "urls",
376    "web-service-addresses",
377    "groups",
378    "im-interaction-count",
379    "last-im-interaction-datetime",
380    "call-interaction-count",
381    "last-call-interaction-datetime",
382    "anti-links",
383    "extended-info"
384  };
385
386  /**
387   * Returns the key corresponding to @detail, for use in
388   * the details param of {@link PersonaStore.add_persona_from_details}.
389   *
390   * @param detail the {@link PersonaDetail} to lookup
391   * @return the corresponding property name, or ``null`` if ``detail`` is
392   * invalid
393   *
394   * @since 0.5.0
395   */
396  public static unowned string? detail_key (Folks.PersonaDetail detail)
397    {
398      if (detail == PersonaDetail.INVALID ||
399          detail >= PersonaStore._PERSONA_DETAIL.length)
400        {
401          return null;
402        }
403
404      return PersonaStore._PERSONA_DETAIL[detail];
405    }
406
407  /**
408   * Emitted when one or more {@link Persona}s are added to or removed from
409   * the store.
410   *
411   * This will not be emitted until after {@link PersonaStore.prepare} has been
412   * called.
413   *
414   * @param added a set of {@link Persona}s which have been removed
415   * @param removed a set of {@link Persona}s which have been removed
416   * @param message a string message from the backend, if any
417   * @param actor the {@link Persona} who made the change, if known
418   * @param reason the reason for the change
419   *
420   * @since 0.5.1
421   */
422  public signal void personas_changed (Set<Persona> added,
423      Set<Persona> removed,
424      string? message,
425      Persona? actor,
426      GroupDetails.ChangeReason reason);
427
428  /* Emit the personas-changed signal, turning null parameters into empty sets
429   * and only passing a read-only view to the signal handlers. */
430  protected void _emit_personas_changed (Set<Persona>? added,
431      Set<Persona>? removed,
432      string? message = null,
433      Persona? actor = null,
434      GroupDetails.ChangeReason reason = GroupDetails.ChangeReason.NONE)
435    {
436      var _added = added;
437      var _removed = removed;
438
439      if ((added == null || ((!) added).size == 0) &&
440          (removed == null || ((!) removed).size == 0))
441        {
442          /* Don't bother signalling if nothing's changed */
443          return;
444        }
445      else if (added == null)
446        {
447          _added = new HashSet<Persona> ();
448        }
449      else if (removed == null)
450        {
451          _removed = new HashSet<Persona> ();
452        }
453
454      Internal.profiling_point ("emitting PersonaStore::personas-changed " +
455          "(ID: %s, count: %u)", this.id, _added.size + _removed.size);
456
457      // We've now guaranteed that both _added and _removed are non-null.
458      this.personas_changed (((!) _added).read_only_view,
459          ((!) _removed).read_only_view, message, actor, reason);
460    }
461
462  /**
463   * Emitted when the backing store for this PersonaStore has been removed.
464   *
465   * At this point, the PersonaStore and all its {@link Persona}s are invalid,
466   * so any client referencing it should unreference it.
467   *
468   * This will not be emitted until after {@link PersonaStore.prepare} has been
469   * called.
470   */
471  public abstract signal void removed ();
472
473  /**
474   * The type of PersonaStore this is.
475   *
476   * This is the same for all PersonaStores provided by a given {@link Backend}.
477   *
478   * This is guaranteed to always be available; even before
479   * {@link PersonaStore.prepare} is called. It is immutable over the life of
480   * the {@link PersonaStore}.
481   */
482  public abstract string type_id
483    {
484      /* Note: the type_id must not contain colons because the primary writeable
485       * store is configured, either via GSettings or the FOLKS_PRIMARY_STORE
486       * env variable, with a string of the form 'type_id:store_id'. */
487      get;
488    }
489
490  /**
491   * The human-readable, service-specific name used to represent the
492   * PersonaStore to the user.
493   *
494   * For example: ``foo@@xmpp.example.org``.
495   *
496   * This should be used whenever the user needs to be presented with a
497   * familiar, service-specific name. For instance, in a prompt for the user to
498   * select a specific IM account from which to initiate a chat.
499   *
500   * This is not guaranteed to be unique even within this PersonaStore's
501   * {@link Backend}. Its value may change throughout the life of the store.
502   *
503   * @since 0.1.13
504   */
505  public string display_name { get; construct; }
506
507  /**
508   * The instance identifier for this PersonaStore.
509   *
510   * Since each {@link Backend} can provide multiple different PersonaStores
511   * for different accounts or servers (for example), they each need an ID
512   * which is unique within the backend.
513   *
514   * It is immutable over the life of the {@link PersonaStore}.
515   */
516  public string id { get; construct; }
517
518  /**
519   * The {@link Persona}s exposed by this PersonaStore.
520   *
521   * @since 0.5.1
522   */
523  public abstract Map<string, Persona> personas { get; }
524
525  /**
526   * Whether this {@link PersonaStore} can add {@link Persona}s.
527   *
528   * This value may change throughout the life of the {@link PersonaStore}.
529   *
530   * @since 0.3.1
531   */
532  public abstract MaybeBool can_add_personas { get; default = MaybeBool.UNSET; }
533
534  /**
535   * Whether this {@link PersonaStore} can set the alias of {@link Persona}s.
536   *
537   * @since 0.3.1
538   */
539  [Version (deprecated = true, deprecated_since = "0.6.3.1",
540      replacement = "PersonaStore.always_writeable_properties")]
541  public abstract MaybeBool can_alias_personas
542    {
543      get;
544      default = MaybeBool.UNSET;
545    }
546
547  /**
548   * Whether this {@link PersonaStore} can set the groups of {@link Persona}s.
549   *
550   * @since 0.3.1
551   */
552  [Version (deprecated = true, deprecated_since = "0.6.3.1",
553      replacement = "PersonaStore.always_writeable_properties")]
554  public abstract MaybeBool can_group_personas
555    {
556      get;
557      default = MaybeBool.UNSET;
558    }
559
560  /**
561   * Whether this {@link PersonaStore} can remove {@link Persona}s.
562   *
563   * This value may change throughout the life of the {@link PersonaStore}.
564   *
565   * @since 0.3.1
566   */
567  public abstract MaybeBool can_remove_personas
568    {
569      get;
570      default = MaybeBool.UNSET;
571    }
572
573  /**
574   * Whether {@link PersonaStore.prepare} has successfully completed for this
575   * store.
576   *
577   * It’s guaranteed that this will only ever change from ``false`` to ``true``
578   * in the lifetime of the {@link PersonaStore}.
579   *
580   * @since 0.3.0
581   */
582  public abstract bool is_prepared { get; default = false; }
583
584  /**
585   * Whether the store has reached a quiescent state. This will happen at some
586   * point after {@link PersonaStore.prepare} has successfully completed for the
587   * store. A store is in a quiescent state when all the {@link Persona}s that
588   * it originally knows about have been loaded.
589   *
590   * It's guaranteed that this property's value will only ever change after
591   * {@link IndividualAggregator.is_prepared} has changed to ``true``.
592   *
593   * @since 0.6.2
594   */
595  public abstract bool is_quiescent { get; default = false; }
596
597   /**
598   * Whether the PersonaStore is writeable.
599   *
600   * Only if a PersonaStore is writeable will its {@link Persona}s be updated by
601   * changes to the {@link Individual}s containing them, and those changes then
602   * be written out to the relevant backing store.
603   *
604   * If this property is ``false``, it doesn't mean that {@link Persona}s in
605   * this persona store aren't writeable at all. If their properties are updated
606   * through the {@link Persona}, rather than through the {@link Individual}
607   * containing that persona, changes may be propagated to the backing store.
608   *
609   * PersonaStores must not set this property themselves; it will be set as
610   * appropriate by the {@link IndividualAggregator}.
611   *
612   * @since 0.1.13
613   */
614  [Version (deprecated = true, deprecated_since = "0.6.3",
615      replacement = "PersonaStore.is_primary_store")]
616  public bool is_writeable { get; set; default = false; }
617
618  private PersonaStoreTrust _trust_level = PersonaStoreTrust.NONE;
619
620  /**
621   * The trust level of the PersonaStore for linking.
622   *
623   * Each {@link PersonaStore} is assigned a trust level by the
624   * IndividualAggregator, designating whether to trust the properties of its
625   * {@link Persona}s for linking to produce {@link Individual}s.
626   *
627   * This value may change throughout the life of the {@link PersonaStore}.
628   *
629   * The trust level may be queried by clients, but must not be set by them. The
630   * setter for this property is for libfolks internal use only.
631   *
632   * @see PersonaStoreTrust
633   * @since 0.1.13
634   */
635  public PersonaStoreTrust trust_level
636    {
637      get
638        {
639          return this._trust_level;
640        }
641
642      /* FIXME: At the next API break, make this an abstract property and have
643       * implemented by the backends, to avoid exposing the setter in the C
644       * API. The IndividualAggregator can always disregard the backend’s
645       * suggested trust level.
646       *
647       * https://bugzilla.gnome.org/show_bug.cgi?id=722421 */
648      set
649        {
650          if (value > trust_level)
651            {
652              this._trust_level = value;
653              this.notify_property ("trust-level");
654            }
655          else
656            {
657              debug ("Unable to lower Persona Store trust_level");
658            }
659        }
660    }
661
662  /**
663   * The names of the properties of the {@link Persona}s in this store which are
664   * always writeable.
665   *
666   * If a property name is in this list, setting the property on a persona
667   * should result in the updated value being stored in the backend's permanent
668   * storage (unless it gets rejected due to being invalid, or a different error
669   * occurs).
670   *
671   * This property value is guaranteed to be constant for a given persona store,
672   * but may vary between persona stores in the same backend. It's guaranteed
673   * that this will always be a subset of the value of
674   * {@link Persona.writeable_properties} for the personas in this persona
675   * store.
676   *
677   * @since 0.6.2
678   */
679  public abstract string[] always_writeable_properties { get; }
680
681  /**
682   * Prepare the PersonaStore for use.
683   *
684   * This connects the PersonaStore to whichever backend-specific services it
685   * requires to be able to provide {@link Persona}s. This should be called
686   * //after// connecting to the {@link PersonaStore.personas_changed} signal,
687   * or a race condition could occur, with the signal being emitted before your
688   * code has connected to it, and {@link Persona}s getting "lost" as a result.
689   *
690   * This is normally handled transparently by the {@link IndividualAggregator}.
691   *
692   * If this function throws an error, the PersonaStore will not be functional.
693   *
694   * This function is guaranteed to be idempotent (since version 0.3.0).
695   *
696   * Concurrent calls to this function from different threads will block until
697   * preparation has completed. However, concurrent calls to this function from
698   * a single thread might not, i.e. the first call will block but subsequent
699   * calls might return before the first one. (Though they will be safe in every
700   * other respect.)
701   *
702   * @throws GLib.Error if preparing the backend-specific services failed — this
703   * will be a backend-specific error
704   *
705   * @since 0.1.11
706   */
707  public abstract async void prepare () throws GLib.Error;
708
709  /**
710   * Flush any pending changes to the PersonaStore's backing store.
711   *
712   * PersonaStores may (transparently) implement caching or I/O queueing which
713   * means that changes to their {@link Persona}s may not be immediately written
714   * to the PersonaStore's backing store. Calling this function will force all
715   * pending changes to be flushed to the backing store.
716   *
717   * This must not be called before {@link PersonaStore.prepare}.
718   *
719   * @since 0.1.17
720   */
721  public virtual async void flush ()
722    {
723      /* Default implementation doesn't have to do anything */
724    }
725
726  /**
727   * Add a new {@link Persona} to the PersonaStore.
728   *
729   * The {@link Persona} will be created by the PersonaStore backend from the
730   * key-value pairs given in ``details``.
731   *
732   * All additions through this function will later be emitted through the
733   * personas-changed signal to be notified of the new {@link Persona}. The
734   * return value is purely for convenience, since it can be complicated to
735   * correlate the provided details with the final Persona.
736   *
737   * If the store is offline (or {@link PersonaStore.prepare} hasn't yet been
738   * called successfully), this function will throw
739   * {@link PersonaStoreError.STORE_OFFLINE}. It's the responsibility of the
740   * caller to cache details and re-try this function if it wishes to make
741   * offline adds work.
742   *
743   * If the details are not recognised or are invalid,
744   * {@link PersonaStoreError.INVALID_ARGUMENT} will be thrown. A default set
745   * of possible details are defined by {@link Folks.PersonaDetail} but backends
746   * can either support a subset or superset of the suggested defaults.
747   *
748   * If a {@link Persona} with the given details already exists in the store, no
749   * error will be thrown and this function will return ``null``.
750   *
751   * @param details a key-value map of details to use in creating the new
752   * {@link Persona}
753   *
754   * @return the new {@link Persona} or ``null`` if the corresponding Persona
755   * already existed. If non-``null``, the new {@link Persona} will also be
756   * amongst the {@link Persona}(s) in a future emission of
757   * {@link PersonaStore.personas_changed}.
758   * @throws PersonaStoreError if adding the persona failed
759   */
760  public abstract async Persona? add_persona_from_details (
761      HashTable<string, Value?> details) throws Folks.PersonaStoreError;
762
763  /**
764   * Remove a {@link Persona} from the PersonaStore.
765   *
766   * It isn't guaranteed that the Persona will actually be removed by the time
767   * this asynchronous function finishes. The successful removal of the Persona
768   * will be signalled through emission of
769   * {@link PersonaStore.personas_changed}.
770   *
771   * If the store is offline (or {@link PersonaStore.prepare} hasn't yet been
772   * called successfully), this function will throw
773   * {@link PersonaStoreError.STORE_OFFLINE}. It's the responsibility of the
774   * caller to cache details and re-try this function if it wishes to make
775   * offline removals work.
776   *
777   * @param persona the {@link Persona} to remove
778   * @throws PersonaStoreError if removing the persona failed
779   *
780   * @since 0.1.11
781   */
782  public abstract async void remove_persona (Persona persona)
783      throws Folks.PersonaStoreError;
784
785  /**
786   * Whether this {@link PersonaStore} is the primary store to be used for
787   * linking {@link Persona}s.
788   *
789   * @since 0.6.3
790   */
791  public bool is_primary_store { get; internal set; default = false; }
792
793  /* The setter folks_persona_store_set_is_user_set_default() is redeclared
794   * in folks/redeclare-internal-api.h so that libfolks-eds can use it.
795   * If you alter this property, check the generated C and update that
796   * header if necessary. https://bugzilla.gnome.org/show_bug.cgi?id=697354 */
797  /**
798   * Whether this {@link PersonaStore} is marked as the default in its backend
799   * by the user.
800   *
801   * i.e. A {@link PersonaStore} for the EDS backend would set this to ``true``
802   * if it represents the user’s default address book.
803   *
804   * @since 0.6.3
805   */
806  public bool is_user_set_default { get; internal set; default = false; }
807}
808