use super::{
dto::CURRENT_SCHEMA_VERSION,
format::{payload_version_of, payload_with_name, plan_summary},
naming::{current_unix_time, format_unix_date},
};
use indicatrix_vault::{db::sqlite::Database, model::saved_rough_plan::SavedRoughPlan};
use std::sync::{Mutex, PoisonError};
use tracing::warn;
const UNREADABLE_SUMMARY: &str = "Saved plan";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListedPlan {
pub id: i64,
pub name: String,
pub summary: String,
pub date: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StoredPlan {
pub name: String,
pub created_at: i64,
pub payload_version: u32,
pub payload: String,
}
fn with_db<T>(db: &Mutex<Database>, f: impl FnOnce(&Database) -> T) -> T {
f(&db.lock().unwrap_or_else(PoisonError::into_inner))
}
fn gone_message(id: i64) -> String {
format!("Saved rough plan #{id} no longer exists.")
}
type ReadPlan<'a> = dyn FnMut(i64) -> Result<Option<SavedRoughPlan>, String> + 'a;
fn fill_summary(db: &Mutex<Database>, read_plan: &mut ReadPlan<'_>, id: i64) -> Option<String> {
match read_plan(id) {
Ok(Some(plan)) => {
let summary = plan_summary(&plan.payload);
if let Err(e) = with_db(db, |d| d.set_saved_rough_plan_summary(id, &summary)) {
warn!("Rough planner: could not store the summary of saved plan {id}: {e}");
}
Some(summary)
}
Ok(None) => None,
Err(e) => {
warn!("Rough planner: could not read saved plan {id}: {e}");
Some(UNREADABLE_SUMMARY.to_string())
}
}
}
pub(super) fn list_saved_with(
db: &Mutex<Database>,
read_plan: &mut ReadPlan<'_>,
) -> Result<Vec<ListedPlan>, String> {
let metas = with_db(db, Database::list_saved_rough_plans)
.map_err(|e| format!("Could not list the saved rough plans: {e}"))?;
let mut rows = Vec::with_capacity(metas.len());
for meta in metas {
let stored = meta.summary.filter(|summary| !summary.trim().is_empty());
let Some(summary) = stored.or_else(|| fill_summary(db, read_plan, meta.plan_id)) else {
continue;
};
rows.push(ListedPlan {
id: meta.plan_id,
name: meta.name,
summary,
date: format_unix_date(meta.created_at),
});
}
Ok(rows)
}
pub fn list_saved(db: &Mutex<Database>) -> Result<Vec<ListedPlan>, String> {
list_saved_with(db, &mut |id| {
with_db(db, |d| d.get_saved_rough_plan(id)).map_err(|e| e.to_string())
})
}
pub fn save_plan(db: &Mutex<Database>, name: &str, payload: &str) -> Result<i64, String> {
let now = current_unix_time();
let version = payload_version_of(payload).unwrap_or(CURRENT_SCHEMA_VERSION);
let summary = plan_summary(payload);
with_db(db, |d| {
d.save_rough_plan(name, version, payload, &summary, now)
})
.map_err(|e| format!("Could not save the rough plan: {e}"))
}
pub fn load_saved(db: &Mutex<Database>, id: i64) -> Result<StoredPlan, String> {
let plan = with_db(db, |d| d.get_saved_rough_plan(id))
.map_err(|e| format!("Could not load saved rough plan #{id}: {e}"))?
.ok_or_else(|| gone_message(id))?;
Ok(StoredPlan {
name: plan.meta.name,
created_at: plan.meta.created_at,
payload_version: plan.payload_version,
payload: plan.payload,
})
}
pub fn rename_saved(db: &Mutex<Database>, id: i64, new_name: &str) -> Result<(), String> {
let now = current_unix_time();
let changed = with_db(db, |d| {
let payload = d
.get_saved_rough_plan(id)?
.map(|plan| payload_with_name(&plan.payload, new_name));
d.rename_saved_rough_plan(id, new_name, payload.as_deref(), now)
})
.map_err(|e| format!("Could not rename rough plan #{id}: {e}"))?;
if changed == 0 {
Err(gone_message(id))
} else {
Ok(())
}
}
pub fn delete_saved(db: &Mutex<Database>, id: i64) -> Result<(), String> {
let deleted = with_db(db, |d| d.delete_saved_rough_plan(id))
.map_err(|e| format!("Could not delete rough plan #{id}: {e}"))?;
if deleted == 0 {
Err(gone_message(id))
} else {
Ok(())
}
}
#[must_use]
pub fn library_stamp(db: &Mutex<Database>) -> Option<u32> {
with_db(db, Database::library_stamp)
.inspect_err(|e| warn!("Rough planner: could not read the library stamp: {e}"))
.ok()
}