starting to make db universal across app

This commit is contained in:
Chris Cochrun 2025-03-04 11:13:40 -06:00
parent 804850505e
commit d1ae7ba4f5
6 changed files with 196 additions and 122 deletions

View file

@ -9,7 +9,7 @@ use super::{
use crisp::types::{Keyword, Symbol, Value}; use crisp::types::{Keyword, Symbol, Value};
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{query_as, SqliteConnection}; use sqlx::{query_as, SqliteConnection, SqlitePool};
use std::path::PathBuf; use std::path::PathBuf;
use tracing::error; use tracing::error;
@ -125,13 +125,15 @@ impl ServiceTrait for Image {
} }
impl Model<Image> { impl Model<Image> {
pub async fn new_image_model(db: &mut SqliteConnection) -> Self { pub async fn new_image_model(db: &mut SqlitePool) -> Self {
let mut model = Self { let mut model = Self {
items: vec![], items: vec![],
kind: LibraryKind::Image, kind: LibraryKind::Image,
}; };
model.load_from_db(db).await; let mut db = db.acquire().await.expect("probs");
model.load_from_db(&mut db).await;
model model
} }

View file

@ -2,7 +2,8 @@ use crisp::types::{Keyword, Symbol, Value};
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{ use sqlx::{
prelude::FromRow, query, sqlite::SqliteRow, Row, SqliteConnection, prelude::FromRow, query, sqlite::SqliteRow, Row,
SqliteConnection, SqlitePool,
}; };
use std::path::PathBuf; use std::path::PathBuf;
use tracing::error; use tracing::error;
@ -166,15 +167,14 @@ impl FromRow<'_, SqliteRow> for Presentation {
} }
impl Model<Presentation> { impl Model<Presentation> {
pub async fn new_presentation_model( pub async fn new_presentation_model(db: &mut SqlitePool) -> Self {
db: &mut SqliteConnection,
) -> Self {
let mut model = Self { let mut model = Self {
items: vec![], items: vec![],
kind: LibraryKind::Presentation, kind: LibraryKind::Presentation,
}; };
let mut db = db.acquire().await.expect("probs");
model.load_from_db(db).await; model.load_from_db(&mut db).await;
model model
} }

View file

@ -6,6 +6,7 @@ use miette::{miette, IntoDiagnostic, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{ use sqlx::{
query, sqlite::SqliteRow, FromRow, Row, SqliteConnection, query, sqlite::SqliteRow, FromRow, Row, SqliteConnection,
SqlitePool,
}; };
use tracing::{debug, error}; use tracing::{debug, error};
@ -380,25 +381,68 @@ impl Model<Song> {
index: i32, index: i32,
db: &mut SqliteConnection, db: &mut SqliteConnection,
) -> Result<()> { ) -> Result<()> {
self.update_item(item, index)?; self.update_item(item.clone(), index)?;
let verse_order = {
if let Some(vo) = item.verse_order {
vo.into_iter()
.map(|mut s| {
s.push_str(" ");
s
})
.collect::<String>()
} else {
String::from("")
}
};
let audio = item
.audio
.map(|a| a.to_str().unwrap_or_default().to_string());
let background = item
.background
.map(|b| b.path.to_str().unwrap_or_default().to_string());
// let text_alignment = item.text_alignment.map(|ta| match ta {
// TextAlignment::TopLeft => todo!(),
// TextAlignment::TopCenter => todo!(),
// TextAlignment::TopRight => todo!(),
// TextAlignment::MiddleLeft => todo!(),
// TextAlignment::MiddleCenter => todo!(),
// TextAlignment::MiddleRight => todo!(),
// TextAlignment::BottomLeft => todo!(),
// TextAlignment::BottomCenter => todo!(),
// TextAlignment::BottomRight => todo!(),
// })
query!( query!(
r#"UPDATE songs SET title = {} WHERE id = {}"#, r#"UPDATE songs SET title = $2, lyrics = $3, author = $4, ccli = $5, verse_order = $6, audio = $7, font = $8, font_size = $9, background = $10 WHERE id = $1"#,
item.id,
item.title, item.title,
item.id item.lyrics,
item.author,
item.ccli,
verse_order,
audio,
item.font,
item.font_size,
background
) )
.fetch_one(db) .execute(db)
.await?; .await
.into_diagnostic()?;
Ok(()) Ok(())
} }
pub async fn new_song_model(db: &mut SqliteConnection) -> Self { pub async fn new_song_model(db: &mut SqlitePool) -> Self {
let mut model = Self { let mut model = Self {
items: vec![], items: vec![],
kind: LibraryKind::Song, kind: LibraryKind::Song,
}; };
let mut db = db.acquire().await.expect("probs");
model.load_from_db(db).await; model.load_from_db(&mut db).await;
model model
} }

View file

@ -11,7 +11,7 @@ use cosmic::iced::Executor;
use crisp::types::{Keyword, Symbol, Value}; use crisp::types::{Keyword, Symbol, Value};
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{query_as, SqliteConnection}; use sqlx::{query_as, SqliteConnection, SqlitePool};
use std::path::PathBuf; use std::path::PathBuf;
use tracing::error; use tracing::error;
@ -170,13 +170,14 @@ impl ServiceTrait for Video {
} }
impl Model<Video> { impl Model<Video> {
pub async fn new_video_model(db: &mut SqliteConnection) -> Self { pub async fn new_video_model(db: &mut SqlitePool) -> Self {
let mut model = Self { let mut model = Self {
items: vec![], items: vec![],
kind: LibraryKind::Video, kind: LibraryKind::Video,
}; };
let mut db = db.acquire().await.expect("probs");
model.load_from_db(db).await; model.load_from_db(&mut db).await;
model model
} }

View file

@ -1,5 +1,5 @@
use clap::{command, Parser}; use clap::{command, Parser};
use core::model::LibraryKind; use core::model::{get_db, LibraryKind};
use core::service_items::{ServiceItem, ServiceItemModel}; use core::service_items::{ServiceItem, ServiceItemModel};
use core::slide::*; use core::slide::*;
use cosmic::app::context_drawer::ContextDrawer; use cosmic::app::context_drawer::ContextDrawer;
@ -27,6 +27,7 @@ use cosmic::{widget::Container, Theme};
use crisp::types::Value; use crisp::types::Value;
use lisp::parse_lisp; use lisp::parse_lisp;
use miette::{miette, Result}; use miette::{miette, Result};
use sqlx::{SqliteConnection, SqlitePool};
use std::fs::read_to_string; use std::fs::read_to_string;
use std::path::PathBuf; use std::path::PathBuf;
use tracing::{debug, level_filters::LevelFilter}; use tracing::{debug, level_filters::LevelFilter};
@ -417,60 +418,34 @@ impl cosmic::Application for App {
} }
Message::SongEditor(message) => { Message::SongEditor(message) => {
debug!(?message); debug!(?message);
match message { let library_task = if let Some(mut song) =
song_editor::Message::ChangeFont(ref font) => { self.song_editor.song.clone()
if let Some(mut song) = {
self.song_editor.song.clone() match message {
{ song_editor::Message::ChangeFont(
ref font,
) => {
song.font = Some(font.to_string()); song.font = Some(font.to_string());
self.song_editor.song = self.song_editor.song =
Some(song.clone()); Some(song.clone());
if let Some(library) = &mut self.library { }
match library.update_song(song) { song_editor::Message::ChangeFontSize(
Ok(_) => (), font_size,
Err(e) => error!(?e), ) => {
}
};
};
}
song_editor::Message::ChangeFontSize(
font_size,
) => {
if let Some(mut song) =
self.song_editor.song.clone()
{
song.font_size = Some(font_size as i32); song.font_size = Some(font_size as i32);
self.song_editor.song = self.song_editor.song =
Some(song.clone()); Some(song.clone());
if let Some(library) = &mut self.library { }
match library.update_song(song) { song_editor::Message::ChangeTitle(
Ok(_) => (), ref title,
Err(e) => error!(?e), ) => {
}
};
};
}
song_editor::Message::ChangeTitle(ref title) => {
if let Some(mut song) =
self.song_editor.song.clone()
{
song.title = title.to_string(); song.title = title.to_string();
self.song_editor.song = self.song_editor.song =
Some(song.clone()); Some(song.clone());
if let Some(library) = &mut self.library { }
match library.update_song(song) { song_editor::Message::ChangeVerseOrder(
Ok(_) => (), ref vo,
Err(e) => error!(?e), ) => {
}
};
};
}
song_editor::Message::ChangeVerseOrder(
ref vo,
) => {
if let Some(mut song) =
self.song_editor.song.clone()
{
let verse_order = vo let verse_order = vo
.split(" ") .split(" ")
.into_iter() .into_iter()
@ -479,59 +454,49 @@ impl cosmic::Application for App {
song.verse_order = Some(verse_order); song.verse_order = Some(verse_order);
self.song_editor.song = self.song_editor.song =
Some(song.clone()); Some(song.clone());
if let Some(library) = &mut self.library { }
match library.update_song(song) { song_editor::Message::ChangeLyrics(
Ok(_) => (), ref action,
Err(e) => error!(?e), ) => {
} self.song_editor
}; .lyrics
}; .perform(action.clone());
} let lyrics =
song_editor::Message::ChangeLyrics( self.song_editor.lyrics.text();
ref action,
) => {
self.song_editor
.lyrics
.perform(action.clone());
let lyrics = self.song_editor.lyrics.text();
if let Some(mut song) =
self.song_editor.song.clone()
{
song.lyrics = Some(lyrics.to_string()); song.lyrics = Some(lyrics.to_string());
self.song_editor.song = self.song_editor.song =
Some(song.clone()); Some(song.clone());
if let Some(library) = &mut self.library { }
match library.update_song(song) { song_editor::Message::ChangeAuthor(
Ok(_) => (), ref author,
Err(e) => error!(?e), ) => {
}
};
};
}
song_editor::Message::ChangeAuthor(
ref author,
) => {
if let Some(mut song) =
self.song_editor.song.clone()
{
song.author = Some(author.to_string()); song.author = Some(author.to_string());
self.song_editor.song = self.song_editor.song =
Some(song.clone()); Some(song.clone());
if let Some(library) = &mut self.library { }
match library.update_song(song) { song_editor::Message::Edit(_) => todo!(),
Ok(_) => (), _ => (),
Err(e) => error!(?e), };
}
}; if let Some(library) = &mut self.library {
}; let task = library.update(
library::Message::UpdateSong(song),
);
task.map(|m| {
cosmic::app::Message::App(Message::None)
})
} else {
Task::none()
} }
song_editor::Message::Edit(_) => todo!(), } else {
_ => (), Task::none()
}; };
self.song_editor.update(message).map(|m| { let song_editor_task =
debug!(?m); self.song_editor.update(message).map(|m| {
cosmic::app::Message::App(Message::None) debug!(?m);
}) cosmic::app::Message::App(Message::None)
});
Task::batch(vec![song_editor_task, library_task])
} }
Message::Present(message) => { Message::Present(message) => {
// debug!(?message); // debug!(?message);
@ -853,7 +818,7 @@ where
} }
fn add_library(&mut self) -> Task<Message> { fn add_library(&mut self) -> Task<Message> {
Task::perform(async { Library::new().await }, |x| { Task::perform(async move { Library::new().await }, |x| {
cosmic::app::Message::App(Message::AddLibrary(x)) cosmic::app::Message::App(Message::AddLibrary(x))
}) })
} }

View file

@ -1,21 +1,20 @@
use cosmic::{ use cosmic::{
iced::{alignment::Vertical, Background, Border, Length}, iced::{alignment::Vertical, Background, Border, Length},
iced_core::widget::tree,
iced_widget::{column, row as rowm, text as textm}, iced_widget::{column, row as rowm, text as textm},
theme,
widget::{ widget::{
container, horizontal_space, icon, mouse_area, responsive, container, horizontal_space, icon, mouse_area, responsive,
row, scrollable, text, Container, DndSource, Space, Widget, row, scrollable, text, Container, DndSource, Space, Widget,
}, },
Element, Task, Element, Task,
}; };
use miette::{miette, Result}; use miette::{miette, IntoDiagnostic, Result};
use tracing::debug; use sqlx::{SqliteConnection, SqlitePool};
use tracing::{debug, error};
use crate::core::{ use crate::core::{
content::Content, content::Content,
images::Image, images::Image,
model::{get_db, LibraryKind, Model}, model::{LibraryKind, Model},
presentations::Presentation, presentations::Presentation,
service_items::ServiceItem, service_items::ServiceItem,
songs::Song, songs::Song,
@ -34,6 +33,7 @@ pub(crate) struct Library {
hovered_item: Option<(LibraryKind, i32)>, hovered_item: Option<(LibraryKind, i32)>,
dragged_item: Option<(LibraryKind, i32)>, dragged_item: Option<(LibraryKind, i32)>,
editing_item: Option<(LibraryKind, i32)>, editing_item: Option<(LibraryKind, i32)>,
db: SqlitePool,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -45,12 +45,13 @@ pub(crate) enum Message {
OpenLibrary(Option<LibraryKind>), OpenLibrary(Option<LibraryKind>),
HoverItem(Option<(LibraryKind, i32)>), HoverItem(Option<(LibraryKind, i32)>),
SelectItem(Option<(LibraryKind, i32)>), SelectItem(Option<(LibraryKind, i32)>),
UpdateSong(Song),
None, None,
} }
impl Library { impl<'a> Library {
pub async fn new() -> Self { pub async fn new() -> Self {
let mut db = get_db().await; let mut db = add_db().await.expect("probs");
Self { Self {
song_library: Model::new_song_model(&mut db).await, song_library: Model::new_song_model(&mut db).await,
image_library: Model::new_image_model(&mut db).await, image_library: Model::new_image_model(&mut db).await,
@ -65,6 +66,7 @@ impl Library {
hovered_item: None, hovered_item: None,
dragged_item: None, dragged_item: None,
editing_item: None, editing_item: None,
db,
} }
} }
@ -72,7 +74,7 @@ impl Library {
self.song_library.get_item(index) self.song_library.get_item(index)
} }
pub fn update(&mut self, message: Message) -> Task<Message> { pub fn update(&'a mut self, message: Message) -> Task<Message> {
match message { match message {
Message::AddItem => Task::none(), Message::AddItem => Task::none(),
Message::None => Task::none(), Message::None => Task::none(),
@ -98,6 +100,33 @@ impl Library {
self.selected_item = item; self.selected_item = item;
Task::none() Task::none()
} }
Message::UpdateSong(song) => {
let Some((kind, index)) = self.editing_item else {
error!("Not editing an item");
return Task::none();
};
if kind != LibraryKind::Song {
error!("Not editing a song item");
return Task::none();
}
let mut db = self.db.clone();
let mut song_library = self.song_library.clone();
let future = update_song(
song,
index as usize,
&mut db,
&mut song_library,
);
Task::perform(future, |r| {
match r {
Ok(_) => (),
Err(e) => error!(?e),
};
Message::None
})
}
} }
} }
@ -116,7 +145,7 @@ impl Library {
column.height(Length::Fill).spacing(5).into() column.height(Length::Fill).spacing(5).into()
} }
pub fn library_item<'a, T>( pub fn library_item<T>(
&'a self, &'a self,
model: &'a Model<T>, model: &'a Model<T>,
) -> Element<'a, Message> ) -> Element<'a, Message>
@ -257,7 +286,7 @@ impl Library {
column![button, lib_container].into() column![button, lib_container].into()
} }
fn single_item<'a, T>( fn single_item<T>(
&'a self, &'a self,
index: usize, index: usize,
item: &'a T, item: &'a T,
@ -342,7 +371,10 @@ impl Library {
.into() .into()
} }
pub(crate) fn update_song(&mut self, song: Song) -> Result<()> { pub(crate) async fn update_song(
&'a mut self,
song: Song,
) -> Result<()> {
let Some((kind, index)) = self.editing_item else { let Some((kind, index)) = self.editing_item else {
return Err(miette!("Not editing an item")); return Err(miette!("Not editing an item"));
}; };
@ -352,7 +384,11 @@ impl Library {
} }
if let Some(_) = self.song_library.items.get(index as usize) { if let Some(_) = self.song_library.items.get(index as usize) {
self.song_library.update_item(song, index); let mut db = self.db.acquire().await.expect("probs");
self.song_library
.update_song(song, index, &mut db)
.await?;
Ok(()) Ok(())
} else { } else {
Err(miette!("Song not found")) Err(miette!("Song not found"))
@ -360,6 +396,32 @@ impl Library {
} }
} }
async fn add_db() -> Result<SqlitePool> {
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());
SqlitePool::connect(&db_url).await.into_diagnostic()
}
pub(crate) async fn update_song(
song: Song,
index: usize,
db: &mut SqlitePool,
song_library: &mut Model<Song>,
) -> Result<()> {
if let Some(_) = song_library.items.get(index) {
let mut db = db.acquire().await.expect("foo");
song_library
.update_song(song, index as i32, &mut db)
.await?;
Ok(())
} else {
Err(miette!("Song not found"))
}
}
fn elide_text(text: String, width: f32) -> String { fn elide_text(text: String, width: f32) -> String {
const CHAR_SIZE: f32 = 8.0; const CHAR_SIZE: f32 = 8.0;
let text_length = text.len() as f32 * CHAR_SIZE; let text_length = text.len() as f32 * CHAR_SIZE;