Files
gree-controller/src/db/core_devices.rs
T
2026-09-14 16:32:28 +02:00

104 lines
3.8 KiB
Rust

impl Db {
pub fn open(path: &Path) -> Result<Self> {
let conn = Connection::open(path)
.with_context(|| format!("cannot open SQLite database {}", path.display()))?;
conn.busy_timeout(std::time::Duration::from_secs(5))?;
conn.execute_batch(queries::INIT_SCHEMA)?;
Ok(Self {
conn: Arc::new(Mutex::new(conn)),
})
}
fn lock(&self) -> Result<std::sync::MutexGuard<'_, Connection>> {
self.conn
.lock()
.map_err(|_| anyhow::anyhow!("database mutex poisoned"))
}
fn from_json<T: DeserializeOwned>(payload: String) -> Result<T> {
Ok(serde_json::from_str(&payload)?)
}
fn to_json<T: Serialize>(value: &T) -> Result<String> {
Ok(serde_json::to_string(value)?)
}
/// Device protocol keys are intentionally omitted by the public `Device` serializer.
/// Persist them only in the private SQLite payload so normal API responses never expose them.
fn device_to_storage_json(device: &Device) -> Result<String> {
let mut value = serde_json::to_value(device)?;
if let Some(object) = value.as_object_mut() {
if let Some(key) = device.key.as_ref().filter(|key| !key.is_empty()) {
object.insert("key".into(), serde_json::Value::String(key.clone()));
}
}
Ok(serde_json::to_string(&value)?)
}
pub fn count_devices(&self) -> Result<u64> {
let conn = self.lock()?;
let count: i64 = conn.query_row(queries::COUNT_DEVICES, [], |row| row.get(0))?;
Ok(count.max(0) as u64)
}
pub fn save_device(&self, device: &Device) -> Result<()> {
let payload = Self::device_to_storage_json(device)?;
let index_mac = match device.connection_type {
ConnectionType::Local => device.mac.clone(),
ConnectionType::GreeCloud => format!("cloud:{}", device.cloud_device_id.as_deref().unwrap_or(&device.mac)),
};
let conn = self.lock()?;
conn.execute(
queries::UPSERT_DEVICE,
params![
device.id,
index_mac,
device.name,
device.ip,
device.simulated as i64,
payload,
device.updated_at.to_rfc3339()
],
)?;
Ok(())
}
pub fn list_devices(&self) -> Result<Vec<Device>> {
let conn = self.lock()?;
let mut stmt = conn.prepare(queries::LIST_DEVICES)?;
let payloads = stmt
.query_map([], |row| row.get::<_, String>(0))?
.collect::<std::result::Result<Vec<_>, _>>()?;
payloads.into_iter().map(Self::from_json).collect()
}
pub fn get_device(&self, id: &str) -> Result<Option<Device>> {
let conn = self.lock()?;
let payload: Option<String> = conn
.query_row(queries::GET_DEVICE_BY_ID, [id], |row| row.get(0))
.optional()?;
payload.map(Self::from_json).transpose()
}
pub fn get_device_by_mac(&self, mac: &str) -> Result<Option<Device>> {
let conn = self.lock()?;
let payload: Option<String> = conn
.query_row(queries::GET_DEVICE_BY_MAC, [mac], |row| row.get(0))
.optional()?;
payload.map(Self::from_json).transpose()
}
pub fn delete_device(&self, id: &str) -> Result<bool> {
let mut conn = self.lock()?;
let tx = conn.transaction()?;
tx.execute(queries::DELETE_DEVICE_READINGS, [id])?;
tx.execute(queries::DELETE_DEVICE_ENERGY_READINGS, [id])?;
tx.execute(queries::DELETE_ZONE_READINGS_BY_DEVICE_ID, [id])?;
tx.execute(queries::DELETE_SCHEDULES_BY_DEVICE_ID, [id])?;
tx.execute(queries::DELETE_ZONES_BY_DEVICE_ID, [id])?;
let changed = tx.execute(queries::DELETE_DEVICE, [id])? > 0;
tx.commit()?;
Ok(changed)
}
}