2023-09-19 08:52:12 +00:00
|
|
|
use std::path::Path;
|
|
|
|
|
|
|
|
use anyhow::Result;
|
2023-09-19 09:18:22 +00:00
|
|
|
use rusqlite::{Connection, ToSql};
|
2023-09-19 08:52:12 +00:00
|
|
|
|
2023-09-21 08:08:23 +00:00
|
|
|
use crate::devices::{Devices, DevicesWithName};
|
|
|
|
|
2023-09-19 08:52:12 +00:00
|
|
|
pub struct DataBase {
|
|
|
|
sql: Connection,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DataBase {
|
2023-09-21 08:08:23 +00:00
|
|
|
const VERSION_0_1_0: &'static str = "0.1.0";
|
|
|
|
|
2023-09-19 08:52:12 +00:00
|
|
|
pub async fn new(path: impl AsRef<Path>) -> Result<Self> {
|
|
|
|
let me = Self {
|
|
|
|
sql: Connection::open(path)?,
|
|
|
|
};
|
|
|
|
|
|
|
|
me.generate_tables()?;
|
2023-09-21 08:08:23 +00:00
|
|
|
me.init()?;
|
2023-09-19 08:52:12 +00:00
|
|
|
|
|
|
|
Ok(me)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn generate_tables(&self) -> Result<()> {
|
2023-09-19 09:18:22 +00:00
|
|
|
self.sql.execute(
|
2023-09-21 08:08:23 +00:00
|
|
|
"CREATE TABLE IF NOT EXISTS meta (
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
version INTEGER NOT NULL
|
|
|
|
)",
|
|
|
|
[],
|
|
|
|
)?;
|
|
|
|
|
|
|
|
self.sql.execute(
|
|
|
|
"CREATE TABLE IF NOT EXISTS devices(
|
2023-09-19 09:18:22 +00:00
|
|
|
id INTEGER PRIMARY KEY,
|
2023-09-21 08:08:23 +00:00
|
|
|
device VARCHAR(60) NOT NULL,
|
|
|
|
type VARCHAR(30) NOT NULL,
|
|
|
|
name VARCHAR(80)
|
2023-09-19 09:18:22 +00:00
|
|
|
)",
|
|
|
|
[],
|
|
|
|
)?;
|
|
|
|
|
2023-09-21 08:08:23 +00:00
|
|
|
self.sql.execute(
|
|
|
|
"CREATE TABLE IF NOT EXISTS data (
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
time BIGINT NOT NULL,
|
|
|
|
watts REAL NOT NULL,
|
|
|
|
device_id INTEGER NOT NULL,
|
|
|
|
FOREIGN KEY(device_id) REFERENCES devices(id)
|
|
|
|
)",
|
|
|
|
[],
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn version(&self) -> Result<String> {
|
|
|
|
Ok(self
|
|
|
|
.sql
|
|
|
|
.query_row("SELECT version FROM meta WHERE id=1", [], |row| row.get(0))?)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn init(&self) -> Result<()> {
|
|
|
|
if self.version().is_err() {
|
|
|
|
self.sql.execute(
|
|
|
|
"INSERT INTO meta (version)
|
|
|
|
VALUES (?1)",
|
|
|
|
&[Self::VERSION_0_1_0],
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn register_devices(&self, devices: &Devices) -> Result<()> {
|
|
|
|
for device in devices.plugs.iter() {
|
|
|
|
self.sql.execute(
|
2023-09-21 08:46:23 +00:00
|
|
|
&format!(
|
|
|
|
"INSERT INTO devices (device, type)
|
|
|
|
SELECT \"{device}\", \"plug\"
|
2023-09-21 08:08:23 +00:00
|
|
|
WHERE
|
|
|
|
NOT EXISTS (
|
|
|
|
SELECT device
|
|
|
|
FROM devices
|
2023-09-21 08:46:23 +00:00
|
|
|
WHERE device=\"{device}\"
|
2023-09-21 08:08:23 +00:00
|
|
|
)
|
2023-09-21 08:46:23 +00:00
|
|
|
"
|
|
|
|
),
|
|
|
|
[],
|
2023-09-21 08:08:23 +00:00
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
2023-09-19 09:18:22 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn write(&self, device_name: &str, time: u64, watts: f32) -> Result<()> {
|
2023-09-21 08:46:23 +00:00
|
|
|
let params: &[&dyn ToSql] = &[&time, &watts];
|
2023-09-19 09:18:22 +00:00
|
|
|
|
|
|
|
self.sql.execute(
|
2023-09-21 08:08:23 +00:00
|
|
|
&format!(
|
|
|
|
"INSERT INTO data (time, watts, device_id)
|
2023-09-21 08:46:23 +00:00
|
|
|
VALUES (?1, ?2, (SELECT id FROM devices WHERE device=\"{device_name}\") )"
|
2023-09-21 08:08:23 +00:00
|
|
|
),
|
2023-09-19 09:18:22 +00:00
|
|
|
params,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-09-20 10:19:41 +00:00
|
|
|
|
2023-09-21 08:08:23 +00:00
|
|
|
pub fn devices(&self) -> Result<DevicesWithName> {
|
|
|
|
let mut devices = DevicesWithName::default();
|
|
|
|
|
|
|
|
for row in self
|
|
|
|
.sql
|
|
|
|
.prepare(&format!(
|
|
|
|
"
|
|
|
|
SELECT device, type, name
|
|
|
|
FROM devices
|
|
|
|
"
|
|
|
|
))?
|
|
|
|
.query_map([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))?
|
|
|
|
{
|
2023-09-21 08:46:23 +00:00
|
|
|
let (device, dev_type, name): (String, String, Option<String>) = row?;
|
2023-09-21 08:08:23 +00:00
|
|
|
|
|
|
|
match dev_type.as_str() {
|
|
|
|
"plug" => devices.plugs.push((device, name)),
|
|
|
|
|
|
|
|
_ => panic!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(devices)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn change_device_name(&self, device: &str, description: &str) -> Result<()> {
|
|
|
|
self.sql.execute(
|
|
|
|
&format!(
|
|
|
|
"
|
|
|
|
UPDATE devices
|
2023-09-21 08:46:23 +00:00
|
|
|
SET name=\"{description}\"
|
|
|
|
WHERE device=\"{device}\"
|
2023-09-21 08:08:23 +00:00
|
|
|
"
|
|
|
|
),
|
2023-09-21 08:46:23 +00:00
|
|
|
[],
|
2023-09-21 08:08:23 +00:00
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-09-20 10:19:41 +00:00
|
|
|
pub fn read(&self, device: &str) -> Result<Vec<(u64, f32)>> {
|
|
|
|
self.sql
|
|
|
|
.prepare(&format!(
|
|
|
|
"
|
|
|
|
SELECT time, watts
|
2023-09-20 13:35:35 +00:00
|
|
|
FROM data
|
|
|
|
WHERE device=\"{device}\"
|
2023-09-20 10:19:41 +00:00
|
|
|
"
|
|
|
|
))?
|
|
|
|
.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))?
|
|
|
|
.map(|row| {
|
|
|
|
let (time, watts) = row?;
|
|
|
|
|
|
|
|
Ok((time, watts))
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
2023-09-19 09:18:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use std::fs;
|
|
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
|
2023-09-21 08:08:23 +00:00
|
|
|
use crate::devices::Devices;
|
|
|
|
|
2023-09-19 09:18:22 +00:00
|
|
|
use super::DataBase;
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_connection() -> Result<()> {
|
2023-09-21 08:08:23 +00:00
|
|
|
let db = DataBase::new("connection_test.db").await?;
|
|
|
|
assert_eq!(DataBase::VERSION_0_1_0, db.version()?);
|
2023-09-19 09:18:22 +00:00
|
|
|
|
|
|
|
fs::remove_file("connection_test.db")?;
|
|
|
|
|
|
|
|
Ok(())
|
2023-09-19 08:52:12 +00:00
|
|
|
}
|
|
|
|
|
2023-09-21 08:08:23 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_startup() -> Result<()> {
|
|
|
|
let db = DataBase::new("startup_test.db").await?;
|
|
|
|
|
|
|
|
db.register_devices(&Devices {
|
|
|
|
plugs: vec!["test".to_string()],
|
|
|
|
})?;
|
|
|
|
|
|
|
|
fs::remove_file("startup_test.db")?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-09-19 09:18:22 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_write() -> Result<()> {
|
|
|
|
let db = DataBase::new("write_test.db").await?;
|
|
|
|
|
2023-09-21 08:08:23 +00:00
|
|
|
let device_name = "test";
|
|
|
|
|
|
|
|
db.register_devices(&Devices {
|
|
|
|
plugs: vec![device_name.to_string()],
|
|
|
|
})?;
|
|
|
|
|
|
|
|
db.write(device_name, 0, 5.5)?;
|
2023-09-21 08:46:23 +00:00
|
|
|
|
|
|
|
let device_descriptor = "udo";
|
|
|
|
db.change_device_name(device_name, device_descriptor)?;
|
|
|
|
|
|
|
|
let devices = db.devices()?;
|
|
|
|
|
|
|
|
assert_eq!(devices.plugs[0].1.as_ref().unwrap(), device_descriptor);
|
|
|
|
assert_eq!(devices.plugs[0].0, device_name);
|
2023-09-19 09:18:22 +00:00
|
|
|
|
|
|
|
fs::remove_file("write_test.db")?;
|
|
|
|
|
|
|
|
Ok(())
|
2023-09-19 08:52:12 +00:00
|
|
|
}
|
|
|
|
}
|