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,
|
2023-10-25 17:45:32 +02:00
|
|
|
num::ParseIntError,
|
2023-10-07 22:49:20 +02:00
|
|
|
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-11-05 14:27:03 +01:00
|
|
|
let composers = self
|
|
|
|
|
.con()
|
|
|
|
|
.prepare(
|
|
|
|
|
"SELECT DISTINCT persons.id, persons.first_name, persons.last_name \
|
|
|
|
|
FROM persons \
|
|
|
|
|
JOIN works ON works.composer = persons.id \
|
|
|
|
|
WHERE persons.first_name LIKE ?1 OR persons.last_name LIKE ?1 \
|
|
|
|
|
LIMIT 9",
|
|
|
|
|
)
|
2023-10-08 15:11:47 +02:00
|
|
|
.unwrap()
|
|
|
|
|
.query_map([&search], Person::from_row)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.collect::<rusqlite::Result<Vec<Person>>>()
|
|
|
|
|
.unwrap();
|
2023-10-08 16:40:59 +02:00
|
|
|
|
2023-11-05 14:27:03 +01:00
|
|
|
let performers = self
|
|
|
|
|
.con()
|
|
|
|
|
.prepare(
|
|
|
|
|
"SELECT DISTINCT persons.id, persons.first_name, persons.last_name \
|
|
|
|
|
FROM persons \
|
|
|
|
|
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()
|
2023-11-05 14:27:03 +01:00
|
|
|
.prepare(
|
|
|
|
|
"SELECT works.id, works.title, persons.id, persons.first_name, persons.last_name \
|
|
|
|
|
FROM works \
|
|
|
|
|
JOIN persons ON works.composer = persons.id \
|
|
|
|
|
WHERE title LIKE ?1 \
|
|
|
|
|
LIMIT 9"
|
|
|
|
|
)
|
2023-10-08 00:16:41 +02:00
|
|
|
.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-11-05 14:27:03 +01:00
|
|
|
let performers = self
|
|
|
|
|
.con()
|
|
|
|
|
.prepare(
|
|
|
|
|
"SELECT DISTINCT persons.id, persons.first_name, persons.last_name \
|
|
|
|
|
FROM persons \
|
|
|
|
|
JOIN performances ON performances.person = persons.id \
|
|
|
|
|
JOIN recordings ON recordings.id = performances.recording \
|
|
|
|
|
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",
|
|
|
|
|
)
|
2023-10-08 00:16:41 +02:00
|
|
|
.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()
|
2023-11-05 14:27:03 +01:00
|
|
|
.prepare(
|
|
|
|
|
"SELECT DISTINCT ensembles.id, ensembles.name \
|
|
|
|
|
FROM ensembles \
|
|
|
|
|
JOIN performances ON performances.ensemble = ensembles.id \
|
|
|
|
|
JOIN recordings ON recordings.id = performances.recording \
|
|
|
|
|
JOIN works ON works.id = recordings.work \
|
|
|
|
|
WHERE works.composer IS ?1 AND ensembles.name LIKE ?2 \
|
|
|
|
|
LIMIT 9",
|
|
|
|
|
)
|
2023-10-08 00:16:41 +02:00
|
|
|
.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()
|
2023-11-05 14:27:03 +01:00
|
|
|
.prepare(
|
|
|
|
|
"SELECT DISTINCT works.id, works.title, persons.id, persons.first_name, persons.last_name \
|
|
|
|
|
FROM works \
|
|
|
|
|
JOIN persons ON works.composer = persons.id \
|
|
|
|
|
WHERE works.composer = ?1 AND title LIKE ?2 \
|
|
|
|
|
LIMIT 9")
|
2023-10-08 00:16:41 +02:00
|
|
|
.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-11-05 14:27:03 +01:00
|
|
|
let composers = self
|
|
|
|
|
.con()
|
|
|
|
|
.prepare(
|
|
|
|
|
"SELECT DISTINCT persons.id, persons.first_name, persons.last_name \
|
|
|
|
|
FROM persons \
|
|
|
|
|
JOIN works ON works.composer = persons.id \
|
|
|
|
|
JOIN recordings ON recordings.work = works.id \
|
|
|
|
|
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",
|
|
|
|
|
)
|
2023-10-08 14:50:27 +02:00
|
|
|
.unwrap()
|
|
|
|
|
.query_map([&ensemble.id, &search], Person::from_row)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.collect::<rusqlite::Result<Vec<Person>>>()
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let recordings = self
|
|
|
|
|
.con()
|
2023-11-05 14:27:03 +01:00
|
|
|
.prepare(
|
|
|
|
|
"SELECT DISTINCT recordings.id, works.id, works.title, persons.id, persons.first_name, persons.last_name \
|
|
|
|
|
FROM recordings \
|
|
|
|
|
JOIN works ON recordings.work = works.id \
|
|
|
|
|
JOIN persons ON works.composer = persons.id \
|
|
|
|
|
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")
|
2023-10-08 14:50:27 +02:00
|
|
|
.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,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
2023-11-05 14:27:03 +01:00
|
|
|
let composers = self
|
|
|
|
|
.con()
|
|
|
|
|
.prepare(
|
|
|
|
|
"SELECT DISTINCT persons.id, persons.first_name, persons.last_name \
|
|
|
|
|
FROM persons \
|
|
|
|
|
JOIN works ON works.composer = persons.id \
|
|
|
|
|
JOIN recordings ON recordings.work = works.id \
|
|
|
|
|
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",
|
|
|
|
|
)
|
2023-10-08 15:11:47 +02:00
|
|
|
.unwrap()
|
|
|
|
|
.query_map([&performer.id, &search], Person::from_row)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.collect::<rusqlite::Result<Vec<Person>>>()
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let recordings = self
|
|
|
|
|
.con()
|
2023-11-05 14:27:03 +01:00
|
|
|
.prepare(
|
|
|
|
|
"SELECT DISTINCT recordings.id, works.id, works.title, persons.id, persons.first_name, persons.last_name \
|
|
|
|
|
FROM recordings \
|
|
|
|
|
JOIN works ON recordings.work = works.id \
|
|
|
|
|
JOIN persons ON works.composer = persons.id \
|
|
|
|
|
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")
|
2023-10-08 15:11:47 +02:00
|
|
|
.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-11-05 14:27:03 +01:00
|
|
|
.prepare(
|
|
|
|
|
"SELECT DISTINCT recordings.id, works.id, works.title, persons.id, persons.first_name, persons.last_name \
|
|
|
|
|
FROM recordings \
|
|
|
|
|
JOIN works ON recordings.work = works.id \
|
|
|
|
|
JOIN persons ON works.composer = persons.id \
|
|
|
|
|
JOIN performances ON recordings.id = performances.recording \
|
|
|
|
|
WHERE works.composer IS ?1 \
|
|
|
|
|
AND performances.ensemble IS ?2 \
|
|
|
|
|
AND works.title LIKE ?3 \
|
|
|
|
|
LIMIT 9")
|
2023-10-08 15:11:47 +02:00
|
|
|
.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()
|
2023-11-05 14:27:03 +01:00
|
|
|
.prepare(
|
|
|
|
|
"SELECT DISTINCT recordings.id, works.id, works.title, persons.id, persons.first_name, persons.last_name \
|
|
|
|
|
FROM recordings \
|
|
|
|
|
JOIN works ON recordings.work = works.id \
|
|
|
|
|
JOIN persons ON works.composer = persons.id \
|
|
|
|
|
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()
|
2023-11-05 14:27:03 +01:00
|
|
|
.prepare(
|
|
|
|
|
"SELECT DISTINCT recordings.id, works.id, works.title, persons.id, persons.first_name, persons.last_name \
|
|
|
|
|
FROM recordings \
|
|
|
|
|
JOIN works ON recordings.work = works.id \
|
|
|
|
|
JOIN persons ON works.composer IS persons.id \
|
|
|
|
|
WHERE works.id IS ?1")
|
2023-10-08 14:50:27 +02:00
|
|
|
.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-25 17:45:32 +02:00
|
|
|
pub fn work_parts(&self, work: &Work) -> Vec<String> {
|
|
|
|
|
self.con()
|
|
|
|
|
.prepare("SELECT * FROM work_parts WHERE work IS ?1 ORDER BY part_index")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.query_map([&work.id], |row| row.get::<_, String>(3))
|
|
|
|
|
.unwrap()
|
|
|
|
|
.collect::<rusqlite::Result<Vec<String>>>()
|
|
|
|
|
.unwrap()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn tracks(&self, recording: &Recording) -> Vec<Track> {
|
|
|
|
|
self.con()
|
|
|
|
|
.prepare("SELECT * FROM tracks WHERE recording IS ?1 ORDER BY \"index\"")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.query_map([&recording.id], |row| {
|
|
|
|
|
Ok(Track {
|
|
|
|
|
work_parts: row
|
|
|
|
|
.get::<_, String>(4)?
|
|
|
|
|
.split(',')
|
|
|
|
|
.filter(|s| !s.is_empty())
|
|
|
|
|
.map(|s| str::parse::<usize>(s))
|
|
|
|
|
.collect::<Result<Vec<usize>, ParseIntError>>()
|
|
|
|
|
.expect("work part IDs should be valid integers"),
|
|
|
|
|
path: PathBuf::from(self.folder()).join(row.get::<_, String>(6)?),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.unwrap()
|
|
|
|
|
.collect::<rusqlite::Result<Vec<Track>>>()
|
|
|
|
|
.unwrap()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn random_recording(&self, query: &LibraryQuery) -> Option<Recording> {
|
|
|
|
|
match query {
|
|
|
|
|
LibraryQuery { .. } => self
|
|
|
|
|
.con()
|
|
|
|
|
.prepare("SELECT * FROM recordings ORDER BY RANDOM() LIMIT 1")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.query_map([], Recording::from_row)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.next()
|
|
|
|
|
.map(|r| r.unwrap()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn performances(&self, recording: &Recording) -> Vec<String> {
|
2023-10-08 16:40:59 +02:00
|
|
|
let mut performances = self
|
|
|
|
|
.con()
|
2023-11-05 14:27:03 +01:00
|
|
|
.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")
|
2023-10-08 16:40:59 +02:00
|
|
|
.unwrap()
|
|
|
|
|
.query_map([&recording.id], Performance::from_person_row)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.collect::<rusqlite::Result<Vec<Performance>>>()
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2023-11-05 14:27:03 +01:00
|
|
|
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(),
|
|
|
|
|
);
|
2023-10-08 16:40:59 +02:00
|
|
|
|
|
|
|
|
performances
|
2023-10-25 17:45:32 +02:00
|
|
|
.into_iter()
|
|
|
|
|
.map(|performance| match performance {
|
|
|
|
|
Performance::Person(person, role) => {
|
|
|
|
|
let mut result = person.name_fl();
|
|
|
|
|
if let Some(role) = role {
|
|
|
|
|
result.push_str(&format!(" ({})", role.name));
|
|
|
|
|
}
|
|
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
Performance::Ensemble(ensemble, role) => {
|
|
|
|
|
let mut result = ensemble.name;
|
|
|
|
|
if let Some(role) = role {
|
|
|
|
|
result.push_str(&format!(" ({})", role.name));
|
|
|
|
|
}
|
|
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<String>>()
|
2023-10-08 16:40:59 +02:00
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-25 17:45:32 +02:00
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct Track {
|
|
|
|
|
pub work_parts: Vec<usize>,
|
|
|
|
|
pub path: PathBuf,
|
|
|
|
|
}
|