whiskers.git / crates / whiskers-store / src / household.rs

The grown-ups' choices and the day's time, in household and time_spent.

3use ::log::{debug, error};
4use rusqlite::params;
5use whiskers_core::{Household, HouseholdRows, HouseholdStore, HouseholdStoreError, Setting, SettingRow, TimeRow};
7use crate::{DbError, Store};

The household document of one device (or of the service). Each setting is a row with its own time and each device's time on each day is a row of its own, so two devices never write the same row.

11pub struct SqliteHousehold {
12    store: Store,
13}
15impl SqliteHousehold {
16    pub fn new(store: Store) -> Self {
17        Self { store }
18    }
19}
20
21fn read_rows(store: &Store) -> Result<HouseholdRows, DbError> {
22    let conn = store.lock();
23    let mut rows = HouseholdRows::default();
24    {
25        let mut st = conn.prepare("SELECT name, value, stamp FROM household ORDER BY name")?;
26        let mut q = st.query([])?;
27        while let Some(r) = q.next()? {
28            let name: String = r.get(0)?;
29            let setting = Setting::from_name(&name).ok_or_else(|| DbError::Damaged(format!("a setting called {name:?}")))?;
30            rows.settings.push(SettingRow { setting, value: r.get(1)?, stamp: r.get::<_, i64>(2)? as u64 });
31        }
32    }
33    // Only the latest day is the document's: older days are kept as history and are not read back.
34    let mut st = conn.prepare("SELECT device, day, used_ms, extra_minutes FROM time_spent WHERE day = (SELECT MAX(day) FROM time_spent) ORDER BY device")?;
35    let mut q = st.query([])?;
36    while let Some(r) = q.next()? {
37        rows.time.push(TimeRow { device: r.get(0)?, day: r.get::<_, i64>(1)? as u32, used_ms: r.get::<_, i64>(2)? as u64, extra_minutes: r.get::<_, i64>(3)? as u32 });
38    }
39    Ok(rows)
40}
41
42impl HouseholdStore for SqliteHousehold {
43    fn load(&self) -> Result<Household, HouseholdStoreError> {
44        let rows = read_rows(&self.store).map_err(|e| {
45            error!("the household cannot be read: {e}");
46            HouseholdStoreError(e.to_string())
47        })?;
48        debug!("household loaded: {} setting(s), {} device day(s)", rows.settings.len(), rows.time.len());
49        Household::from_rows(&rows).map_err(|e| {
50            error!("the household does not read back: {e}");
51            HouseholdStoreError(e)
52        })
53    }
54
55    fn save(&mut self, household: &Household) -> Result<(), HouseholdStoreError> {
56        let rows = household.to_rows();
57        self.store
58            .transaction(|tx| {
59                for s in &rows.settings {
60                    tx.execute(
61                        "INSERT INTO household (name, value, stamp) VALUES (?1, ?2, ?3)
62                         ON CONFLICT (name) DO UPDATE SET value = excluded.value, stamp = excluded.stamp",
63                        params![s.setting.name(), s.value, s.stamp as i64],
64                    )?;
65                }
66                // Counts only rise: a save from a copy that is behind cannot take time back.
67                for t in &rows.time {
68                    tx.execute(
69                        "INSERT INTO time_spent (device, day, used_ms, extra_minutes) VALUES (?1, ?2, ?3, ?4)
70                         ON CONFLICT (device, day) DO UPDATE SET used_ms = MAX(used_ms, excluded.used_ms),
71                                                                 extra_minutes = MAX(extra_minutes, excluded.extra_minutes)",
72                        params![t.device, t.day as i64, t.used_ms as i64, t.extra_minutes as i64],
73                    )?;
74                }
75                Ok(())
76            })
77            .map_err(|e| {
78                error!("the household was not kept: {e}");
79                HouseholdStoreError(e.to_string())
80            })
81    }
82}