Browse Source

Add support for reading programs from database

Closes #8
master
Alex Mikhalev 4 years ago
parent
commit
0fe30fa7a9
  1. 3
      src/db.rs
  2. 46
      src/main.rs
  3. 3
      src/migrations/0003-table_programs-down.sql
  4. 20
      src/migrations/0003-table_programs-up.sql
  5. 2
      src/migrations/0004-program_rows-down.sql
  6. 14
      src/migrations/0004-program_rows-up.sql
  7. 1
      src/migrations/0005-view_program_sequence-down.sql
  8. 6
      src/migrations/0005-view_program_sequence-up.sql
  9. 81
      src/model/program.rs
  10. 2
      src/schedule.rs

3
src/db.rs

@ -14,6 +14,9 @@ pub fn create_migrations() -> Migrations { @@ -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

@ -18,10 +18,8 @@ mod section_runner; @@ -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> { @@ -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<()> { @@ -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

@ -0,0 +1,3 @@ @@ -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

@ -0,0 +1,20 @@ @@ -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

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
DELETE FROM program_sequence_items;
DELETE FROM programs;

14
src/migrations/0004-program_rows-up.sql

@ -0,0 +1,14 @@ @@ -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

@ -0,0 +1 @@ @@ -0,0 +1 @@
DROP VIEW program_sequences;

6
src/migrations/0005-view_program_sequence-up.sql

@ -0,0 +1,6 @@ @@ -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;

81
src/model/program.rs

@ -1,11 +1,36 @@ @@ -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 { @@ -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>;

2
src/schedule.rs

@ -188,7 +188,7 @@ mod ser { @@ -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…
Cancel
Save