musicus/src/library.rs

111 lines
2.9 KiB
Rust
Raw Normal View History

2025-03-01 09:57:01 +01:00
use std::{
2025-03-03 17:28:07 +01:00
cell::OnceCell,
2025-03-01 09:57:01 +01:00
path::{Path, PathBuf},
2025-03-03 17:28:07 +01:00
sync::{Arc, Mutex},
2023-09-30 18:26:11 +02:00
};
2025-01-17 18:16:01 +01:00
use adw::{
glib::{self, subclass::Signal, Properties},
prelude::*,
subclass::prelude::*,
};
2025-04-27 15:22:04 +02:00
use anyhow::{anyhow, Context, Result};
use diesel::{prelude::*, SqliteConnection};
2025-01-17 18:16:01 +01:00
use once_cell::sync::Lazy;
2024-03-23 18:06:46 +01:00
2025-04-27 15:22:04 +02:00
use crate::db::{self, schema::*, tables};
pub use query::LibraryQuery;
2024-03-23 18:06:46 +01:00
2025-04-27 15:22:04 +02:00
pub mod edit;
pub mod exchange;
pub mod query;
2024-03-23 18:06:46 +01:00
2023-09-30 18:26:11 +02:00
mod imp {
use super::*;
#[derive(Properties, Default)]
2025-03-01 09:57:01 +01:00
#[properties(wrapper_type = super::Library)]
pub struct Library {
2023-10-07 22:49:20 +02:00
#[property(get, construct_only)]
pub folder: OnceCell<String>,
2025-03-03 17:28:07 +01:00
pub connection: OnceCell<Arc<Mutex<SqliteConnection>>>,
2023-09-30 18:26:11 +02:00
}
#[glib::object_subclass]
2025-03-01 09:57:01 +01:00
impl ObjectSubclass for Library {
2023-09-30 18:26:11 +02:00
const NAME: &'static str = "MusicusLibrary";
2025-03-01 09:57:01 +01:00
type Type = super::Library;
2023-09-30 18:26:11 +02:00
}
#[glib::derived_properties]
2025-03-01 09:57:01 +01:00
impl ObjectImpl for Library {
2025-01-17 18:16:01 +01:00
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> =
Lazy::new(|| vec![Signal::builder("changed").build()]);
SIGNALS.as_ref()
}
2023-10-07 22:49:20 +02:00
}
2023-09-30 18:26:11 +02:00
}
glib::wrapper! {
2025-03-01 09:57:01 +01:00
pub struct Library(ObjectSubclass<imp::Library>);
2023-09-30 18:26:11 +02:00
}
2025-03-01 09:57:01 +01:00
impl Library {
pub fn new(path: impl AsRef<Path>) -> Result<Self> {
let obj: Self = glib::Object::builder()
2023-10-07 22:49:20 +02:00
.property("folder", path.as_ref().to_str().unwrap())
.build();
obj.init()?;
Ok(obj)
2023-10-07 22:49:20 +02:00
}
2023-09-30 18:26:11 +02:00
2025-03-23 16:04:14 +01:00
/// Whether this library is empty. The library is considered empty, if
/// there are no tracks.
pub fn is_empty(&self) -> Result<bool> {
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
Ok(tracks::table
.first::<tables::Track>(connection)
.optional()?
.is_none())
}
2025-01-17 18:16:01 +01:00
pub fn connect_changed<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
self.connect_local("changed", true, move |values| {
let obj = values[0].get::<Self>().unwrap();
f(&obj);
None
})
}
2025-03-03 17:28:07 +01:00
pub fn changed(&self) {
2025-01-17 18:16:01 +01:00
let obj = self.clone();
// Note: This is a dirty hack to let the calling function return before
// signal handlers are called. This is neccessary because RefCells
// may still be borrowed otherwise.
glib::spawn_future_local(async move {
obj.emit_by_name::<()>("changed", &[]);
});
}
fn init(&self) -> Result<()> {
let db_path = PathBuf::from(&self.folder()).join("musicus.db");
let connection = db::connect(
db_path
.to_str()
.ok_or_else(|| anyhow!("Failed to convert libary path to string"))?,
)
.context("Failed to connect to music library database")?;
self.imp()
.connection
.set(Arc::new(Mutex::new(connection)))
.map_err(|_| anyhow!("Library already initialized"))?;
Ok(())
}
2023-09-30 18:26:11 +02:00
}