musicus/src/library.rs

475 lines
17 KiB
Rust
Raw Normal View History

2023-09-30 18:26:11 +02:00
use gtk::{glib, glib::Properties, prelude::*, subclass::prelude::*};
2023-10-07 22:49:20 +02:00
use rusqlite::{Connection, Row};
2023-09-30 18:26:11 +02:00
use std::{
2023-10-07 22:49:20 +02:00
cell::OnceCell,
path::{Path, PathBuf},
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>,
pub connection: OnceCell<Connection>,
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();
let db_path = PathBuf::from(self.folder.get().unwrap()).join("musicus.db");
self.connection
.set(Connection::open(db_path).unwrap())
.unwrap();
}
}
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
2023-10-07 22:49:20 +02:00
pub fn query(&self, query: &LibraryQuery) -> LibraryResults {
let search = format!("%{}%", query.search);
2023-09-30 18:26:11 +02:00
2023-10-07 22:49:20 +02:00
match query {
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,
..
} => {
2023-10-08 15:11:47 +02:00
let composers = self.con()
.prepare("SELECT DISTINCT persons.id, persons.first_name, persons.last_name FROM persons INNER JOIN works ON works.composer = persons.id WHERE persons.first_name LIKE ?1 OR persons.last_name LIKE ?1 LIMIT 9")
.unwrap()
.query_map([&search], Person::from_row)
.unwrap()
.collect::<rusqlite::Result<Vec<Person>>>()
.unwrap();
2023-10-08 16:40:59 +02:00
2023-10-08 15:11:47 +02:00
let performers = self.con()
.prepare("SELECT DISTINCT persons.id, persons.first_name, persons.last_name FROM persons INNER JOIN performances ON performances.person = persons.id WHERE persons.first_name LIKE ?1 OR persons.last_name LIKE ?1 LIMIT 9")
2023-10-07 22:49:20 +02:00
.unwrap()
.query_map([&search], Person::from_row)
.unwrap()
.collect::<rusqlite::Result<Vec<Person>>>()
.unwrap();
2023-09-30 18:26:11 +02:00
2023-10-08 00:16:41 +02:00
let ensembles = self
.con()
.prepare("SELECT id, name FROM ensembles WHERE name LIKE ?1 LIMIT 9")
.unwrap()
.query_map([&search], Ensemble::from_row)
.unwrap()
.collect::<rusqlite::Result<Vec<Ensemble>>>()
.unwrap();
let works = self
.con()
.prepare("SELECT works.id, works.title, persons.id, persons.first_name, persons.last_name FROM works INNER JOIN persons ON works.composer = persons.id WHERE title LIKE ?1 LIMIT 9")
.unwrap()
.query_map([&search], Work::from_row)
.unwrap()
.collect::<rusqlite::Result<Vec<Work>>>()
.unwrap();
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,
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,
..
} => {
2023-10-08 15:11:47 +02:00
let performers = self.con()
2023-10-08 00:16:41 +02:00
.prepare("SELECT DISTINCT persons.id, persons.first_name, persons.last_name FROM persons INNER JOIN performances ON performances.person = persons.id INNER JOIN recordings ON recordings.id = performances.recording INNER JOIN works ON works.id = recordings.work WHERE works.composer IS ?1 AND (persons.first_name LIKE ?2 OR persons.last_name LIKE ?2) LIMIT 9")
.unwrap()
2023-10-08 15:11:47 +02:00
.query_map([&composer.id, &search], Person::from_row)
2023-10-08 00:16:41 +02:00
.unwrap()
.collect::<rusqlite::Result<Vec<Person>>>()
.unwrap();
let ensembles = self
.con()
.prepare("SELECT DISTINCT ensembles.id, ensembles.name FROM ensembles INNER JOIN performances ON performances.ensemble = ensembles.id INNER JOIN recordings ON recordings.id = performances.recording INNER JOIN works ON works.id = recordings.work WHERE works.composer IS ?1 AND ensembles.name LIKE ?2 LIMIT 9")
.unwrap()
2023-10-08 15:11:47 +02:00
.query_map([&composer.id, &search], Ensemble::from_row)
2023-10-08 00:16:41 +02:00
.unwrap()
.collect::<rusqlite::Result<Vec<Ensemble>>>()
.unwrap();
let works = self
.con()
.prepare("SELECT DISTINCT works.id, works.title, persons.id, persons.first_name, persons.last_name FROM works INNER JOIN persons ON works.composer = persons.id WHERE works.composer = ?1 AND title LIKE ?2 LIMIT 9")
.unwrap()
2023-10-08 15:11:47 +02:00
.query_map([&composer.id, &search], Work::from_row)
2023-10-08 00:16:41 +02:00
.unwrap()
.collect::<rusqlite::Result<Vec<Work>>>()
.unwrap();
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,
..
} => {
2023-10-08 15:11:47 +02:00
let composers = self.con()
2023-10-08 14:50:27 +02:00
.prepare("SELECT DISTINCT persons.id, persons.first_name, persons.last_name FROM persons INNER JOIN works ON works.composer = persons.id INNER JOIN recordings ON recordings.work = works.id INNER JOIN performances ON performances.recording = recordings.id WHERE performances.ensemble IS ?1 AND (persons.first_name LIKE ?2 OR persons.last_name LIKE ?2) LIMIT 9")
.unwrap()
.query_map([&ensemble.id, &search], Person::from_row)
.unwrap()
.collect::<rusqlite::Result<Vec<Person>>>()
.unwrap();
let recordings = self
.con()
.prepare("SELECT DISTINCT recordings.id, works.id, works.title, persons.id, persons.first_name, persons.last_name FROM recordings INNER JOIN works ON recordings.work = works.id INNER JOIN persons ON works.composer = persons.id INNER JOIN performances ON recordings.id = performances.recording WHERE performances.ensemble IS ?1 AND (works.title LIKE ?2 OR persons.first_name LIKE ?2 OR persons.last_name LIKE ?2) LIMIT 9")
.unwrap()
.query_map([&ensemble.id, &search], Recording::from_row)
.unwrap()
.collect::<rusqlite::Result<Vec<Recording>>>()
.unwrap();
LibraryResults {
2023-10-08 15:11:47 +02:00
composers,
2023-10-08 14:50:27 +02:00
recordings,
..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,
..
} => {
let composers = self.con()
.prepare("SELECT DISTINCT persons.id, persons.first_name, persons.last_name FROM persons INNER JOIN works ON works.composer = persons.id INNER JOIN recordings ON recordings.work = works.id INNER JOIN performances ON performances.recording = recordings.id WHERE performances.person IS ?1 AND (persons.first_name LIKE ?2 OR persons.last_name LIKE ?2) LIMIT 9")
.unwrap()
.query_map([&performer.id, &search], Person::from_row)
.unwrap()
.collect::<rusqlite::Result<Vec<Person>>>()
.unwrap();
let recordings = self
.con()
.prepare("SELECT DISTINCT recordings.id, works.id, works.title, persons.id, persons.first_name, persons.last_name FROM recordings INNER JOIN works ON recordings.work = works.id INNER JOIN persons ON works.composer = persons.id INNER JOIN performances ON recordings.id = performances.recording WHERE performances.person IS ?1 AND (works.title LIKE ?2 OR persons.first_name LIKE ?2 OR persons.last_name LIKE ?2) LIMIT 9")
.unwrap()
.query_map([&performer.id, &search], Recording::from_row)
.unwrap()
.collect::<rusqlite::Result<Vec<Recording>>>()
.unwrap();
LibraryResults {
composers,
recordings,
..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,
..
} => {
let recordings = self
.con()
2023-10-08 15:11:47 +02:00
.prepare("SELECT DISTINCT recordings.id, works.id, works.title, persons.id, persons.first_name, persons.last_name FROM recordings INNER JOIN works ON recordings.work = works.id INNER JOIN persons ON works.composer = persons.id INNER JOIN performances ON recordings.id = performances.recording WHERE works.composer IS ?1 AND performances.ensemble IS ?2 AND works.title LIKE ?3 LIMIT 9")
.unwrap()
.query_map([&composer.id, &ensemble.id, &search], Recording::from_row)
.unwrap()
.collect::<rusqlite::Result<Vec<Recording>>>()
.unwrap();
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,
..
} => {
let recordings = self
.con()
.prepare("SELECT DISTINCT recordings.id, works.id, works.title, persons.id, persons.first_name, persons.last_name FROM recordings INNER JOIN works ON recordings.work = works.id INNER JOIN persons ON works.composer = persons.id INNER JOIN performances ON recordings.id = performances.recording WHERE works.composer IS ?1 AND performances.person IS ?2 AND works.title LIKE ?3 LIMIT 9")
2023-10-08 14:50:27 +02:00
.unwrap()
2023-10-08 15:11:47 +02:00
.query_map([&composer.id, &performer.id, &search], Recording::from_row)
2023-10-08 14:50:27 +02:00
.unwrap()
.collect::<rusqlite::Result<Vec<Recording>>>()
.unwrap();
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
} => {
let recordings = self
.con()
.prepare("SELECT DISTINCT recordings.id, works.id, works.title, persons.id, persons.first_name, persons.last_name FROM recordings INNER JOIN works ON recordings.work = works.id INNER JOIN persons ON works.composer IS persons.id WHERE works.id IS ?1")
.unwrap()
.query_map([&work.id], Recording::from_row)
.unwrap()
.collect::<rusqlite::Result<Vec<Recording>>>()
.unwrap();
LibraryResults {
recordings,
..Default::default()
}
}
2023-10-07 22:49:20 +02:00
}
2023-09-30 18:26:11 +02:00
}
2023-10-08 16:40:59 +02:00
pub fn performances(&self, recording: &Recording) -> Vec<Performance> {
let mut performances = self
.con()
.prepare("SELECT persons.id, persons.first_name, persons.last_name, instruments.id, instruments.name FROM performances INNER JOIN persons ON persons.id = performances.person LEFT JOIN instruments ON instruments.id = performances.role INNER JOIN recordings ON performances.recording = recordings.id WHERE recordings.id IS ?1")
.unwrap()
.query_map([&recording.id], Performance::from_person_row)
.unwrap()
.collect::<rusqlite::Result<Vec<Performance>>>()
.unwrap();
performances.append(&mut self
.con()
.prepare("SELECT ensembles.id, ensembles.name, instruments.id, instruments.name FROM performances INNER JOIN ensembles ON ensembles.id = performances.ensemble LEFT JOIN instruments ON instruments.id = performances.role INNER JOIN recordings ON performances.recording = recordings.id WHERE recordings.id IS ?1")
.unwrap()
.query_map([&recording.id], Performance::from_ensemble_row)
.unwrap()
.collect::<rusqlite::Result<Vec<Performance>>>()
.unwrap());
performances
}
2023-10-07 22:49:20 +02:00
fn con(&self) -> &Connection {
2023-09-30 18:26:11 +02:00
self.imp().connection.get().unwrap()
}
}
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,
}
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>,
}
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()
2023-10-07 22:49:20 +02:00
}
}
2023-10-08 15:25:00 +02:00
#[derive(Debug, Clone, Eq)]
2023-10-07 22:49:20 +02:00
pub struct Person {
2023-10-08 00:16:41 +02:00
pub id: String,
2023-10-07 22:49:20 +02:00
pub first_name: String,
pub last_name: String,
}
2023-10-08 15:25:00 +02:00
impl PartialEq for Person {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
2023-10-07 22:49:20 +02:00
impl Person {
pub fn from_row(row: &Row) -> rusqlite::Result<Self> {
Ok(Self {
2023-10-08 00:16:41 +02:00
id: row.get(0)?,
first_name: row.get(1)?,
last_name: row.get(2)?,
2023-10-07 22:49:20 +02:00
})
}
pub fn name_fl(&self) -> String {
format!("{} {}", self.first_name, self.last_name)
}
}
2023-10-08 15:25:00 +02:00
#[derive(Debug, Clone, Eq)]
2023-10-07 22:49:20 +02:00
pub struct Ensemble {
2023-10-08 00:16:41 +02:00
pub id: String,
2023-10-07 22:49:20 +02:00
pub name: String,
}
2023-10-08 15:25:00 +02:00
impl PartialEq for Ensemble {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
2023-10-08 00:16:41 +02:00
impl Ensemble {
pub fn from_row(row: &Row) -> rusqlite::Result<Self> {
Ok(Self {
id: row.get(0)?,
name: row.get(1)?,
})
}
}
2023-10-08 15:25:00 +02:00
#[derive(Debug, Clone, Eq)]
2023-10-07 22:49:20 +02:00
pub struct Work {
2023-10-08 00:16:41 +02:00
pub id: String,
2023-10-07 22:49:20 +02:00
pub title: String,
2023-10-08 00:16:41 +02:00
pub composer: Person,
2023-10-07 22:49:20 +02:00
}
2023-10-08 15:25:00 +02:00
impl PartialEq for Work {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
2023-10-08 00:16:41 +02:00
impl Work {
pub fn from_row(row: &Row) -> rusqlite::Result<Self> {
Ok(Self {
id: row.get(0)?,
title: row.get(1)?,
composer: Person {
id: row.get(2)?,
first_name: row.get(3)?,
last_name: row.get(4)?,
},
})
}
}
2023-10-08 15:25:00 +02:00
#[derive(Debug, Clone, Eq)]
2023-10-08 00:16:41 +02:00
pub struct Recording {
pub id: String,
pub work: Work,
}
2023-10-08 15:25:00 +02:00
impl PartialEq for Recording {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
2023-10-08 00:16:41 +02:00
impl Recording {
pub fn from_row(row: &Row) -> rusqlite::Result<Self> {
Ok(Self {
id: row.get(0)?,
work: Work {
id: row.get(1)?,
title: row.get(2)?,
composer: Person {
id: row.get(3)?,
first_name: row.get(4)?,
last_name: row.get(5)?,
},
},
})
}
}
2023-10-08 16:40:59 +02:00
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Performance {
Person(Person, Option<Role>),
Ensemble(Ensemble, Option<Role>),
}
impl Performance {
pub fn from_person_row(row: &Row) -> rusqlite::Result<Self> {
let person = Person {
id: row.get(0)?,
first_name: row.get(1)?,
last_name: row.get(2)?,
};
Ok(match row.get::<_, Option<String>>(3)? {
None => Self::Person(person, None),
Some(role_id) => Self::Person(
person,
Some(Role {
id: role_id,
name: row.get(4)?,
}),
),
})
}
pub fn from_ensemble_row(row: &Row) -> rusqlite::Result<Self> {
let ensemble = Ensemble {
id: row.get(0)?,
name: row.get(1)?,
};
Ok(match row.get::<_, Option<String>>(2)? {
None => Self::Ensemble(ensemble, None),
Some(role_id) => Self::Ensemble(
ensemble,
Some(Role {
id: role_id,
name: row.get(3)?,
}),
),
})
}
}
#[derive(Debug, Clone, Eq)]
pub struct Role {
pub id: String,
pub name: String,
}
impl PartialEq for Role {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}