Add support for reading programs from database
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Closes #8
This commit is contained in:
parent
e5a2e169a2
commit
0fe30fa7a9
@ -14,6 +14,9 @@ pub fn create_migrations() -> Migrations {
|
||||
let mut migs = Migrations::new();
|
||||
migs.add(include_file_migration!(1, "0001-table_sections"));
|
||||
migs.add(include_file_migration!(2, "0002-section_rows"));
|
||||
migs.add(include_file_migration!(3, "0003-table_programs"));
|
||||
migs.add(include_file_migration!(4, "0004-program_rows"));
|
||||
migs.add(include_file_migration!(5, "0005-view_program_sequence"));
|
||||
// INSERT MIGRATION ABOVE -- DO NOT EDIT THIS COMMENT
|
||||
migs
|
||||
}
|
||||
|
46
src/main.rs
46
src/main.rs
@ -18,10 +18,8 @@ mod section_runner;
|
||||
#[cfg(test)]
|
||||
mod trace_listeners;
|
||||
|
||||
use im::ordmap;
|
||||
use model::{Program, ProgramItem, ProgramRef, Section, Sections};
|
||||
use schedule::Schedule;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use model::{Program, Programs, Section, Sections};
|
||||
use std::sync::Arc;
|
||||
|
||||
fn setup_db() -> Result<DbConnection> {
|
||||
// let conn = DbConnection::open_in_memory()?;
|
||||
@ -47,6 +45,23 @@ fn query_sections(conn: &DbConnection) -> Result<Sections> {
|
||||
Ok(sections)
|
||||
}
|
||||
|
||||
fn query_programs(conn: &DbConnection) -> Result<Programs> {
|
||||
let mut statement = conn.prepare_cached(
|
||||
"
|
||||
SELECT p.id, p.name, ps.sequence, p.enabled, p.schedule
|
||||
FROM programs AS p
|
||||
INNER JOIN program_sequences AS ps
|
||||
ON ps.program_id = p.id;",
|
||||
)?;
|
||||
let rows = statement.query_map(NO_PARAMS, Program::from_sql)?;
|
||||
let mut programs = Programs::new();
|
||||
for row in rows {
|
||||
let program = row?;
|
||||
programs.insert(program.id, program.into());
|
||||
}
|
||||
Ok(programs)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
@ -69,28 +84,7 @@ async fn main() -> Result<()> {
|
||||
let mut section_runner = section_runner::SectionRunner::new(section_interface);
|
||||
let mut program_runner = program_runner::ProgramRunner::new(section_runner.clone());
|
||||
|
||||
let run_time = chrono::Local::now().time() + chrono::Duration::seconds(5);
|
||||
let schedule = Schedule::new(
|
||||
vec![run_time],
|
||||
schedule::every_day(),
|
||||
schedule::DateTimeBound::None,
|
||||
schedule::DateTimeBound::None,
|
||||
);
|
||||
let program: ProgramRef = Program {
|
||||
id: 1,
|
||||
name: "Test Program".into(),
|
||||
sequence: sections
|
||||
.values()
|
||||
.map(|sec| ProgramItem {
|
||||
section_id: sec.id,
|
||||
duration: Duration::from_secs(2),
|
||||
})
|
||||
.collect(),
|
||||
enabled: true,
|
||||
schedule,
|
||||
}
|
||||
.into();
|
||||
let programs = ordmap![1 => program];
|
||||
let programs = query_programs(&conn)?;
|
||||
|
||||
program_runner.update_sections(sections.clone()).await?;
|
||||
program_runner.update_programs(programs.clone()).await?;
|
||||
|
3
src/migrations/0003-table_programs-down.sql
Normal file
3
src/migrations/0003-table_programs-down.sql
Normal file
@ -0,0 +1,3 @@
|
||||
DROP INDEX program_sequence_items_idx1;
|
||||
DROP TABLE program_sequence_items;
|
||||
DROP TABLE programs;
|
20
src/migrations/0003-table_programs-up.sql
Normal file
20
src/migrations/0003-table_programs-up.sql
Normal file
@ -0,0 +1,20 @@
|
||||
CREATE TABLE programs
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
enabled BOOLEAN NOT NULL,
|
||||
schedule TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE program_sequence_items
|
||||
(
|
||||
seq_num INTEGER NOT NULL,
|
||||
program_id INTEGER NOT NULL,
|
||||
section_id INTEGER NOT NULL,
|
||||
duration REAL NOT NULL,
|
||||
FOREIGN KEY (program_id) REFERENCES programs (id),
|
||||
FOREIGN KEY (section_id) REFERENCES sections (id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX program_sequence_items_idx1
|
||||
ON program_sequence_items (program_id, seq_num);
|
2
src/migrations/0004-program_rows-down.sql
Normal file
2
src/migrations/0004-program_rows-down.sql
Normal file
@ -0,0 +1,2 @@
|
||||
DELETE FROM program_sequence_items;
|
||||
DELETE FROM programs;
|
14
src/migrations/0004-program_rows-up.sql
Normal file
14
src/migrations/0004-program_rows-up.sql
Normal file
@ -0,0 +1,14 @@
|
||||
INSERT INTO programs (id, name, enabled, schedule)
|
||||
VALUES (1, 'Test Program', TRUE,
|
||||
json_object(
|
||||
'times', json('[{"hour": 16, "minute": 1, "second": 0}]'),
|
||||
'weekdays', json_array(0, 1, 2, 3, 4, 5, 6),
|
||||
'from', NULL,
|
||||
'to', NULL));
|
||||
|
||||
INSERT INTO program_sequence_items (seq_num, program_id, section_id, duration)
|
||||
SELECT row_number() OVER (ORDER BY s.id) seq_num,
|
||||
(SELECT p.id FROM programs as p WHERE p.name = 'Test Program') program_id,
|
||||
s.id section_id,
|
||||
2.0 duration
|
||||
FROM sections AS s;
|
1
src/migrations/0005-view_program_sequence-down.sql
Normal file
1
src/migrations/0005-view_program_sequence-down.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP VIEW program_sequences;
|
6
src/migrations/0005-view_program_sequence-up.sql
Normal file
6
src/migrations/0005-view_program_sequence-up.sql
Normal file
@ -0,0 +1,6 @@
|
||||
CREATE VIEW program_sequences AS
|
||||
SELECT psi.program_id program_id,
|
||||
json_group_array(json_object(
|
||||
'section_id', psi.section_id,
|
||||
'duration', psi.duration)) sequence
|
||||
FROM program_sequence_items as psi;
|
@ -1,11 +1,36 @@
|
||||
use std::{time::Duration, sync::Arc};
|
||||
use super::section::SectionId;
|
||||
use crate::schedule::Schedule;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProgramItem {
|
||||
pub section_id: SectionId,
|
||||
pub duration: Duration,
|
||||
pub section_id: SectionId,
|
||||
#[serde(
|
||||
serialize_with = "ser::serialize_duration",
|
||||
deserialize_with = "ser::deserialize_duration"
|
||||
)]
|
||||
pub duration: Duration,
|
||||
}
|
||||
|
||||
mod ser {
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn serialize_duration<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
duration.as_secs_f64().serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let secs: f64 = Deserialize::deserialize(deserializer)?;
|
||||
Ok(Duration::from_secs_f64(secs))
|
||||
}
|
||||
}
|
||||
|
||||
pub type ProgramSequence = Vec<ProgramItem>;
|
||||
@ -21,6 +46,54 @@ pub struct Program {
|
||||
pub schedule: Schedule,
|
||||
}
|
||||
|
||||
mod sql {
|
||||
use super::{Program, ProgramSequence};
|
||||
use crate::schedule::Schedule;
|
||||
use rusqlite::{
|
||||
types::{FromSql, FromSqlError, FromSqlResult, ValueRef},
|
||||
Result as SqlResult, Row as SqlRow,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
struct SqlJson<T>(T);
|
||||
|
||||
impl<T> SqlJson<T> {
|
||||
fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromSql for SqlJson<T>
|
||||
where
|
||||
for<'de> T: Deserialize<'de>,
|
||||
{
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
if let ValueRef::Text(text) = value {
|
||||
let deser_value: T = serde_json::from_slice(text)
|
||||
.map_err(|err| FromSqlError::Other(Box::new(err)))?;
|
||||
Ok(SqlJson(deser_value))
|
||||
} else {
|
||||
Err(FromSqlError::InvalidType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type SqlProgramSequence = SqlJson<ProgramSequence>;
|
||||
type SqlSchedule = SqlJson<Schedule>;
|
||||
|
||||
impl Program {
|
||||
pub fn from_sql<'a>(row: &SqlRow<'a>) -> SqlResult<Self> {
|
||||
Ok(Self {
|
||||
id: row.get(0)?,
|
||||
name: row.get(1)?,
|
||||
sequence: row.get::<_, SqlProgramSequence>(2)?.into_inner(),
|
||||
enabled: row.get(3)?,
|
||||
schedule: row.get::<_, SqlSchedule>(4)?.into_inner(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ProgramRef = Arc<Program>;
|
||||
|
||||
pub type Programs = im::OrdMap<ProgramId, ProgramRef>;
|
||||
|
@ -188,7 +188,7 @@ mod ser {
|
||||
|
||||
impl fmt::Display for InvalidWeekday {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
"invalid weekday".fmt(f)
|
||||
"weekday out of range 0 to 6".fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user