clipmem 0.5.4

macOS clipboard memory backed by SQLite and searchable from agent runtimes
Documentation
use anyhow::{Context, Result};
use rusqlite::params;

use crate::db::sqlite_helpers::row_u64;
use crate::db::types::{ArchiveChangeKind, ArchiveRevision, Database};

impl Database {
    pub fn archive_revision(&self) -> Result<ArchiveRevision> {
        self.conn
            .query_row(
                r"
                    SELECT
                        revision,
                        archive_content_revision,
                        settings_revision,
                        ocr_revision,
                        storage_revision,
                        service_revision,
                        app_preferences_revision,
                        last_change_kind,
                        updated_at
                    FROM archive_revisions
                    WHERE id = 1
                ",
                [],
                |row| {
                    Ok(ArchiveRevision {
                        revision: row_u64(row, 0)?,
                        archive_content_revision: row_u64(row, 1)?,
                        settings_revision: row_u64(row, 2)?,
                        ocr_revision: row_u64(row, 3)?,
                        storage_revision: row_u64(row, 4)?,
                        service_revision: row_u64(row, 5)?,
                        app_preferences_revision: row_u64(row, 6)?,
                        last_change_kind: row.get(7)?,
                        updated_at: row.get(8)?,
                    })
                },
            )
            .context("load archive revision")
    }

    pub(crate) fn bump_service_revision(&self) -> Result<ArchiveRevision> {
        bump_revision(&self.conn, &[ArchiveChangeKind::Service])?;
        self.archive_revision()
    }

    pub(crate) fn bump_app_preferences_revision(&self) -> Result<ArchiveRevision> {
        bump_revision(&self.conn, &[ArchiveChangeKind::AppPreferences])?;
        self.archive_revision()
    }
}

pub(in crate::db) fn bump_revision(
    conn: &rusqlite::Connection,
    kinds: &[ArchiveChangeKind],
) -> Result<()> {
    if kinds.is_empty() {
        return Ok(());
    }

    let assignments = kinds
        .iter()
        .map(|kind| {
            format!(
                "{} = {} + 1",
                kind.revision_column(),
                kind.revision_column()
            )
        })
        .collect::<Vec<_>>()
        .join(", ");
    let kind_label = change_kind_label(kinds);
    let sql = format!(
        r"
            INSERT INTO archive_revisions (id, revision, {first_column}, last_change_kind, updated_at)
            VALUES (1, 1, 1, ?1, CURRENT_TIMESTAMP)
            ON CONFLICT(id) DO UPDATE SET
                revision = revision + 1,
                {assignments},
                last_change_kind = excluded.last_change_kind,
                updated_at = CURRENT_TIMESTAMP
        ",
        first_column = kinds[0].revision_column(),
    );
    conn.execute(&sql, params![kind_label])
        .context("bump archive revision")?;
    Ok(())
}

pub(in crate::db) fn bump_revision_tx(
    tx: &rusqlite::Transaction<'_>,
    kinds: &[ArchiveChangeKind],
) -> Result<()> {
    bump_revision(tx, kinds)
}

fn change_kind_label(kinds: &[ArchiveChangeKind]) -> String {
    kinds
        .iter()
        .map(|kind| kind.as_str())
        .collect::<Vec<_>>()
        .join(",")
}