# `bookmarks` – Bookmark support (XEP-0048)

This module provides support for storing and retrieving bookmarks on the server as per Bookmarks.

## Service

class aioxmpp.BookmarkClient(client, **kwargs)

  Supports retrieval and storage of bookmarks on the server. It currently only supports Private XML Storage as backend.

  There is the general rule never to modify the bookmark instances retrieved from this class (either by `get_bookmarks()` or as an argument to one of the signals). If you need to modify a bookmark for use with `update_bookmark()` use `copy.copy()` to create a copy.

  async sync()

    Sync the bookmarks between the local representation and the server.

    This must be called periodically to assure that the signals are fired.

  async get_bookmarks()

    Get the stored bookmarks from the server. Causes signals to be fired to reflect the changes.

    Returns

      a list of bookmarks

  async set_bookmarks(bookmarks)

    Store the sequence of bookmarks bookmarks.

    Causes signals to be fired to reflect the changes.

    Note

    This should normally not be used. It does not mitigate the race condition between clients concurrently modifying the bookmarks and may lead to data loss. Use `add_bookmark()`, `discard_bookmark()` and `update_bookmark()` instead. This method still has use-cases (modifying the bookmarklist at large, e.g. by syncing the remote store with local data).

  The following methods change the bookmark list in a get-modify-set pattern, to mitigate the danger of race conditions and should be used in most circumstances:

  async add_bookmark(new_bookmark, *, max_retries=3)

    Add a bookmark and check whether it was successfully added to the bookmark list. Already existent bookmarks are not added twice.

    Parameters

      - new_bookmark (an instance of `Bookmark`) – the bookmark to add

      - max_retries (`int`) – the number of retries if setting the bookmark fails

    Raises

      RuntimeError – if the bookmark is not in the bookmark list after max_retries retries.

    After setting the bookmark it is checked, whether the bookmark is in the online storage, if it is not it is tried again at most max_retries times to add the bookmark. A `RuntimeError` is raised if the bookmark could not be added successfully after max_retries.

  async discard_bookmark(bookmark_to_remove, *, max_retries=3)

    Remove a bookmark and check it has been removed.

    Parameters

      - bookmark_to_remove (a `Bookmark` subclass.) – the bookmark to remove

      - max_retries (`int`) – the number of retries of removing the bookmark fails.

    Raises

      RuntimeError – if the bookmark is not removed from bookmark list after max_retries retries.

    If there are multiple occurrences of the same bookmark exactly one is removed.

    This does nothing if the bookmarks does not match an existing bookmark according to bookmark-equality.

    After setting the bookmark it is checked, whether the bookmark is removed in the online storage, if it is not it is tried again at most max_retries times to remove the bookmark. A `RuntimeError` is raised if the bookmark could not be removed successfully after max_retries.

  async update_bookmark(old, new, *, max_retries=3)

    Update a bookmark and check it was successful.

    The bookmark matches an existing bookmark old according to bookmark equalitiy and replaces it by new. The bookmark new is added if no bookmark matching old exists.

    Parameters

      - old – the bookmark to replace

      - new – the replacement bookmark

      - max_retries (`int`) – the number of retries of removing the bookmark fails.

    Raises

      RuntimeError – if the bookmark is not in the bookmark list after max_retries retries.

    After replacing the bookmark it is checked, whether the bookmark new is in the online storage, if it is not it is tried again at most max_retries times to replace the bookmark. A `RuntimeError` is raised if the bookmark could not be replaced successfully after max_retries.

    Note

    Do not modify a bookmark retrieved from the signals or from `get_bookmarks()` to obtain the bookmark new, this will lead to data corruption as they are passed by reference. Instead use `copy.copy()` and modify the copy.

  The following signals are provided that allow tracking the changes to the bookmark list:

  signal on_bookmark_added(added_bookmark)

    Fires when a new bookmark is added.

  signal on_bookmark_removed(removed_bookmark)

    Fires when a bookmark is removed.

  signal on_bookmark_changed(old_bookmark, new_bookmark)

    Fires when a bookmark is changed.

  Note

  A heuristic is used to determine the change of bookmarks and the reported changes may not directly reflect the used methods, but it will always be possible to construct the list of bookmarks from the events. For example, when using `update_bookmark()` to change the JID of a `Conference` bookmark a removed and a added signal will fire.

  Note

  The bookmark protocol is prone to race conditions if several clients access it concurrently. Be careful to use a get-modify-set pattern or the provided highlevel interface.

  Note

  Some other clients extend the bookmark format. For now those extensions are silently dropped by our XSOs, and therefore are lost, when changing the bookmarks with aioxmpp. This is considered a bug to be fixed in the future.

## XSOs

All bookmark types must adhere to the following ABC:

class aioxmpp.bookmarks.Bookmark(*args, **kwargs)

  A bookmark XSO abstract base class.

  Every XSO class registered as child of `Storage` must be a `Bookmark` subclass.

  Bookmarks must provide the following interface:

  primary

    Return the primary category of the bookmark.

    The internal structure of the category is opaque to the code using it; only equality and hashing must be provided and operate by value. It is recommended that this be either a single datum (e.g. a string or JID) or a tuple of data items.

    Together with the type and `secondary` this must fully determine the value of the bookmark.

    This is used in the computation of the change signals. Bookmarks with different type or `primary` keys cannot be identified as changed from/to one another.

  secondary

    Return the tuple of secondary categories of the bookmark.

    Together with the type and `primary` they must fully determine the value of the bookmark.

    This is used in the computation of the change signals. The categories in the tuple are ordered in decreasing precedence, when calculating which bookmarks have changed the ones which mismatch in the category with the lowest precedence are grouped together.

    The length of the tuple must be the same for all bookmarks of a type.

  name

    The human-readable label or description of the bookmark.

  Equality is defined in terms of those properties:

  __eq__(other)

    Compare for equality by value and type.

    The value of a bookmark must be fully determined by the values of the `primary` and `secondary` properties.

    This is used for generating the bookmark list change signals and for the get-modify-set methods.

  It is highly recommended not to redefine `__eq__()` in a subclass, if you do so make sure that the following axiom relating `__eq__()`, `primary` and `secondary` holds:

      (type(a) == type(b) and
       a.primary == b.primary and
       a.secondary == b.secondary)

  if and only if:

      a == b

  Otherwise the generation of bookmark change signals is not guaranteed to be correct.

The following XSOs are used to represent an manipulate bookmark lists.

class aioxmpp.bookmarks.Conference(*args, **kwargs)

  An bookmark for a groupchat.

  name

    The name of the bookmark.

  jid

    The jid under which the groupchat is accessible.

  autojoin

    Whether to join automatically, when the client starts.

  nick

    The nick to use in the groupchat.

  password

    The password used to access the groupchat.

class aioxmpp.bookmarks.URL(*args, **kwargs)

  An URL bookmark.

  name

    The name of the bookmark.

  url

    The URL the bookmark saves.

To register custom bookmark classes use:

aioxmpp.bookmarks.as_bookmark_class(xso_class)

  Decorator to register xso_class as a custom bookmark class.

  This is necessary to store and retrieve such bookmarks. The registered class must be a subclass of the abstract base class `Bookmark`.

  Raises

    TypeError – if xso_class is not a subclass of `Bookmark`.

The following is used internally as the XSO container for bookmarks.

class aioxmpp.bookmarks.Storage(*args, **kwargs)

  The container for storing bookmarks.

  bookmarks

    A `XSOList` of bookmarks.

## Notes on usage

It is highly recommended to interact with the bookmark client via the provided signals and the get-modify-set methods `add_bookmark()`, `discard_bookmark()` and `update_bookmark()`. Using `set_bookmarks()` directly is error prone and might cause data loss due to race conditions.