feat: gsmol-compatible backup export and import behind the store feature
This commit is contained in:
parent
96d2412370
commit
4a780ed648
6 changed files with 528 additions and 17 deletions
|
|
@ -283,6 +283,21 @@ impl Store {
|
|||
.and_then(|k| k.try_into().ok()))
|
||||
}
|
||||
|
||||
/// Every pin, host and key, ordered by host — the export's server map.
|
||||
pub fn pins(&self) -> Result<Vec<(String, [u8; KEY_LEN])>, Error> {
|
||||
let db = self.db();
|
||||
let mut stmt = db.prepare("SELECT host, static FROM servers ORDER BY host")?;
|
||||
let rows = stmt
|
||||
.query_map([], |row| {
|
||||
Ok((row.get::<_, String>(0)?, row.get::<_, Vec<u8>>(1)?))
|
||||
})?
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.filter_map(|(host, key)| Some((host, key.try_into().ok()?)))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn pin_server(&self, host: &str, key: &[u8; KEY_LEN]) -> Result<(), Error> {
|
||||
self.db()
|
||||
.execute(
|
||||
|
|
@ -541,6 +556,21 @@ impl Store {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Whether a message id is already stored in `folder` ("inbox" or "sent"),
|
||||
/// so an import can skip rather than re-store.
|
||||
pub fn stored(&self, folder: &str, id: &[u8; ID_LEN]) -> Result<bool, Error> {
|
||||
let table = match folder {
|
||||
"sent" => "sent",
|
||||
_ => "inbox",
|
||||
};
|
||||
Ok(self
|
||||
.one::<i64>(
|
||||
&format!("SELECT 1 FROM {table} WHERE id = ?1"),
|
||||
&[id],
|
||||
)?
|
||||
.is_some())
|
||||
}
|
||||
|
||||
/// One folder, ordered by arrival or sending time. `folder` is "inbox",
|
||||
/// "requests", "sent" or "all".
|
||||
pub fn mail(&self, folder: &str) -> Result<Vec<Stored>, Error> {
|
||||
|
|
@ -597,13 +627,16 @@ mod tests {
|
|||
core::array::from_fn(|i| i as u8)
|
||||
}
|
||||
|
||||
/// A fresh scratch path per call: a monotonic counter, so two tests or
|
||||
/// two runs cannot collide the way pid-and-second can.
|
||||
fn scratch_path(tag: &str) -> std::path::PathBuf {
|
||||
static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||
let n = NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
std::env::temp_dir().join(format!("fumi-store-{tag}-{n}-{}.db", std::process::id()))
|
||||
}
|
||||
|
||||
fn temp_store(tag: &str) -> Store {
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
"fumi-store-{tag}-{}-{}.db",
|
||||
std::process::id(),
|
||||
now()
|
||||
));
|
||||
Store::open(&path).expect("open store")
|
||||
Store::open(&scratch_path(tag)).expect("open store")
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -761,11 +794,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn schema_is_versioned_and_refuses_a_newer_store() {
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
"fumi-store-schema-{}-{}.db",
|
||||
std::process::id(),
|
||||
now()
|
||||
));
|
||||
let path = scratch_path("schema");
|
||||
drop(Store::open(&path).expect("open store"));
|
||||
let db = Connection::open(&path).unwrap();
|
||||
let version: i32 = db.query_row("PRAGMA user_version", [], |row| row.get(0)).unwrap();
|
||||
|
|
@ -783,11 +812,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn v1_store_migrates_at_open() {
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
"fumi-store-v1-{}-{}.db",
|
||||
std::process::id(),
|
||||
now()
|
||||
));
|
||||
let path = scratch_path("v1");
|
||||
// A schema-version-1 store: the inbox has no `kept` column and the
|
||||
// history table does not exist yet.
|
||||
let db = Connection::open(&path).unwrap();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue