// SPDX-FileCopyrightText: 2010-2026 Marc-Antoine Perennou <Marc-Antoine@Perennou.com>
// SPDX-License-Identifier: BSD-2-Clause

#include <gpaste-3/gpaste-gsettings-keys.h>
#include <gpaste-3/gpaste-update-enums.h>
#include <gpaste-3/gpaste-util.h>

#include <gpaste-daemon/gpaste-daemon-util.h>
#include <gpaste-daemon/gpaste-history.h>
#include <gpaste-daemon/gpaste-history-saver.h>
#include <gpaste-daemon/gpaste-storage-backend.h>
#include <gpaste-daemon/gpaste-text-item.h>
#include <gpaste-daemon/gpaste-uris-item.h>

#include <gio/gio.h>

/* Takes the history lock for the rest of the scope */
#define G_PASTE_DO_LOCK_HISTORY                 \
    g_debug ("%s: Locking history", G_STRFUNC); \
    g_autoptr (GMutexLocker) locker = g_mutex_locker_new (&self->lock)

/* Same, and emits the pending SELECTED signal, if any, once the lock has been
 * released again: the guard is declared before the locker so that its cleanup
 * runs after the unlock.
 */
#define G_PASTE_LOCK_HISTORY                                                              \
    g_auto (GPasteHistorySelectionScope) selection_scope = { .self = self };              \
    G_PASTE_DO_LOCK_HISTORY

struct _GPasteHistory
{
    GObject parent_instance;

    GMutex                lock;

    GPasteStorageBackend *backend;
    GPasteHistorySaver   *saver;
    GPasteSettings       *settings;
    GSignalGroup         *settings_signals;

    /* The model: newest first, holding a ref on each item, plus an index from
     * uuid to the very same items. The hash borrows both — the key is the
     * item's own uuid string and the value is the item the array owns — so an
     * entry is only ever valid while the item is in the array, and the two are
     * updated together. That is safe because a uuid never changes once its item
     * is in the history: only the storage backends call
     * g_paste_item_set_uuid(), while loading, before the item gets here. */
    GPtrArray            *history;
    GHashTable           *by_uuid;
    gsize                 size;

    gchar                *name;

    /* Set once the history has been flushed for shutdown/handover: no further
     * change is persisted, so a successor daemon owns the on-disk state. Only
     * the deliberate counterpart of that handover lifts it (g_paste_history_resume,
     * or reload_backend once a migration has rewritten the store) — never the
     * mere start of a load, which can happen throughout a handover window that
     * keeps running (the in-shell daemon stays on the bus across its migration
     * dialog, so a history switch can land right in the middle of it). */
    gboolean              stopped;

    /* Set when *this* history is on disk but could not be read back, so the
     * empty model we ended up with is never written over the intact data.
     * Unlike @stopped it belongs to the loaded history alone: starting another
     * load clears it, and that load's own result sets it again. */
    gboolean              unreadable;

    /* Note: we never track the first (active) item here */
    const gchar          *biggest_uuid;
    guint64               biggest_size;

    /* Recorded by g_paste_history_selected and emitted by G_PASTE_LOCK_HISTORY
     * once the lock is released again */
    GPasteItem           *pending_selection;
};

G_PASTE_DEFINE_TYPE (History, history, G_TYPE_OBJECT)

enum
{
    SELECTED,
    SWITCH,
    UPDATE,

    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

typedef struct
{
    GPasteHistory *self;
} GPasteHistorySelectionScope;

static void
g_paste_history_emit_pending_selection (GPasteHistorySelectionScope *scope)
{
    GPasteHistory *self = scope->self;
    g_autoptr (GPasteItem) item = NULL;

    {
        G_PASTE_DO_LOCK_HISTORY;

        item = g_steal_pointer (&self->pending_selection);
    }

    if (!item)
        return;

    g_debug ("history: selected");

    g_signal_emit (self,
                   signals[SELECTED],
                   0, /* detail */
                   item,
                   NULL);
}

G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (GPasteHistorySelectionScope, g_paste_history_emit_pending_selection)

static void
g_paste_history_private_elect_new_biggest (GPasteHistory *self)
{
    g_debug ("history: elect biggest");

    self->biggest_uuid = NULL;
    self->biggest_size = 0;

    /* From 1: the first (active) item is never tracked. Favourites are never
     * tracked either: this elects what the memory cap may evict, and evicting a
     * favourite is precisely what being one rules out. With nothing electable
     * biggest_uuid stays %NULL, which is what stops the cap's loop. */
    for (guint i = 1; i < self->history->len; ++i)
    {
        GPasteItem *item = g_ptr_array_index (self->history, i);

        if (g_paste_item_is_favourite (item))
            continue;

        guint64 size = g_paste_item_get_size (item);

        if (size > self->biggest_size)
        {
            self->biggest_uuid = g_paste_item_get_uuid (item);
            self->biggest_size = size;
        }
    }
}

/* The item goes, and with it whatever the storage backend materialized outside
 * its own store for it -- an image's cache file, for the flavours that keep
 * one. Which of them did is the backend's own business: a database blob has
 * nothing here to clean up. */
static void
g_paste_history_item_free (GPasteHistory *self,
                           gpointer       data)
{
    g_autoptr (GPasteItem) item = data;

    /* A history that has not been named yet was never persisted, so no backend
     * has written anything for this item to begin with. */
    if (self->name)
        g_paste_storage_backend_drop_item_data (self->backend, self->name, item);
}

/* Drop the item at @index from the model. The array's ref is *stolen* rather
 * than released, so this never frees on its own: with @remove_leftovers the
 * item (and any backing file it owns) is disposed of here, without it the
 * caller has taken the ref over — either to free it itself or because the item
 * is only being moved to the front. */
static void
g_paste_history_private_remove (GPasteHistory *self,
                                guint          index,
                                gboolean       remove_leftovers)
{
    g_return_if_fail (index < self->history->len);

    GPasteItem *item = g_ptr_array_index (self->history, index);

    self->size -= g_paste_item_get_size (item);

    /* Before the steal: the key belongs to the item. */
    g_hash_table_remove (self->by_uuid, g_paste_item_get_uuid (item));
    g_ptr_array_steal_index (self->history, index);

    if (remove_leftovers)
        g_paste_history_item_free (self, item);
}

static void
g_paste_history_selected (GPasteHistory *self,
                          GPasteItem    *item)
{
    /* Emitting now would drive the clipboards manager, hence GTK's clipboard
     * and the X11 selection machinery, with our lock held; handlers may also
     * call back into us (g_paste_history_remove when an item turns out to be
     * invalid), which would deadlock on our non-recursive lock. Just record
     * the item: G_PASTE_LOCK_HISTORY emits it once the lock is released.
     */
    g_set_object (&self->pending_selection, item);
}

static void
g_paste_history_emit_switch (GPasteHistory *self,
                             const gchar   *name)
{
    g_debug ("history: switch");

    g_signal_emit (self,
                   signals[SWITCH],
                   0, /* detail */
                   name,
                   NULL);
}

/* A ref-holding copy of the current history, to be handed to the saver.
 *
 * GList is the storage layer's interchange type and stays that way: writing a
 * history out is a one-shot bulk operation where a list costs nothing, so the
 * backends, the saver and the migration code keep taking one. Only the live
 * model is an array. Built back to front, so the list comes out newest-first
 * like the array it is copied from. */
static GList *
g_paste_history_snapshot (GPasteHistory *self)
{
    GList *snapshot = NULL;

    for (guint i = self->history->len; i > 0; --i)
        snapshot = g_list_prepend (snapshot, g_object_ref (g_ptr_array_index (self->history, i - 1)));

    return snapshot;
}

/* Drop every item, keeping the containers. Releases the array's refs through
 * its free func (a plain unref: unlike g_paste_history_empty this is a history
 * being swapped out, not thrown away, so backing files are left alone). */
static void
g_paste_history_private_clear (GPasteHistory *self)
{
    g_ptr_array_set_size (self->history, 0);
    g_hash_table_remove_all (self->by_uuid);
}

/* Take over an item list from the storage layer (transfer full) and install it
 * as the model, rebuilding the uuid index with it. */
static void
g_paste_history_private_set_from_list (GPasteHistory *self,
                                       GList         *history)
{
    g_paste_history_private_clear (self);

    for (GList *h = history; h; h = g_list_next (h))
    {
        GPasteItem *item = h->data;

        g_ptr_array_add (self->history, item);
        g_hash_table_insert (self->by_uuid, (gpointer) g_paste_item_get_uuid (item), item);
    }

    /* The refs moved into the array; only the links are ours to free. */
    g_list_free (history);
}

static void
g_paste_history_emit_update (GPasteHistory     *self,
                             GPasteUpdateAction action,
                             GPasteUpdateTarget target,
                             const gchar       *uuid,
                             guint64            position)
{
    g_debug ("history: update");

    g_signal_emit (self,
                   signals[UPDATE],
                   0, /* detail */
                   action,
                   target,
                   (uuid) ?: "",
                   position,
                   NULL);
}

/* @displaced says whether anything left the history alongside the change: a
 * dedup, a grown line replacing its shorter self, or an eviction. It only means
 * anything for an add on an incremental backend -- see below. */
static void
g_paste_history_update (GPasteHistory      *self,
                        GPasteUpdateAction  action,
                        GPasteUpdateTarget  target,
                        guint64             position,
                        GPasteHistorySaveOp op,
                        GPasteItem         *item,
                        const gchar        *uuid,
                        gboolean            displaced)
{
    /* Don't persist intermediate states while an async load is replacing the
     * history (the load result triggers its own save when appropriate), nor once
     * we have been flushed for handover (a successor daemon owns the file now). */
    if (!self->stopped && !self->unreadable && !g_paste_history_saver_is_loading (self->saver))
    {
        /* A non-incremental backend rewrites everything and always needs the
         * snapshot. An incremental one only reconciles what rode along with an
         * add -- and reconciliation only ever deletes rows the history no longer
         * has, so with nothing displaced there is provably nothing to delete.
         * That is the common case, and it is what spares a per-item ref-copy of
         * the whole history on every single clipboard change. */
        gboolean full = !g_paste_storage_backend_is_incremental (self->backend);
        GList *snapshot = (full || op == G_PASTE_HISTORY_SAVE_FULL || (op == G_PASTE_HISTORY_SAVE_ADD && displaced))
            ? g_paste_history_snapshot (self)
            : NULL;

        g_paste_history_saver_record (self->saver, op, self->name, item, uuid, snapshot);
    }

    /* The item the change is about: whichever one it left behind, or the uuid
     * alone when it left none (a removal). Neither rides along with an ALL,
     * which is about the history and not about any one item of it: the
     * interface says both are empty there, and a client that reads the uuid
     * whenever it is non-empty would otherwise act on an item the update was
     * never about. The saver still gets @item -- what it persists and what the
     * signal is about are two different questions. */
    if (target == G_PASTE_UPDATE_TARGET_ALL)
        g_paste_history_emit_update (self, action, target, NULL, 0);
    else
        g_paste_history_emit_update (self, action, target, (item) ? g_paste_item_get_uuid (item) : uuid, position);
}

static void
g_paste_history_activate_first (GPasteHistory *self,
                                gboolean       select)
{
    if (!self->history->len)
        return;

    GPasteItem *first = g_ptr_array_index (self->history, 0);

    self->size -= g_paste_item_get_size (first);
    g_paste_item_set_state (first, G_PASTE_ITEM_STATE_ACTIVE);
    self->size += g_paste_item_get_size (first);

    if (select)
        g_paste_history_selected (self, first);
}

static GPasteItem *
g_paste_history_private_get_by_uuid (GPasteHistory *self,
                                     const gchar   *uuid)
{
    return (uuid) ? g_hash_table_lookup (self->by_uuid, uuid) : NULL;
}

/* The item plus where it currently sits.
 *
 * The position is looked up rather than indexed, because indexing it would cost
 * more than it saves: adding prepends, which shifts every position, so a
 * uuid->position map would have to be rewritten on every single add. The scan
 * compares pointers, the item having already been found by uuid. */
static GPasteItem *
g_paste_history_private_get_indexed_by_uuid (GPasteHistory *self,
                                             const gchar   *uuid,
                                             guint         *index)
{
    GPasteItem *item = g_paste_history_private_get_by_uuid (self, uuid);

    if (!item)
        return NULL;

    /* In the index but not in the array would mean the two had drifted apart.
     * Checked with a real branch rather than g_return_val_if_fail(): that one
     * compiles away entirely under G_DISABLE_CHECKS, taking the lookup with it
     * and handing the caller an @index its stack happened to hold. */
    if (!g_ptr_array_find (self->history, item, index))
    {
        g_critical ("The item with uuid \"%s\" is indexed but not in the history", uuid);
        return NULL;
    }

    return item;
}

static void
g_paste_history_private_check_memory_usage (GPasteHistory *self)
{
    guint64 max_memory = g_paste_settings_get_max_memory_usage (self->settings) * 1024 * 1024;

    while (self->size > max_memory && self->biggest_uuid)
    {
        guint index;

        if (g_paste_history_private_get_indexed_by_uuid (self, self->biggest_uuid, &index))
            g_paste_history_private_remove (self, index, TRUE);

        /* Also re-points biggest_uuid, which borrowed its string from the item
         * just freed. */
        g_paste_history_private_elect_new_biggest (self);
    }
}

static void
g_paste_history_private_check_size (GPasteHistory *self)
{
    guint64 max_history_size = g_paste_settings_get_max_history_size (self->settings);

    /* Truncate from the tail: private_remove already does the size bookkeeping,
     * the uuid index and the leftover backing files.
     *
     * Favourites are skipped rather than dropped, so this walks the tail once
     * instead of always taking the last item. A history holding nothing else
     * therefore grows past max_history_size, which is the whole point of
     * pinning: the cap gives way, not the item.
     *
     * Down to 1, never 0: the first item is the one just added or selected, and
     * a history whose every other entry is pinned would otherwise evict it on
     * the spot -- the clipboard would stop keeping anything at all. The memory
     * cap leaves that slot alone for the same reason. */
    gboolean dropped_biggest = FALSE;

    for (guint i = self->history->len; --i > 0 && self->history->len > max_history_size; )
    {
        GPasteItem *item = g_ptr_array_index (self->history, i);

        if (g_paste_item_is_favourite (item))
            continue;

        /* Dropped the moment the elected item is the one going, not once the
         * loop is over: biggest_uuid borrows that item's own string, and the
         * removal below frees it, so every later iteration would be comparing
         * against freed memory. */
        if (g_paste_str_equal (self->biggest_uuid, g_paste_item_get_uuid (item)))
        {
            self->biggest_uuid = NULL;
            self->biggest_size = 0;
            dropped_biggest = TRUE;
        }

        g_paste_history_private_remove (self, i, TRUE);
    }

    /* Left to the end: the runner-up can only be named once the loop has taken
     * everything it is going to take. The memory cap re-elects after each of its
     * own evictions instead, because each one decides what it evicts next. */
    if (dropped_biggest)
        g_paste_history_private_elect_new_biggest (self);
}

/* Both caps, applied when a cap itself moves rather than the history under it.
 * The same trim g_paste_history_on_loaded () performs, saved for the same
 * reason: an eviction frees the backing files of what it drops, so a store left
 * pointing at them is a history that cannot be read back -- and every client
 * would go on showing rows the daemon no longer has until the next clipboard
 * change came to redraw them. Which is also why a flushed or unreadable history
 * is left alone: neither can be saved (the first belongs to a successor daemon
 * now, the second must never be written over), so a trim there would delete the
 * images a store still points at. */
static void
g_paste_history_private_trim (GPasteHistory *self)
{
    if (self->stopped || self->unreadable)
        return;

    guint length_before = self->history->len;

    g_paste_history_private_check_size (self);
    g_paste_history_private_check_memory_usage (self);

    if (self->history->len != length_before)
        g_paste_history_update (self, G_PASTE_UPDATE_ACTION_REPLACE, G_PASTE_UPDATE_TARGET_ALL, 0, G_PASTE_HISTORY_SAVE_FULL, NULL, NULL, FALSE);
}

/* A line grows as it is typed or selected, which only plain text and uri lists
 * do. An image has no meaningful prefix, and a password is never merged into
 * its neighbour. */
static gboolean
g_paste_history_private_kind_grows (GPasteItemKind kind)
{
    return kind == G_PASTE_ITEM_KIND_TEXT || kind == G_PASTE_ITEM_KIND_URIS;
}

static gboolean
g_paste_history_private_is_growing_line (GPasteHistory *self,
                                         GPasteItem    *old,
                                         GPasteItem    *new)
{
    if (!g_paste_settings_get_growing_lines (self->settings) ||
        !g_paste_history_private_kind_grows (g_paste_item_get_kind (old)) ||
        !g_paste_history_private_kind_grows (g_paste_item_get_kind (new)))
        return FALSE;

    const gchar *n = g_paste_item_get_value (new);
    const gchar *o = g_paste_item_get_value (old);

    return (g_str_has_prefix (n, o) || g_str_has_suffix (n, o));
}

static void
_g_paste_history_add (GPasteHistory *self,
                      GPasteItem    *item,
                      gboolean       new_selection)
{
    g_return_if_fail (G_PASTE_IS_HISTORY (self));
    g_return_if_fail (G_PASTE_IS_ITEM (item));

    guint64 max_memory = g_paste_settings_get_max_memory_usage (self->settings) * 1024 * 1024;

    /* On add (new_selection) we own @item (transfer full) and must consume it on
     * every path; on select it is borrowed (already in the history) and merely
     * moved. Holding it here frees it automatically on the early-return paths;
     * it is g_steal_pointer'd into the list once it is actually stored. */
    g_autoptr (GPasteItem) owned = new_selection ? item : NULL;

    if (g_paste_item_get_size (item) > max_memory)
        return;

    guint length_before = self->history->len;
    gboolean election_needed = !length_before; // If we don't have an history we want to initalize the biggest

    g_debug ("history: add");

    if (self->history->len)
    {
        GPasteItem *old_first = g_ptr_array_index (self->history, 0);

        if (g_paste_item_equals (old_first, item))
            return;

        if (new_selection && g_paste_history_private_is_growing_line (self, old_first, item))
        {
            if (g_paste_str_equal (self->biggest_uuid, g_paste_item_get_uuid (old_first)))
                election_needed = TRUE;
            /* The grown line is the same line the user pinned, only longer, so
             * it inherits the pin: dropping it here would quietly make what a
             * user asked to keep evictable again, one keystroke at a time. */
            if (g_paste_item_is_favourite (old_first))
                g_paste_item_set_favourite (item, TRUE);
            /* old_first is a distinct object replaced by the grown item; free it
             * (its shared backing file, if any, is kept). */
            g_autoptr (GPasteItem) dropped = old_first;
            g_paste_history_private_remove (self, 0, FALSE);
        }
        else
        {
            /* Going idle changes what the item weighs, so it is taken out at
             * its old size and put back at its new one. */
            self->size -= g_paste_item_get_size (old_first);
            g_paste_item_set_state (old_first, G_PASTE_ITEM_STATE_IDLE);

            guint64 size = g_paste_item_get_size (old_first);

            self->size += size;

            /* It just stopped being the untracked active item, so it becomes a
             * candidate -- unless it is a favourite, which is never one. */
            if (size >= self->biggest_size && !g_paste_item_is_favourite (old_first))
            {
                self->biggest_uuid = g_paste_item_get_uuid (old_first);
                self->biggest_size = size;
            }

            /* Dedup is by value, not by uuid, so the index cannot help here. */
            for (guint i = 1; i < self->history->len; ++i)
            {
                GPasteItem *entry = g_ptr_array_index (self->history, i);

                if (g_paste_item_equals (entry, item) || (new_selection && g_paste_history_private_is_growing_line (self, entry, item)))
                {
                    if (g_paste_str_equal (self->biggest_uuid, g_paste_item_get_uuid (entry)))
                        election_needed = TRUE;
                    /* Same as above: what arrives takes the place of a pinned
                     * entry, so it takes its pin with it. On select the two are
                     * one object and this says nothing new. */
                    if (g_paste_item_is_favourite (entry))
                        g_paste_item_set_favourite (item, TRUE);
                    /* On add, @entry is a distinct duplicate to free (its shared
                     * backing file is kept). On select, @entry IS @item being moved
                     * to the front, so its ref must be left alone. */
                    g_autoptr (GPasteItem) dropped = new_selection ? entry : NULL;
                    g_paste_history_private_remove (self, i, FALSE);
                    break;
                }
            }
        }
    }

    g_ptr_array_insert (self->history, 0, item);
    g_hash_table_insert (self->by_uuid, (gpointer) g_paste_item_get_uuid (item), item);
    g_steal_pointer (&owned); /* ownership transferred to the history */

    g_paste_history_activate_first (self, FALSE);
    self->size += g_paste_item_get_size (item);

    /* Before the caps, not after: whatever was displaced above has already been
     * freed, and biggest_uuid borrows its string from the item it names, so an
     * election left until later would leave check_size () comparing against
     * memory that is gone. */
    if (election_needed)
        g_paste_history_private_elect_new_biggest (self);

    g_paste_history_private_check_size (self);
    g_paste_history_private_check_memory_usage (self);

    /* One in and nothing out means nothing was deduped, grown over or evicted;
     * anything else and the store has rows the history no longer has. */
    gboolean displaced = self->history->len != length_before + 1;

    /* TARGET_ALL whichever path got here. An ordinary add shifts every position
     * along; a grown line is a new item, with a uuid of its own, standing where
     * the one it grew from stood -- and one uuid cannot say both which item went
     * and which arrived, the same reason _g_paste_history_replace () reports
     * ALL. A view keyed on the old one would go on showing it. */
    g_paste_history_update (self, G_PASTE_UPDATE_ACTION_REPLACE, G_PASTE_UPDATE_TARGET_ALL, 0, G_PASTE_HISTORY_SAVE_ADD, item, NULL, displaced);
}

/**
 * g_paste_history_add:
 * @self: a #GPasteHistory instance
 * @item: (transfer full): the #GPasteItem to add
 *
 * Add a #GPasteItem to the #GPasteHistory
 */
G_PASTE_VISIBLE void
g_paste_history_add (GPasteHistory *self,
                     GPasteItem    *item)
{
    g_return_if_fail (G_PASTE_IS_HISTORY (self));
    g_return_if_fail (G_PASTE_IS_ITEM (item));

    G_PASTE_LOCK_HISTORY;

    _g_paste_history_add (self, item, TRUE);
}

static void
g_paste_history_remove_common (GPasteHistory *self,
                               GPasteItem    *item,
                               guint          index)
{
    if (!item)
        return;

    g_autofree gchar *uuid = g_strdup (g_paste_item_get_uuid (item));
    gboolean was_biggest = g_paste_str_equal (self->biggest_uuid, uuid);

    g_paste_history_private_remove (self, index, TRUE);

    if (!index)
        g_paste_history_activate_first (self, TRUE);

    if (was_biggest)
        g_paste_history_private_elect_new_biggest (self);

    g_paste_history_update (self, G_PASTE_UPDATE_ACTION_REMOVE, G_PASTE_UPDATE_TARGET_ITEM, index, G_PASTE_HISTORY_SAVE_REMOVE, NULL, uuid, FALSE);
}

static void
g_paste_history_remove_locked (GPasteHistory *self,
                               guint64        index)
{
    g_debug ("history: remove '%" G_GUINT64_FORMAT "'", index);

    if (index >= self->history->len)
        return;

    g_paste_history_remove_common (self, g_ptr_array_index (self->history, index), (guint) index);
}

/**
 * g_paste_history_remove:
 * @self: a #GPasteHistory instance
 * @index: the index of the #GPasteItem to delete
 *
 * Delete a #GPasteItem from the #GPasteHistory
 */
G_PASTE_VISIBLE void
g_paste_history_remove (GPasteHistory *self,
                        guint64        index)
{
    g_return_if_fail (G_PASTE_IS_HISTORY (self));

    G_PASTE_LOCK_HISTORY;

    g_paste_history_remove_locked (self, index);
}

/**
 * g_paste_history_remove_by_uuid:
 * @self: a #GPasteHistory instance
 * @uuid: the uuid of the #GPasteItem to delete
 *
 * Delete a #GPasteItem from the #GPasteHistory
 *
 * Returns: whether we removed anything
 */
G_PASTE_VISIBLE gboolean
g_paste_history_remove_by_uuid (GPasteHistory *self,
                                const gchar   *uuid)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), FALSE);

    G_PASTE_LOCK_HISTORY;

    g_debug ("history: remove '%s", uuid);

    guint index;
    GPasteItem *item = g_paste_history_private_get_indexed_by_uuid (self, uuid, &index);

    if (!item)
        return FALSE;

    g_paste_history_remove_common (self, item, index);
    return TRUE;
}

static GPasteItem *
g_paste_history_private_get (GPasteHistory *self,
                             guint64        index)
{
    return (index < self->history->len) ? g_ptr_array_index (self->history, index) : NULL;
}

/**
 * g_paste_history_get:
 * @self: a #GPasteHistory instance
 * @index: the index of the #GPasteItem
 *
 * Get a #GPasteItem from the #GPasteHistory
 *
 * Returns: (transfer none) (nullable): the #GPasteItem at @index, still owned by
 *          the history, or %NULL when @index is past its end
 */
G_PASTE_VISIBLE GPasteItem *
g_paste_history_get (GPasteHistory *self,
                     guint64        index)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), NULL);

    G_PASTE_LOCK_HISTORY;

    return g_paste_history_private_get (self, index);
}

/**
 * g_paste_history_get_by_uuid:
 * @self: a #GPasteHistory instance
 * @uuid: the uuid of the #GPasteItem
 *
 * Get a #GPasteItem from the #GPasteHistory
 *
 * Returns: (transfer none) (nullable): the #GPasteItem carrying @uuid, still
 *          owned by the history, or %NULL when no item does
 */
G_PASTE_VISIBLE GPasteItem *
g_paste_history_get_by_uuid (GPasteHistory *self,
                             const gchar   *uuid)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), NULL);

    G_PASTE_LOCK_HISTORY;

    return g_paste_history_private_get_by_uuid (self, uuid);
}

/**
 * g_paste_history_dup:
 * @self: a #GPasteHistory instance
 * @index: the index of the #GPasteItem
 *
 * Get a #GPasteItem from the #GPasteHistory
 * free it with g_object_unref
 *
 * Returns: (transfer full) (nullable): a #GPasteItem, or %NULL when @index is
 *          out of range (an empty history has no item 0)
 */
G_PASTE_VISIBLE GPasteItem *
g_paste_history_dup (GPasteHistory *self,
                     guint64        index)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), NULL);

    G_PASTE_LOCK_HISTORY;

    GPasteItem *item = g_paste_history_private_get (self, index);

    /* Callers null-check the result; ref'ing NULL would only add a critical. */
    return (item) ? g_object_ref (item) : NULL;
}

/**
 * g_paste_history_select:
 * @self: a #GPasteHistory instance
 * @uuid: the uuid of the #GPasteItem to select
 *
 * Select a #GPasteItem from the #GPasteHistory
 *
 * Returns: whether the item could be selected
 */
G_PASTE_VISIBLE gboolean
g_paste_history_select (GPasteHistory *self,
                        const gchar   *uuid)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), FALSE);
    g_debug ("history: select '%s'", uuid);

    G_PASTE_LOCK_HISTORY;
    GPasteItem *item = g_paste_history_private_get_by_uuid (self, uuid);

    if (!item)
        return FALSE;

    _g_paste_history_add (self, item, FALSE);
    g_paste_history_selected (self, item);
    return TRUE;
}

static void
_g_paste_history_replace (GPasteHistory *self,
                          guint          index,
                          GPasteItem    *new)
{
    GPasteItem *old = g_ptr_array_index (self->history, index);
    g_autofree gchar *old_uuid = g_strdup (g_paste_item_get_uuid (old));
    gboolean was_biggest = g_paste_str_equal (self->biggest_uuid, old_uuid);

    /* The pin belongs to the entry, not to the object holding its contents:
     * editing a pinned item, or turning one into a password, changes what it
     * says and nothing about the user having asked to keep it. Carried over
     * before the election below, which is the one thing the flag steers. */
    g_paste_item_set_favourite (new, g_paste_item_is_favourite (old));

    self->size -= g_paste_item_get_size (old);
    self->size += g_paste_item_get_size (new);

    /* Swap in place: the steal hands back the array's ref (its free func does
     * not run) so we drop it ourselves, and @new takes the same slot. Unlike a
     * removal this keeps any backing file, since only the item wrapping it is
     * being replaced. */
    g_hash_table_remove (self->by_uuid, old_uuid);
    g_ptr_array_steal_index (self->history, index);
    g_object_unref (old);
    g_ptr_array_insert (self->history, index, new);
    g_hash_table_insert (self->by_uuid, (gpointer) g_paste_item_get_uuid (new), new);

    if (was_biggest)
        g_paste_history_private_elect_new_biggest (self);

    /* TARGET_ALL, not TARGET_ITEM: @new is a different item with a uuid of its
     * own, so one uuid cannot say both which one went and which one arrived. A
     * view keyed on the old one would go on showing it. */
    g_paste_history_update (self, G_PASTE_UPDATE_ACTION_REPLACE, G_PASTE_UPDATE_TARGET_ALL, index, G_PASTE_HISTORY_SAVE_REPLACE, new, old_uuid, FALSE);
}

/**
 * g_paste_history_replace:
 * @self: a #GPasteHistory instance
 * @uuid: the uuid of the #GPasteTextItem to replace
 * @contents: the new contents
 *
 * Replace the contents of the text item @uuid names
 *
 * The replacement is an item of its own, so it has a uuid of its own: that is
 * what comes back, since the update this raises names no item (see
 * _g_paste_history_replace()) and a caller would otherwise have no way to reach
 * what it just created.
 *
 * Returns: (transfer full) (nullable): the new item's uuid, or %NULL when @uuid
 *          matched nothing
 */
G_PASTE_VISIBLE gchar *
g_paste_history_replace (GPasteHistory *self,
                         const gchar   *uuid,
                         const gchar   *contents)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), NULL);
    g_return_val_if_fail (!contents || g_utf8_validate (contents, -1, NULL), NULL);

    G_PASTE_LOCK_HISTORY;
    guint index;
    GPasteItem *item = g_paste_history_private_get_indexed_by_uuid (self, uuid, &index);

    if (!item)
        return NULL;

    g_return_val_if_fail (G_PASTE_IS_TEXT_ITEM (item), NULL);

    GPasteItem *new = g_paste_text_item_new (contents);

    _g_paste_history_replace (self, index, new);

    if (!index)
        g_paste_history_selected (self, new);

    return g_strdup (g_paste_item_get_uuid (new));
}

/**
 * g_paste_history_set_favourite:
 * @self: a #GPasteHistory instance
 * @uuid: the uuid of the #GPasteItem to pin, or to let go of
 * @favourite: whether the item should be pinned
 *
 * Pin an item, exempting it from the size and memory caps, or let it go again.
 *
 * Un-pinning puts the item back under both caps at once, so it may well be the
 * very call that evicts it — this returns %TRUE all the same: the item is gone
 * because the caller asked for it to be evictable.
 *
 * Returns: %FALSE when @uuid matches no item, %TRUE otherwise
 */
G_PASTE_VISIBLE gboolean
g_paste_history_set_favourite (GPasteHistory *self,
                               const gchar   *uuid,
                               gboolean       favourite)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), FALSE);

    G_PASTE_LOCK_HISTORY;
    guint index;
    GPasteItem *item = g_paste_history_private_get_indexed_by_uuid (self, uuid, &index);

    if (!item)
        return FALSE;

    if (g_paste_item_is_favourite (item) == favourite)
        return TRUE;

    g_debug ("history: set favourite '%s'", uuid);

    /* Our own copy: un-pinning can evict the very item we are holding. */
    g_autofree gchar *item_uuid = g_strdup (uuid);
    guint length_before = self->history->len;

    g_paste_item_set_favourite (item, favourite);

    if (favourite)
    {
        /* The pool of eviction candidates lost this item. Only a full election
         * can name the runner-up, and only when it was the one elected. */
        if (g_paste_str_equal (self->biggest_uuid, item_uuid))
            g_paste_history_private_elect_new_biggest (self);
    }
    else
    {
        /* The pool gained it, so nothing has to be re-elected: one comparison
         * places it, the same way _g_paste_history_add places an item leaving
         * the active slot. Index 0 stays untracked, as ever. */
        guint64 size = g_paste_item_get_size (item);

        if (index && size >= self->biggest_size)
        {
            self->biggest_uuid = g_paste_item_get_uuid (item);
            self->biggest_size = size;
        }

        /* Both caps may have been giving way to this item for a while. */
        g_paste_history_private_check_size (self);
        g_paste_history_private_check_memory_usage (self);
    }

    if (self->history->len != length_before)
    {
        /* Evictions rode along. A replace carries no snapshot for an incremental
         * backend to reconcile against — unlike an add, which is the one
         * operation that expects to displace anything — so rewrite the history
         * outright rather than leave those rows behind. Rare: it takes a history
         * this item was holding over one of its caps. @item may be among the
         * evicted, hence neither it nor @index is touched from here on. */
        g_paste_history_update (self, G_PASTE_UPDATE_ACTION_REPLACE, G_PASTE_UPDATE_TARGET_ALL, 0, G_PASTE_HISTORY_SAVE_FULL, NULL, NULL, FALSE);
    }
    else
        g_paste_history_update (self, G_PASTE_UPDATE_ACTION_REPLACE, G_PASTE_UPDATE_TARGET_ITEM, index, G_PASTE_HISTORY_SAVE_REPLACE, item, item_uuid, FALSE);

    return TRUE;
}

static GPasteItem *
_g_paste_history_private_get_password (GPasteHistory *self,
                                       const gchar   *name,
                                       guint64       *index)
{
    for (guint idx = 0; idx < self->history->len; ++idx)
    {
        GPasteItem *i = g_ptr_array_index (self->history, idx);

        if (G_PASTE_IS_PASSWORD_ITEM (i) &&
            g_paste_str_equal (g_paste_password_item_get_name ((GPastePasswordItem *) i), name))
        {
            if (index)
                *index = idx;
            return i;
        }
    }

    if (index)
        *index = -1;
    return NULL;
}

/**
 * g_paste_history_make_password:
 * @self: a #GPasteHistory instance
 * @uuid: the uuid of the #GPasteTextItem to turn into a password, or of the
 *        #GPastePasswordItem to update
 * @name: (nullable): the name to give to the password, or %NULL to leave a
 *        password's own name alone -- a text item becoming one takes
 *        %G_PASTE_PASSWORD_ITEM_NO_NAME
 * @timeout: how long it may stay on the clipboard, in seconds, 0 for as long as
 *           anything else, or %G_PASTE_PASSWORD_TIMEOUT_UNCHANGED to write none
 *           -- a password keeping the one it carries, a text item becoming one
 *           taking the "password-timeout" default
 *
 * Mark a text item as password, or write a password's name and timeout
 *
 * The two hand back different uuids. A text item is *replaced*: a password item
 * is an item of its own, with a uuid of its own, for the same reason
 * g_paste_history_replace() hands one back. A password item is updated where it
 * stands -- its name and its timeout, never its value -- so what comes back is
 * the uuid that went in.
 *
 * Returns: (transfer full): the uuid of the item this left in the history
 */
G_PASTE_VISIBLE gchar *
g_paste_history_make_password (GPasteHistory *self,
                               const gchar   *uuid,
                               const gchar   *name,
                               guint          timeout)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), NULL);
    g_return_val_if_fail (!name || g_utf8_validate (name, -1, NULL), NULL);

    G_PASTE_LOCK_HISTORY;
    guint index;
    GPasteItem *item = g_paste_history_private_get_indexed_by_uuid (self, uuid, &index);

    g_return_val_if_fail (item, NULL);
    g_return_val_if_fail (G_PASTE_IS_TEXT_ITEM (item) || G_PASTE_IS_PASSWORD_ITEM (item), NULL);

    /* Keeping the name it already carries is not a collision with itself: only
     * the timeout may be what the caller came to change. Nor is a nameless one,
     * since they all go by G_PASTE_PASSWORD_ITEM_NO_NAME: that is no name a user
     * picked, so it is the one a history may hold several of. */
    gboolean named = (name && !g_paste_str_equal (name, G_PASTE_PASSWORD_ITEM_NO_NAME));
    GPasteItem *namesake = (named) ? _g_paste_history_private_get_password (self, name, NULL) : NULL;

    /* Not a g_return_val_if_fail (): a name the user picked twice is an ordinary
     * refusal and not a programming error, so it neither warrants a CRITICAL nor
     * may compile away under G_DISABLE_CHECKS -- g_paste_daemon_methods_make_password ()
     * reports it as .Error.AlreadyExists before ever reaching here, and a caller
     * coming straight to the library gets the same rule rather than a second
     * password under a name already taken. */
    if (namesake && namesake != item)
        return NULL;

    /* Nothing to write means whatever it already has: what a password carries,
     * and for a text item -- which carries none -- the default the settings hold
     * for a password being made. */
    if (timeout == G_PASTE_PASSWORD_TIMEOUT_UNCHANGED)
    {
        timeout = (G_PASTE_IS_PASSWORD_ITEM (item))
                ? g_paste_password_item_get_timeout (G_PASTE_PASSWORD_ITEM (item))
                : (guint) g_paste_settings_get_password_timeout (self->settings);
    }

    /* Our own copy: the memory cap the in-place branch runs can evict the very
     * item being renamed. */
    g_autofree gchar *new_uuid = NULL;

    if (G_PASTE_IS_PASSWORD_ITEM (item))
    {
        gboolean was_biggest = g_paste_str_equal (self->biggest_uuid, g_paste_item_get_uuid (item));
        guint length_before = self->history->len;

        new_uuid = g_strdup (g_paste_item_get_uuid (item));

        /* The name is part of what the item weighs, so the history's own total
         * has to follow it out and back in. Left out, a rename that lengthens
         * the name is subtracted once more than it was ever added when the item
         * goes, and self->size is unsigned. */
        self->size -= g_paste_item_get_size (item);
        /* A password already carries a name of its own, so %NULL is nothing to
         * write over it: the placeholder every nameless one reads as is not a
         * name the caller asked for, and putting it here would leave this one
         * unreachable by the name the user gave it. */
        if (name)
            g_paste_password_item_set_name (G_PASTE_PASSWORD_ITEM (item), name);
        g_paste_password_item_set_timeout (G_PASTE_PASSWORD_ITEM (item), timeout);
        self->size += g_paste_item_get_size (item);

        if (was_biggest)
        {
            /* A shorter name may have taken it below the runner-up, which only
             * a full election can name. */
            g_paste_history_private_elect_new_biggest (self);
        }
        else
        {
            /* A longer one may have taken it past the item elected, which one
             * comparison places, the same way _g_paste_history_add places an
             * item leaving the active slot. Index 0 and favourites stay
             * untracked, as ever. */
            guint64 size = g_paste_item_get_size (item);

            if (index && !g_paste_item_is_favourite (item) && size >= self->biggest_size)
            {
                self->biggest_uuid = g_paste_item_get_uuid (item);
                self->biggest_size = size;
            }
        }

        /* A longer name weighs more, so the total this cap watches has just
         * moved and the sweep has to run here -- left to whatever is added next,
         * the eviction it calls for would be blamed on that instead. The item
         * count did not change, which is what leaves the other cap out. */
        g_paste_history_private_check_memory_usage (self);

        if (self->history->len != length_before)
        {
            /* As in g_paste_history_set_favourite (): evictions rode along, a
             * replace carries no snapshot for an incremental backend to
             * reconcile against, and @item may be among them -- hence neither it
             * nor @index is touched from here on. */
            g_paste_history_update (self, G_PASTE_UPDATE_ACTION_REPLACE, G_PASTE_UPDATE_TARGET_ALL, 0, G_PASTE_HISTORY_SAVE_FULL, NULL, NULL, FALSE);
        }
        else
            g_paste_history_update (self, G_PASTE_UPDATE_ACTION_REPLACE, G_PASTE_UPDATE_TARGET_ITEM, index, G_PASTE_HISTORY_SAVE_REPLACE, item, new_uuid, FALSE);
    }
    else
    {
        GPasteItem *password = g_paste_password_item_new (name, g_paste_item_get_real_value (item), timeout);

        _g_paste_history_replace (self, index, password);
        new_uuid = g_strdup (g_paste_item_get_uuid (password));
    }

    /* Nothing is selected, wherever the item sits: a password is published as
     * g_paste_item_get_real_value (), which neither branch touches, and the item
     * at the front is not necessarily what a selection holds -- selecting would
     * be writing its cleartext over whatever the user has copied since. Arming
     * the countdown the new timeout calls for is
     * g_paste_clipboards_manager_rearm_password ()'s job, the clipboards being
     * what the history has no reach into. */
    return g_steal_pointer (&new_uuid);
}

/**
 * g_paste_history_get_password:
 * @self: a #GPasteHistory instance
 * @name: the name of the #GPastePasswordItem
 *
 * Get the first password matching name
 *
 * Returns: (transfer none) (nullable): the #GPastePasswordItem, still owned by
 *          the history, or %NULL when no password carries @name
 */
G_PASTE_VISIBLE GPastePasswordItem *
g_paste_history_get_password (GPasteHistory *self,
                              const gchar   *name)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), NULL);
    g_return_val_if_fail (!name || g_utf8_validate (name, -1, NULL), NULL);

    G_PASTE_LOCK_HISTORY;
    GPasteItem *item = _g_paste_history_private_get_password (self, name, NULL);

    return (item) ? G_PASTE_PASSWORD_ITEM (item) : NULL;
}

/**
 * g_paste_history_delete_password:
 * @self: a #GPasteHistory instance
 * @name: the name of the #GPastePasswordItem
 *
 * Delete the password matching name
 *
 * Returns: %FALSE when no password carries @name, %TRUE otherwise
 */
G_PASTE_VISIBLE gboolean
g_paste_history_delete_password (GPasteHistory *self,
                                 const gchar   *name)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), FALSE);
    g_return_val_if_fail (!name || g_utf8_validate (name, -1, NULL), FALSE);

    G_PASTE_LOCK_HISTORY;
    guint64 index;

    if (!_g_paste_history_private_get_password (self, name, &index))
        return FALSE;

    g_paste_history_remove_locked (self, index);

    return TRUE;
}

/**
 * g_paste_history_empty:
 * @self: a #GPasteHistory instance
 *
 * Empty the #GPasteHistory
 */
G_PASTE_VISIBLE void
g_paste_history_empty (GPasteHistory *self)
{
    g_return_if_fail (G_PASTE_IS_HISTORY (self));

    G_PASTE_LOCK_HISTORY;

    /* Through private_remove rather than the array's own free func: emptying
     * also drops each item's backing file, and keeps the uuid index in step. */
    while (self->history->len)
        g_paste_history_private_remove (self, self->history->len - 1, TRUE);

    self->size = 0;

    g_paste_history_private_elect_new_biggest (self);
    g_paste_history_update (self, G_PASTE_UPDATE_ACTION_REMOVE, G_PASTE_UPDATE_TARGET_ALL, 0, G_PASTE_HISTORY_SAVE_CLEAR, NULL, NULL, FALSE);
}

/* The name a history is about to be loaded or saved under: the one given, or
 * the configured one. GSettings is writable by anything the user runs, so the
 * configured name is no more trusted than a name off the bus -- and a name that
 * is a path names a history file outside the history directory. Fall back to the
 * default rather than refuse to have a history at all: a setting somebody
 * mangled should not cost the user their clipboard. */
static const gchar *
g_paste_history_resolve_name (GPasteHistory *self,
                              const gchar   *name)
{
    if (!name)
        name = g_paste_settings_get_history_name (self->settings);

    if (g_paste_util_history_name_is_valid (name))
        return name;

    /* @name is what a NULL setting resolved to, so it can still be NULL here,
     * and printf ("%s", NULL) is undefined -- it is glibc that prints
     * "(null)", not C. */
    g_warning ("“%s” does not name a history; using “%s” instead",
               (name) ?: "(none)", G_PASTE_DEFAULT_HISTORY);

    return G_PASTE_DEFAULT_HISTORY;
}

/**
 * g_paste_history_save:
 * @self: a #GPasteHistory instance
 * @name: (nullable): the name to save the history to (defaults to the configured one)
 *
 * Save the #GPasteHistory to the history file
 */
G_PASTE_VISIBLE void
g_paste_history_save (GPasteHistory *self,
                      const gchar   *name)
{
    g_return_if_fail (G_PASTE_IS_HISTORY (self));

    G_PASTE_LOCK_HISTORY;

    g_autolist (GPasteItem) snapshot = g_paste_history_snapshot (self);

    /* Resolved the same way a load is: an unchecked name reaches
     * g_paste_util_get_history_file_path (), which answers %NULL for one that is
     * a path -- and a save that builds no path writes nothing at all, where a
     * load would have fallen back to the default. */
    g_paste_storage_backend_write_history (self->backend,
                                           g_paste_history_resolve_name (self, (name) ? name : self->name),
                                           snapshot);
}

/**
 * g_paste_history_flush:
 * @self: a #GPasteHistory instance
 *
 * Persist every pending change and wait for it to hit the disk, then stop
 * recording further changes. Meant for an exit/handover path: after this returns
 * the on-disk history is up to date and can be handed over to a successor daemon.
 */
G_PASTE_VISIBLE void
g_paste_history_flush (GPasteHistory *self)
{
    g_return_if_fail (G_PASTE_IS_HISTORY (self));

    G_PASTE_LOCK_HISTORY;

    self->stopped = TRUE;
    g_paste_history_saver_drain (self->saver);
}

/**
 * g_paste_history_resume:
 * @self: a #GPasteHistory instance
 *
 * Undo a previous g_paste_history_flush(): resume recording changes. Meant for a
 * handover that did not actually happen (e.g. a re-exec whose exec failed), so
 * the surviving daemon keeps persisting the history.
 */
G_PASTE_VISIBLE void
g_paste_history_resume (GPasteHistory *self)
{
    g_return_if_fail (G_PASTE_IS_HISTORY (self));

    G_PASTE_LOCK_HISTORY;

    self->stopped = FALSE;
}

/* Read a history in, synchronously, and install it as the model.
 *
 * No cap is applied to what comes back, unlike the load g_paste_history_on_loaded ()
 * installs: this one reads a history the daemon is not keeping -- another one, to
 * size it or to copy it -- and nothing saves what it read, so a trim would free
 * the backing files of the items it dropped and leave a store still pointing at
 * them. What is on disk is what is answered. */
static void
g_paste_history_load_locked (GPasteHistory *self,
                             const gchar   *name)
{
    g_paste_history_private_clear (self);
    self->size = 0;

    g_set_str (&self->name, g_paste_history_resolve_name (self, name));

    /* A history that is on disk but unreadable (wrong passphrase, corrupt or
     * truncated file, I/O error) must not be persisted over: stop recording so
     * the next clipboard change cannot rewrite the still-intact data from the
     * empty model we ended up with. Conversely a history that *did* read back
     * clears the flag: it is this history's own readability that decides,
     * otherwise one unreadable history would silently stop persisting every
     * other one loaded afterwards. */
    GList *history = NULL;

    self->unreadable = !g_paste_storage_backend_read_history (self->backend, self->name, &history, &self->size);
    g_paste_history_private_set_from_list (self, history);

    if (self->unreadable)
        g_warning ("Could not read the history back; it will not be overwritten");

    if (self->history->len)
        g_paste_history_activate_first (self, TRUE);

    /* Unconditional: biggest_uuid borrows the uuid of an item we just freed, so
     * it has to be re-elected (to NULL) even when the new history is empty. */
    g_paste_history_private_elect_new_biggest (self);
}

/**
 * g_paste_history_load:
 * @self: a #GPasteHistory instance
 * @name: (nullable): the name of the history to load, defaults to the configured one
 *
 * Load the #GPasteHistory from the history file (synchronous)
 */
G_PASTE_VISIBLE void
g_paste_history_load (GPasteHistory *self,
                      const gchar   *name)
{
    g_return_if_fail (G_PASTE_IS_HISTORY (self));
    g_return_if_fail (!name || g_utf8_validate (name, -1, NULL));

    G_PASTE_LOCK_HISTORY;

    if (self->name && g_paste_str_equal (name, self->name))
        return;

    g_paste_history_load_locked (self, name);
}

/* Install the result of an async load (driven by the saver) into the model. */
static void
g_paste_history_on_loaded (gpointer user_data,
                           GList   *history,
                           gsize    size,
                           gboolean save_after,
                           gboolean readable)
{
    GPasteHistory *self = user_data;
    G_PASTE_LOCK_HISTORY;

    g_paste_history_private_set_from_list (self, history);
    self->size = size;

    if (self->history->len)
        g_paste_history_activate_first (self, TRUE);

    /* Unconditional: biggest_uuid borrows a uuid from the list we just freed. */
    g_paste_history_private_elect_new_biggest (self);

    /* The history is on disk but could not be read back: never write our empty
     * model over it (see g_paste_history_load_locked). */
    self->unreadable = !readable;

    if (self->unreadable)
    {
        g_warning ("Could not read the history back; it will not be overwritten");
        save_after = FALSE;
    }
    /* Both caps, applied to what was just read. A backend reads a favourite back
     * whatever it costs -- it counts only the items its cap could evict, or one
     * that had sunk past the cap would be dropped on load and destroyed by the
     * next save -- so what arrives here can hold more than max-history-size once
     * the favourites are counted, which is how the caps count. Applying them now
     * is what keeps the two answers the same: left to the next clipboard change,
     * the rows over the cap would sit there until it came and then vanish all at
     * once.
     *
     * An eviction frees the backing files of what it drops, so the store must
     * not be left referencing them: whatever the load asked for, a trim is
     * saved. Which is also why a history that is unreadable or flushed is left
     * alone: neither can be saved (the first must never be written over, the
     * second belongs to a successor daemon now), so a trim there would delete
     * the images a store still points at. A switch can land right inside that
     * flushed window -- see g_paste_history_history_name_changed (). */
    else if (!self->stopped)
    {
        guint length_before = self->history->len;

        g_paste_history_private_check_size (self);
        g_paste_history_private_check_memory_usage (self);

        if (self->history->len != length_before)
            save_after = TRUE;
    }

    if (save_after)
        g_paste_history_update (self, G_PASTE_UPDATE_ACTION_REPLACE, G_PASTE_UPDATE_TARGET_ALL, 0, G_PASTE_HISTORY_SAVE_FULL, NULL, NULL, FALSE);
    else
        g_paste_history_emit_update (self, G_PASTE_UPDATE_ACTION_REPLACE, G_PASTE_UPDATE_TARGET_ALL, NULL, 0);
}

/**
 * g_paste_history_reload_backend:
 * @self: a #GPasteHistory instance
 *
 * Rebuild the storage backend from the (possibly changed) "storage-backend"
 * setting and re-read the history from it, then resume recording. Meant to be
 * called after a storage migration has rewritten the store on disk: it assumes
 * g_paste_history_flush() already stopped recording and drained the saver, so
 * the backend swap does not race any pending write.
 */
G_PASTE_VISIBLE void
g_paste_history_reload_backend (GPasteHistory *self)
{
    g_return_if_fail (G_PASTE_IS_HISTORY (self));

    G_PASTE_LOCK_HISTORY;

    /* The migration ran out of process (or in a re-exec'd one), so the new
     * "storage-backend" value may not have reached our cached settings yet: read
     * it back before rebuilding, or we would resurrect the flavour we just left. */
    g_paste_settings_reload (self->settings);

    /* A load still in flight keeps the old saver alive through the task's own
     * reference; detach it so its (pre-migration) result is dropped instead of
     * being installed over the one we are about to load. */
    if (self->saver)
        g_paste_history_saver_detach (self->saver);

    g_clear_object (&self->saver);
    g_clear_object (&self->backend);
    self->backend = g_paste_storage_backend_new (g_paste_settings_get_storage_backend (self->settings), self->settings);
    self->saver = g_paste_history_saver_new (self->backend, self, g_paste_history_on_loaded);

    g_paste_history_private_clear (self);
    self->size = 0;
    g_paste_history_private_elect_new_biggest (self);
    /* The deliberate end of the handover g_paste_history_flush() opened: the
     * store has been rewritten and we are the one that owns it again. Whatever
     * the backend we just dropped could not read is moot too. */
    self->stopped = FALSE;
    self->unreadable = FALSE;

    /* Tell every UI to reload the whole history from the new backend. */
    g_paste_history_emit_switch (self, self->name);

    /* Asynchronously, like every other load: the only caller is the gnome-shell
     * host, where reading (and, for an encrypted flavour, deriving the key with
     * Argon2id) inline would freeze the whole compositor. The store was just
     * written by the migration, so there is nothing to normalize back. */
    g_paste_history_saver_load (self->saver, self->name, FALSE);
}

/**
 * g_paste_history_load_async:
 * @self: a #GPasteHistory instance
 * @name: (nullable): the name of the history to load, defaults to the configured one
 *
 * Load the #GPasteHistory from the history file asynchronously.
 * The UPDATE signal is emitted on the main thread when loading is complete.
 */
G_PASTE_VISIBLE void
g_paste_history_load_async (GPasteHistory *self,
                            const gchar   *name)
{
    g_return_if_fail (G_PASTE_IS_HISTORY (self));
    g_return_if_fail (!name || g_utf8_validate (name, -1, NULL));

    G_PASTE_LOCK_HISTORY;

    const gchar *resolved = g_paste_history_resolve_name (self, name);

    if (self->name && g_paste_str_equal (resolved, self->name) && !g_paste_history_saver_is_loading (self->saver))
        return;

    g_set_str (&self->name, resolved);
    g_paste_history_private_clear (self);
    self->size = 0;
    /* A previously unreadable history must not keep the *next* one from being
     * persisted: the load's own result decides (see g_paste_history_on_loaded).
     * Nothing is recorded in the meantime, since a load is now in progress.
     * @stopped is deliberately left alone — a handover is not ours to undo. */
    self->unreadable = FALSE;

    g_paste_history_saver_load (self->saver, self->name, FALSE);
}

/**
 * g_paste_history_switch:
 * @self: a #GPasteHistory instance
 * @name: the name of the new history
 *
 * Switch to a new history
 */
G_PASTE_VISIBLE void
g_paste_history_switch (GPasteHistory *self,
                        const gchar   *name)
{
    g_return_if_fail (G_PASTE_IS_HISTORY (self));
    g_return_if_fail (g_paste_util_history_name_is_valid (name));
    g_return_if_fail (g_utf8_validate (name, -1, NULL));

    g_paste_settings_set_history_name (self->settings, name);
}

/**
 * g_paste_history_delete:
 * @self: a #GPasteHistory instance
 * @name: (nullable): the history to delete (defaults to the configured one)
 * @error: return location for a #GError, or %NULL
 *
 * Delete the current #GPasteHistory
 *
 * Fails with %G_IO_ERROR_BUSY, having deleted nothing, when the store has been
 * handed over (see g_paste_history_flush()); otherwise errors are whatever the
 * storage backend reports for unlinking it, in %G_IO_ERROR or %G_FILE_ERROR.
 *
 * Returns: whether the history was deleted
 */
G_PASTE_VISIBLE gboolean
g_paste_history_delete (GPasteHistory *self,
                        const gchar   *name,
                        GError       **error)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), FALSE);

    const gchar *history_name = (name) ? name : self->name;

    /* We have handed the store over: either to a successor daemon, or — for the
     * in-shell one, which keeps answering the bus right through its migration —
     * to a migration that is reading and rewriting it as we speak. Deleting an
     * entry from underneath it would race that rewrite (and lose to it), so
     * refuse rather than half-do it. */
    if (self->stopped)
    {
        g_set_error (error, G_IO_ERROR, G_IO_ERROR_BUSY,
                     "The history storage is being handed over, cannot delete “%s” right now",
                     history_name);
        return FALSE;
    }

    if (g_paste_str_equal (history_name, self->name))
    {
        /* A load still in flight is reading the very history we are about to
         * unlink: its result would be installed right after, and the next
         * clipboard change would then write the whole pre-delete history back.
         * Also lets the empty() below actually record its write (a save is
         * skipped while a load is in progress). */
        g_paste_history_saver_abandon_load (self->saver);

        g_paste_history_empty (self);
        /* Emptying records a write for this very history, which the saver would
         * otherwise apply *after* the file is gone — re-creating it (both
         * backends create the store on open/write), so the deleted history would
         * come back, empty, in the next listing. Land it first. */
        g_paste_history_saver_drain (self->saver);

        /* Whatever we could not read back is about to be unlinked, so there is
         * nothing left to protect: recording must resume, or the gate would stay
         * shut for the rest of the process and nothing would ever be persisted
         * again. */
        self->unreadable = FALSE;
    }

    g_autoptr (GError) local_error = NULL;

    g_paste_storage_backend_delete_history (self->backend, history_name, &local_error);

    if (local_error)
    {
        g_propagate_error (error, g_steal_pointer (&local_error));
        return FALSE;
    }

    return TRUE;
}

static void
g_paste_history_history_name_changed (GPasteHistory *self)
{
    g_set_str (&self->name, g_paste_history_resolve_name (self, NULL));

    g_debug ("history: name changed to '%s'", self->name);

    g_paste_history_private_clear (self);
    self->size = 0;
    /* Switching away from an unreadable history resumes recording: the new one
     * stands on its own (see g_paste_history_load_async). A flushed history
     * stays flushed, though — the in-shell daemon keeps answering the bus right
     * through its migration, so a switch can land inside that window and must
     * not put the pre-migration backend back to work. */
    self->unreadable = FALSE;

    g_paste_history_emit_switch (self, self->name);
    g_paste_history_saver_load (self->saver, self->name, TRUE);
}

/* One detailed "notify::<key>" handler per setting the history reacts to, rather
 * than one undetailed handler dispatching on the key, so the history is never
 * woken for the twenty-odd settings that mean nothing to it. */

/* Either cap: both are answered by applying both, since a trim is what the
 * history owes each of them at all times. */
static void
g_paste_history_on_cap_changed (GPasteSettings *settings G_GNUC_UNUSED,
                                GParamSpec     *pspec G_GNUC_UNUSED,
                                gpointer        user_data)
{
    GPasteHistory *self = user_data;
    G_PASTE_LOCK_HISTORY;

    g_paste_history_private_trim (self);
}

static void
g_paste_history_on_history_name_changed (GPasteSettings *settings G_GNUC_UNUSED,
                                         GParamSpec     *pspec G_GNUC_UNUSED,
                                         gpointer        user_data)
{
    GPasteHistory *self = user_data;
    G_PASTE_LOCK_HISTORY;

    g_paste_history_history_name_changed (self);
}

static void
g_paste_history_dispose (GObject *object)
{
    GPasteHistory *self = G_PASTE_HISTORY (object);

    g_clear_object (&self->saver);
    g_clear_object (&self->backend);
    g_clear_object (&self->pending_selection);
    g_clear_pointer (&self->history, g_ptr_array_unref);
    g_clear_pointer (&self->by_uuid, g_hash_table_unref);
    g_clear_object (&self->settings_signals);
    g_clear_object (&self->settings);

    G_OBJECT_CLASS (g_paste_history_parent_class)->dispose (object);
}

static void
g_paste_history_finalize (GObject *object)
{
    GPasteHistory *self = G_PASTE_HISTORY (object);

    g_free (self->name);
    g_mutex_clear (&self->lock);

    G_OBJECT_CLASS (g_paste_history_parent_class)->finalize (object);
}

static void
g_paste_history_class_init (GPasteHistoryClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    object_class->dispose = g_paste_history_dispose;
    object_class->finalize = g_paste_history_finalize;

    /**
     * GPasteHistory::selected:
     * @history: the object on which the signal was emitted
     * @item: the new selected item
     *
     * The "selected" signal is emitted when the user has just
     * selected a new item form the history.
     */
    signals[SELECTED] = g_signal_new ("selected",
                                      G_PASTE_TYPE_HISTORY,
                                      G_SIGNAL_RUN_LAST,
                                      0, /* class offset */
                                      NULL, /* accumulator */
                                      NULL, /* accumulator data */
                                      g_cclosure_marshal_VOID__OBJECT,
                                      G_TYPE_NONE,
                                      1, /* number of params */
                                      G_PASTE_TYPE_ITEM);

    /**
     * GPasteHistory::switch:
     * @history: the object on which the signal was emitted
     * @name: the new history name
     *
     * The "switch" signal is emitted when the user has just
     * switched to a new history
     */
    signals[SWITCH] = g_signal_new ("switch",
                                    G_PASTE_TYPE_HISTORY,
                                    G_SIGNAL_RUN_LAST,
                                    0, /* class offset */
                                    NULL, /* accumulator */
                                    NULL, /* accumulator data */
                                    g_cclosure_marshal_VOID__STRING,
                                    G_TYPE_NONE,
                                    1, /* number of params */
                                    G_TYPE_STRING);

    /**
     * GPasteHistory::update:
     * @history: the object on which the signal was emitted
     * @action: the kind of update
     * @target: the items which need updating
     * @uuid: the item the update is about, when the target is ITEM; "" otherwise
     * @index: where that item sits, when the target is ITEM
     *
     * The "update" signal is emitted whenever anything changed
     * in the history (something was added, removed, selected, replaced...).
     */
    signals[UPDATE] = g_signal_new ("update",
                                    G_PASTE_TYPE_HISTORY,
                                    G_SIGNAL_RUN_LAST,
                                    0, /* class offset */
                                    NULL, /* accumulator */
                                    NULL, /* accumulator data */
                                    g_cclosure_marshal_generic,
                                    G_TYPE_NONE,
                                    4, /* number of params */
                                    G_PASTE_TYPE_UPDATE_ACTION,
                                    G_PASTE_TYPE_UPDATE_TARGET,
                                    G_TYPE_STRING,
                                    G_TYPE_UINT64);
}

static void
g_paste_history_init (GPasteHistory *self)
{
    g_mutex_init (&self->lock);

    /* The array owns a ref per item; the index borrows both its keys and its
     * values from it, so it gets no free funcs of its own. */
    self->history = g_ptr_array_new_with_free_func (g_object_unref);
    self->by_uuid = g_hash_table_new (g_str_hash, g_str_equal);

    G_PASTE_LOCK_HISTORY;

    g_paste_history_private_elect_new_biggest (self);
}

/**
 * g_paste_history_get_history:
 * @self: a #GPasteHistory instance
 *
 * Get the inner history of a #GPasteHistory
 *
 * Returns: (element-type GPasteItem) (transfer none): The inner history,
 *          newest first
 */
G_PASTE_VISIBLE const GPtrArray *
g_paste_history_get_history (GPasteHistory *self)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), NULL);

    return self->history;
}

/**
 * g_paste_history_get_length:
 * @self: a #GPasteHistory instance
 *
 * Get the length of a #GPasteHistory
 *
 * Returns: The length of the inner history
 */
G_PASTE_VISIBLE guint64
g_paste_history_get_length (GPasteHistory *self)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), 0);

    G_PASTE_LOCK_HISTORY;

    return self->history->len;
}

/**
 * g_paste_history_get_current:
 * @self: a #GPasteHistory instance
 *
 * Get the name of the current history
 *
 * Returns: The name of the current history
 */
G_PASTE_VISIBLE const gchar *
g_paste_history_get_current (GPasteHistory *self)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), 0);

    return self->name;
}

/**
 * g_paste_history_is_unreadable:
 * @self: a #GPasteHistory instance
 *
 * Whether the history that was last loaded is on disk but could not be read
 * (wrong passphrase, corrupt or truncated file, I/O error), leaving this
 * instance holding an empty model that is not what the store actually has.
 *
 * Recording stops on its own for that case, so nothing overwrites the intact
 * data; a caller that copies the model out somewhere else -- a backup -- has to
 * ask, an empty answer being indistinguishable from a genuinely empty history.
 *
 * Returns: %TRUE when the model is empty because the read failed
 */
G_PASTE_VISIBLE gboolean
g_paste_history_is_unreadable (GPasteHistory *self)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), FALSE);

    return self->unreadable;
}

/**
 * g_paste_history_search:
 * @self: a #GPasteHistory instance
 * @pattern: the pattern to match
 *
 * Get the elements matching @pattern in the history
 *
 * Returns: (transfer full): The uuids of the matching elements
 */
G_PASTE_VISIBLE GStrv
g_paste_history_search (GPasteHistory *self,
                        const gchar   *pattern)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), NULL);
    g_return_val_if_fail (pattern && g_utf8_validate (pattern, -1, NULL), NULL);
    g_return_val_if_fail (strlen (pattern) <= 256, NULL);

    g_debug ("history: search '%s'", pattern);

    G_PASTE_LOCK_HISTORY;
    g_autoptr (GError) error = NULL;
    g_autoptr (GRegex) regex = g_regex_new (pattern,
                                            G_REGEX_CASELESS|G_REGEX_MULTILINE|G_REGEX_DOTALL|G_REGEX_OPTIMIZE,
                                            G_REGEX_MATCH_NOTEMPTY|G_REGEX_MATCH_NEWLINE_ANY,
                                            &error);

    if (error)
    {
        g_warning ("error while creating regex: %s", error->message);
        return NULL;
    }
    if (!regex)
        return NULL;

    g_autoptr (GStrvBuilder) results = g_strv_builder_new ();
    for (guint i = 0; i < self->history->len; ++i)
    {
        GPasteItem *item = g_ptr_array_index (self->history, i);
        const gchar *uuid = g_paste_item_get_uuid (item);
        gboolean match = FALSE;

        if (g_paste_str_equal (pattern, uuid))
            match = TRUE;
        else if (G_PASTE_IS_PASSWORD_ITEM (item) && g_paste_str_equal (pattern, g_paste_password_item_get_name (G_PASTE_PASSWORD_ITEM (item))))
            match = TRUE;
        else if (g_regex_match (regex, g_paste_item_get_value (item), G_REGEX_MATCH_NOTEMPTY|G_REGEX_MATCH_NEWLINE_ANY, NULL))
            match = TRUE;

        if (match)
            g_strv_builder_add (results, uuid);
    }

    return g_strv_builder_end (results);
}

/**
 * g_paste_history_new:
 * @settings: (transfer none): a #GPasteSettings instance
 *
 * Create a new instance of #GPasteHistory
 *
 * Returns: a newly allocated #GPasteHistory
 *          free it with g_object_unref
 */
G_PASTE_VISIBLE GPasteHistory *
g_paste_history_new (GPasteSettings *settings)
{
    g_return_val_if_fail (G_PASTE_IS_SETTINGS (settings), NULL);

    GPasteHistory *self = g_object_new (G_PASTE_TYPE_HISTORY, NULL);

    self->backend = g_paste_storage_backend_new (g_paste_settings_get_storage_backend (settings), settings);
    self->saver = g_paste_history_saver_new (self->backend, self, g_paste_history_on_loaded);
    self->settings = g_object_ref (settings);

    /* The text item size settings are deliberately absent: they filter what
     * gets in (see g_paste_clipboard_content_*), and narrowing them must not
     * retroactively delete items already recorded under the old bounds, the way
     * the size and memory limits below do. */
    GSignalGroup *settings_signals = self->settings_signals = g_signal_group_new (G_PASTE_TYPE_SETTINGS);
    g_signal_group_connect (settings_signals, "notify::" G_PASTE_MAX_HISTORY_SIZE_SETTING,
                            G_CALLBACK (g_paste_history_on_cap_changed), self);
    g_signal_group_connect (settings_signals, "notify::" G_PASTE_MAX_MEMORY_USAGE_SETTING,
                            G_CALLBACK (g_paste_history_on_cap_changed), self);
    g_signal_group_connect (settings_signals, "notify::" G_PASTE_HISTORY_NAME_SETTING,
                            G_CALLBACK (g_paste_history_on_history_name_changed), self);
    g_signal_group_set_target (settings_signals, settings);

    return self;
}

/**
 * g_paste_history_list:
 * @self: a #GPasteHistory instance
 * @error: return location for a #GError, or %NULL
 *
 * Get the list of available histories
 *
 * Errors are the storage backend's own, in %G_IO_ERROR or %G_FILE_ERROR.
 *
 * Returns: (transfer full): The list of history names
 *                           free it with g_strfreev
 */
G_PASTE_VISIBLE GStrv
g_paste_history_list (GPasteHistory *self,
                      GError       **error)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), NULL);
    g_return_val_if_fail (!error || !(*error), NULL);

    return g_paste_storage_backend_list_histories (self->backend, error);
}

/**
 * g_paste_history_get_size:
 * @self: a #GPasteHistory instance
 * @name: the name of the history to size
 *
 * How many items the history called @name holds.
 *
 * The one in use answers from what is already loaded; any other is counted in
 * the store, which a backend that can count never loads at all.
 *
 * Returns: the number of items @name holds
 */
G_PASTE_VISIBLE guint64
g_paste_history_get_size (GPasteHistory *self,
                          const gchar   *name)
{
    g_return_val_if_fail (G_PASTE_IS_HISTORY (self), 0);
    g_return_val_if_fail (name, 0);

    if (g_paste_str_equal (name, g_paste_history_get_current (self)))
        return g_paste_history_get_length (self);

    return g_paste_storage_backend_count_history (self->backend, name);
}
