diff --git a/TODO.org b/TODO.org index b7eedd5..d7b92a4 100644 --- a/TODO.org +++ b/TODO.org @@ -12,6 +12,8 @@ This is working but the right click context menu is all the way on the edge of t Let's build some tests that ensure that these functions are working for the models. Make sure the models are built in such a way as to make sure that they are testable and work fast for the user. +Need to make some tests in the library module that can run things without needing messages as much. I'm redoing how the models work too such that the adding, updating, and removing of items all happens in just the model without needing to have a separate function to do the same action in the database. + * TODO [#B] Font in the song editor doesn't always use the original version There seems to be some issue with fontdb not able to decipher all the versions of some fonts that are OTF and then end up loading the wrong ones in some issues. diff --git a/src/core/images.rs b/src/core/images.rs index 3667c49..a06ab61 100644 --- a/src/core/images.rs +++ b/src/core/images.rs @@ -156,6 +156,70 @@ impl ServiceTrait for Image { } impl Model { + pub async fn append_image( + &mut self, + image: Image, + db: PoolConnection, + ) -> Result<()> { + todo!() + } + + pub async fn new_image( + &mut self, + db: PoolConnection, + ) -> Result { + todo!() + } + pub async fn update_image( + &mut self, + image: Image, + db: PoolConnection, + ) -> Result<()> { + let id = image.id; + self.update_item(image.clone(), |current_image| { + current_image.id == id + })?; + let path = image + .path + .to_str() + .map(std::string::ToString::to_string) + .unwrap_or_default(); + let mut db = db.detach(); + debug!(?image, "should be been updated"); + let result = query!( + r#"UPDATE images SET title = $2, file_path = $3 WHERE id = $1"#, + image.id, + image.title, + path, + ) + .execute(&mut db) + .await.into_diagnostic(); + + match result { + Ok(_) => { + debug!("should have been updated"); + Ok(()) + } + Err(e) => { + error! {?e}; + Err(e) + } + } + } + + pub async fn remove_image( + &mut self, + id: i32, + db: PoolConnection, + ) -> Result<()> { + self.remove_item(|image| image.id == id)?; + query!("DELETE FROM images WHERE id = $1", id) + .execute(&mut db.detach()) + .await + .into_diagnostic() + .map(|_| ()) + } + pub async fn new_image_model(db: &mut SqlitePool) -> Self { let mut model = Self { items: vec![], diff --git a/src/core/model.rs b/src/core/model.rs index 03615b4..6762a84 100644 --- a/src/core/model.rs +++ b/src/core/model.rs @@ -83,30 +83,35 @@ impl Model { todo!() } - pub fn update_item(&mut self, item: T, index: i32) -> Result<()> { + pub fn update_item

( + &mut self, + item: T, + predicate: P, + ) -> Result<()> + where + P: Fn(&T) -> bool, + { self.items - .get_mut( - usize::try_from(index) - .expect("Shouldn't be negative"), - ) - .map_or_else( - || { - Err(miette!( - "Item doesn't exist in model. Id was {index}" - )) - }, - |current_item| { - let _old_item = replace(current_item, item); - Ok(()) - }, - ) + .iter() + .position(predicate) + .ok_or(miette!("Item cannot be found")) + .map(|index| self.items.get_mut(index).expect("Since we found position this should always exist")) + .map(|current_item| { + let _old_item = replace(current_item, item); + }) } - pub fn remove_item(&mut self, index: i32) -> Result<()> { - self.items.remove( - usize::try_from(index).expect("Shouldn't be negative"), - ); - Ok(()) + pub fn remove_item

(&mut self, predicate: P) -> Result<()> + where + P: Fn(&T) -> bool, + { + self.items + .iter() + .position(predicate) + .ok_or(miette!("Item cannot be found")) + .map(|index| { + self.items.remove(index); + }) } #[must_use] @@ -123,11 +128,12 @@ impl Model { self.items.iter().find(f) } - pub fn insert_item(&mut self, item: T, index: i32) -> Result<()> { - self.items.insert( - usize::try_from(index).expect("Shouldn't be negative"), - item, - ); + pub fn insert_item( + &mut self, + item: T, + index: usize, + ) -> Result<()> { + self.items.insert(index, item); Ok(()) } } diff --git a/src/core/presentations.rs b/src/core/presentations.rs index 4a74f79..ce4dcac 100644 --- a/src/core/presentations.rs +++ b/src/core/presentations.rs @@ -298,6 +298,35 @@ impl FromRow<'_, SqliteRow> for Presentation { } impl Model { + pub async fn append_presentation( + &mut self, + presentation: Presentation, + db: PoolConnection, + ) -> Result<()> { + todo!() + } + + pub async fn new_presentation( + &mut self, + db: PoolConnection, + ) -> Result<()> { + todo!() + } + + pub async fn update_presentation( + &mut self, + presentation: Presentation, + db: PoolConnection, + ) -> Result<()> { + todo!() + } + pub async fn remove_presentation( + &mut self, + id: i32, + db: PoolConnection, + ) -> Result<()> { + todo!() + } pub async fn new_presentation_model(db: &mut SqlitePool) -> Self { let mut model = Self { items: vec![], diff --git a/src/core/songs.rs b/src/core/songs.rs index 77ddee2..eedfc98 100644 --- a/src/core/songs.rs +++ b/src/core/songs.rs @@ -14,9 +14,8 @@ use itertools::Itertools; use miette::{IntoDiagnostic, Result, miette}; use serde::{Deserialize, Serialize}; use sqlx::{ - FromRow, Row, Sqlite, SqliteConnection, SqliteExecutor, - SqlitePool, Transaction, pool::PoolConnection, query, - sqlite::SqliteRow, + FromRow, Row, Sqlite, SqliteConnection, SqlitePool, + pool::PoolConnection, query, sqlite::SqliteRow, }; use tracing::{debug, error}; @@ -740,6 +739,187 @@ pub async fn get_song_from_db( } impl Model { + // Not sure we will use this function. As it is, it makes more sense for + // a new song to be made within the model and then passed back out. + // But maybe for encapsulation reasons, it makes sense to have this? + pub async fn append_song( + &mut self, + song: Song, + db: PoolConnection, + ) -> Result<()> { + self.add_item(song)?; + todo!() + } + + pub async fn new_song( + &mut self, + db: PoolConnection, + ) -> Result { + let mut song = Song::default(); + + let verse_order = { + song.verse_order.clone().map_or_else(String::new, |vo| { + vo.into_iter() + .map(|mut s| { + s.push(' '); + s + }) + .collect::() + }) + }; + + let audio = song + .audio + .clone() + .map(|a| a.to_str().unwrap_or_default().to_string()); + + let background = song + .background + .clone() + .map(|b| b.path.to_str().unwrap_or_default().to_string()); + + let res = query!( + r#"INSERT INTO songs (title, lyrics, author, ccli, verse_order, audio, font, font_size, background) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)"#, + song.title, + song.lyrics, + song.author, + song.ccli, + verse_order, + audio, + song.font, + song.font_size, + background + ) + .execute(&mut db.detach()) + .await + .into_diagnostic()?; + song.id = i32::try_from(res.last_insert_rowid()).expect( + "Fairly confident that this number won't get that high", + ); + self.add_item(song.clone())?; + Ok(song) + } + + pub async fn update_song( + &mut self, + song: Song, + db: PoolConnection, + ) -> Result<()> { + let id = song.id; + self.update_item(song.clone(), |song| song.id == id)?; + // debug!(?item); + let verse_order = + ron::ser::to_string(&song.verses).into_diagnostic()?; + + let audio = song + .audio + .map(|a| a.to_str().unwrap_or_default().to_string()); + + let background = song + .background + .map(|b| b.path.to_str().unwrap_or_default().to_string()); + + let lyrics = song.verse_map.map(|map| { + map.iter() + .map(|(name, lyric)| { + let lyric = + lyric.trim_end_matches('\n').to_string(); + (name.to_owned(), lyric) + }) + .collect::>() + }); + let lyrics = + ron::ser::to_string(&lyrics).into_diagnostic()?; + + let (vertical_alignment, horizontal_alignment) = + song.text_alignment.map_or_else( + || ("center", "center"), + |ta| match ta { + TextAlignment::TopLeft => ("top", "left"), + TextAlignment::TopCenter => ("top", "center"), + TextAlignment::TopRight => ("top", "right"), + TextAlignment::MiddleLeft => ("center", "left"), + TextAlignment::MiddleCenter => { + ("center", "center") + } + TextAlignment::MiddleRight => ("center", "right"), + TextAlignment::BottomLeft => ("bottom", "left"), + TextAlignment::BottomCenter => { + ("bottom", "center") + } + TextAlignment::BottomRight => ("bottom", "right"), + }, + ); + + let stroke_size = song.stroke_size.unwrap_or_default(); + let shadow_size = song.shadow_size.unwrap_or_default(); + let (shadow_offset_x, shadow_offset_y) = + song.shadow_offset.unwrap_or_default(); + + let stroke_color = ron::ser::to_string(&song.stroke_color) + .into_diagnostic()?; + let shadow_color = ron::ser::to_string(&song.shadow_color) + .into_diagnostic()?; + + let style = ron::ser::to_string(&song.font_style) + .into_diagnostic()?; + let weight = ron::ser::to_string(&song.font_weight) + .into_diagnostic()?; + + // debug!( + // ?stroke_size, + // ?stroke_color, + // ?shadow_size, + // ?shadow_color, + // ?shadow_offset_x, + // ?shadow_offset_y + // ); + + let result = query!( + r#"UPDATE songs SET title = $2, lyrics = $3, author = $4, ccli = $5, verse_order = $6, audio = $7, font = $8, font_size = $9, background = $10, horizontal_text_alignment = $11, vertical_text_alignment = $12, stroke_color = $13, shadow_color = $14, stroke_size = $15, shadow_size = $16, shadow_offset_x = $17, shadow_offset_y = $18, style = $19, weight = $20 WHERE id = $1"#, + song.id, + song.title, + lyrics, + song.author, + song.ccli, + verse_order, + audio, + song.font, + song.font_size, + background, + horizontal_alignment, + vertical_alignment, + stroke_color, + shadow_color, + stroke_size, + shadow_size, + shadow_offset_x, + shadow_offset_y, + style, + weight + ) + .execute(&mut db.detach()) + .await + .into_diagnostic()?; + + debug!(rows_affected = ?result.rows_affected()); + + Ok(()) + } + + pub async fn remove_song( + &mut self, + id: i32, + db: PoolConnection, + ) -> Result<()> { + self.remove_item(|current_song| id == current_song.id)?; + query!("DELETE FROM songs WHERE id = $1", id) + .execute(&mut db.detach()) + .await + .into_diagnostic() + .map(|_| ()) + } + pub async fn new_song_model(db: &mut SqlitePool) -> Self { let mut model = Self { items: vec![], @@ -1429,7 +1609,9 @@ You saved my soul" let test_song = song_model.get_item(2); assert_ne!(test_song, Some(&cloned_song)); - match song_model.update_item(song, 2) { + match song_model + .update_item(song, |song| song.id == cloned_song.id) + { Ok(()) => { let updated_model_song = song_model.find(|s| s.id == 7).unwrap(); diff --git a/src/core/videos.rs b/src/core/videos.rs index 970669b..05bfaa1 100644 --- a/src/core/videos.rs +++ b/src/core/videos.rs @@ -198,6 +198,36 @@ impl ServiceTrait for Video { } impl Model