2023-09-30 18:26:11 +02:00
|
|
|
use std::{
|
2024-03-23 18:06:46 +01:00
|
|
|
cell::{OnceCell, RefCell},
|
2023-10-07 22:49:20 +02:00
|
|
|
path::{Path, PathBuf},
|
2023-09-30 18:26:11 +02:00
|
|
|
};
|
|
|
|
|
|
2024-03-23 18:06:46 +01:00
|
|
|
use anyhow::Result;
|
2024-06-06 15:17:56 +02:00
|
|
|
use chrono::prelude::*;
|
2024-06-10 20:53:15 +02:00
|
|
|
use diesel::{
|
|
|
|
|
dsl::{exists, sql},
|
|
|
|
|
prelude::*,
|
|
|
|
|
sql_query,
|
|
|
|
|
sql_types::BigInt,
|
|
|
|
|
QueryDsl, SqliteConnection,
|
|
|
|
|
};
|
2024-03-23 18:06:46 +01:00
|
|
|
use gtk::{glib, glib::Properties, prelude::*, subclass::prelude::*};
|
|
|
|
|
|
2024-06-10 16:57:36 +02:00
|
|
|
use crate::{
|
|
|
|
|
db::{self, models::*, schema::*, tables, TranslatedString},
|
|
|
|
|
program::Program,
|
|
|
|
|
};
|
2024-03-23 18:06:46 +01:00
|
|
|
|
2024-06-10 14:34:46 +02:00
|
|
|
diesel::define_sql_function! {
|
2024-03-23 18:06:46 +01:00
|
|
|
/// Represents the SQL RANDOM() function.
|
|
|
|
|
fn random() -> Integer
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-30 18:26:11 +02:00
|
|
|
mod imp {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[derive(Properties, Default)]
|
|
|
|
|
#[properties(wrapper_type = super::MusicusLibrary)]
|
|
|
|
|
pub struct MusicusLibrary {
|
2023-10-07 22:49:20 +02:00
|
|
|
#[property(get, construct_only)]
|
|
|
|
|
pub folder: OnceCell<String>,
|
2024-03-23 18:06:46 +01:00
|
|
|
pub connection: RefCell<Option<SqliteConnection>>,
|
2023-09-30 18:26:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[glib::object_subclass]
|
|
|
|
|
impl ObjectSubclass for MusicusLibrary {
|
|
|
|
|
const NAME: &'static str = "MusicusLibrary";
|
|
|
|
|
type Type = super::MusicusLibrary;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[glib::derived_properties]
|
2023-10-07 22:49:20 +02:00
|
|
|
impl ObjectImpl for MusicusLibrary {
|
|
|
|
|
fn constructed(&self) {
|
|
|
|
|
self.parent_constructed();
|
2024-03-23 18:06:46 +01:00
|
|
|
|
|
|
|
|
let db_path = PathBuf::from(&self.folder.get().unwrap()).join("musicus.db");
|
|
|
|
|
let connection = db::connect(db_path.to_str().unwrap()).unwrap();
|
|
|
|
|
self.connection.set(Some(connection));
|
2023-10-07 22:49:20 +02:00
|
|
|
}
|
|
|
|
|
}
|
2023-09-30 18:26:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glib::wrapper! {
|
|
|
|
|
pub struct MusicusLibrary(ObjectSubclass<imp::MusicusLibrary>);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MusicusLibrary {
|
|
|
|
|
pub fn new(path: impl AsRef<Path>) -> Self {
|
2023-10-07 22:49:20 +02:00
|
|
|
glib::Object::builder()
|
|
|
|
|
.property("folder", path.as_ref().to_str().unwrap())
|
|
|
|
|
.build()
|
|
|
|
|
}
|
2023-09-30 18:26:11 +02:00
|
|
|
|
2024-03-23 18:06:46 +01:00
|
|
|
pub fn query(&self, query: &LibraryQuery) -> Result<LibraryResults> {
|
2023-10-07 22:49:20 +02:00
|
|
|
let search = format!("%{}%", query.search);
|
2024-03-23 18:06:46 +01:00
|
|
|
let mut binding = self.imp().connection.borrow_mut();
|
|
|
|
|
let connection = &mut *binding.as_mut().unwrap();
|
2023-09-30 18:26:11 +02:00
|
|
|
|
2024-03-23 18:06:46 +01:00
|
|
|
Ok(match query {
|
2023-10-07 22:49:20 +02:00
|
|
|
LibraryQuery {
|
2023-10-08 15:11:47 +02:00
|
|
|
composer: None,
|
|
|
|
|
performer: None,
|
2023-10-07 22:49:20 +02:00
|
|
|
ensemble: None,
|
|
|
|
|
work: None,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
2024-03-23 18:06:46 +01:00
|
|
|
let composers: Vec<Person> = persons::table
|
|
|
|
|
.filter(
|
|
|
|
|
exists(
|
|
|
|
|
work_persons::table
|
|
|
|
|
.filter(work_persons::person_id.eq(persons::person_id)),
|
|
|
|
|
)
|
|
|
|
|
.and(persons::name.like(&search)),
|
2023-11-05 14:27:03 +01:00
|
|
|
)
|
2024-03-23 18:06:46 +01:00
|
|
|
.limit(9)
|
|
|
|
|
.load(connection)?;
|
|
|
|
|
|
|
|
|
|
let performers: Vec<Person> = persons::table
|
|
|
|
|
.filter(
|
|
|
|
|
exists(
|
|
|
|
|
recording_persons::table
|
|
|
|
|
.filter(recording_persons::person_id.eq(persons::person_id)),
|
|
|
|
|
)
|
|
|
|
|
.and(persons::name.like(&search)),
|
2023-11-05 14:27:03 +01:00
|
|
|
)
|
2024-03-23 18:06:46 +01:00
|
|
|
.limit(9)
|
|
|
|
|
.load(connection)?;
|
|
|
|
|
|
|
|
|
|
// TODO: Search ensemble persons as well.
|
|
|
|
|
let ensembles: Vec<Ensemble> = ensembles::table
|
|
|
|
|
.filter(ensembles::name.like(&search))
|
|
|
|
|
.limit(9)
|
|
|
|
|
.load::<tables::Ensemble>(connection)?
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|e| Ensemble::from_table(e, connection))
|
|
|
|
|
.collect::<Result<Vec<Ensemble>>>()?;
|
|
|
|
|
|
|
|
|
|
let works: Vec<Work> = works::table
|
|
|
|
|
.inner_join(work_persons::table.inner_join(persons::table))
|
|
|
|
|
.filter(works::name.like(&search).or(persons::name.like(&search)))
|
|
|
|
|
.limit(9)
|
|
|
|
|
.select(works::all_columns)
|
|
|
|
|
.distinct()
|
|
|
|
|
.load::<tables::Work>(connection)?
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|w| Work::from_table(w, connection))
|
|
|
|
|
.collect::<Result<Vec<Work>>>()?;
|
2023-10-08 00:16:41 +02:00
|
|
|
|
2024-06-05 13:26:47 +02:00
|
|
|
let albums: Vec<Album> = albums::table
|
|
|
|
|
.filter(albums::name.like(&search))
|
|
|
|
|
.limit(9)
|
|
|
|
|
.load(connection)?;
|
|
|
|
|
|
2023-10-07 22:49:20 +02:00
|
|
|
LibraryResults {
|
2023-10-08 15:11:47 +02:00
|
|
|
composers,
|
|
|
|
|
performers,
|
2023-10-08 00:16:41 +02:00
|
|
|
ensembles,
|
|
|
|
|
works,
|
2024-06-05 13:26:47 +02:00
|
|
|
albums,
|
2023-10-07 22:49:20 +02:00
|
|
|
..Default::default()
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-08 00:16:41 +02:00
|
|
|
LibraryQuery {
|
2023-10-08 15:11:47 +02:00
|
|
|
composer: Some(composer),
|
|
|
|
|
performer: None,
|
2023-10-08 00:16:41 +02:00
|
|
|
ensemble: None,
|
|
|
|
|
work: None,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
2024-03-23 18:06:46 +01:00
|
|
|
let performers: Vec<Person> = persons::table
|
|
|
|
|
.inner_join(recording_persons::table.inner_join(
|
|
|
|
|
recordings::table.inner_join(works::table.inner_join(work_persons::table)),
|
|
|
|
|
))
|
|
|
|
|
.filter(
|
|
|
|
|
work_persons::person_id
|
|
|
|
|
.eq(&composer.person_id)
|
|
|
|
|
.and(persons::name.like(&search)),
|
2023-11-05 14:27:03 +01:00
|
|
|
)
|
2024-03-23 18:06:46 +01:00
|
|
|
.limit(9)
|
|
|
|
|
.select(persons::all_columns)
|
|
|
|
|
.distinct()
|
|
|
|
|
.load(connection)?;
|
|
|
|
|
|
|
|
|
|
let ensembles: Vec<Ensemble> = ensembles::table
|
|
|
|
|
.inner_join(recording_ensembles::table.inner_join(
|
|
|
|
|
recordings::table.inner_join(works::table.inner_join(work_persons::table)),
|
|
|
|
|
))
|
|
|
|
|
.filter(
|
|
|
|
|
work_persons::person_id
|
|
|
|
|
.eq(&composer.person_id)
|
|
|
|
|
.and(ensembles::name.like(&search)),
|
2023-11-05 14:27:03 +01:00
|
|
|
)
|
2024-03-23 18:06:46 +01:00
|
|
|
.limit(9)
|
|
|
|
|
.select(ensembles::all_columns)
|
|
|
|
|
.distinct()
|
|
|
|
|
.load::<tables::Ensemble>(connection)?
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|e| Ensemble::from_table(e, connection))
|
|
|
|
|
.collect::<Result<Vec<Ensemble>>>()?;
|
|
|
|
|
|
|
|
|
|
let works: Vec<Work> = works::table
|
|
|
|
|
.inner_join(work_persons::table)
|
|
|
|
|
.filter(
|
|
|
|
|
work_persons::person_id
|
|
|
|
|
.eq(&composer.person_id)
|
|
|
|
|
.and(works::name.like(&search)),
|
|
|
|
|
)
|
|
|
|
|
.limit(9)
|
|
|
|
|
.select(works::all_columns)
|
|
|
|
|
.distinct()
|
|
|
|
|
.load::<tables::Work>(connection)?
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|w| Work::from_table(w, connection))
|
|
|
|
|
.collect::<Result<Vec<Work>>>()?;
|
2023-10-08 00:16:41 +02:00
|
|
|
|
|
|
|
|
LibraryResults {
|
2023-10-08 15:11:47 +02:00
|
|
|
performers,
|
2023-10-08 00:16:41 +02:00
|
|
|
ensembles,
|
|
|
|
|
works,
|
2023-10-08 15:11:47 +02:00
|
|
|
..Default::default()
|
2023-10-08 00:16:41 +02:00
|
|
|
}
|
|
|
|
|
}
|
2023-10-08 14:50:27 +02:00
|
|
|
LibraryQuery {
|
2023-10-08 15:11:47 +02:00
|
|
|
composer: None,
|
|
|
|
|
performer: None,
|
2023-10-08 14:50:27 +02:00
|
|
|
ensemble: Some(ensemble),
|
|
|
|
|
work: None,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
2024-03-23 18:06:46 +01:00
|
|
|
let composers: Vec<Person> =
|
|
|
|
|
persons::table
|
|
|
|
|
.inner_join(work_persons::table.inner_join(
|
|
|
|
|
works::table.inner_join(
|
|
|
|
|
recordings::table.inner_join(recording_ensembles::table),
|
|
|
|
|
),
|
|
|
|
|
))
|
|
|
|
|
.filter(
|
|
|
|
|
recording_ensembles::ensemble_id
|
|
|
|
|
.eq(&ensemble.ensemble_id)
|
|
|
|
|
.and(persons::name.like(&search)),
|
|
|
|
|
)
|
|
|
|
|
.limit(9)
|
|
|
|
|
.select(persons::all_columns)
|
|
|
|
|
.distinct()
|
|
|
|
|
.load(connection)?;
|
|
|
|
|
|
|
|
|
|
let recordings = recordings::table
|
|
|
|
|
.inner_join(
|
|
|
|
|
works::table.inner_join(work_persons::table.inner_join(persons::table)),
|
2023-11-05 14:27:03 +01:00
|
|
|
)
|
2024-03-23 18:06:46 +01:00
|
|
|
// .inner_join(recording_persons::table.inner_join(persons::table))
|
|
|
|
|
.inner_join(recording_ensembles::table)
|
|
|
|
|
.filter(
|
|
|
|
|
recording_ensembles::ensemble_id
|
|
|
|
|
.eq(&ensemble.ensemble_id)
|
|
|
|
|
.and(works::name.like(&search).or(persons::name.like(&search))),
|
|
|
|
|
)
|
|
|
|
|
.select(recordings::all_columns)
|
|
|
|
|
.distinct()
|
|
|
|
|
.load::<tables::Recording>(connection)?
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|r| Recording::from_table(r, &&self.folder(), connection))
|
|
|
|
|
.collect::<Result<Vec<Recording>>>()?;
|
2023-10-08 14:50:27 +02:00
|
|
|
|
2024-06-05 13:26:47 +02:00
|
|
|
let albums = albums::table
|
|
|
|
|
.inner_join(
|
|
|
|
|
album_recordings::table
|
|
|
|
|
.inner_join(recordings::table.inner_join(recording_ensembles::table)),
|
|
|
|
|
)
|
|
|
|
|
.filter(
|
|
|
|
|
recording_ensembles::ensemble_id
|
|
|
|
|
.eq(&ensemble.ensemble_id)
|
|
|
|
|
.and(albums::name.like(&search)),
|
|
|
|
|
)
|
|
|
|
|
.select(albums::all_columns)
|
|
|
|
|
.distinct()
|
|
|
|
|
.load(connection)?;
|
|
|
|
|
|
2023-10-08 14:50:27 +02:00
|
|
|
LibraryResults {
|
2023-10-08 15:11:47 +02:00
|
|
|
composers,
|
2023-10-08 14:50:27 +02:00
|
|
|
recordings,
|
2024-06-05 13:26:47 +02:00
|
|
|
albums,
|
2023-10-08 14:50:27 +02:00
|
|
|
..Default::default()
|
|
|
|
|
}
|
2023-10-08 16:40:59 +02:00
|
|
|
}
|
2023-10-08 14:50:27 +02:00
|
|
|
LibraryQuery {
|
2023-10-08 15:11:47 +02:00
|
|
|
composer: None,
|
|
|
|
|
performer: Some(performer),
|
|
|
|
|
work: None,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
2024-03-23 18:06:46 +01:00
|
|
|
let composers: Vec<Person> = persons::table
|
|
|
|
|
.inner_join(
|
|
|
|
|
work_persons::table
|
|
|
|
|
.inner_join(works::table.inner_join(
|
|
|
|
|
recordings::table.inner_join(recording_persons::table),
|
|
|
|
|
)),
|
2023-11-05 14:27:03 +01:00
|
|
|
)
|
2024-03-23 18:06:46 +01:00
|
|
|
.filter(
|
|
|
|
|
recording_persons::person_id
|
|
|
|
|
.eq(&performer.person_id)
|
|
|
|
|
.and(persons::name.like(&search)),
|
|
|
|
|
)
|
|
|
|
|
.limit(9)
|
|
|
|
|
.select(persons::all_columns)
|
|
|
|
|
.distinct()
|
|
|
|
|
.load(connection)?;
|
|
|
|
|
|
|
|
|
|
let recordings = recordings::table
|
|
|
|
|
.inner_join(
|
|
|
|
|
works::table.inner_join(work_persons::table.inner_join(persons::table)),
|
|
|
|
|
)
|
|
|
|
|
.inner_join(recording_persons::table)
|
|
|
|
|
.filter(
|
|
|
|
|
recording_persons::person_id
|
|
|
|
|
.eq(&performer.person_id)
|
|
|
|
|
.and(works::name.like(&search).or(persons::name.like(&search))),
|
|
|
|
|
)
|
|
|
|
|
.select(recordings::all_columns)
|
|
|
|
|
.distinct()
|
|
|
|
|
.load::<tables::Recording>(connection)?
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|r| Recording::from_table(r, &self.folder(), connection))
|
|
|
|
|
.collect::<Result<Vec<Recording>>>()?;
|
2023-10-08 15:11:47 +02:00
|
|
|
|
2024-06-05 13:26:47 +02:00
|
|
|
let albums = albums::table
|
|
|
|
|
.inner_join(
|
|
|
|
|
album_recordings::table
|
|
|
|
|
.inner_join(recordings::table.inner_join(recording_persons::table)),
|
|
|
|
|
)
|
|
|
|
|
.filter(
|
|
|
|
|
recording_persons::person_id
|
|
|
|
|
.eq(&performer.person_id)
|
|
|
|
|
.and(albums::name.like(&search)),
|
|
|
|
|
)
|
|
|
|
|
.select(albums::all_columns)
|
|
|
|
|
.distinct()
|
|
|
|
|
.load(connection)?;
|
|
|
|
|
|
2023-10-08 15:11:47 +02:00
|
|
|
LibraryResults {
|
|
|
|
|
composers,
|
|
|
|
|
recordings,
|
2024-06-05 13:26:47 +02:00
|
|
|
albums,
|
2023-10-08 15:11:47 +02:00
|
|
|
..Default::default()
|
|
|
|
|
}
|
2023-10-08 16:40:59 +02:00
|
|
|
}
|
2023-10-08 15:11:47 +02:00
|
|
|
LibraryQuery {
|
|
|
|
|
composer: Some(composer),
|
2023-10-08 14:50:27 +02:00
|
|
|
ensemble: Some(ensemble),
|
|
|
|
|
work: None,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
2024-03-23 18:06:46 +01:00
|
|
|
let recordings = recordings::table
|
|
|
|
|
.inner_join(works::table.inner_join(work_persons::table))
|
|
|
|
|
.inner_join(recording_ensembles::table)
|
|
|
|
|
.filter(
|
|
|
|
|
work_persons::person_id
|
|
|
|
|
.eq(&composer.person_id)
|
|
|
|
|
.and(recording_ensembles::ensemble_id.eq(&ensemble.ensemble_id))
|
|
|
|
|
.and(works::name.like(search)),
|
|
|
|
|
)
|
|
|
|
|
.select(recordings::all_columns)
|
|
|
|
|
.distinct()
|
|
|
|
|
.load::<tables::Recording>(connection)?
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|r| Recording::from_table(r, &self.folder(), connection))
|
|
|
|
|
.collect::<Result<Vec<Recording>>>()?;
|
2023-10-08 15:11:47 +02:00
|
|
|
|
|
|
|
|
LibraryResults {
|
|
|
|
|
recordings,
|
|
|
|
|
..Default::default()
|
|
|
|
|
}
|
2023-10-08 16:40:59 +02:00
|
|
|
}
|
2023-10-08 15:11:47 +02:00
|
|
|
LibraryQuery {
|
|
|
|
|
composer: Some(composer),
|
|
|
|
|
performer: Some(performer),
|
|
|
|
|
work: None,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
2024-03-23 18:06:46 +01:00
|
|
|
let recordings = recordings::table
|
|
|
|
|
.inner_join(works::table.inner_join(work_persons::table))
|
|
|
|
|
.inner_join(recording_persons::table)
|
|
|
|
|
.filter(
|
|
|
|
|
work_persons::person_id
|
|
|
|
|
.eq(&composer.person_id)
|
|
|
|
|
.and(recording_persons::person_id.eq(&performer.person_id))
|
|
|
|
|
.and(works::name.like(search)),
|
|
|
|
|
)
|
|
|
|
|
.select(recordings::all_columns)
|
|
|
|
|
.distinct()
|
|
|
|
|
.load::<tables::Recording>(connection)?
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|r| Recording::from_table(r, &self.folder(), connection))
|
|
|
|
|
.collect::<Result<Vec<Recording>>>()?;
|
2023-10-08 14:50:27 +02:00
|
|
|
|
|
|
|
|
LibraryResults {
|
|
|
|
|
recordings,
|
|
|
|
|
..Default::default()
|
|
|
|
|
}
|
2023-10-08 16:40:59 +02:00
|
|
|
}
|
2023-10-08 14:50:27 +02:00
|
|
|
LibraryQuery {
|
2023-10-08 16:40:59 +02:00
|
|
|
work: Some(work), ..
|
2023-10-08 14:50:27 +02:00
|
|
|
} => {
|
2024-03-23 18:06:46 +01:00
|
|
|
let recordings = recordings::table
|
|
|
|
|
.filter(recordings::work_id.eq(&work.work_id))
|
|
|
|
|
.load::<tables::Recording>(connection)?
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|r| Recording::from_table(r, &self.folder(), connection))
|
|
|
|
|
.collect::<Result<Vec<Recording>>>()?;
|
2023-10-08 14:50:27 +02:00
|
|
|
|
|
|
|
|
LibraryResults {
|
|
|
|
|
recordings,
|
|
|
|
|
..Default::default()
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-23 18:06:46 +01:00
|
|
|
})
|
2023-10-25 17:45:32 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-10 16:57:36 +02:00
|
|
|
pub fn generate_recording(&self, program: &Program) -> Result<Recording> {
|
2024-03-23 18:06:46 +01:00
|
|
|
let mut binding = self.imp().connection.borrow_mut();
|
|
|
|
|
let connection = &mut *binding.as_mut().unwrap();
|
2023-10-25 17:45:32 +02:00
|
|
|
|
2024-06-10 16:57:36 +02:00
|
|
|
let mut query = recordings::table
|
|
|
|
|
.inner_join(works::table.inner_join(work_persons::table))
|
|
|
|
|
.inner_join(recording_persons::table)
|
|
|
|
|
.inner_join(recording_ensembles::table)
|
|
|
|
|
.inner_join(album_recordings::table)
|
|
|
|
|
.into_boxed();
|
|
|
|
|
|
|
|
|
|
if let Some(composer_id) = program.composer_id() {
|
|
|
|
|
query = query.filter(work_persons::person_id.eq(composer_id));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(performer_id) = program.performer_id() {
|
|
|
|
|
query = query.filter(recording_persons::person_id.eq(performer_id));
|
2023-10-25 17:45:32 +02:00
|
|
|
}
|
2024-06-10 16:57:36 +02:00
|
|
|
|
|
|
|
|
if let Some(ensemble_id) = program.ensemble_id() {
|
|
|
|
|
query = query.filter(recording_ensembles::ensemble_id.eq(ensemble_id));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(work_id) = program.work_id() {
|
|
|
|
|
query = query.filter(recordings::work_id.eq(work_id));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(album_id) = program.album_id() {
|
|
|
|
|
query = query.filter(album_recordings::album_id.eq(album_id));
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-10 20:53:15 +02:00
|
|
|
if program.prefer_recently_added() > 0.0 {
|
|
|
|
|
let oldest_timestamp = sql_query(
|
|
|
|
|
"SELECT CAST(STRFTIME('%s', MIN(created_at)) AS INTEGER) AS value FROM recordings",
|
|
|
|
|
)
|
|
|
|
|
.get_result::<IntegerValue>(connection)?
|
|
|
|
|
.value;
|
|
|
|
|
|
|
|
|
|
let newest_timestamp = sql_query(
|
|
|
|
|
"SELECT CAST(STRFTIME('%s', MAX(created_at)) AS INTEGER) AS value FROM recordings",
|
|
|
|
|
)
|
|
|
|
|
.get_result::<IntegerValue>(connection)?
|
|
|
|
|
.value;
|
|
|
|
|
|
|
|
|
|
let range = newest_timestamp - oldest_timestamp;
|
|
|
|
|
|
|
|
|
|
if range >= 60 {
|
|
|
|
|
let proportion = program.prefer_recently_added().max(1.0) * 0.9;
|
|
|
|
|
let cutoff_timestamp =
|
|
|
|
|
oldest_timestamp + (proportion * range as f64).floor() as i64;
|
|
|
|
|
|
|
|
|
|
query = query.filter(
|
|
|
|
|
sql::<BigInt>("CAST(STRFTIME('%s', recordings.created_at) AS INTEGER)")
|
|
|
|
|
.ge(cutoff_timestamp)
|
|
|
|
|
.or(recordings::last_played_at.is_null()),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if program.prefer_least_recently_played() > 0.0 {
|
|
|
|
|
let oldest_timestamp =
|
|
|
|
|
sql_query("SELECT CAST(STRFTIME('%s', MIN(last_played_at)) AS INTEGER) AS value FROM recordings")
|
|
|
|
|
.get_result::<IntegerValue>(connection)?
|
|
|
|
|
.value;
|
|
|
|
|
|
|
|
|
|
let newest_timestamp =
|
|
|
|
|
sql_query("SELECT CAST(STRFTIME('%s', MAX(last_played_at)) AS INTEGER) AS value FROM recordings")
|
|
|
|
|
.get_result::<IntegerValue>(connection)?
|
|
|
|
|
.value;
|
|
|
|
|
|
|
|
|
|
let range = newest_timestamp - oldest_timestamp;
|
|
|
|
|
|
|
|
|
|
if range >= 60 {
|
|
|
|
|
let proportion = 1.0 - program.prefer_least_recently_played().max(1.0) * 0.9;
|
|
|
|
|
let cutoff_timestamp =
|
|
|
|
|
oldest_timestamp + (proportion * range as f64).floor() as i64;
|
|
|
|
|
|
|
|
|
|
query = query.filter(
|
|
|
|
|
sql::<BigInt>("CAST(STRFTIME('%s', recordings.last_played_at) AS INTEGER)")
|
|
|
|
|
.le(cutoff_timestamp)
|
|
|
|
|
.or(recordings::last_played_at.is_null()),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-10 16:57:36 +02:00
|
|
|
|
|
|
|
|
let row = query
|
|
|
|
|
.order(random())
|
|
|
|
|
.select(tables::Recording::as_select())
|
|
|
|
|
.first::<tables::Recording>(connection)?;
|
|
|
|
|
|
|
|
|
|
Recording::from_table(row, &self.folder(), connection)
|
2023-10-25 17:45:32 +02:00
|
|
|
}
|
2024-05-31 13:39:27 +02:00
|
|
|
|
2024-06-10 20:53:15 +02:00
|
|
|
pub fn track_played(&self, track_id: &str) -> Result<()> {
|
|
|
|
|
let mut binding = self.imp().connection.borrow_mut();
|
|
|
|
|
let connection = &mut *binding.as_mut().unwrap();
|
|
|
|
|
|
|
|
|
|
let now = Local::now().naive_local();
|
|
|
|
|
|
|
|
|
|
diesel::update(recordings::table)
|
|
|
|
|
.filter(exists(
|
|
|
|
|
tracks::table.filter(
|
|
|
|
|
tracks::track_id
|
|
|
|
|
.eq(track_id)
|
|
|
|
|
.and(tracks::recording_id.eq(recordings::recording_id)),
|
|
|
|
|
),
|
|
|
|
|
))
|
|
|
|
|
.set(recordings::last_played_at.eq(now))
|
|
|
|
|
.execute(connection)?;
|
|
|
|
|
|
|
|
|
|
diesel::update(tracks::table)
|
|
|
|
|
.filter(tracks::track_id.eq(track_id))
|
|
|
|
|
.set(tracks::last_played_at.eq(now))
|
|
|
|
|
.execute(connection)?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-31 13:39:27 +02:00
|
|
|
pub fn search_persons(&self, search: &str) -> Result<Vec<Person>> {
|
|
|
|
|
let search = format!("%{}%", search);
|
|
|
|
|
let mut binding = self.imp().connection.borrow_mut();
|
|
|
|
|
let connection = &mut *binding.as_mut().unwrap();
|
|
|
|
|
|
|
|
|
|
let persons = persons::table
|
|
|
|
|
.order(persons::last_used_at.desc())
|
|
|
|
|
.filter(persons::name.like(&search))
|
|
|
|
|
.limit(20)
|
|
|
|
|
.load(connection)?;
|
|
|
|
|
|
|
|
|
|
Ok(persons)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn search_roles(&self, search: &str) -> Result<Vec<Role>> {
|
|
|
|
|
let search = format!("%{}%", search);
|
|
|
|
|
let mut binding = self.imp().connection.borrow_mut();
|
|
|
|
|
let connection = &mut *binding.as_mut().unwrap();
|
|
|
|
|
|
|
|
|
|
let roles = roles::table
|
|
|
|
|
.order(roles::last_used_at.desc())
|
|
|
|
|
.filter(roles::name.like(&search))
|
|
|
|
|
.limit(20)
|
|
|
|
|
.load(connection)?;
|
|
|
|
|
|
|
|
|
|
Ok(roles)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn search_instruments(&self, search: &str) -> Result<Vec<Instrument>> {
|
|
|
|
|
let search = format!("%{}%", search);
|
|
|
|
|
let mut binding = self.imp().connection.borrow_mut();
|
|
|
|
|
let connection = &mut *binding.as_mut().unwrap();
|
|
|
|
|
|
|
|
|
|
let instruments = instruments::table
|
|
|
|
|
.order(instruments::last_used_at.desc())
|
|
|
|
|
.filter(instruments::name.like(&search))
|
|
|
|
|
.limit(20)
|
|
|
|
|
.load(connection)?;
|
|
|
|
|
|
|
|
|
|
Ok(instruments)
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-14 13:03:44 +02:00
|
|
|
pub fn search_works(&self, composer: &Person, search: &str) -> Result<Vec<Work>> {
|
|
|
|
|
let search = format!("%{}%", search);
|
|
|
|
|
let mut binding = self.imp().connection.borrow_mut();
|
|
|
|
|
let connection = &mut *binding.as_mut().unwrap();
|
|
|
|
|
|
|
|
|
|
let works: Vec<Work> = works::table
|
|
|
|
|
.inner_join(work_persons::table)
|
|
|
|
|
.filter(
|
|
|
|
|
works::name
|
|
|
|
|
.like(&search)
|
|
|
|
|
.and(work_persons::person_id.eq(&composer.person_id)),
|
|
|
|
|
)
|
|
|
|
|
.limit(9)
|
|
|
|
|
.select(works::all_columns)
|
|
|
|
|
.distinct()
|
|
|
|
|
.load::<tables::Work>(connection)?
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|w| Work::from_table(w, connection))
|
|
|
|
|
.collect::<Result<Vec<Work>>>()?;
|
|
|
|
|
|
|
|
|
|
Ok(works)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-31 13:39:27 +02:00
|
|
|
pub fn composer_default_role(&self) -> Result<Role> {
|
|
|
|
|
let mut binding = self.imp().connection.borrow_mut();
|
|
|
|
|
let connection = &mut *binding.as_mut().unwrap();
|
|
|
|
|
|
|
|
|
|
Ok(roles::table
|
|
|
|
|
.filter(roles::role_id.eq("380d7e09eb2f49c1a90db2ba4acb6ffd"))
|
|
|
|
|
.first::<Role>(connection)?)
|
|
|
|
|
}
|
2024-06-06 15:17:56 +02:00
|
|
|
|
|
|
|
|
pub fn create_person(&self, name: TranslatedString) -> Result<Person> {
|
|
|
|
|
let mut binding = self.imp().connection.borrow_mut();
|
|
|
|
|
let connection = &mut *binding.as_mut().unwrap();
|
|
|
|
|
|
|
|
|
|
let now = Local::now().naive_local();
|
|
|
|
|
|
|
|
|
|
let person = Person {
|
|
|
|
|
person_id: db::generate_id(),
|
|
|
|
|
name,
|
|
|
|
|
created_at: now,
|
|
|
|
|
edited_at: now,
|
|
|
|
|
last_used_at: now,
|
|
|
|
|
last_played_at: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
diesel::insert_into(persons::table)
|
|
|
|
|
.values(&person)
|
|
|
|
|
.execute(connection)?;
|
|
|
|
|
|
|
|
|
|
Ok(person)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn update_person(&self, id: &str, name: TranslatedString) -> Result<()> {
|
|
|
|
|
let mut binding = self.imp().connection.borrow_mut();
|
|
|
|
|
let connection = &mut *binding.as_mut().unwrap();
|
|
|
|
|
|
|
|
|
|
let now = Local::now().naive_local();
|
|
|
|
|
|
|
|
|
|
diesel::update(persons::table)
|
|
|
|
|
.filter(persons::person_id.eq(id))
|
|
|
|
|
.set((
|
|
|
|
|
persons::name.eq(name),
|
|
|
|
|
persons::edited_at.eq(now),
|
|
|
|
|
persons::last_used_at.eq(now),
|
|
|
|
|
))
|
|
|
|
|
.execute(connection)?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2024-07-14 13:03:44 +02:00
|
|
|
|
|
|
|
|
pub fn create_role(&self, name: TranslatedString) -> Result<Role> {
|
|
|
|
|
let mut binding = self.imp().connection.borrow_mut();
|
|
|
|
|
let connection = &mut *binding.as_mut().unwrap();
|
|
|
|
|
|
|
|
|
|
let now = Local::now().naive_local();
|
|
|
|
|
|
|
|
|
|
let role = Role {
|
|
|
|
|
role_id: db::generate_id(),
|
|
|
|
|
name,
|
|
|
|
|
created_at: now,
|
|
|
|
|
edited_at: now,
|
|
|
|
|
last_used_at: now,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
diesel::insert_into(roles::table)
|
|
|
|
|
.values(&role)
|
|
|
|
|
.execute(connection)?;
|
|
|
|
|
|
|
|
|
|
Ok(role)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn update_role(&self, id: &str, name: TranslatedString) -> Result<()> {
|
|
|
|
|
let mut binding = self.imp().connection.borrow_mut();
|
|
|
|
|
let connection = &mut *binding.as_mut().unwrap();
|
|
|
|
|
|
|
|
|
|
let now = Local::now().naive_local();
|
|
|
|
|
|
|
|
|
|
diesel::update(roles::table)
|
|
|
|
|
.filter(roles::role_id.eq(id))
|
|
|
|
|
.set((
|
|
|
|
|
roles::name.eq(name),
|
|
|
|
|
roles::edited_at.eq(now),
|
|
|
|
|
roles::last_used_at.eq(now),
|
|
|
|
|
))
|
|
|
|
|
.execute(connection)?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-09-30 18:26:11 +02:00
|
|
|
}
|
2023-10-07 22:49:20 +02:00
|
|
|
|
2023-10-08 00:16:41 +02:00
|
|
|
#[derive(Default, Debug)]
|
2023-10-07 22:49:20 +02:00
|
|
|
pub struct LibraryQuery {
|
2023-10-08 15:11:47 +02:00
|
|
|
pub composer: Option<Person>,
|
|
|
|
|
pub performer: Option<Person>,
|
2023-10-07 22:49:20 +02:00
|
|
|
pub ensemble: Option<Ensemble>,
|
|
|
|
|
pub work: Option<Work>,
|
|
|
|
|
pub search: String,
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-01 18:43:00 +02:00
|
|
|
impl LibraryQuery {
|
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
|
self.composer.is_none()
|
|
|
|
|
&& self.performer.is_none()
|
|
|
|
|
&& self.ensemble.is_none()
|
|
|
|
|
&& self.work.is_none()
|
|
|
|
|
&& self.search.is_empty()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-08 00:16:41 +02:00
|
|
|
#[derive(Default, Debug)]
|
2023-10-07 22:49:20 +02:00
|
|
|
pub struct LibraryResults {
|
2023-10-08 15:11:47 +02:00
|
|
|
pub composers: Vec<Person>,
|
|
|
|
|
pub performers: Vec<Person>,
|
2023-10-07 22:49:20 +02:00
|
|
|
pub ensembles: Vec<Ensemble>,
|
|
|
|
|
pub works: Vec<Work>,
|
|
|
|
|
pub recordings: Vec<Recording>,
|
2024-06-05 13:26:47 +02:00
|
|
|
pub albums: Vec<Album>,
|
2023-10-07 22:49:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LibraryResults {
|
|
|
|
|
pub fn is_empty(&self) -> bool {
|
2023-10-08 15:11:47 +02:00
|
|
|
self.composers.is_empty()
|
|
|
|
|
&& self.performers.is_empty()
|
2023-10-08 00:16:41 +02:00
|
|
|
&& self.ensembles.is_empty()
|
|
|
|
|
&& self.works.is_empty()
|
|
|
|
|
&& self.recordings.is_empty()
|
2024-06-05 13:26:47 +02:00
|
|
|
&& self.albums.is_empty()
|
2023-10-07 22:49:20 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-06-10 20:53:15 +02:00
|
|
|
|
|
|
|
|
#[derive(QueryableByName)]
|
|
|
|
|
pub struct IntegerValue {
|
|
|
|
|
#[diesel(sql_type = diesel::sql_types::BigInt)]
|
|
|
|
|
pub value: i64,
|
|
|
|
|
}
|