104 lines
3.8 KiB
Rust
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)
|
|
}
|
|
}
|