Saving functionality is looking better
This commit is contained in:
parent
3e1e46ce2b
commit
a99e4fb3cf
15 changed files with 421 additions and 54 deletions
|
@ -38,7 +38,7 @@ serde_json = "1.0.104"
|
||||||
fastrand = "2.0.0"
|
fastrand = "2.0.0"
|
||||||
rfd = { version = "0.12.1", features = ["xdg-portal"], default-features = false }
|
rfd = { version = "0.12.1", features = ["xdg-portal"], default-features = false }
|
||||||
sqlx = { version = "0.8.2", features = ["sqlite", "runtime-tokio", "macros"] }
|
sqlx = { version = "0.8.2", features = ["sqlite", "runtime-tokio", "macros"] }
|
||||||
tokio = { version = "1.32.0", features = ["full"] }
|
tokio = { version = "1.40.0", features = ["full"] }
|
||||||
tracing-subscriber = { version = "0.3.17", features = ["fmt", "std", "chrono", "time", "local-time", "env-filter"] }
|
tracing-subscriber = { version = "0.3.17", features = ["fmt", "std", "chrono", "time", "local-time", "env-filter"] }
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
time = { version = "0.3.29", features = ["formatting", "macros"] }
|
time = { version = "0.3.29", features = ["formatting", "macros"] }
|
||||||
|
|
|
@ -35,7 +35,7 @@ serde_json = "1.0.104"
|
||||||
fastrand = "2.0.0"
|
fastrand = "2.0.0"
|
||||||
rfd = { version = "0.12.1", features = ["xdg-portal"], default-features = false }
|
rfd = { version = "0.12.1", features = ["xdg-portal"], default-features = false }
|
||||||
sqlx = { version = "0.8.2", features = ["sqlite", "runtime-tokio", "macros"] }
|
sqlx = { version = "0.8.2", features = ["sqlite", "runtime-tokio", "macros"] }
|
||||||
tokio = { version = "1.32.0", features = ["full"] }
|
tokio = { version = "1.40.0", features = ["full", "macros"] }
|
||||||
tracing-subscriber = { version = "0.3.17", features = ["fmt", "std", "chrono", "time", "local-time", "env-filter"] }
|
tracing-subscriber = { version = "0.3.17", features = ["fmt", "std", "chrono", "time", "local-time", "env-filter"] }
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
time = { version = "0.3.29", features = ["formatting", "macros"] }
|
time = { version = "0.3.29", features = ["formatting", "macros"] }
|
||||||
|
|
|
@ -1,39 +1,350 @@
|
||||||
use color_eyre::eyre::{eyre, Result};
|
use tar::{Archive, Builder};
|
||||||
use sqlx::{query, sqlite::SqliteRow, Error, FromRow};
|
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
use zstd::Encoder;
|
||||||
|
use std::{fs::{self, File}, future::Future, iter, path::{Path, PathBuf}};
|
||||||
|
use color_eyre::eyre::{eyre, Result};
|
||||||
|
use serde_json::Value;
|
||||||
|
use sqlx::{query, query_as, FromRow, SqliteConnection};
|
||||||
|
use crate::{images::{get_image_from_db, Image}, kinds::ServiceItemKind, model::get_db, presentations::{get_presentation_from_db, PresKind, Presentation}, service_items::ServiceItem, slides::Background, songs::{get_song_from_db, Song}, videos::{get_video_from_db, Video}};
|
||||||
|
|
||||||
use crate::{kinds::ServiceItemKind, model::get_db, service_items::ServiceItem, slides::TextAlignment, songs::Song};
|
pub async fn save(list: Vec<ServiceItem>, path: impl AsRef<Path>) -> Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let save_file = File::create(path)?;
|
||||||
|
let mut db = get_db().await;
|
||||||
|
let json = process_service_items(&list, &mut db).await?;
|
||||||
|
let archive = store_service_items(&list, &mut db, &save_file, &json).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn store_service_items(items: &Vec<ServiceItem>, db: &mut SqliteConnection, save_file: &File, json: &Value) -> Result<()> {
|
||||||
pub fn save(list: Vec<ServiceItem>) -> Result<()> {
|
let encoder = Encoder::new(save_file, 3).unwrap();
|
||||||
let mut db = get_db();
|
let mut tar = Builder::new(encoder);
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
let mut temp_dir = dirs::data_dir().unwrap();
|
||||||
for item in list {
|
temp_dir.push("lumina");
|
||||||
|
let mut s: String =
|
||||||
|
iter::repeat_with(fastrand::alphanumeric)
|
||||||
|
.take(5)
|
||||||
|
.collect();
|
||||||
|
s.insert_str(0, "temp_");
|
||||||
|
temp_dir.push(s);
|
||||||
|
fs::create_dir_all(&temp_dir)?;
|
||||||
|
let service_file = temp_dir.join("serviceitems.json");
|
||||||
|
fs::File::create(&service_file)?;
|
||||||
|
match fs::File::options().read(true).write(true).open(service_file) {
|
||||||
|
Ok(f) => {
|
||||||
|
serde_json::to_writer_pretty(f, json)?;
|
||||||
|
},
|
||||||
|
Err(e) => error!("There were problems making a file i guess: {e}"),
|
||||||
|
};
|
||||||
|
for item in items {
|
||||||
|
let background;
|
||||||
|
let audio: Option<PathBuf>;
|
||||||
match item.kind {
|
match item.kind {
|
||||||
ServiceItemKind::Song => {
|
ServiceItemKind::Song => {
|
||||||
rt.block_on(async {
|
let song = get_song_from_db(item.database_id, db).await?;
|
||||||
let result = query(r#"SELECT vorder as "verse_order!", fontSize as "font_size!: i32", backgroundType as "background_type!", horizontalTextAlignment as "horizontal_text_alignment!", verticalTextAlignment as "vertical_text_alignment!", title as "title!", font as "font!", background as "background!", lyrics as "lyrics!", ccli as "ccli!", author as "author!", audio as "audio!", id as "id: i32" from songs where id = $1"#).bind(item.database_id).fetch_one(&mut db).await;
|
background = song.background;
|
||||||
process_song(result);
|
audio = song.audio;
|
||||||
});
|
|
||||||
},
|
},
|
||||||
ServiceItemKind::Image => {
|
ServiceItemKind::Image => {
|
||||||
todo!()
|
let image = get_image_from_db(item.database_id, db).await?;
|
||||||
|
background = Some(Background::try_from(image.path)?);
|
||||||
|
audio = None;
|
||||||
},
|
},
|
||||||
ServiceItemKind::Video => {
|
ServiceItemKind::Video => {
|
||||||
todo!()
|
let video = get_video_from_db(item.database_id, db).await?;
|
||||||
|
background = Some(Background::try_from(video.path)?);
|
||||||
|
audio = None;
|
||||||
},
|
},
|
||||||
ServiceItemKind::Presentation(_) => {
|
ServiceItemKind::Presentation(_) => {
|
||||||
|
let presentation = get_presentation_from_db(item.database_id, db).await?;
|
||||||
|
background = Some(Background::try_from(presentation.path)?);
|
||||||
|
audio = None;
|
||||||
|
},
|
||||||
|
ServiceItemKind::Content => {
|
||||||
todo!()
|
todo!()
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
if let Some(file) = audio {
|
||||||
|
let audio_file = temp_dir.join(file.file_name().expect("Audio file couldn't be added to temp_dir"));
|
||||||
|
match fs::File::create(&audio_file) {
|
||||||
|
Ok(_) => Ok(fs::copy(file, &audio_file)?),
|
||||||
|
Err(e) => Err(eyre!("Couldn't create audio file: {e}")),
|
||||||
|
}?;
|
||||||
|
};
|
||||||
|
if let Some(file) = background {
|
||||||
|
let background_file = temp_dir.join(file.path.file_name().expect("Background file couldn't be added to temp_dir"));
|
||||||
|
match fs::File::create(&background_file) {
|
||||||
|
Ok(_) => Ok(fs::copy(file.path, &background_file)?),
|
||||||
|
Err(e) => Err(eyre!("Couldn't create background file: {e}")),
|
||||||
|
}?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn clear_temp_dir(temp_dir: &Path) -> Result<()> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_service_items(items: &Vec<ServiceItem>, db: &mut SqliteConnection) -> Result<Value> {
|
||||||
|
let mut values: Vec<Value> = vec![];
|
||||||
|
for item in items {
|
||||||
|
match item.kind {
|
||||||
|
ServiceItemKind::Song => {
|
||||||
|
let value = process_song(item.database_id, db).await?;
|
||||||
|
values.push(value);
|
||||||
|
},
|
||||||
|
ServiceItemKind::Image => {
|
||||||
|
let value = process_image(item.database_id, db).await?;
|
||||||
|
values.push(value);
|
||||||
|
},
|
||||||
|
ServiceItemKind::Video => {
|
||||||
|
let value = process_video(item.database_id, db).await?;
|
||||||
|
values.push(value);
|
||||||
|
},
|
||||||
|
ServiceItemKind::Presentation(_) => {
|
||||||
|
let value = process_presentation(item.database_id, db).await?;
|
||||||
|
values.push(value);
|
||||||
|
},
|
||||||
ServiceItemKind::Content => {
|
ServiceItemKind::Content => {
|
||||||
todo!()
|
todo!()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
let json = Value::from(values);
|
||||||
|
Ok(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_song(song_result: Result<SqliteRow, Error>) -> Result<()> {
|
async fn process_song(database_id: i32, db: &mut SqliteConnection) -> Result<Value> {
|
||||||
let song = Song::from_row(&song_result?)?;
|
let song = get_song_from_db(database_id, db).await?;
|
||||||
Ok(())
|
let song_json = serde_json::to_value(&song)?;
|
||||||
|
let kind_json = serde_json::to_value(ServiceItemKind::Song)?;
|
||||||
|
let json = serde_json::json!({"item": song_json, "kind": kind_json});
|
||||||
|
Ok(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_image(database_id: i32, db: &mut SqliteConnection) -> Result<Value> {
|
||||||
|
let image = get_image_from_db(database_id, db).await?;
|
||||||
|
let image_json = serde_json::to_value(&image)?;
|
||||||
|
let kind_json = serde_json::to_value(ServiceItemKind::Image)?;
|
||||||
|
let json = serde_json::json!({"item": image_json, "kind": kind_json});
|
||||||
|
Ok(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_video(database_id: i32, db: &mut SqliteConnection) -> Result<Value> {
|
||||||
|
let video = get_video_from_db(database_id, db).await?;
|
||||||
|
let video_json = serde_json::to_value(&video)?;
|
||||||
|
let kind_json = serde_json::to_value(ServiceItemKind::Video)?;
|
||||||
|
let json = serde_json::json!({"item": video_json, "kind": kind_json});
|
||||||
|
Ok(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_presentation(database_id: i32, db: &mut SqliteConnection) -> Result<Value> {
|
||||||
|
let presentation = get_presentation_from_db(database_id, db).await?;
|
||||||
|
let presentation_json = serde_json::to_value(&presentation)?;
|
||||||
|
let kind_json = match presentation.kind {
|
||||||
|
PresKind::Html => serde_json::to_value(ServiceItemKind::Presentation(PresKind::Html))?,
|
||||||
|
PresKind::Pdf => serde_json::to_value(ServiceItemKind::Presentation(PresKind::Pdf))?,
|
||||||
|
PresKind::Generic => serde_json::to_value(ServiceItemKind::Presentation(PresKind::Generic))?,
|
||||||
|
};
|
||||||
|
let json = serde_json::json!({"item": presentation_json, "kind": kind_json});
|
||||||
|
Ok(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use fs::canonicalize;
|
||||||
|
use sqlx::Connection;
|
||||||
|
use pretty_assertions::{assert_eq, assert_ne};
|
||||||
|
use tracing::debug;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
async fn get_db() -> SqliteConnection {
|
||||||
|
let mut data = dirs::data_local_dir().unwrap();
|
||||||
|
data.push("lumina");
|
||||||
|
data.push("library-db.sqlite3");
|
||||||
|
let mut db_url = String::from("sqlite://");
|
||||||
|
db_url.push_str(data.to_str().unwrap());
|
||||||
|
SqliteConnection::connect(&db_url)
|
||||||
|
.await
|
||||||
|
.expect("problems")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn test_process_song() {
|
||||||
|
let mut db = get_db().await;
|
||||||
|
let result = process_song(7, &mut db).await;
|
||||||
|
let json_song_file = PathBuf::from("./test/test_song.json");
|
||||||
|
if let Ok(path) = canonicalize(json_song_file) {
|
||||||
|
debug!(file = ?&path);
|
||||||
|
if let Ok(s) = fs::read_to_string(path) {
|
||||||
|
debug!(s);
|
||||||
|
match result {
|
||||||
|
Ok(json) => assert_eq!(json.to_string(), s),
|
||||||
|
Err(e) => panic!("There was an error in processing the song: {e}"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("String wasn't read from file");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Cannot find absolute path to test_song.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn test_process_image() {
|
||||||
|
let mut db = get_db().await;
|
||||||
|
let result = process_image(3, &mut db).await;
|
||||||
|
let json_image_file = PathBuf::from("./test/test_image.json");
|
||||||
|
if let Ok(path) = canonicalize(json_image_file) {
|
||||||
|
debug!(file = ?&path);
|
||||||
|
if let Ok(s) = fs::read_to_string(path) {
|
||||||
|
debug!(s);
|
||||||
|
match result {
|
||||||
|
Ok(json) => assert_eq!(json.to_string(), s),
|
||||||
|
Err(e) => panic!("There was an error in processing the image: {e}"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("String wasn't read from file");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Cannot find absolute path to test_image.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn test_process_video() {
|
||||||
|
let mut db = get_db().await;
|
||||||
|
let result = process_video(73, &mut db).await;
|
||||||
|
let json_video_file = PathBuf::from("./test/test_video.json");
|
||||||
|
if let Ok(path) = canonicalize(json_video_file) {
|
||||||
|
debug!(file = ?&path);
|
||||||
|
if let Ok(s) = fs::read_to_string(path) {
|
||||||
|
debug!(s);
|
||||||
|
match result {
|
||||||
|
Ok(json) => assert_eq!(json.to_string(), s),
|
||||||
|
Err(e) => panic!("There was an error in processing the video: {e}"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("String wasn't read from file");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Cannot find absolute path to test_video.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn test_process_presentation() {
|
||||||
|
let mut db = get_db().await;
|
||||||
|
let result = process_presentation(54, &mut db).await;
|
||||||
|
let json_presentation_file = PathBuf::from("./test/test_presentation.json");
|
||||||
|
if let Ok(path) = canonicalize(json_presentation_file) {
|
||||||
|
debug!(file = ?&path);
|
||||||
|
if let Ok(s) = fs::read_to_string(path) {
|
||||||
|
debug!(s);
|
||||||
|
match result {
|
||||||
|
Ok(json) => assert_eq!(json.to_string(), s),
|
||||||
|
Err(e) => panic!("There was an error in processing the presentation: {e}"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("String wasn't read from file");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Cannot find absolute path to test_presentation.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_items() -> Vec<ServiceItem> {
|
||||||
|
let items = vec![
|
||||||
|
ServiceItem {
|
||||||
|
database_id: 7,
|
||||||
|
kind: ServiceItemKind::Song,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
ServiceItem {
|
||||||
|
database_id: 54,
|
||||||
|
kind: ServiceItemKind::Presentation(PresKind::Html),
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
ServiceItem {
|
||||||
|
database_id: 73,
|
||||||
|
kind: ServiceItemKind::Video,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
items
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_service_items() {
|
||||||
|
let mut db = get_db().await;
|
||||||
|
let items = get_items();
|
||||||
|
let json_item_file = PathBuf::from("./test/test_service_items.json");
|
||||||
|
let result = process_service_items(&items, &mut db).await;
|
||||||
|
if let Ok(path) = canonicalize(json_item_file) {
|
||||||
|
if let Ok(s) = fs::read_to_string(path) {
|
||||||
|
match result {
|
||||||
|
Ok(strings) => assert_eq!(strings.to_string(), s),
|
||||||
|
Err(e) => panic!("There was an error: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[tokio::test]
|
||||||
|
// async fn test_save() {
|
||||||
|
// let path = PathBuf::from("~/dev/lumina/src/rust/core/test.pres");
|
||||||
|
// let list = get_items();
|
||||||
|
// match save(list, path).await {
|
||||||
|
// Ok(_) => assert!(true),
|
||||||
|
// Err(e) => panic!("There was an error: {e}"),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_store() {
|
||||||
|
let path = PathBuf::from("/home/chris/dev/lumina/src/rust/core/test.pres");
|
||||||
|
let save_file = match File::create(path) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(e) => panic!("Couldn't create save_file: {e}"),
|
||||||
|
};
|
||||||
|
let mut db = get_db().await;
|
||||||
|
let list = get_items();
|
||||||
|
if let Ok(json) = process_service_items(&list, &mut db).await {
|
||||||
|
println!("{:?}", json);
|
||||||
|
match store_service_items(&list, &mut db, &save_file, &json).await {
|
||||||
|
Ok(_) => assert!(true),
|
||||||
|
Err(e) => panic!("There was an error: {e}"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("There was an error getting the json value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[tokio::test]
|
||||||
|
// async fn test_things() {
|
||||||
|
// let mut temp_dir = dirs::data_dir().unwrap();
|
||||||
|
// temp_dir.push("lumina");
|
||||||
|
// let mut s: String =
|
||||||
|
// iter::repeat_with(fastrand::alphanumeric)
|
||||||
|
// .take(5)
|
||||||
|
// .collect();
|
||||||
|
// s.insert_str(0, "temp_");
|
||||||
|
// temp_dir.push(s);
|
||||||
|
// let _ = fs::create_dir_all(&temp_dir);
|
||||||
|
// let mut db = get_db().await;
|
||||||
|
// let service_file = temp_dir.join("serviceitems.json");
|
||||||
|
// let list = get_items();
|
||||||
|
// if let Ok(json) = process_service_items(&list, &mut db).await {
|
||||||
|
// let _ = fs::File::create(&service_file);
|
||||||
|
// match fs::write(service_file, json.to_string()) {
|
||||||
|
// Ok(_) => assert!(true),
|
||||||
|
// Err(e) => panic!("There was an error: {e}"),
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// panic!("There was an error getting the json value");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::model::Model;
|
use crate::model::Model;
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::query_as;
|
use sqlx::{query_as, SqliteConnection};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
|
@ -28,6 +29,11 @@ impl Model<Image> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn get_image_from_db(database_id: i32, db: &mut SqliteConnection) -> Result<Image> {
|
||||||
|
Ok(query_as!(Image, r#"SELECT title as "title!", filePath as "path!", id as "id: i32" from images where id = ?"#, database_id).fetch_one(db).await?)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use std::{error::Error, fmt::Display};
|
use std::{error::Error, fmt::Display};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::presentations::PresKind;
|
use crate::presentations::PresKind;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum ServiceItemKind {
|
pub enum ServiceItemKind {
|
||||||
#[default]
|
#[default]
|
||||||
Song,
|
Song,
|
||||||
|
|
|
@ -58,23 +58,25 @@ impl<T> Default for Model<T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
items: vec![],
|
items: vec![],
|
||||||
db: get_db(),
|
db: {
|
||||||
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
|
rt.block_on(async {
|
||||||
|
get_db().await
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_db() -> SqliteConnection {
|
pub async fn get_db() -> SqliteConnection {
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
||||||
let mut data = dirs::data_local_dir().unwrap();
|
let mut data = dirs::data_local_dir().unwrap();
|
||||||
data.push("lumina");
|
data.push("lumina");
|
||||||
data.push("library-db.sqlite3");
|
data.push("library-db.sqlite3");
|
||||||
let mut db_url = String::from("sqlite://");
|
let mut db_url = String::from("sqlite://");
|
||||||
db_url.push_str(data.to_str().unwrap());
|
db_url.push_str(data.to_str().unwrap());
|
||||||
rt.block_on(async {
|
SqliteConnection::connect(&db_url)
|
||||||
SqliteConnection::connect(&db_url)
|
.await
|
||||||
.await
|
.expect("problems")
|
||||||
.expect("problems")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Modeling {
|
pub trait Modeling {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::query;
|
use sqlx::{prelude::FromRow, query, sqlite::SqliteRow, Row, SqliteConnection};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::model::Model;
|
use crate::model::Model;
|
||||||
|
@ -35,6 +35,24 @@ impl Presentation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromRow<'_, SqliteRow> for Presentation {
|
||||||
|
fn from_row(row: &SqliteRow) -> sqlx::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
id: row.try_get(0)?,
|
||||||
|
title: row.try_get(1)?,
|
||||||
|
path: PathBuf::from({
|
||||||
|
let string: String = row.try_get(2)?;
|
||||||
|
string
|
||||||
|
}),
|
||||||
|
kind: if row.try_get(3)? {
|
||||||
|
PresKind::Html
|
||||||
|
} else {
|
||||||
|
PresKind::Pdf
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Model<Presentation> {
|
impl Model<Presentation> {
|
||||||
pub fn load_from_db(&mut self) {
|
pub fn load_from_db(&mut self) {
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
|
@ -61,6 +79,11 @@ impl Model<Presentation> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_presentation_from_db(database_id: i32, db: &mut SqliteConnection) -> Result<Presentation> {
|
||||||
|
let row = query(r#"SELECT id as "id: i32", title, filePath as "path", html from presentations where id = $1"#).bind(database_id).fetch_one(db).await?;
|
||||||
|
Ok(Presentation::from_row(&row)?)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{error::Error, fmt::Display, path::PathBuf};
|
use std::{error::Error, fmt::Display, path::{Path, PathBuf}};
|
||||||
|
|
||||||
use color_eyre::eyre::{eyre, Result};
|
use color_eyre::eyre::{eyre, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -26,8 +26,8 @@ pub enum TextAlignment {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Background {
|
pub struct Background {
|
||||||
path: PathBuf,
|
pub path: PathBuf,
|
||||||
kind: BackgroundKind,
|
pub kind: BackgroundKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<String> for Background {
|
impl TryFrom<String> for Background {
|
||||||
|
@ -36,11 +36,11 @@ impl TryFrom<String> for Background {
|
||||||
let value = value.trim_start_matches("file://");
|
let value = value.trim_start_matches("file://");
|
||||||
let path = PathBuf::from(value);
|
let path = PathBuf::from(value);
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Err(ParseError::NonBackgroundFile)
|
return Err(ParseError::DoesNotExist)
|
||||||
}
|
}
|
||||||
let extension = value.rsplit_once('.').unwrap_or_default();
|
let extension = value.rsplit_once('.').unwrap_or_default();
|
||||||
match extension.1 {
|
match extension.1 {
|
||||||
"jpg" | "png" | "webp" => Ok(Self {
|
"jpg" | "png" | "webp" | "html" => Ok(Self {
|
||||||
path,
|
path,
|
||||||
kind: BackgroundKind::Image,
|
kind: BackgroundKind::Image,
|
||||||
}),
|
}),
|
||||||
|
@ -62,7 +62,7 @@ impl TryFrom<PathBuf> for Background {
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
match extension {
|
match extension {
|
||||||
"jpg" | "png" | "webp" => Ok(Self {
|
"jpg" | "png" | "webp" | "html" => Ok(Self {
|
||||||
path: value,
|
path: value,
|
||||||
kind: BackgroundKind::Image,
|
kind: BackgroundKind::Image,
|
||||||
}),
|
}),
|
||||||
|
@ -75,9 +75,24 @@ impl TryFrom<PathBuf> for Background {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for Background {
|
||||||
|
type Error = ParseError;
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self::try_from(String::from(value))?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&Path> for Background {
|
||||||
|
type Error = ParseError;
|
||||||
|
fn try_from(value: &Path) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self::try_from(PathBuf::from(value))?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ParseError {
|
pub enum ParseError {
|
||||||
NonBackgroundFile,
|
NonBackgroundFile,
|
||||||
|
DoesNotExist,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for ParseError {}
|
impl Error for ParseError {}
|
||||||
|
@ -113,6 +128,9 @@ impl Display for ParseError {
|
||||||
Self::NonBackgroundFile => {
|
Self::NonBackgroundFile => {
|
||||||
"The file is not a recognized image or video type"
|
"The file is not a recognized image or video type"
|
||||||
}
|
}
|
||||||
|
Self::DoesNotExist => {
|
||||||
|
"This file doesn't exist"
|
||||||
|
}
|
||||||
};
|
};
|
||||||
write!(f, "Error: {message}")
|
write!(f, "Error: {message}")
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use color_eyre::eyre::{eyre, Context, Result};
|
use color_eyre::eyre::{eyre, Context, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{query, query_as, sqlite::SqliteRow, FromRow, Row};
|
use sqlx::{query, query_as, sqlite::SqliteRow, FromRow, Row, SqliteConnection};
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -76,16 +76,9 @@ impl FromRow<'_, SqliteRow> for Song {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn get_song_from_db(index: i32) -> Result<Song> {
|
pub async fn get_song_from_db(index: i32, db: &mut SqliteConnection) -> Result<Song> {
|
||||||
let mut db = get_db();
|
let row = query(r#"SELECT vorder as "verse_order!", fontSize as "font_size!: i32", backgroundType as "background_type!", horizontalTextAlignment as "horizontal_text_alignment!", verticalTextAlignment as "vertical_text_alignment!", title as "title!", font as "font!", background as "background!", lyrics as "lyrics!", ccli as "ccli!", author as "author!", audio as "audio!", id as "id: i32" from songs where id = $1"#).bind(index).fetch_one(db).await?;
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
Ok(Song::from_row(&row)?)
|
||||||
rt.block_on(async {
|
|
||||||
let result = query(r#"SELECT vorder as "verse_order!", fontSize as "font_size!: i32", backgroundType as "background_type!", horizontalTextAlignment as "horizontal_text_alignment!", verticalTextAlignment as "vertical_text_alignment!", title as "title!", font as "font!", background as "background!", lyrics as "lyrics!", ccli as "ccli!", author as "author!", audio as "audio!", id as "id: i32" from songs where id = $1"#).bind(index).fetch_one(&mut db).await;
|
|
||||||
match result {
|
|
||||||
Ok(record) => Ok(Song::from_row(&record)?),
|
|
||||||
Err(e) => Err(eyre!("There was an error getting the song from the db: {e}")),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -290,10 +283,10 @@ You saved my soul"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
pub fn test_song_from_db() {
|
pub async fn test_song_from_db() {
|
||||||
let song = test_song();
|
let song = test_song();
|
||||||
let result = get_song_from_db(7);
|
let result = get_song_from_db(7, &mut get_db().await).await;
|
||||||
match result {
|
match result {
|
||||||
Ok(db_song) => assert_eq!(song, db_song),
|
Ok(db_song) => assert_eq!(song, db_song),
|
||||||
Err(e) => assert!(false, "{e}"),
|
Err(e) => assert!(false, "{e}"),
|
||||||
|
@ -340,7 +333,7 @@ You saved my soul"
|
||||||
"E1".to_string(),
|
"E1".to_string(),
|
||||||
"E2".to_string(),
|
"E2".to_string(),
|
||||||
]),
|
]),
|
||||||
background: Some(Background::try_from("file:///home/chris/nc/tfc/openlp/CMG - Bright Mountains 01.jpg".to_string()).unwrap()),
|
background: Some(Background::try_from("file:///home/chris/nc/tfc/openlp/CMG - Bright Mountains 01.jpg").unwrap()),
|
||||||
text_alignment: Some(TextAlignment::MiddleCenter),
|
text_alignment: Some(TextAlignment::MiddleCenter),
|
||||||
font: Some("Quicksand Bold".to_string()),
|
font: Some("Quicksand Bold".to_string()),
|
||||||
font_size: Some(60)
|
font_size: Some(60)
|
||||||
|
|
1
src/rust/core/test/test_image.json
Normal file
1
src/rust/core/test/test_image.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"item":{"id":3,"path":"file:///home/chris/nc/tfc/Photos/NVTFC/nccq5.png","title":"nccq5"},"kind":"Image"}
|
1
src/rust/core/test/test_presentation.json
Normal file
1
src/rust/core/test/test_presentation.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"item":{"id":54,"kind":"Html","path":"file:///home/chris/docs/notes/lessons/20240327T133649--12-isaiah-and-jesus__lesson_project_tfc.html","title":"20240327T133649--12-isaiah-and-jesus__lesson_project_tfc"},"kind":{"Presentation":"Html"}}
|
1
src/rust/core/test/test_service_items.json
Normal file
1
src/rust/core/test/test_service_items.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[{"item":{"audio":"file:///home/chris/music/North Point InsideOut/Nothing Ordinary, Pt. 1 (Live)/05 Death Was Arrested (feat. Seth Condrey).mp3","author":"North Point Worship","background":{"kind":"Image","path":"/home/chris/nc/tfc/openlp/CMG - Bright Mountains 01.jpg"},"ccli":null,"font":"Quicksand Bold","font_size":60,"id":7,"lyrics":"Intro 1\nDeath Was Arrested\nNorth Point Worship\n\nVerse 1\nAlone in my sorrow\nAnd dead in my sin\n\nLost without hope\nWith no place to begin\n\nYour love made a way\nTo let mercy come in\n\nWhen death was arrested\nAnd my life began\n\nVerse 2\nAsh was redeemed\nOnly beauty remains\n\nMy orphan heart\nWas given a name\n\nMy mourning grew quiet,\nMy feet rose to dance\n\nWhen death was arrested\nAnd my life began\n\nChorus 1\nOh, Your grace so free,\nWashes over me\n\nYou have made me new,\nNow life begins with You\n\nIt's Your endless love,\nPouring down on us\n\nYou have made us new,\nNow life begins with You\n\nVerse 3\nReleased from my chains,\nI'm a prisoner no more\n\nMy shame was a ransom\nHe faithfully bore\n\nHe cancelled my debt and\nHe called me His friend\n\nWhen death was arrested\nAnd my life began\n\nVerse 4\nOur Savior displayed\nOn a criminal's cross\n\nDarkness rejoiced as though\nHeaven had lost\n\nBut then Jesus arose\nWith our freedom in hand\n\nThat's when death was arrested\nAnd my life began\n\nThat's when death was arrested\nAnd my life began\n\nBridge 1\nOh, we're free, free,\nForever we're free\n\nCome join the song\nOf all the redeemed\n\nYes, we're free, free,\nForever amen\n\nWhen death was arrested\nAnd my life began\n\nOh, we're free, free,\nForever we're free\n\nCome join the song\nOf all the redeemed\n\nYes, we're free, free,\nForever amen\n\nWhen death was arrested\nAnd my life began\n\nEnding 1\nWhen death was arrested\nAnd my life began\n\nThat's when death was arrested\nAnd my life began","text_alignment":"MiddleCenter","title":"Death Was Arrested","verse_order":["I1","V1","V2","C1","V3","C1","V4","C1","B1","B1","E1","E2"]},"kind":"Song"},{"item":{"id":54,"kind":"Html","path":"file:///home/chris/docs/notes/lessons/20240327T133649--12-isaiah-and-jesus__lesson_project_tfc.html","title":"20240327T133649--12-isaiah-and-jesus__lesson_project_tfc"},"kind":{"Presentation":"Html"}},{"item":{"end_time":0.0,"id":73,"looping":false,"path":"/home/chris/.local/share/librepresenter/ytdl/Getting started with Tokio. The ultimate starter guide to writing async Rust..webm","start_time":0.0,"title":"Getting started with Tokio. The ultimate starter guide to writing async Rust."},"kind":"Video"}]
|
1
src/rust/core/test/test_song.json
Normal file
1
src/rust/core/test/test_song.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"item":{"audio":"file:///home/chris/music/North Point InsideOut/Nothing Ordinary, Pt. 1 (Live)/05 Death Was Arrested (feat. Seth Condrey).mp3","author":"North Point Worship","background":{"kind":"Image","path":"/home/chris/nc/tfc/openlp/CMG - Bright Mountains 01.jpg"},"ccli":null,"font":"Quicksand Bold","font_size":60,"id":7,"lyrics":"Intro 1\nDeath Was Arrested\nNorth Point Worship\n\nVerse 1\nAlone in my sorrow\nAnd dead in my sin\n\nLost without hope\nWith no place to begin\n\nYour love made a way\nTo let mercy come in\n\nWhen death was arrested\nAnd my life began\n\nVerse 2\nAsh was redeemed\nOnly beauty remains\n\nMy orphan heart\nWas given a name\n\nMy mourning grew quiet,\nMy feet rose to dance\n\nWhen death was arrested\nAnd my life began\n\nChorus 1\nOh, Your grace so free,\nWashes over me\n\nYou have made me new,\nNow life begins with You\n\nIt's Your endless love,\nPouring down on us\n\nYou have made us new,\nNow life begins with You\n\nVerse 3\nReleased from my chains,\nI'm a prisoner no more\n\nMy shame was a ransom\nHe faithfully bore\n\nHe cancelled my debt and\nHe called me His friend\n\nWhen death was arrested\nAnd my life began\n\nVerse 4\nOur Savior displayed\nOn a criminal's cross\n\nDarkness rejoiced as though\nHeaven had lost\n\nBut then Jesus arose\nWith our freedom in hand\n\nThat's when death was arrested\nAnd my life began\n\nThat's when death was arrested\nAnd my life began\n\nBridge 1\nOh, we're free, free,\nForever we're free\n\nCome join the song\nOf all the redeemed\n\nYes, we're free, free,\nForever amen\n\nWhen death was arrested\nAnd my life began\n\nOh, we're free, free,\nForever we're free\n\nCome join the song\nOf all the redeemed\n\nYes, we're free, free,\nForever amen\n\nWhen death was arrested\nAnd my life began\n\nEnding 1\nWhen death was arrested\nAnd my life began\n\nThat's when death was arrested\nAnd my life began","text_alignment":"MiddleCenter","title":"Death Was Arrested","verse_order":["I1","V1","V2","C1","V3","C1","V4","C1","B1","B1","E1","E2"]},"kind":"Song"}
|
1
src/rust/core/test/test_video.json
Normal file
1
src/rust/core/test/test_video.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"item":{"end_time":0.0,"id":73,"looping":false,"path":"/home/chris/.local/share/librepresenter/ytdl/Getting started with Tokio. The ultimate starter guide to writing async Rust..webm","start_time":0.0,"title":"Getting started with Tokio. The ultimate starter guide to writing async Rust."},"kind":"Video"}
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::model::Model;
|
use crate::model::Model;
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::query_as;
|
use sqlx::{query_as, SqliteConnection};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
|
@ -31,6 +32,12 @@ impl Model<Video> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn get_video_from_db(database_id: i32, db: &mut SqliteConnection) -> Result<Video> {
|
||||||
|
Ok(query_as!(Video, r#"SELECT title as "title!", filePath as "path!", startTime as "start_time!: f32", endTime as "end_time!: f32", loop as "looping!", id as "id: i32" from videos where id = ?"#, database_id).fetch_one(db).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue