adding a lot more funcitonality to core library
This commit is contained in:
parent
ba51c56169
commit
3c87385895
7 changed files with 378 additions and 105 deletions
|
@ -6,3 +6,4 @@ pub mod service_items;
|
||||||
pub mod slides;
|
pub mod slides;
|
||||||
pub mod songs;
|
pub mod songs;
|
||||||
pub mod videos;
|
pub mod videos;
|
||||||
|
pub mod file;
|
||||||
|
|
|
@ -50,7 +50,12 @@ impl<T> Default for Model<T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
items: vec![],
|
items: vec![],
|
||||||
db: {
|
db: get_db(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_db() -> SqliteConnection {
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
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");
|
||||||
|
@ -62,9 +67,6 @@ impl<T> Default for Model<T> {
|
||||||
.await
|
.await
|
||||||
.expect("problems")
|
.expect("problems")
|
||||||
})
|
})
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Modeling {
|
pub trait Modeling {
|
||||||
|
@ -86,5 +88,4 @@ pub trait Modeling {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {}
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::query;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use crate::model::Model;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum PresKind {
|
pub enum PresKind {
|
||||||
Html,
|
Html,
|
||||||
#[default]
|
#[default]
|
||||||
|
@ -6,10 +14,12 @@ pub enum PresKind {
|
||||||
Generic,
|
Generic,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Presentation {
|
pub struct Presentation {
|
||||||
title: String,
|
pub id: i32,
|
||||||
kind: PresKind,
|
pub title: String,
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub kind: PresKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Presentation {
|
impl Presentation {
|
||||||
|
@ -25,13 +35,64 @@ impl Presentation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Model<Presentation> {
|
||||||
|
pub fn load_from_db(&mut self) {
|
||||||
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
|
rt.block_on(async {
|
||||||
|
let result = query!(r#"SELECT id as "id: i32", title, filePath as "path", html from presentations"#).fetch_all(&mut self.db).await;
|
||||||
|
match result {
|
||||||
|
Ok(v) => {
|
||||||
|
for presentation in v.into_iter() {
|
||||||
|
let _ = self.add_item(Presentation {
|
||||||
|
id: presentation.id,
|
||||||
|
title: presentation.title,
|
||||||
|
path: presentation.path.into(),
|
||||||
|
kind: if presentation.html {
|
||||||
|
PresKind::Html
|
||||||
|
} else {
|
||||||
|
PresKind::Pdf
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => error!("There was an error in converting presentations: {e}"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::presentations::{PresKind, Presentation};
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
fn test_presentation() -> Presentation {
|
||||||
|
Presentation {
|
||||||
|
id: 54,
|
||||||
|
title: "20240327T133649--12-isaiah-and-jesus__lesson_project_tfc".into(),
|
||||||
|
path: PathBuf::from(
|
||||||
|
"file:///home/chris/docs/notes/lessons/20240327T133649--12-isaiah-and-jesus__lesson_project_tfc.html",
|
||||||
|
),
|
||||||
|
kind: PresKind::Html,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_presentation() {
|
pub fn test_pres() {
|
||||||
let pres = Presentation::new();
|
let pres = Presentation::new();
|
||||||
assert_eq!(pres.get_kind(), &PresKind::Pdf)
|
assert_eq!(pres.get_kind(), &PresKind::Pdf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_db_and_model() {
|
||||||
|
let mut presentation_model: Model<Presentation> =
|
||||||
|
Model::default();
|
||||||
|
presentation_model.load_from_db();
|
||||||
|
if let Some(presentation) = presentation_model.get_item(10) {
|
||||||
|
let test_presentation = test_presentation();
|
||||||
|
assert_eq!(&test_presentation, presentation);
|
||||||
|
} else {
|
||||||
|
assert!(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
|
|
||||||
use crate::images::Image;
|
use crate::images::Image;
|
||||||
use crate::presentations::Presentation;
|
use crate::presentations::Presentation;
|
||||||
use crate::songs::Song;
|
use crate::songs::Song;
|
||||||
|
@ -6,10 +8,10 @@ use crate::videos::Video;
|
||||||
use super::kinds::ServiceItemKind;
|
use super::kinds::ServiceItemKind;
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
struct ServiceItem {
|
pub struct ServiceItem {
|
||||||
id: i32,
|
pub id: i32,
|
||||||
database_id: i32,
|
pub database_id: i32,
|
||||||
kind: ServiceItemKind,
|
pub kind: ServiceItemKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
|
@ -17,47 +19,96 @@ struct ServiceItemModel {
|
||||||
items: Vec<ServiceItem>,
|
items: Vec<ServiceItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Song> for ServiceItem {
|
impl From<&Song> for ServiceItem {
|
||||||
fn from(_song: Song) -> Self {
|
fn from(song: &Song) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kind: ServiceItemKind::Song,
|
kind: ServiceItemKind::Song,
|
||||||
|
database_id: song.id,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Video> for ServiceItem {
|
impl From<&Video> for ServiceItem {
|
||||||
fn from(_video: Video) -> Self {
|
fn from(video: &Video) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kind: ServiceItemKind::Video,
|
kind: ServiceItemKind::Video,
|
||||||
|
database_id: video.id,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Image> for ServiceItem {
|
impl From<&Image> for ServiceItem {
|
||||||
fn from(_image: Image) -> Self {
|
fn from(image: &Image) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kind: ServiceItemKind::Image,
|
kind: ServiceItemKind::Image,
|
||||||
|
database_id: image.id,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Presentation> for ServiceItem {
|
impl From<&Presentation> for ServiceItem {
|
||||||
fn from(presentation: Presentation) -> Self {
|
fn from(presentation: &Presentation) -> Self {
|
||||||
let preskind = presentation.get_kind();
|
|
||||||
Self {
|
Self {
|
||||||
kind: ServiceItemKind::Presentation(preskind.clone()),
|
kind: ServiceItemKind::Presentation(presentation.kind.clone()),
|
||||||
|
database_id: presentation.id,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ServiceItemModel {
|
||||||
|
fn add_item(&mut self, item: impl Into<ServiceItem>) -> Result<()> {
|
||||||
|
let service_item: ServiceItem = item.into();
|
||||||
|
self.items.push(service_item);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::presentations::PresKind;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use pretty_assertions::{assert_eq, assert_ne};
|
||||||
|
|
||||||
|
fn test_song() -> Song {
|
||||||
|
Song {
|
||||||
|
title: "Death Was Arrested".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_presentation() -> Presentation {
|
||||||
|
Presentation {
|
||||||
|
id: 0,
|
||||||
|
title: "20240327T133649--12-isaiah-and-jesus__lesson_project_tfc".into(),
|
||||||
|
path: PathBuf::from(
|
||||||
|
"~/docs/notes/lessons/20240327T133649--12-isaiah-and-jesus__lesson_project_tfc.html",
|
||||||
|
),
|
||||||
|
kind: PresKind::Html,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_service_item() {
|
pub fn test_service_item() {
|
||||||
assert_eq!(true, true)
|
let song = test_song();
|
||||||
|
let service_item = ServiceItem::from(&song);
|
||||||
|
let pres = test_presentation();
|
||||||
|
let pres_item = ServiceItem::from(&pres);
|
||||||
|
let mut service_model = ServiceItemModel::default();
|
||||||
|
match service_model.add_item(&song) {
|
||||||
|
Ok(_) => {
|
||||||
|
assert_eq!(ServiceItemKind::Song, service_model.items[0].kind);
|
||||||
|
assert_eq!(ServiceItemKind::Presentation(PresKind::Html), pres_item.kind);
|
||||||
|
assert_eq!(service_item, service_model.items[0]);
|
||||||
|
},
|
||||||
|
Err(e) => panic!("Problem adding item: {:?}", e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use std::{error::Error, fmt::Display, path::PathBuf};
|
use std::{error::Error, fmt::Display, path::PathBuf};
|
||||||
|
|
||||||
use color_eyre::eyre::{eyre, Result};
|
use color_eyre::eyre::{eyre, Result};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::error::DatabaseError;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -8,7 +10,7 @@ use crate::{
|
||||||
presentations::Presentation, songs::Song, videos::Video,
|
presentations::Presentation, songs::Song, videos::Video,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum TextAlignment {
|
pub enum TextAlignment {
|
||||||
TopLeft,
|
TopLeft,
|
||||||
TopCenter,
|
TopCenter,
|
||||||
|
@ -22,7 +24,7 @@ pub enum TextAlignment {
|
||||||
BottomRight,
|
BottomRight,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Background {
|
pub struct Background {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
kind: BackgroundKind,
|
kind: BackgroundKind,
|
||||||
|
@ -31,17 +33,22 @@ pub struct Background {
|
||||||
impl TryFrom<String> for Background {
|
impl TryFrom<String> for Background {
|
||||||
type Error = ParseError;
|
type Error = ParseError;
|
||||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
let value = value.trim_start_matches("file://");
|
||||||
|
let path = PathBuf::from(value);
|
||||||
|
if !path.exists() {
|
||||||
|
return Err(ParseError::NonBackgroundFile)
|
||||||
|
}
|
||||||
let extension = value.rsplit_once('.').unwrap_or_default();
|
let extension = value.rsplit_once('.').unwrap_or_default();
|
||||||
match extension.0 {
|
match extension.1 {
|
||||||
"jpg" | "png" | "webp" => Ok(Self {
|
"jpg" | "png" | "webp" => Ok(Self {
|
||||||
path: PathBuf::from(value),
|
path,
|
||||||
kind: BackgroundKind::Image,
|
kind: BackgroundKind::Image,
|
||||||
}),
|
}),
|
||||||
"mp4" | "mkv" | "webm" => Ok(Self {
|
"mp4" | "mkv" | "webm" => Ok(Self {
|
||||||
path: PathBuf::from(value),
|
path,
|
||||||
kind: BackgroundKind::Video,
|
kind: BackgroundKind::Video,
|
||||||
}),
|
}),
|
||||||
_ => Err(ParseError::NonBackgroundFile)
|
_ => Err(ParseError::NonBackgroundFile),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +56,11 @@ impl TryFrom<String> for Background {
|
||||||
impl TryFrom<PathBuf> for Background {
|
impl TryFrom<PathBuf> for Background {
|
||||||
type Error = ParseError;
|
type Error = ParseError;
|
||||||
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
|
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
|
||||||
let extension = value.extension().unwrap_or_default().to_str().unwrap_or_default();
|
let extension = value
|
||||||
|
.extension()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or_default();
|
||||||
match extension {
|
match extension {
|
||||||
"jpg" | "png" | "webp" => Ok(Self {
|
"jpg" | "png" | "webp" => Ok(Self {
|
||||||
path: value,
|
path: value,
|
||||||
|
@ -59,12 +70,11 @@ impl TryFrom<PathBuf> for Background {
|
||||||
path: value,
|
path: value,
|
||||||
kind: BackgroundKind::Video,
|
kind: BackgroundKind::Video,
|
||||||
}),
|
}),
|
||||||
_ => Err(ParseError::NonBackgroundFile)
|
_ => Err(ParseError::NonBackgroundFile),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ParseError {
|
pub enum ParseError {
|
||||||
NonBackgroundFile,
|
NonBackgroundFile,
|
||||||
|
@ -72,19 +82,43 @@ pub enum ParseError {
|
||||||
|
|
||||||
impl Error for ParseError {}
|
impl Error for ParseError {}
|
||||||
|
|
||||||
|
impl DatabaseError for ParseError {
|
||||||
|
fn message(&self) -> &str {
|
||||||
|
"The file in the database is not a recognized image or video type"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_error(&self) -> &(dyn Error + Send + Sync + 'static) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_error_mut(&mut self) -> &mut (dyn Error + Send + Sync + 'static) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_error(self: Box<Self>) -> Box<dyn Error + Send + Sync + 'static> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kind(&self) -> sqlx::error::ErrorKind {
|
||||||
|
sqlx::error::ErrorKind::Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for ParseError {
|
impl Display for ParseError {
|
||||||
fn fmt(
|
fn fmt(
|
||||||
&self,
|
&self,
|
||||||
f: &mut std::fmt::Formatter<'_>,
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
) -> std::fmt::Result {
|
) -> std::fmt::Result {
|
||||||
let message = match self {
|
let message = match self {
|
||||||
Self::NonBackgroundFile => "The file is not a recognized image or video type",
|
Self::NonBackgroundFile => {
|
||||||
|
"The file is not a recognized image or video type"
|
||||||
|
}
|
||||||
};
|
};
|
||||||
write!(f, "Error: {message}")
|
write!(f, "Error: {message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum BackgroundKind {
|
pub enum BackgroundKind {
|
||||||
#[default]
|
#[default]
|
||||||
Image,
|
Image,
|
||||||
|
@ -141,7 +175,8 @@ impl From<Video> for Slide {
|
||||||
fn from(video: Video) -> Self {
|
fn from(video: Video) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kind: ServiceItemKind::Video,
|
kind: ServiceItemKind::Video,
|
||||||
background: Background::try_from(video.path).unwrap_or_default(),
|
background: Background::try_from(video.path)
|
||||||
|
.unwrap_or_default(),
|
||||||
video_loop: video.looping,
|
video_loop: video.looping,
|
||||||
video_start_time: video.start_time.unwrap_or_default(),
|
video_start_time: video.start_time.unwrap_or_default(),
|
||||||
video_end_time: video.end_time.unwrap_or_default(),
|
video_end_time: video.end_time.unwrap_or_default(),
|
||||||
|
@ -176,17 +211,23 @@ impl SlideModel {
|
||||||
let slides: Vec<Slide> = lyrics
|
let slides: Vec<Slide> = lyrics
|
||||||
.iter()
|
.iter()
|
||||||
.map(|lyric| Slide {
|
.map(|lyric| Slide {
|
||||||
background: song.background.clone().unwrap_or_default(),
|
background: song
|
||||||
|
.background
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default(),
|
||||||
text: lyric.to_owned(),
|
text: lyric.to_owned(),
|
||||||
font: song.font.clone().unwrap_or_default(),
|
font: song.font.clone().unwrap_or_default(),
|
||||||
font_size: song.font_size.unwrap_or_default(),
|
font_size: song.font_size.unwrap_or_default(),
|
||||||
kind: ServiceItemKind::Song,
|
kind: ServiceItemKind::Song,
|
||||||
text_alignment: song.text_alignment.unwrap_or_default(),
|
text_alignment: song
|
||||||
|
.text_alignment
|
||||||
|
.unwrap_or_default(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
for (location, slide) in slides.iter().enumerate() {
|
for (location, slide) in slides.iter().enumerate() {
|
||||||
self.slides.insert(location + index as usize, slide.clone());
|
self.slides
|
||||||
|
.insert(location + index as usize, slide.clone());
|
||||||
debug!("Here is the current slide_model: {:?}", self);
|
debug!("Here is the current slide_model: {:?}", self);
|
||||||
}
|
}
|
||||||
if self.slides.len() > current_length {
|
if self.slides.len() > current_length {
|
||||||
|
@ -200,32 +241,46 @@ impl SlideModel {
|
||||||
self.slides.get(index as usize)
|
self.slides.get(index as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_video(&mut self, video: Video, index: i32) -> Result<()> {
|
pub fn add_video(
|
||||||
|
&mut self,
|
||||||
|
video: Video,
|
||||||
|
index: i32,
|
||||||
|
) -> Result<()> {
|
||||||
self.slides.insert(index as usize, Slide::from(video));
|
self.slides.insert(index as usize, Slide::from(video));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_image(&mut self, image: Image, index: i32) -> Result<()> {
|
pub fn add_image(
|
||||||
|
&mut self,
|
||||||
|
image: Image,
|
||||||
|
index: i32,
|
||||||
|
) -> Result<()> {
|
||||||
self.slides.insert(index as usize, Slide::from(image));
|
self.slides.insert(index as usize, Slide::from(image));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_presentation(&mut self, presentation: Presentation, index: i32) -> Result<()> {
|
pub fn add_presentation(
|
||||||
self.slides.insert(index as usize, Slide::from(presentation));
|
&mut self,
|
||||||
|
presentation: Presentation,
|
||||||
|
index: i32,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.slides
|
||||||
|
.insert(index as usize, Slide::from(presentation));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use super::*;
|
||||||
use crate::songs::Song;
|
use crate::songs::Song;
|
||||||
use pretty_assertions::{assert_eq, assert_ne};
|
use pretty_assertions::{assert_eq, assert_ne};
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn test_song() -> Song {
|
fn test_song() -> Song {
|
||||||
Song {
|
Song {
|
||||||
title: "From The Day".to_string(),
|
title: "From The Day".to_string(),
|
||||||
lyrics: Some("Verse 1
|
lyrics: Some(
|
||||||
|
"Verse 1
|
||||||
When You found me,
|
When You found me,
|
||||||
I was so blind
|
I was so blind
|
||||||
My sin was before me,
|
My sin was before me,
|
||||||
|
@ -281,22 +336,26 @@ Other 2
|
||||||
Ending 1
|
Ending 1
|
||||||
Oh Oh Oh
|
Oh Oh Oh
|
||||||
From the day
|
From the day
|
||||||
You saved my soul".to_string()),
|
You saved my soul"
|
||||||
verse_order: Some("O1 V1 C1 C2 O2 V2 C3 C2 O2 B1 C2 C2 E1 O2"
|
.to_string(),
|
||||||
|
),
|
||||||
|
verse_order: Some(
|
||||||
|
"O1 V1 C1 C2 O2 V2 C3 C2 O2 B1 C2 C2 E1 O2"
|
||||||
.to_string()
|
.to_string()
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.collect()),
|
.collect(),
|
||||||
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_song_verse_one() -> String {
|
fn test_song_verse_one() -> String {
|
||||||
"When You found me,\nI was so blind\nMy sin was before me,\nI was swallowed by pride".to_string()
|
"When You found me,\nI was so blind\nMy sin was before me,\nI was swallowed by pride".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_song_last_verse() -> String {
|
fn test_song_last_verse() -> String {
|
||||||
"Oh Oh Oh\nFrom the day\nYou saved my soul\n".to_string()
|
"Oh Oh Oh\nFrom the day\nYou saved my soul\n".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -318,9 +377,12 @@ You saved my soul".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert_eq!(slide, &new_slide);
|
assert_eq!(slide, &new_slide);
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
panic!("There was a problem adding the slide: {:?}", e);
|
panic!(
|
||||||
|
"There was a problem adding the slide: {:?}",
|
||||||
|
e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use color_eyre::eyre::{eyre, Result};
|
use color_eyre::eyre::{eyre, Context, Result};
|
||||||
use sqlx::query;
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::{query, query_as, sqlite::SqliteRow, FromRow, Row};
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
model::Model,
|
model::{get_db, Model},
|
||||||
slides::{Background, TextAlignment},
|
slides::{Background, TextAlignment},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Song {
|
pub struct Song {
|
||||||
|
pub id: i32,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub lyrics: Option<String>,
|
pub lyrics: Option<String>,
|
||||||
pub author: Option<String>,
|
pub author: Option<String>,
|
||||||
|
@ -31,34 +33,78 @@ const VERSE_KEYWORDS: [&str; 24] = [
|
||||||
"Other 2", "Other 3", "Other 4",
|
"Other 2", "Other 3", "Other 4",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
impl FromRow<'_, SqliteRow> for Song {
|
||||||
|
fn from_row(row: &SqliteRow) -> sqlx::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
id: row.try_get(12)?,
|
||||||
|
title: row.try_get(5)?,
|
||||||
|
lyrics: row.try_get(8)?,
|
||||||
|
author: row.try_get(10)?,
|
||||||
|
ccli: row.try_get(9)?,
|
||||||
|
audio: Some(PathBuf::from({
|
||||||
|
let string: String = row.try_get(11)?;
|
||||||
|
string
|
||||||
|
})),
|
||||||
|
verse_order: Some({
|
||||||
|
let str: &str = row.try_get(0)?;
|
||||||
|
str.split(' ').map(|s| s.to_string()).collect()
|
||||||
|
}),
|
||||||
|
background: Some({
|
||||||
|
let string: String = row.try_get(7)?;
|
||||||
|
Background::try_from(string)?
|
||||||
|
}),
|
||||||
|
text_alignment: Some({
|
||||||
|
let horizontal_alignment: String = row.try_get(3)?;
|
||||||
|
let vertical_alignment: String = row.try_get(4)?;
|
||||||
|
match (horizontal_alignment.to_lowercase().as_str(), vertical_alignment.to_lowercase().as_str()) {
|
||||||
|
("left", "top") => TextAlignment::TopLeft,
|
||||||
|
("left", "center") => TextAlignment::MiddleLeft,
|
||||||
|
("left", "bottom") => TextAlignment::BottomLeft,
|
||||||
|
("center", "top") => TextAlignment::TopCenter,
|
||||||
|
("center", "center") => TextAlignment::MiddleCenter,
|
||||||
|
("center", "bottom") => TextAlignment::BottomCenter,
|
||||||
|
("right", "top") => TextAlignment::TopRight,
|
||||||
|
("right", "center") => TextAlignment::MiddleRight,
|
||||||
|
("right", "bottom") => TextAlignment::BottomRight,
|
||||||
|
_ => TextAlignment::MiddleCenter
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
font: row.try_get(6)?,
|
||||||
|
font_size: row.try_get(1)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn get_song_from_db(index: i32) -> Result<Song> {
|
||||||
|
let mut db = get_db();
|
||||||
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
|
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}")),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Model<Song> {
|
impl Model<Song> {
|
||||||
pub fn load_from_db(&mut self) {
|
pub fn load_from_db(&mut self) {
|
||||||
// static DATABASE_URL: &str = "sqlite:///home/chris/.local/share/lumina/library-db.sqlite3";
|
// static DATABASE_URL: &str = "sqlite:///home/chris/.local/share/lumina/library-db.sqlite3";
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
rt.block_on(async {
|
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"#).fetch_all(&mut self.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"#).fetch_all(&mut self.db).await;
|
||||||
match result {
|
match result {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
for song in s.into_iter() {
|
for song in s.into_iter() {
|
||||||
let _ = self.add_item(Song {
|
match Song::from_row(&song) {
|
||||||
title: song.title,
|
Ok(song) => {
|
||||||
lyrics: Some(song.lyrics),
|
let _ = self.add_item(song);
|
||||||
author: Some(song.author),
|
|
||||||
ccli: Some(song.ccli),
|
|
||||||
audio: Some(song.audio.into()),
|
|
||||||
verse_order: Some(song.verse_order.split(" ").map(|s| s.to_string()).collect()),
|
|
||||||
background: Some(song.background.try_into().unwrap_or_default()),
|
|
||||||
text_alignment: {
|
|
||||||
if song.horizontal_text_alignment == "center" && song.vertical_text_alignment == "center" {
|
|
||||||
Some(TextAlignment::MiddleCenter)
|
|
||||||
} else {
|
|
||||||
Some(TextAlignment::TopCenter)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
font: Some(song.font),
|
Err(e) => error!("Could not convert song: {e}"),
|
||||||
font_size: Some(song.font_size),
|
};
|
||||||
});
|
};
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("There was an error in converting songs: {e}");
|
error!("There was an error in converting songs: {e}");
|
||||||
|
@ -66,17 +112,26 @@ impl Model<Song> {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_item(&self, index: i32) -> Option<&Song> {
|
||||||
|
self.items.iter().find(|s| s.id == index)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Song {
|
impl Song {
|
||||||
pub fn get_lyrics(&self) -> Result<Vec<String>> {
|
pub fn get_lyrics(&self) -> Result<Vec<String>> {
|
||||||
let mut lyric_list = Vec::new();
|
let mut lyric_list = Vec::new();
|
||||||
if self.lyrics.is_none() {
|
if self.lyrics.is_none() {
|
||||||
return Err(eyre!("There is no lyrics here"))
|
return Err(eyre!("There is no lyrics here"));
|
||||||
} else if self.verse_order.is_none() {
|
} else if self.verse_order.is_none() {
|
||||||
return Err(eyre!("There is no verse_order here"))
|
return Err(eyre!("There is no verse_order here"));
|
||||||
} else if self.verse_order.clone().is_some_and(|v| v.is_empty()) {
|
} else if self
|
||||||
return Err(eyre!("There is no verse_order here"))
|
.verse_order
|
||||||
|
.clone()
|
||||||
|
.is_some_and(|v| v.is_empty())
|
||||||
|
{
|
||||||
|
return Err(eyre!("There is no verse_order here"));
|
||||||
}
|
}
|
||||||
if let Some(raw_lyrics) = self.lyrics.clone() {
|
if let Some(raw_lyrics) = self.lyrics.clone() {
|
||||||
let raw_lyrics = raw_lyrics.as_str();
|
let raw_lyrics = raw_lyrics.as_str();
|
||||||
|
@ -232,14 +287,24 @@ You saved my soul"
|
||||||
pub fn test_db_and_model() {
|
pub fn test_db_and_model() {
|
||||||
let mut song_model: Model<Song> = Model::default();
|
let mut song_model: Model<Song> = Model::default();
|
||||||
song_model.load_from_db();
|
song_model.load_from_db();
|
||||||
if let Some(song) = song_model.get_item(3) {
|
if let Some(song) = song_model.get_item(7) {
|
||||||
let test_song = test_song();
|
let test_song = test_song();
|
||||||
assert_eq!(test_song.title, song.title);
|
assert_eq!(&test_song, song);
|
||||||
} else {
|
} else {
|
||||||
assert!(false);
|
assert!(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_song_from_db() {
|
||||||
|
let song = test_song();
|
||||||
|
let result = get_song_from_db(7);
|
||||||
|
match result {
|
||||||
|
Ok(db_song) => assert_eq!(song, db_song),
|
||||||
|
Err(e) => assert!(false, "{e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_update() {
|
pub fn test_update() {
|
||||||
let song = test_song();
|
let song = test_song();
|
||||||
|
@ -252,14 +317,38 @@ You saved my soul"
|
||||||
&cloned_song,
|
&cloned_song,
|
||||||
song_model.get_item(2).unwrap()
|
song_model.get_item(2).unwrap()
|
||||||
),
|
),
|
||||||
Err(e) => assert!(false, "{:?}", e),
|
Err(e) => assert!(false, "{e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_song() -> Song {
|
fn test_song() -> Song {
|
||||||
Song {
|
Song {
|
||||||
|
id: 7,
|
||||||
title: "Death Was Arrested".to_string(),
|
title: "Death Was Arrested".to_string(),
|
||||||
..Default::default()
|
lyrics: Some("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".to_string()),
|
||||||
|
author: Some(
|
||||||
|
"North Point Worship".to_string(),
|
||||||
|
),
|
||||||
|
ccli: None,
|
||||||
|
audio: Some("file:///home/chris/music/North Point InsideOut/Nothing Ordinary, Pt. 1 (Live)/05 Death Was Arrested (feat. Seth Condrey).mp3".into()),
|
||||||
|
verse_order: Some(vec![
|
||||||
|
"I1".to_string(),
|
||||||
|
"V1".to_string(),
|
||||||
|
"V2".to_string(),
|
||||||
|
"C1".to_string(),
|
||||||
|
"V3".to_string(),
|
||||||
|
"C1".to_string(),
|
||||||
|
"V4".to_string(),
|
||||||
|
"C1".to_string(),
|
||||||
|
"B1".to_string(),
|
||||||
|
"B1".to_string(),
|
||||||
|
"E1".to_string(),
|
||||||
|
"E2".to_string(),
|
||||||
|
]),
|
||||||
|
background: Some(Background::try_from("file:///home/chris/nc/tfc/openlp/CMG - Bright Mountains 01.jpg".to_string()).unwrap()),
|
||||||
|
text_alignment: Some(TextAlignment::MiddleCenter),
|
||||||
|
font: Some("Quicksand Bold".to_string()),
|
||||||
|
font_size: Some(60)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use crate::model::Model;
|
use crate::model::Model;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::query_as;
|
use sqlx::query_as;
|
||||||
use tracing::error;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Video {
|
pub struct Video {
|
||||||
|
pub id: i32,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub start_time: Option<f32>,
|
pub start_time: Option<f32>,
|
||||||
|
@ -16,20 +18,19 @@ impl Model<Video> {
|
||||||
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();
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
let result = query_as!(Video, r#"SELECT title as "title!", filePath as "path!", startTime as "start_time!: f32", endTime as "end_time!: f32", loop as "looping!" from videos"#).fetch_all(&mut self.db).await;
|
let result = 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"#).fetch_all(&mut self.db).await;
|
||||||
match result {
|
match result {
|
||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
for video in v.into_iter() {
|
for video in v.into_iter() {
|
||||||
let _ = self.add_item(video);
|
let _ = self.add_item(video);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => error!("There was an error in converting songs: {e}"),
|
Err(e) => error!("There was an error in converting videos: {e}"),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -64,9 +65,16 @@ mod test {
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
assert_eq!(&video, video_model.get_item(0).unwrap());
|
assert_eq!(&video, video_model.get_item(0).unwrap());
|
||||||
assert_ne!(&new_video, video_model.get_item(0).unwrap());
|
assert_ne!(
|
||||||
|
&new_video,
|
||||||
|
video_model.get_item(0).unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Err(e) => assert!(false, "There was an error adding the video: {:?}", e),
|
Err(e) => assert!(
|
||||||
|
false,
|
||||||
|
"There was an error adding the video: {:?}",
|
||||||
|
e
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue