mirror of
https://github.com/NotAShelf/stash.git
synced 2026-04-12 22:17:41 +00:00
db: switch to sqlite as the primary backend
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I6a6a69648f81d0d094e11a3e0f0a19d3b8eccd5d
This commit is contained in:
parent
efcbe17d73
commit
4f725425fc
12 changed files with 246 additions and 274 deletions
173
Cargo.lock
generated
173
Cargo.lock
generated
|
|
@ -424,6 +424,18 @@ dependencies = [
|
||||||
"zune-inflate",
|
"zune-inflate",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-iterator"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-streaming-iterator"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
|
|
@ -456,23 +468,10 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fs2"
|
name = "foldhash"
|
||||||
version = "0.4.3"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fxhash"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
|
|
@ -522,6 +521,18 @@ name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
|
dependencies = [
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashlink"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
|
|
@ -578,15 +589,6 @@ dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "instant"
|
|
||||||
version = "0.1.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "interpolate_name"
|
name = "interpolate_name"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
|
@ -685,6 +687,17 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libsqlite3-sys"
|
||||||
|
version = "0.35.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.15"
|
version = "0.4.15"
|
||||||
|
|
@ -697,16 +710,6 @@ version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lock_api"
|
|
||||||
version = "0.4.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"scopeguard",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.27"
|
version = "0.4.27"
|
||||||
|
|
@ -854,31 +857,6 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot"
|
|
||||||
version = "0.11.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
|
||||||
dependencies = [
|
|
||||||
"instant",
|
|
||||||
"lock_api",
|
|
||||||
"parking_lot_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot_core"
|
|
||||||
version = "0.8.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"instant",
|
|
||||||
"libc",
|
|
||||||
"redox_syscall",
|
|
||||||
"smallvec",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
|
|
@ -1105,15 +1083,6 @@ dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_syscall"
|
|
||||||
version = "0.2.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_users"
|
name = "redox_users"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
|
@ -1182,6 +1151,20 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusqlite"
|
||||||
|
version = "0.37.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.1",
|
||||||
|
"fallible-iterator",
|
||||||
|
"fallible-streaming-iterator",
|
||||||
|
"hashlink",
|
||||||
|
"libsqlite3-sys",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.44"
|
version = "0.38.44"
|
||||||
|
|
@ -1214,12 +1197,6 @@ version = "1.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "scopeguard"
|
|
||||||
version = "1.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.219"
|
version = "1.0.219"
|
||||||
|
|
@ -1270,22 +1247,6 @@ dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sled"
|
|
||||||
version = "0.34.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
|
|
||||||
dependencies = [
|
|
||||||
"crc32fast",
|
|
||||||
"crossbeam-epoch",
|
|
||||||
"crossbeam-utils",
|
|
||||||
"fs2",
|
|
||||||
"fxhash",
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"parking_lot",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.15.1"
|
version = "1.15.1"
|
||||||
|
|
@ -1303,8 +1264,8 @@ dependencies = [
|
||||||
"image",
|
"image",
|
||||||
"log",
|
"log",
|
||||||
"rmp-serde",
|
"rmp-serde",
|
||||||
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
"sled",
|
|
||||||
"thiserror 2.0.14",
|
"thiserror 2.0.14",
|
||||||
"wl-clipboard-rs",
|
"wl-clipboard-rs",
|
||||||
]
|
]
|
||||||
|
|
@ -1478,6 +1439,12 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version-compare"
|
name = "version-compare"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -1633,28 +1600,6 @@ version = "0.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3"
|
checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi"
|
|
||||||
version = "0.3.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-i686-pc-windows-gnu",
|
|
||||||
"winapi-x86_64-pc-windows-gnu",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-i686-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ rust-version = "1.85"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.44", features = ["derive"] }
|
clap = { version = "4.5.44", features = ["derive"] }
|
||||||
sled = "0.34.7"
|
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
rmp-serde = "1.3.0"
|
rmp-serde = "1.3.0"
|
||||||
|
|
@ -20,3 +19,4 @@ env_logger = "0.11.8"
|
||||||
clap-verbosity-flag = "3.0.3"
|
clap-verbosity-flag = "3.0.3"
|
||||||
thiserror = "2.0.14"
|
thiserror = "2.0.14"
|
||||||
wl-clipboard-rs = "0.9.2"
|
wl-clipboard-rs = "0.9.2"
|
||||||
|
rusqlite = {version = "0.37.0", features = [ "bundled" ] }
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ must handle the migration yourself, with one simple command.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cliphist list --db ~/.cache/cliphist/db | stash --import-tsv
|
$ cliphist list --db ~/.cache/cliphist/db | stash --import-tsv
|
||||||
# > Imported 750 records from TSV into sled database.
|
# > Imported 750 records from TSV into database.
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, you may first export from Cliphist and _then_ import the
|
Alternatively, you may first export from Cliphist and _then_ import the
|
||||||
|
|
@ -88,5 +88,5 @@ database.
|
||||||
```bash
|
```bash
|
||||||
$ cliphist list --db ~/.cache/cliphist/db > cliphist.tsv
|
$ cliphist list --db ~/.cache/cliphist/db > cliphist.tsv
|
||||||
$ stash --import-tsv < cliphist.tsv
|
$ stash --import-tsv < cliphist.tsv
|
||||||
# > Imported 750 records from TSV into sled database.
|
# > Imported 750 records from TSV into database.
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::db::{ClipboardDb, SledClipboardDb};
|
use crate::db::{ClipboardDb, SqliteClipboardDb};
|
||||||
|
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub trait DecodeCommand {
|
||||||
) -> Result<(), StashError>;
|
) -> Result<(), StashError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DecodeCommand for SledClipboardDb {
|
impl DecodeCommand for SqliteClipboardDb {
|
||||||
fn decode(
|
fn decode(
|
||||||
&self,
|
&self,
|
||||||
in_: impl Read,
|
in_: impl Read,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::db::{ClipboardDb, SledClipboardDb, StashError};
|
use crate::db::{ClipboardDb, SqliteClipboardDb, StashError};
|
||||||
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
|
|
@ -6,7 +6,7 @@ pub trait DeleteCommand {
|
||||||
fn delete(&self, input: impl Read) -> Result<usize, StashError>;
|
fn delete(&self, input: impl Read) -> Result<usize, StashError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeleteCommand for SledClipboardDb {
|
impl DeleteCommand for SqliteClipboardDb {
|
||||||
fn delete(&self, input: impl Read) -> Result<usize, StashError> {
|
fn delete(&self, input: impl Read) -> Result<usize, StashError> {
|
||||||
match self.delete_entries(input) {
|
match self.delete_entries(input) {
|
||||||
Ok(deleted) => {
|
Ok(deleted) => {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::db::{ClipboardDb, SledClipboardDb};
|
use crate::db::{ClipboardDb, SqliteClipboardDb};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
pub trait ListCommand {
|
pub trait ListCommand {
|
||||||
fn list(&self, out: impl Write, preview_width: u32) -> Result<(), crate::db::StashError>;
|
fn list(&self, out: impl Write, preview_width: u32) -> Result<(), crate::db::StashError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListCommand for SledClipboardDb {
|
impl ListCommand for SqliteClipboardDb {
|
||||||
fn list(&self, out: impl Write, preview_width: u32) -> Result<(), crate::db::StashError> {
|
fn list(&self, out: impl Write, preview_width: u32) -> Result<(), crate::db::StashError> {
|
||||||
self.list_entries(out, preview_width)?;
|
self.list_entries(out, preview_width)?;
|
||||||
log::info!("Listed clipboard entries");
|
log::info!("Listed clipboard entries");
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::db::{ClipboardDb, SledClipboardDb};
|
use crate::db::{ClipboardDb, SqliteClipboardDb};
|
||||||
|
|
||||||
use crate::db::StashError;
|
use crate::db::StashError;
|
||||||
|
|
||||||
|
|
@ -6,8 +6,8 @@ pub trait QueryCommand {
|
||||||
fn query_delete(&self, query: &str) -> Result<usize, StashError>;
|
fn query_delete(&self, query: &str) -> Result<usize, StashError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QueryCommand for SledClipboardDb {
|
impl QueryCommand for SqliteClipboardDb {
|
||||||
fn query_delete(&self, query: &str) -> Result<usize, StashError> {
|
fn query_delete(&self, query: &str) -> Result<usize, StashError> {
|
||||||
<SledClipboardDb as ClipboardDb>::delete_query(self, query)
|
<SqliteClipboardDb as ClipboardDb>::delete_query(self, query)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::db::{ClipboardDb, SledClipboardDb};
|
use crate::db::{ClipboardDb, SqliteClipboardDb};
|
||||||
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
|
|
@ -12,7 +12,7 @@ pub trait StoreCommand {
|
||||||
) -> Result<(), crate::db::StashError>;
|
) -> Result<(), crate::db::StashError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StoreCommand for SledClipboardDb {
|
impl StoreCommand for SqliteClipboardDb {
|
||||||
fn store(
|
fn store(
|
||||||
&self,
|
&self,
|
||||||
input: impl Read,
|
input: impl Read,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::db::{ClipboardDb, SledClipboardDb};
|
use crate::db::{ClipboardDb, SqliteClipboardDb};
|
||||||
|
|
||||||
use crate::db::StashError;
|
use crate::db::StashError;
|
||||||
|
|
||||||
|
|
@ -6,7 +6,7 @@ pub trait WipeCommand {
|
||||||
fn wipe(&self) -> Result<(), StashError>;
|
fn wipe(&self) -> Result<(), StashError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WipeCommand for SledClipboardDb {
|
impl WipeCommand for SqliteClipboardDb {
|
||||||
fn wipe(&self) -> Result<(), StashError> {
|
fn wipe(&self) -> Result<(), StashError> {
|
||||||
self.wipe_db()?;
|
self.wipe_db()?;
|
||||||
log::info!("Database wiped");
|
log::info!("Database wiped");
|
||||||
|
|
|
||||||
277
src/db/mod.rs
277
src/db/mod.rs
|
|
@ -4,9 +4,9 @@ use std::str;
|
||||||
|
|
||||||
use image::{GenericImageView, ImageFormat};
|
use image::{GenericImageView, ImageFormat};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use rmp_serde::{decode::from_read, encode::to_vec};
|
|
||||||
|
use rusqlite::{Connection, OptionalExtension, params};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sled::{Db, IVec};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
@ -15,8 +15,7 @@ pub enum StashError {
|
||||||
EmptyOrTooLarge,
|
EmptyOrTooLarge,
|
||||||
#[error("Input is all whitespace, skipping store.")]
|
#[error("Input is all whitespace, skipping store.")]
|
||||||
AllWhitespace,
|
AllWhitespace,
|
||||||
#[error("Failed to serialize entry: {0}")]
|
|
||||||
Serialize(String),
|
|
||||||
#[error("Failed to store entry: {0}")]
|
#[error("Failed to store entry: {0}")]
|
||||||
Store(String),
|
Store(String),
|
||||||
#[error("Error reading entry during deduplication: {0}")]
|
#[error("Error reading entry during deduplication: {0}")]
|
||||||
|
|
@ -41,10 +40,7 @@ pub enum StashError {
|
||||||
DecodeExtractId(String),
|
DecodeExtractId(String),
|
||||||
#[error("Failed to get entry for decode: {0}")]
|
#[error("Failed to get entry for decode: {0}")]
|
||||||
DecodeGet(String),
|
DecodeGet(String),
|
||||||
#[error("No entry found for id {0}")]
|
|
||||||
DecodeNoEntry(u64),
|
|
||||||
#[error("Failed to decode entry: {0}")]
|
|
||||||
DecodeDecode(String),
|
|
||||||
#[error("Failed to write decoded entry: {0}")]
|
#[error("Failed to write decoded entry: {0}")]
|
||||||
DecodeWrite(String),
|
DecodeWrite(String),
|
||||||
#[error("Failed to delete entry during query delete: {0}")]
|
#[error("Failed to delete entry during query delete: {0}")]
|
||||||
|
|
@ -89,11 +85,25 @@ impl fmt::Display for Entry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SledClipboardDb {
|
pub struct SqliteClipboardDb {
|
||||||
pub db: Db,
|
pub conn: Connection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClipboardDb for SledClipboardDb {
|
impl SqliteClipboardDb {
|
||||||
|
pub fn new(conn: Connection) -> Result<Self, StashError> {
|
||||||
|
conn.execute_batch(
|
||||||
|
"CREATE TABLE IF NOT EXISTS clipboard (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
contents BLOB NOT NULL,
|
||||||
|
mime TEXT
|
||||||
|
);",
|
||||||
|
)
|
||||||
|
.map_err(|e| StashError::Store(e.to_string()))?;
|
||||||
|
Ok(Self { conn })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClipboardDb for SqliteClipboardDb {
|
||||||
fn store_entry(
|
fn store_entry(
|
||||||
&self,
|
&self,
|
||||||
mut input: impl Read,
|
mut input: impl Read,
|
||||||
|
|
@ -112,98 +122,111 @@ impl ClipboardDb for SledClipboardDb {
|
||||||
|
|
||||||
self.deduplicate(&buf, max_dedupe_search)?;
|
self.deduplicate(&buf, max_dedupe_search)?;
|
||||||
|
|
||||||
let entry = Entry {
|
self.conn
|
||||||
contents: buf.clone(),
|
.execute(
|
||||||
mime,
|
"INSERT INTO clipboard (contents, mime) VALUES (?1, ?2)",
|
||||||
};
|
params![buf, mime],
|
||||||
|
)
|
||||||
let id = self.next_sequence();
|
|
||||||
let enc = to_vec(&entry).map_err(|e| StashError::Serialize(e.to_string()))?;
|
|
||||||
|
|
||||||
self.db
|
|
||||||
.insert(u64_to_ivec(id), enc)
|
|
||||||
.map_err(|e| StashError::Store(e.to_string()))?;
|
.map_err(|e| StashError::Store(e.to_string()))?;
|
||||||
|
|
||||||
self.trim_db(max_items)?;
|
self.trim_db(max_items)?;
|
||||||
Ok(id)
|
Ok(self.next_sequence())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deduplicate(&self, buf: &[u8], max: u64) -> Result<usize, StashError> {
|
fn deduplicate(&self, buf: &[u8], max: u64) -> Result<usize, StashError> {
|
||||||
let mut count = 0;
|
let mut stmt = self
|
||||||
|
.conn
|
||||||
|
.prepare("SELECT id, contents FROM clipboard ORDER BY id DESC LIMIT ?1")
|
||||||
|
.map_err(|e| StashError::DeduplicationRead(e.to_string()))?;
|
||||||
|
let mut rows = stmt
|
||||||
|
.query(params![i64::try_from(max).unwrap_or(i64::MAX)])
|
||||||
|
.map_err(|e| StashError::DeduplicationRead(e.to_string()))?;
|
||||||
let mut deduped = 0;
|
let mut deduped = 0;
|
||||||
for item in self
|
while let Some(row) = rows
|
||||||
.db
|
.next()
|
||||||
.iter()
|
.map_err(|e| StashError::DeduplicationRead(e.to_string()))?
|
||||||
.rev()
|
|
||||||
.take(usize::try_from(max).unwrap_or(usize::MAX))
|
|
||||||
{
|
{
|
||||||
let (k, v) = match item {
|
let id: u64 = row
|
||||||
Ok((k, v)) => (k, v),
|
.get(0)
|
||||||
Err(e) => return Err(StashError::DeduplicationRead(e.to_string())),
|
.map_err(|e| StashError::DeduplicationDecode(e.to_string()))?;
|
||||||
};
|
let contents: Vec<u8> = row
|
||||||
let entry: Entry = match from_read(v.as_ref()) {
|
.get(1)
|
||||||
Ok(e) => e,
|
.map_err(|e| StashError::DeduplicationDecode(e.to_string()))?;
|
||||||
Err(e) => return Err(StashError::DeduplicationDecode(e.to_string())),
|
if contents == buf {
|
||||||
};
|
self.conn
|
||||||
if entry.contents == buf {
|
.execute("DELETE FROM clipboard WHERE id = ?1", params![id])
|
||||||
self.db
|
|
||||||
.remove(k)
|
|
||||||
.map(|_| {
|
|
||||||
deduped += 1;
|
|
||||||
})
|
|
||||||
.map_err(|e| StashError::DeduplicationRemove(e.to_string()))?;
|
.map_err(|e| StashError::DeduplicationRemove(e.to_string()))?;
|
||||||
}
|
deduped += 1;
|
||||||
count += 1;
|
|
||||||
if count >= max {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(deduped)
|
Ok(deduped)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trim_db(&self, max: u64) -> Result<(), StashError> {
|
fn trim_db(&self, max: u64) -> Result<(), StashError> {
|
||||||
let mut keys: Vec<_> = self
|
let count: u64 = self
|
||||||
.db
|
.conn
|
||||||
.iter()
|
.query_row("SELECT COUNT(*) FROM clipboard", [], |row| row.get(0))
|
||||||
.rev()
|
|
||||||
.filter_map(|kv| match kv {
|
|
||||||
Ok((k, _)) => Some(k),
|
|
||||||
Err(_e) => None,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
if keys.len() as u64 > max {
|
|
||||||
for k in keys.drain(usize::try_from(max).unwrap_or(0)..) {
|
|
||||||
self.db
|
|
||||||
.remove(k)
|
|
||||||
.map_err(|e| StashError::Trim(e.to_string()))?;
|
.map_err(|e| StashError::Trim(e.to_string()))?;
|
||||||
}
|
if count > max {
|
||||||
|
let to_delete = count - max;
|
||||||
|
self.conn.execute(
|
||||||
|
"DELETE FROM clipboard WHERE id IN (SELECT id FROM clipboard ORDER BY id ASC LIMIT ?1)",
|
||||||
|
params![i64::try_from(to_delete).unwrap_or(i64::MAX)],
|
||||||
|
).map_err(|e| StashError::Trim(e.to_string()))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_last(&self) -> Result<(), StashError> {
|
fn delete_last(&self) -> Result<(), StashError> {
|
||||||
if let Some((k, _)) = self.db.iter().next_back().and_then(Result::ok) {
|
let id: Option<u64> = self
|
||||||
self.db
|
.conn
|
||||||
.remove(k)
|
.query_row(
|
||||||
.map(|_| ())
|
"SELECT id FROM clipboard ORDER BY id DESC LIMIT 1",
|
||||||
.map_err(|e| StashError::DeleteLast(e.to_string()))
|
[],
|
||||||
|
|row| row.get(0),
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.map_err(|e| StashError::DeleteLast(e.to_string()))?;
|
||||||
|
if let Some(id) = id {
|
||||||
|
self.conn
|
||||||
|
.execute("DELETE FROM clipboard WHERE id = ?1", params![id])
|
||||||
|
.map_err(|e| StashError::DeleteLast(e.to_string()))?;
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(StashError::NoEntriesToDelete)
|
Err(StashError::NoEntriesToDelete)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wipe_db(&self) -> Result<(), StashError> {
|
fn wipe_db(&self) -> Result<(), StashError> {
|
||||||
self.db.clear().map_err(|e| StashError::Wipe(e.to_string()))
|
self.conn
|
||||||
|
.execute("DELETE FROM clipboard", [])
|
||||||
|
.map_err(|e| StashError::Wipe(e.to_string()))?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_entries(&self, mut out: impl Write, preview_width: u32) -> Result<usize, StashError> {
|
fn list_entries(&self, mut out: impl Write, preview_width: u32) -> Result<usize, StashError> {
|
||||||
|
let mut stmt = self
|
||||||
|
.conn
|
||||||
|
.prepare("SELECT id, contents, mime FROM clipboard ORDER BY id DESC")
|
||||||
|
.map_err(|e| StashError::ListDecode(e.to_string()))?;
|
||||||
|
let mut rows = stmt
|
||||||
|
.query([])
|
||||||
|
.map_err(|e| StashError::ListDecode(e.to_string()))?;
|
||||||
let mut listed = 0;
|
let mut listed = 0;
|
||||||
for (k, v) in self.db.iter().rev().filter_map(Result::ok) {
|
while let Some(row) = rows
|
||||||
let id = ivec_to_u64(&k);
|
.next()
|
||||||
let entry: Entry = match from_read(v.as_ref()) {
|
.map_err(|e| StashError::ListDecode(e.to_string()))?
|
||||||
Ok(e) => e,
|
{
|
||||||
Err(e) => return Err(StashError::ListDecode(e.to_string())),
|
let id: u64 = row
|
||||||
};
|
.get(0)
|
||||||
let preview = preview_entry(&entry.contents, entry.mime.as_deref(), preview_width);
|
.map_err(|e| StashError::ListDecode(e.to_string()))?;
|
||||||
|
let contents: Vec<u8> = row
|
||||||
|
.get(1)
|
||||||
|
.map_err(|e| StashError::ListDecode(e.to_string()))?;
|
||||||
|
let mime: Option<String> = row
|
||||||
|
.get(2)
|
||||||
|
.map_err(|e| StashError::ListDecode(e.to_string()))?;
|
||||||
|
let preview = preview_entry(&contents, mime.as_deref(), preview_width);
|
||||||
if writeln!(out, "{id}\t{preview}").is_ok() {
|
if writeln!(out, "{id}\t{preview}").is_ok() {
|
||||||
listed += 1;
|
listed += 1;
|
||||||
}
|
}
|
||||||
|
|
@ -226,38 +249,44 @@ impl ClipboardDb for SledClipboardDb {
|
||||||
buf
|
buf
|
||||||
};
|
};
|
||||||
let id = extract_id(&s).map_err(|e| StashError::DecodeExtractId(e.to_string()))?;
|
let id = extract_id(&s).map_err(|e| StashError::DecodeExtractId(e.to_string()))?;
|
||||||
let v = self
|
let (contents, _mime): (Vec<u8>, Option<String>) = self
|
||||||
.db
|
.conn
|
||||||
.get(u64_to_ivec(id))
|
.query_row(
|
||||||
.map_err(|e| StashError::DecodeGet(e.to_string()))?
|
"SELECT contents, mime FROM clipboard WHERE id = ?1",
|
||||||
.ok_or(StashError::DecodeNoEntry(id))?;
|
params![id],
|
||||||
let entry: Entry =
|
|row| Ok((row.get(0)?, row.get(1)?)),
|
||||||
from_read(v.as_ref()).map_err(|e| StashError::DecodeDecode(e.to_string()))?;
|
)
|
||||||
|
.map_err(|e| StashError::DecodeGet(e.to_string()))?;
|
||||||
out.write_all(&entry.contents)
|
out.write_all(&contents)
|
||||||
.map_err(|e| StashError::DecodeWrite(e.to_string()))?;
|
.map_err(|e| StashError::DecodeWrite(e.to_string()))?;
|
||||||
info!("Decoded entry with id {id}");
|
info!("Decoded entry with id {id}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_query(&self, query: &str) -> Result<usize, StashError> {
|
fn delete_query(&self, query: &str) -> Result<usize, StashError> {
|
||||||
let mut deleted = 0;
|
let mut stmt = self
|
||||||
for (k, v) in self.db.iter().filter_map(Result::ok) {
|
.conn
|
||||||
let entry: Entry = match from_read(v.as_ref()) {
|
.prepare("SELECT id, contents FROM clipboard")
|
||||||
Ok(e) => e,
|
|
||||||
Err(_) => continue,
|
|
||||||
};
|
|
||||||
if entry
|
|
||||||
.contents
|
|
||||||
.windows(query.len())
|
|
||||||
.any(|w| w == query.as_bytes())
|
|
||||||
{
|
|
||||||
self.db
|
|
||||||
.remove(k)
|
|
||||||
.map(|_| {
|
|
||||||
deleted += 1;
|
|
||||||
})
|
|
||||||
.map_err(|e| StashError::QueryDelete(e.to_string()))?;
|
.map_err(|e| StashError::QueryDelete(e.to_string()))?;
|
||||||
|
let mut rows = stmt
|
||||||
|
.query([])
|
||||||
|
.map_err(|e| StashError::QueryDelete(e.to_string()))?;
|
||||||
|
let mut deleted = 0;
|
||||||
|
while let Some(row) = rows
|
||||||
|
.next()
|
||||||
|
.map_err(|e| StashError::QueryDelete(e.to_string()))?
|
||||||
|
{
|
||||||
|
let id: u64 = row
|
||||||
|
.get(0)
|
||||||
|
.map_err(|e| StashError::QueryDelete(e.to_string()))?;
|
||||||
|
let contents: Vec<u8> = row
|
||||||
|
.get(1)
|
||||||
|
.map_err(|e| StashError::QueryDelete(e.to_string()))?;
|
||||||
|
if contents.windows(query.len()).any(|w| w == query.as_bytes()) {
|
||||||
|
self.conn
|
||||||
|
.execute("DELETE FROM clipboard WHERE id = ?1", params![id])
|
||||||
|
.map_err(|e| StashError::QueryDelete(e.to_string()))?;
|
||||||
|
deleted += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(deleted)
|
Ok(deleted)
|
||||||
|
|
@ -268,25 +297,24 @@ impl ClipboardDb for SledClipboardDb {
|
||||||
let mut deleted = 0;
|
let mut deleted = 0;
|
||||||
for line in reader.lines().map_while(Result::ok) {
|
for line in reader.lines().map_while(Result::ok) {
|
||||||
if let Ok(id) = extract_id(&line) {
|
if let Ok(id) = extract_id(&line) {
|
||||||
self.db
|
self.conn
|
||||||
.remove(u64_to_ivec(id))
|
.execute("DELETE FROM clipboard WHERE id = ?1", params![id])
|
||||||
.map(|_| {
|
|
||||||
deleted += 1;
|
|
||||||
})
|
|
||||||
.map_err(|e| StashError::DeleteEntry(id, e.to_string()))?;
|
.map_err(|e| StashError::DeleteEntry(id, e.to_string()))?;
|
||||||
|
deleted += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(deleted)
|
Ok(deleted)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_sequence(&self) -> u64 {
|
fn next_sequence(&self) -> u64 {
|
||||||
let last = self
|
match self
|
||||||
.db
|
.conn
|
||||||
.iter()
|
.query_row("SELECT MAX(id) FROM clipboard", [], |row| {
|
||||||
.next_back()
|
row.get::<_, Option<u64>>(0)
|
||||||
.and_then(std::result::Result::ok)
|
}) {
|
||||||
.map(|(k, _)| ivec_to_u64(&k));
|
Ok(Some(max_id)) => max_id + 1,
|
||||||
last.unwrap_or(0) + 1
|
Ok(None) | Err(_) => 1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,20 +324,6 @@ pub fn extract_id(input: &str) -> Result<u64, &'static str> {
|
||||||
id_str.parse().map_err(|_| "invalid id")
|
id_str.parse().map_err(|_| "invalid id")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn u64_to_ivec(v: u64) -> IVec {
|
|
||||||
IVec::from(&v.to_be_bytes()[..])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ivec_to_u64(v: &IVec) -> u64 {
|
|
||||||
let arr: [u8; 8] = if let Ok(arr) = v.as_ref().try_into() {
|
|
||||||
arr
|
|
||||||
} else {
|
|
||||||
error!("Failed to convert IVec to u64: invalid length");
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
u64::from_be_bytes(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn detect_mime(data: &[u8]) -> Option<String> {
|
pub fn detect_mime(data: &[u8]) -> Option<String> {
|
||||||
if image::guess_format(data).is_ok() {
|
if image::guess_format(data).is_ok() {
|
||||||
match image::guess_format(data) {
|
match image::guess_format(data) {
|
||||||
|
|
@ -347,7 +361,13 @@ pub fn preview_entry(data: &[u8], mime: Option<&str>, width: u32) -> String {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if mime == "application/json" || mime.starts_with("text/") {
|
} else if mime == "application/json" || mime.starts_with("text/") {
|
||||||
let s = str::from_utf8(data).unwrap_or("");
|
let s = match str::from_utf8(data) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to decode UTF-8 clipboard data: {e}");
|
||||||
|
""
|
||||||
|
}
|
||||||
|
};
|
||||||
let s = s.trim().replace(|c: char| c.is_whitespace(), " ");
|
let s = s.trim().replace(|c: char| c.is_whitespace(), " ");
|
||||||
return truncate(&s, width as usize, "…");
|
return truncate(&s, width as usize, "…");
|
||||||
}
|
}
|
||||||
|
|
@ -366,7 +386,12 @@ pub fn truncate(s: &str, max: usize, ellip: &str) -> String {
|
||||||
|
|
||||||
pub fn size_str(size: usize) -> String {
|
pub fn size_str(size: usize) -> String {
|
||||||
let units = ["B", "KiB", "MiB"];
|
let units = ["B", "KiB", "MiB"];
|
||||||
let mut fsize = f64::from(u32::try_from(size).unwrap_or(u32::MAX));
|
let mut fsize = if let Ok(val) = u32::try_from(size) {
|
||||||
|
f64::from(val)
|
||||||
|
} else {
|
||||||
|
error!("Clipboard entry size too large for display: {size}");
|
||||||
|
f64::from(u32::MAX)
|
||||||
|
};
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while fsize >= 1024.0 && i < units.len() - 1 {
|
while fsize >= 1024.0 && i < units.len() - 1 {
|
||||||
fsize /= 1024.0;
|
fsize /= 1024.0;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::db::{Entry, SledClipboardDb, detect_mime, u64_to_ivec};
|
use crate::db::{Entry, SqliteClipboardDb, detect_mime};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use std::io::{self, BufRead};
|
use std::io::{self, BufRead};
|
||||||
|
|
||||||
|
|
@ -6,31 +6,27 @@ pub trait ImportCommand {
|
||||||
fn import_tsv(&self, input: impl io::Read);
|
fn import_tsv(&self, input: impl io::Read);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImportCommand for SledClipboardDb {
|
impl ImportCommand for SqliteClipboardDb {
|
||||||
fn import_tsv(&self, input: impl io::Read) {
|
fn import_tsv(&self, input: impl io::Read) {
|
||||||
let reader = io::BufReader::new(input);
|
let reader = io::BufReader::new(input);
|
||||||
let mut imported = 0;
|
let mut imported = 0;
|
||||||
for line in reader.lines().map_while(Result::ok) {
|
for line in reader.lines().map_while(Result::ok) {
|
||||||
let mut parts = line.splitn(2, '\t');
|
let mut parts = line.splitn(2, '\t');
|
||||||
if let (Some(id_str), Some(val)) = (parts.next(), parts.next()) {
|
if let (Some(id_str), Some(val)) = (parts.next(), parts.next()) {
|
||||||
if let Ok(id) = id_str.parse::<u64>() {
|
if let Ok(_id) = id_str.parse::<u64>() {
|
||||||
let entry = Entry {
|
let entry = Entry {
|
||||||
contents: val.as_bytes().to_vec(),
|
contents: val.as_bytes().to_vec(),
|
||||||
mime: detect_mime(val.as_bytes()),
|
mime: detect_mime(val.as_bytes()),
|
||||||
};
|
};
|
||||||
let enc = match rmp_serde::encode::to_vec(&entry) {
|
match self.conn.execute(
|
||||||
Ok(enc) => enc,
|
"INSERT INTO clipboard (contents, mime) VALUES (?1, ?2)",
|
||||||
Err(e) => {
|
rusqlite::params![entry.contents, entry.mime],
|
||||||
error!("Failed to encode entry for id {id}: {e}");
|
) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match self.db.insert(u64_to_ivec(id), enc) {
|
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
imported += 1;
|
imported += 1;
|
||||||
info!("Imported entry with id {id}");
|
info!("Imported entry from TSV");
|
||||||
}
|
}
|
||||||
Err(e) => error!("Failed to insert entry with id {id}: {e}"),
|
Err(e) => error!("Failed to insert entry: {e}"),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
error!("Failed to parse id from line: {id_str}");
|
error!("Failed to parse id from line: {id_str}");
|
||||||
|
|
@ -39,6 +35,6 @@ impl ImportCommand for SledClipboardDb {
|
||||||
error!("Malformed TSV line: {line:?}");
|
error!("Malformed TSV line: {line:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!("Imported {imported} records from TSV into sled database.");
|
info!("Imported {imported} records from TSV into SQLite database.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
src/main.rs
14
src/main.rs
|
|
@ -95,7 +95,7 @@ fn report_error<T>(result: Result<T, impl std::fmt::Display>, context: &str) ->
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Watch clipboard and store changes
|
/// Watch clipboard and store changes
|
||||||
fn run_daemon(db: &db::SledClipboardDb, max_dedupe_search: u64, max_items: u64) {
|
fn run_daemon(db: &db::SqliteClipboardDb, max_dedupe_search: u64, max_items: u64) {
|
||||||
log::info!("Starting clipboard watch daemon (Wayland)");
|
log::info!("Starting clipboard watch daemon (Wayland)");
|
||||||
|
|
||||||
let mut last_contents: Option<Vec<u8>> = None;
|
let mut last_contents: Option<Vec<u8>> = None;
|
||||||
|
|
@ -149,12 +149,18 @@ fn main() {
|
||||||
.join("db")
|
.join("db")
|
||||||
});
|
});
|
||||||
|
|
||||||
let sled_db = sled::open(&db_path).unwrap_or_else(|e| {
|
let conn = rusqlite::Connection::open(&db_path).unwrap_or_else(|e| {
|
||||||
log::error!("Failed to open database: {e}");
|
log::error!("Failed to open SQLite database: {e}");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
let db = db::SledClipboardDb { db: sled_db };
|
let db = match db::SqliteClipboardDb::new(conn) {
|
||||||
|
Ok(db) => db,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to initialize SQLite database: {e}");
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Some(Command::Store) => {
|
Some(Command::Store) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue