mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 19:57:25 +01:00
Actually import from medium editor
This commit is contained in:
parent
5348b7750b
commit
aa6b5c6ac4
13 changed files with 328 additions and 239 deletions
|
|
@ -12,5 +12,4 @@ DROP TABLE "performances";
|
||||||
DROP TABLE "mediums";
|
DROP TABLE "mediums";
|
||||||
DROP TABLE "track_sets";
|
DROP TABLE "track_sets";
|
||||||
DROP TABLE "tracks";
|
DROP TABLE "tracks";
|
||||||
DROP TABLE "files";
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,11 +72,7 @@ CREATE TABLE "tracks" (
|
||||||
"id" TEXT NOT NULL PRIMARY KEY,
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
"track_set" TEXT NOT NULL REFERENCES "track_sets"("id") ON DELETE CASCADE,
|
"track_set" TEXT NOT NULL REFERENCES "track_sets"("id") ON DELETE CASCADE,
|
||||||
"index" INTEGER NOT NULL,
|
"index" INTEGER NOT NULL,
|
||||||
"work_parts" TEXT NOT NULL
|
"work_parts" TEXT NOT NULL,
|
||||||
);
|
"path" TEXT NOT NULL
|
||||||
|
|
||||||
CREATE TABLE "files" (
|
|
||||||
"file_name" TEXT NOT NULL PRIMARY KEY,
|
|
||||||
"track" TEXT NOT NULL REFERENCES "tracks"("id")
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,151 +2,181 @@
|
||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk+" version="3.24"/>
|
<requires lib="gtk+" version="3.24"/>
|
||||||
<requires lib="libhandy" version="1.0"/>
|
<requires lib="libhandy" version="1.0"/>
|
||||||
<object class="GtkBox" id="widget">
|
<object class="GtkStack" id="widget">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="orientation">vertical</property>
|
<property name="transition-type">crossfade</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="HdyHeaderBar">
|
<object class="GtkBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="title" translatable="yes">Import music</property>
|
<property name="orientation">vertical</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="back_button">
|
<object class="HdyHeaderBar">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">True</property>
|
<property name="title" translatable="yes">Import music</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImage">
|
<object class="GtkButton" id="back_button">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="icon-name">go-previous-symbolic</property>
|
<property name="can-focus">True</property>
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="done_button">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="sensitive">False</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkStack" id="done_stack">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="transition-type">crossfade</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkSpinner" id="spinner">
|
<object class="GtkImage">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="active">True</property>
|
<property name="icon-name">go-previous-symbolic</property>
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkImage" id="done">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="icon-name">object-select-symbolic</property>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<style>
|
|
||||||
<class name="suggested-action"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="pack-type">end</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkScrolledWindow">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="vexpand">True</property>
|
|
||||||
<child>
|
|
||||||
<object class="HdyClamp">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkButton" id="done_button">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="margin-start">6</property>
|
<property name="can-focus">True</property>
|
||||||
<property name="margin-end">6</property>
|
<property name="sensitive">False</property>
|
||||||
<property name="margin-bottom">6</property>
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel">
|
<object class="GtkStack" id="done_stack">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="halign">start</property>
|
<property name="transition-type">crossfade</property>
|
||||||
<property name="margin-top">12</property>
|
|
||||||
<property name="margin-bottom">6</property>
|
|
||||||
<property name="label" translatable="yes">Medium</property>
|
|
||||||
<attributes>
|
|
||||||
<attribute name="weight" value="bold"/>
|
|
||||||
</attributes>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkFrame">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="shadow-type">in</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkListBox">
|
<object class="GtkSpinner" id="spinner">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="selection-mode">none</property>
|
<property name="active">True</property>
|
||||||
<child>
|
</object>
|
||||||
<object class="HdyActionRow" id="name_row">
|
</child>
|
||||||
<property name="visible">True</property>
|
<child>
|
||||||
<property name="can-focus">True</property>
|
<object class="GtkImage" id="done">
|
||||||
<property name="activatable">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="title" translatable="yes">Name of the medium</property>
|
<property name="icon-name">object-select-symbolic</property>
|
||||||
<property name="activatable-widget">name_entry</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkEntry" id="name_entry">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="valign">center</property>
|
|
||||||
<property name="hexpand">True</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="suggested-action"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="pack-type">end</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkInfoBar" id="info_bar">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="revealed">False</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="HdyClamp">
|
||||||
|
<property name="visible">True</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="orientation">horizontal</property>
|
<property name="margin-start">6</property>
|
||||||
<property name="margin-top">12</property>
|
<property name="margin-end">6</property>
|
||||||
<property name="margin-bottom">6</property>
|
<property name="margin-bottom">6</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel">
|
<object class="GtkLabel">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="halign">start</property>
|
<property name="halign">start</property>
|
||||||
<property name="valign">end</property>
|
<property name="margin-top">12</property>
|
||||||
<property name="hexpand">True</property>
|
<property name="margin-bottom">6</property>
|
||||||
<property name="label" translatable="yes">Recordings</property>
|
<property name="label" translatable="yes">Medium</property>
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="weight" value="bold"/>
|
<attribute name="weight" value="bold"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="add_button">
|
<object class="GtkFrame">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">True</property>
|
<property name="shadow-type">in</property>
|
||||||
<property name="relief">none</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImage">
|
<object class="GtkListBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="icon-name">list-add-symbolic</property>
|
<property name="selection-mode">none</property>
|
||||||
|
<child>
|
||||||
|
<object class="HdyActionRow" id="name_row">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="activatable">True</property>
|
||||||
|
<property name="title" translatable="yes">Name of the medium</property>
|
||||||
|
<property name="activatable-widget">name_entry</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="name_entry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="HdyActionRow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="activatable">True</property>
|
||||||
|
<property name="title" translatable="yes">Publish to the server</property>
|
||||||
|
<property name="activatable-widget">publish_switch</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSwitch" id="publish_switch">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="active">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
<child>
|
||||||
</child>
|
<object class="GtkBox">
|
||||||
<child>
|
<property name="visible">True</property>
|
||||||
<object class="GtkFrame" id="frame">
|
<property name="orientation">horizontal</property>
|
||||||
<property name="visible">True</property>
|
<property name="margin-top">12</property>
|
||||||
<property name="shadow-type">in</property>
|
<property name="margin-bottom">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="valign">end</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="label" translatable="yes">Recordings</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="weight" value="bold"/>
|
||||||
|
</attributes>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="add_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="relief">none</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="icon-name">list-add-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkFrame" id="frame">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="shadow-type">in</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
@ -154,6 +184,18 @@
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="name">content</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSpinner">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="active">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="name">loading</property>
|
||||||
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
use super::schema::files;
|
|
||||||
use super::Database;
|
|
||||||
use anyhow::Result;
|
|
||||||
use diesel::prelude::*;
|
|
||||||
|
|
||||||
/// Table data to associate audio files with tracks.
|
|
||||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
|
||||||
#[table_name = "files"]
|
|
||||||
struct FileRow {
|
|
||||||
pub file_name: String,
|
|
||||||
pub track: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Database {
|
|
||||||
/// Insert or update a file. This assumes that the track is already in the
|
|
||||||
/// database.
|
|
||||||
pub fn update_file(&self, file_name: &str, track_id: &str) -> Result<()> {
|
|
||||||
let row = FileRow {
|
|
||||||
file_name: file_name.to_owned(),
|
|
||||||
track: track_id.to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
diesel::insert_into(files::table)
|
|
||||||
.values(row)
|
|
||||||
.execute(&self.connection)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete an existing file. This will not delete the file from the file
|
|
||||||
/// system but just the representing row from the database.
|
|
||||||
pub fn delete_file(&self, file_name: &str) -> Result<()> {
|
|
||||||
diesel::delete(files::table.filter(files::file_name.eq(file_name)))
|
|
||||||
.execute(&self.connection)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the file name of the audio file for the specified track.
|
|
||||||
pub fn get_file(&self, track_id: &str) -> Result<Option<String>> {
|
|
||||||
let row = files::table
|
|
||||||
.filter(files::track.eq(track_id))
|
|
||||||
.load::<FileRow>(&self.connection)?
|
|
||||||
.into_iter()
|
|
||||||
.next();
|
|
||||||
|
|
||||||
let file_name = match row {
|
|
||||||
Some(row) => Some(row.file_name),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(file_name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use super::generate_id;
|
use super::generate_id;
|
||||||
use super::schema::{mediums, track_sets, tracks};
|
use super::schema::{mediums, recordings, track_sets, tracks};
|
||||||
use super::{Database, Recording};
|
use super::{Database, Recording};
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{anyhow, Error, Result};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
|
@ -41,6 +41,11 @@ pub struct Track {
|
||||||
/// The work parts that are played on this track. They are indices to the
|
/// The work parts that are played on this track. They are indices to the
|
||||||
/// work parts of the work that is associated with the recording.
|
/// work parts of the work that is associated with the recording.
|
||||||
pub work_parts: Vec<usize>,
|
pub work_parts: Vec<usize>,
|
||||||
|
|
||||||
|
/// The path to the audio file containing this track. This will not be
|
||||||
|
/// included when communicating with the server.
|
||||||
|
#[serde(skip)]
|
||||||
|
pub path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Table data for a [`Medium`].
|
/// Table data for a [`Medium`].
|
||||||
|
|
@ -70,6 +75,7 @@ struct TrackRow {
|
||||||
pub track_set: String,
|
pub track_set: String,
|
||||||
pub index: i32,
|
pub index: i32,
|
||||||
pub work_parts: String,
|
pub work_parts: String,
|
||||||
|
pub path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Database {
|
impl Database {
|
||||||
|
|
@ -83,7 +89,28 @@ impl Database {
|
||||||
// This will also delete the track sets and tracks.
|
// This will also delete the track sets and tracks.
|
||||||
self.delete_medium(medium_id)?;
|
self.delete_medium(medium_id)?;
|
||||||
|
|
||||||
|
// Add the new medium.
|
||||||
|
|
||||||
|
let medium_row = MediumRow {
|
||||||
|
id: medium_id.to_owned(),
|
||||||
|
name: medium.name.clone(),
|
||||||
|
discid: medium.discid.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
diesel::insert_into(mediums::table)
|
||||||
|
.values(medium_row)
|
||||||
|
.execute(&self.connection)?;
|
||||||
|
|
||||||
for (index, track_set) in medium.tracks.iter().enumerate() {
|
for (index, track_set) in medium.tracks.iter().enumerate() {
|
||||||
|
// Add associated items from the server, if they don't already
|
||||||
|
// exist.
|
||||||
|
|
||||||
|
if self.get_recording(&track_set.recording.id)?.is_none() {
|
||||||
|
self.update_recording(track_set.recording.clone())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the actual track set data.
|
||||||
|
|
||||||
let track_set_id = generate_id();
|
let track_set_id = generate_id();
|
||||||
|
|
||||||
let track_set_row = TrackSetRow {
|
let track_set_row = TrackSetRow {
|
||||||
|
|
@ -110,6 +137,7 @@ impl Database {
|
||||||
track_set: track_set_id.clone(),
|
track_set: track_set_id.clone(),
|
||||||
index: index as i32,
|
index: index as i32,
|
||||||
work_parts,
|
work_parts,
|
||||||
|
path: track.path.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
diesel::insert_into(tracks::table)
|
diesel::insert_into(tracks::table)
|
||||||
|
|
@ -147,6 +175,35 @@ impl Database {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all tracks for a recording.
|
||||||
|
pub fn get_tracks(&self, recording_id: &str) -> Result<Vec<Track>> {
|
||||||
|
let mut tracks: Vec<Track> = Vec::new();
|
||||||
|
|
||||||
|
let rows = tracks::table
|
||||||
|
.inner_join(track_sets::table.on(track_sets::id.eq(tracks::track_set)))
|
||||||
|
.inner_join(recordings::table.on(recordings::id.eq(track_sets::recording)))
|
||||||
|
.filter(recordings::id.eq(recording_id))
|
||||||
|
.select(tracks::table::all_columns())
|
||||||
|
.load::<TrackRow>(&self.connection)?;
|
||||||
|
|
||||||
|
for row in rows {
|
||||||
|
let work_parts = row
|
||||||
|
.work_parts
|
||||||
|
.split(',')
|
||||||
|
.map(|part_index| Ok(str::parse(part_index)?))
|
||||||
|
.collect::<Result<Vec<usize>>>()?;
|
||||||
|
|
||||||
|
let track = Track {
|
||||||
|
work_parts,
|
||||||
|
path: row.path.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
tracks.push(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(tracks)
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieve all available information on a medium from related tables.
|
/// Retrieve all available information on a medium from related tables.
|
||||||
fn get_medium_data(&self, row: MediumRow) -> Result<Medium> {
|
fn get_medium_data(&self, row: MediumRow) -> Result<Medium> {
|
||||||
let track_set_rows = track_sets::table
|
let track_set_rows = track_sets::table
|
||||||
|
|
@ -177,7 +234,10 @@ impl Database {
|
||||||
.map(|part_index| Ok(str::parse(part_index)?))
|
.map(|part_index| Ok(str::parse(part_index)?))
|
||||||
.collect::<Result<Vec<usize>>>()?;
|
.collect::<Result<Vec<usize>>>()?;
|
||||||
|
|
||||||
let track = Track { work_parts };
|
let track = Track {
|
||||||
|
work_parts,
|
||||||
|
path: track_row.path.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
tracks.push(track);
|
tracks.push(track);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,6 @@ pub use recordings::*;
|
||||||
pub mod thread;
|
pub mod thread;
|
||||||
pub use thread::*;
|
pub use thread::*;
|
||||||
|
|
||||||
pub mod files;
|
|
||||||
pub use files::*;
|
|
||||||
|
|
||||||
pub mod works;
|
pub mod works;
|
||||||
pub use works::*;
|
pub use works::*;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,6 @@ table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table! {
|
|
||||||
files (file_name) {
|
|
||||||
file_name -> Text,
|
|
||||||
track -> Text,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
instrumentations (id) {
|
instrumentations (id) {
|
||||||
id -> BigInt,
|
id -> BigInt,
|
||||||
|
|
@ -76,6 +69,7 @@ table! {
|
||||||
track_set -> Text,
|
track_set -> Text,
|
||||||
index -> Integer,
|
index -> Integer,
|
||||||
work_parts -> Text,
|
work_parts -> Text,
|
||||||
|
path -> Text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,7 +100,6 @@ table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
joinable!(files -> tracks (track));
|
|
||||||
joinable!(instrumentations -> instruments (instrument));
|
joinable!(instrumentations -> instruments (instrument));
|
||||||
joinable!(instrumentations -> works (work));
|
joinable!(instrumentations -> works (work));
|
||||||
joinable!(performances -> ensembles (ensemble));
|
joinable!(performances -> ensembles (ensemble));
|
||||||
|
|
@ -124,7 +117,6 @@ joinable!(works -> persons (composer));
|
||||||
|
|
||||||
allow_tables_to_appear_in_same_query!(
|
allow_tables_to_appear_in_same_query!(
|
||||||
ensembles,
|
ensembles,
|
||||||
files,
|
|
||||||
instrumentations,
|
instrumentations,
|
||||||
instruments,
|
instruments,
|
||||||
mediums,
|
mediums,
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,7 @@ enum Action {
|
||||||
UpdateMedium(Medium, Sender<Result<()>>),
|
UpdateMedium(Medium, Sender<Result<()>>),
|
||||||
GetMedium(String, Sender<Result<Option<Medium>>>),
|
GetMedium(String, Sender<Result<Option<Medium>>>),
|
||||||
DeleteMedium(String, Sender<Result<()>>),
|
DeleteMedium(String, Sender<Result<()>>),
|
||||||
UpdateFile(String, String, Sender<Result<()>>),
|
GetTracks(String, Sender<Result<Vec<Track>>>),
|
||||||
DeleteFile(String, Sender<Result<()>>),
|
|
||||||
GetFile(String, Sender<Result<Option<String>>>),
|
|
||||||
Stop(Sender<()>),
|
Stop(Sender<()>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,14 +134,8 @@ impl DbThread {
|
||||||
DeleteMedium(id, sender) => {
|
DeleteMedium(id, sender) => {
|
||||||
sender.send(db.delete_medium(&id)).unwrap();
|
sender.send(db.delete_medium(&id)).unwrap();
|
||||||
}
|
}
|
||||||
UpdateFile(file_name, track_id, sender) => {
|
GetTracks(recording_id, sender) => {
|
||||||
sender.send(db.update_file(&file_name, &track_id)).unwrap();
|
sender.send(db.get_tracks(&recording_id)).unwrap();
|
||||||
}
|
|
||||||
DeleteFile(file_name, sender) => {
|
|
||||||
sender.send(db.delete_file(&file_name)).unwrap();
|
|
||||||
}
|
|
||||||
GetFile(track_id, sender) => {
|
|
||||||
sender.send(db.get_file(&track_id)).unwrap();
|
|
||||||
}
|
}
|
||||||
Stop(sender) => {
|
Stop(sender) => {
|
||||||
sender.send(()).unwrap();
|
sender.send(()).unwrap();
|
||||||
|
|
@ -347,38 +339,10 @@ impl DbThread {
|
||||||
receiver.await?
|
receiver.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert or update a file. This assumes that the track is already in the
|
/// Get all tracks for a recording.
|
||||||
/// database.
|
pub async fn get_tracks(&self, recording_id: &str) -> Result<Vec<Track>> {
|
||||||
pub async fn update_file(&self, file_name: &str, track_id: &str) -> Result<()> {
|
|
||||||
let (sender, receiver) = oneshot::channel();
|
let (sender, receiver) = oneshot::channel();
|
||||||
|
self.action_sender.send(GetTracks(recording_id.to_owned(), sender))?;
|
||||||
self.action_sender.send(UpdateFile(
|
|
||||||
file_name.to_owned(),
|
|
||||||
track_id.to_owned(),
|
|
||||||
sender,
|
|
||||||
))?;
|
|
||||||
|
|
||||||
receiver.await?
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete an existing file. This will not delete the file from the file
|
|
||||||
/// system but just the representing row from the database.
|
|
||||||
pub async fn delete_file(&self, file_name: &str) -> Result<()> {
|
|
||||||
let (sender, receiver) = oneshot::channel();
|
|
||||||
|
|
||||||
self.action_sender
|
|
||||||
.send(DeleteFile(file_name.to_owned(), sender))?;
|
|
||||||
|
|
||||||
receiver.await?
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the file name of the audio file for the specified track.
|
|
||||||
pub async fn get_file(&self, track_id: &str) -> Result<Option<String>> {
|
|
||||||
let (sender, receiver) = oneshot::channel();
|
|
||||||
|
|
||||||
self.action_sender
|
|
||||||
.send(GetFile(track_id.to_owned(), sender))?;
|
|
||||||
|
|
||||||
receiver.await?
|
receiver.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ pub struct DiscSource {
|
||||||
/// The MusicBrainz DiscID of the CD.
|
/// The MusicBrainz DiscID of the CD.
|
||||||
pub discid: String,
|
pub discid: String,
|
||||||
|
|
||||||
|
/// The path to the temporary directory where the audio files will be.
|
||||||
|
pub path: PathBuf,
|
||||||
|
|
||||||
/// The tracks on this disc.
|
/// The tracks on this disc.
|
||||||
pub tracks: Vec<TrackSource>,
|
pub tracks: Vec<TrackSource>,
|
||||||
}
|
}
|
||||||
|
|
@ -96,6 +99,7 @@ impl DiscSource {
|
||||||
let disc = DiscSource {
|
let disc = DiscSource {
|
||||||
discid: id,
|
discid: id,
|
||||||
tracks,
|
tracks,
|
||||||
|
path: tmp_dir,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(disc)
|
Ok(disc)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
use super::disc_source::DiscSource;
|
use super::disc_source::DiscSource;
|
||||||
use super::track_set_editor::{TrackSetData, TrackSetEditor};
|
use super::track_set_editor::{TrackSetData, TrackSetEditor};
|
||||||
|
use crate::database::{generate_id, Medium, Track, TrackSet};
|
||||||
use crate::backend::Backend;
|
use crate::backend::Backend;
|
||||||
use crate::widgets::{Navigator, NavigatorScreen};
|
use crate::widgets::{Navigator, NavigatorScreen};
|
||||||
use crate::widgets::new_list::List;
|
use crate::widgets::new_list::List;
|
||||||
|
use anyhow::Result;
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
use glib::prelude::*;
|
use glib::prelude::*;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
|
@ -15,11 +17,12 @@ use std::rc::Rc;
|
||||||
pub struct MediumEditor {
|
pub struct MediumEditor {
|
||||||
backend: Rc<Backend>,
|
backend: Rc<Backend>,
|
||||||
source: Rc<DiscSource>,
|
source: Rc<DiscSource>,
|
||||||
widget: gtk::Box,
|
widget: gtk::Stack,
|
||||||
done_button: gtk::Button,
|
done_button: gtk::Button,
|
||||||
done_stack: gtk::Stack,
|
done_stack: gtk::Stack,
|
||||||
done: gtk::Image,
|
done: gtk::Image,
|
||||||
name_entry: gtk::Entry,
|
name_entry: gtk::Entry,
|
||||||
|
publish_switch: gtk::Switch,
|
||||||
track_set_list: List,
|
track_set_list: List,
|
||||||
track_sets: RefCell<Vec<TrackSetData>>,
|
track_sets: RefCell<Vec<TrackSetData>>,
|
||||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||||
|
|
@ -32,12 +35,13 @@ impl MediumEditor {
|
||||||
|
|
||||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/medium_editor.ui");
|
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/medium_editor.ui");
|
||||||
|
|
||||||
get_widget!(builder, gtk::Box, widget);
|
get_widget!(builder, gtk::Stack, widget);
|
||||||
get_widget!(builder, gtk::Button, back_button);
|
get_widget!(builder, gtk::Button, back_button);
|
||||||
get_widget!(builder, gtk::Button, done_button);
|
get_widget!(builder, gtk::Button, done_button);
|
||||||
get_widget!(builder, gtk::Stack, done_stack);
|
get_widget!(builder, gtk::Stack, done_stack);
|
||||||
get_widget!(builder, gtk::Image, done);
|
get_widget!(builder, gtk::Image, done);
|
||||||
get_widget!(builder, gtk::Entry, name_entry);
|
get_widget!(builder, gtk::Entry, name_entry);
|
||||||
|
get_widget!(builder, gtk::Switch, publish_switch);
|
||||||
get_widget!(builder, gtk::Button, add_button);
|
get_widget!(builder, gtk::Button, add_button);
|
||||||
get_widget!(builder, gtk::Frame, frame);
|
get_widget!(builder, gtk::Frame, frame);
|
||||||
|
|
||||||
|
|
@ -52,6 +56,7 @@ impl MediumEditor {
|
||||||
done_stack,
|
done_stack,
|
||||||
done,
|
done,
|
||||||
name_entry,
|
name_entry,
|
||||||
|
publish_switch,
|
||||||
track_set_list: list,
|
track_set_list: list,
|
||||||
track_sets: RefCell::new(Vec::new()),
|
track_sets: RefCell::new(Vec::new()),
|
||||||
navigator: RefCell::new(None),
|
navigator: RefCell::new(None),
|
||||||
|
|
@ -66,6 +71,22 @@ impl MediumEditor {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
this.done_button.connect_clicked(clone!(@strong this => move |_| {
|
||||||
|
let context = glib::MainContext::default();
|
||||||
|
let clone = this.clone();
|
||||||
|
context.spawn_local(async move {
|
||||||
|
clone.widget.set_visible_child_name("loading");
|
||||||
|
match clone.clone().save().await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(err) => {
|
||||||
|
println!("{:?}", err);
|
||||||
|
// clone.info_bar.set_revealed(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
add_button.connect_clicked(clone!(@strong this => move |_| {
|
add_button.connect_clicked(clone!(@strong this => move |_| {
|
||||||
let navigator = this.navigator.borrow().clone();
|
let navigator = this.navigator.borrow().clone();
|
||||||
if let Some(navigator) = navigator {
|
if let Some(navigator) = navigator {
|
||||||
|
|
@ -130,6 +151,79 @@ impl MediumEditor {
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Save the medium and possibly upload it to the server.
|
||||||
|
async fn save(self: Rc<Self>) -> Result<()> {
|
||||||
|
let name = self.name_entry.get_text().to_string();
|
||||||
|
|
||||||
|
// Create a new directory in the music library path for the imported medium.
|
||||||
|
|
||||||
|
let mut path = self.backend.get_music_library_path().unwrap().clone();
|
||||||
|
path.push(&name);
|
||||||
|
std::fs::create_dir(&path)?;
|
||||||
|
|
||||||
|
// Convert the track set data to real track sets.
|
||||||
|
|
||||||
|
let mut track_sets = Vec::new();
|
||||||
|
|
||||||
|
for track_set_data in &*self.track_sets.borrow() {
|
||||||
|
let mut tracks = Vec::new();
|
||||||
|
|
||||||
|
for track_data in &track_set_data.tracks {
|
||||||
|
// Copy the corresponding audio file to the music library.
|
||||||
|
|
||||||
|
let track_source = &self.source.tracks[track_data.track_source];
|
||||||
|
let file_name = format!("track_{:02}.flac", track_source.number);
|
||||||
|
|
||||||
|
let mut track_path = path.clone();
|
||||||
|
track_path.push(&file_name);
|
||||||
|
|
||||||
|
std::fs::copy(&track_source.path, &track_path)?;
|
||||||
|
|
||||||
|
// Create the real track.
|
||||||
|
|
||||||
|
let track = Track {
|
||||||
|
work_parts: track_data.work_parts.clone(),
|
||||||
|
path: track_path.to_str().unwrap().to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
tracks.push(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
let track_set = TrackSet {
|
||||||
|
recording: track_set_data.recording.clone(),
|
||||||
|
tracks,
|
||||||
|
};
|
||||||
|
|
||||||
|
track_sets.push(track_set);
|
||||||
|
}
|
||||||
|
|
||||||
|
let medium = Medium {
|
||||||
|
id: generate_id(),
|
||||||
|
name: self.name_entry.get_text().to_string(),
|
||||||
|
discid: Some(self.source.discid.clone()),
|
||||||
|
tracks: track_sets,
|
||||||
|
};
|
||||||
|
|
||||||
|
let upload = self.publish_switch.get_active();
|
||||||
|
if upload {
|
||||||
|
// self.backend.post_medium(&medium).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.backend
|
||||||
|
.db()
|
||||||
|
.update_medium(medium.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
self.backend.library_changed();
|
||||||
|
|
||||||
|
let navigator = self.navigator.borrow().clone();
|
||||||
|
if let Some(navigator) = navigator {
|
||||||
|
navigator.clone().pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NavigatorScreen for MediumEditor {
|
impl NavigatorScreen for MediumEditor {
|
||||||
|
|
|
||||||
|
|
@ -141,10 +141,6 @@ impl TrackSetEditor {
|
||||||
let mut tracks = Vec::new();
|
let mut tracks = Vec::new();
|
||||||
|
|
||||||
for index in selection {
|
for index in selection {
|
||||||
let track = Track {
|
|
||||||
work_parts: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let data = TrackData {
|
let data = TrackData {
|
||||||
track_source: index,
|
track_source: index,
|
||||||
work_parts: Vec::new(),
|
work_parts: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,6 @@ sources = files(
|
||||||
'backend/mod.rs',
|
'backend/mod.rs',
|
||||||
'backend/secure.rs',
|
'backend/secure.rs',
|
||||||
'database/ensembles.rs',
|
'database/ensembles.rs',
|
||||||
'database/files.rs',
|
|
||||||
'database/instruments.rs',
|
'database/instruments.rs',
|
||||||
'database/medium.rs',
|
'database/medium.rs',
|
||||||
'database/mod.rs',
|
'database/mod.rs',
|
||||||
|
|
|
||||||
|
|
@ -76,15 +76,15 @@ impl RecordingScreen {
|
||||||
title_label.set_ellipsize(pango::EllipsizeMode::End);
|
title_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||||
title_label.set_halign(gtk::Align::Start);
|
title_label.set_halign(gtk::Align::Start);
|
||||||
|
|
||||||
// let file_name_label = gtk::Label::new(Some(&track.file_name));
|
let file_name_label = gtk::Label::new(Some(&track.path));
|
||||||
// file_name_label.set_ellipsize(pango::EllipsizeMode::End);
|
file_name_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||||
// file_name_label.set_opacity(0.5);
|
file_name_label.set_opacity(0.5);
|
||||||
// file_name_label.set_halign(gtk::Align::Start);
|
file_name_label.set_halign(gtk::Align::Start);
|
||||||
|
|
||||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||||
vbox.set_border_width(6);
|
vbox.set_border_width(6);
|
||||||
vbox.add(&title_label);
|
vbox.add(&title_label);
|
||||||
// vbox.add(&file_name_label);
|
vbox.add(&file_name_label);
|
||||||
|
|
||||||
vbox.upcast()
|
vbox.upcast()
|
||||||
}));
|
}));
|
||||||
|
|
@ -138,16 +138,16 @@ impl RecordingScreen {
|
||||||
let context = glib::MainContext::default();
|
let context = glib::MainContext::default();
|
||||||
let clone = result.clone();
|
let clone = result.clone();
|
||||||
context.spawn_local(async move {
|
context.spawn_local(async move {
|
||||||
// let tracks = clone
|
let tracks = clone
|
||||||
// .backend
|
.backend
|
||||||
// .db()
|
.db()
|
||||||
// .get_tracks(&clone.recording.id)
|
.get_tracks(&clone.recording.id)
|
||||||
// .await
|
.await
|
||||||
// .unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// list.show_items(tracks.clone());
|
list.show_items(tracks.clone());
|
||||||
// clone.stack.set_visible_child_name("content");
|
clone.stack.set_visible_child_name("content");
|
||||||
// clone.tracks.replace(tracks);
|
clone.tracks.replace(tracks);
|
||||||
});
|
});
|
||||||
|
|
||||||
result
|
result
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue