138 lines
4.6 KiB
Rust
138 lines
4.6 KiB
Rust
impl Db {
|
|
pub fn save_zone(&self, zone: &Zone) -> Result<()> {
|
|
let payload = Self::to_json(zone)?;
|
|
let conn = self.lock()?;
|
|
conn.execute(
|
|
queries::UPSERT_ZONE,
|
|
params![zone.id, payload, zone.updated_at.to_rfc3339()],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn list_zones(&self) -> Result<Vec<Zone>> {
|
|
self.list_payloads(queries::LIST_ZONES)
|
|
}
|
|
|
|
pub fn get_zone(&self, id: &str) -> Result<Option<Zone>> {
|
|
self.get_payload(queries::GET_ZONE, id)
|
|
}
|
|
|
|
pub fn delete_zone(&self, id: &str) -> Result<bool> {
|
|
let mut conn = self.lock()?;
|
|
let tx = conn.transaction()?;
|
|
tx.execute(queries::DELETE_SCHEDULES_BY_ZONE_ID, [id])?;
|
|
tx.execute(queries::DELETE_ZONE_READINGS_BY_ZONE_ID, [id])?;
|
|
let changed = tx.execute(queries::DELETE_ZONE, [id])? > 0;
|
|
tx.commit()?;
|
|
Ok(changed)
|
|
}
|
|
|
|
pub fn save_group(&self, group: &ClimateGroup) -> Result<()> {
|
|
let payload = Self::to_json(group)?;
|
|
let conn = self.lock()?;
|
|
conn.execute(
|
|
queries::UPSERT_GROUP,
|
|
params![group.id, payload, group.updated_at.to_rfc3339()],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn list_groups(&self) -> Result<Vec<ClimateGroup>> {
|
|
self.list_payloads(queries::LIST_GROUPS)
|
|
}
|
|
|
|
pub fn get_group(&self, id: &str) -> Result<Option<ClimateGroup>> {
|
|
self.get_payload(queries::GET_GROUP, id)
|
|
}
|
|
|
|
pub fn delete_group(&self, id: &str) -> Result<bool> {
|
|
self.delete_by_id("groups", id)
|
|
}
|
|
|
|
pub fn save_device_group(&self, group: &DeviceGroup) -> Result<()> {
|
|
let payload = Self::to_json(group)?;
|
|
let conn = self.lock()?;
|
|
conn.execute(
|
|
queries::UPSERT_DEVICE_GROUP,
|
|
params![group.id, payload, group.updated_at.to_rfc3339()],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn list_device_groups(&self) -> Result<Vec<DeviceGroup>> {
|
|
self.list_payloads(queries::LIST_DEVICE_GROUPS)
|
|
}
|
|
|
|
pub fn get_device_group(&self, id: &str) -> Result<Option<DeviceGroup>> {
|
|
self.get_payload(queries::GET_DEVICE_GROUP, id)
|
|
}
|
|
|
|
pub fn device_group_for_device(&self, device_id: &str) -> Result<Option<DeviceGroup>> {
|
|
Ok(self
|
|
.list_device_groups()?
|
|
.into_iter()
|
|
.find(|group| group.device_ids.iter().any(|id| id == device_id)))
|
|
}
|
|
|
|
pub fn delete_device_group(&self, id: &str) -> Result<bool> {
|
|
self.delete_by_id("device_groups", id)
|
|
}
|
|
}
|
|
|
|
impl Db {
|
|
/// Merge device transition timestamps into zone JSON without replacing newer zone state.
|
|
/// `updated_at` acts as a compare-and-swap token but is intentionally not changed here:
|
|
/// thermostat cycles use it to detect real configuration/ownership updates.
|
|
pub fn merge_zone_device_transition_timestamps(
|
|
&self,
|
|
device_id: &str,
|
|
power_changed: bool,
|
|
mode_changed: bool,
|
|
at: DateTime<Utc>,
|
|
) -> Result<Vec<Zone>> {
|
|
if !power_changed && !mode_changed {
|
|
return Ok(Vec::new());
|
|
}
|
|
let zone_ids: Vec<String> = self
|
|
.list_zones()?
|
|
.into_iter()
|
|
.filter(|zone| zone.device_id == device_id)
|
|
.map(|zone| zone.id)
|
|
.collect();
|
|
let mut updated = Vec::with_capacity(zone_ids.len());
|
|
for zone_id in zone_ids {
|
|
let mut saved = false;
|
|
for _ in 0..8 {
|
|
let Some(mut zone) = self.get_zone(&zone_id)? else {
|
|
break;
|
|
};
|
|
let expected_updated_at = zone.updated_at.to_rfc3339();
|
|
if power_changed {
|
|
zone.last_power_change_at = Some(at);
|
|
}
|
|
if mode_changed {
|
|
zone.last_mode_change_at = Some(at);
|
|
}
|
|
let payload = Self::to_json(&zone)?;
|
|
let conn = self.lock()?;
|
|
let changed = conn.execute(
|
|
"UPDATE zones SET payload = ?1 WHERE id = ?2 AND updated_at = ?3",
|
|
params![payload, zone.id, expected_updated_at],
|
|
)?;
|
|
drop(conn);
|
|
if changed > 0 {
|
|
updated.push(zone);
|
|
saved = true;
|
|
break;
|
|
}
|
|
}
|
|
if !saved && self.get_zone(&zone_id)?.is_some() {
|
|
return Err(anyhow::anyhow!(
|
|
"zone {zone_id} kept changing while device transition timestamps were merged"
|
|
));
|
|
}
|
|
}
|
|
Ok(updated)
|
|
}
|
|
}
|