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