Compare commits

..

No commits in common. "main" and "v0.1.0" have entirely different histories.
main ... v0.1.0

42 changed files with 319 additions and 873 deletions

1
Cargo.lock generated
View file

@ -1880,6 +1880,7 @@ dependencies = [
"glib",
"gstreamer-play",
"gtk4",
"lazy_static",
"libadwaita",
"log",
"mpris-server",

View file

@ -17,6 +17,7 @@ gettext-rs = { version = "0.7", features = ["gettext-system"] }
glib = { version = "0.20", features = ["v2_84"] }
gstreamer-play = "0.23"
gtk = { package = "gtk4", version = "0.9", features = ["v4_18", "blueprint"] }
lazy_static = "1"
log = "0.4"
mpris-server = "0.8"
once_cell = "1"

View file

@ -6,12 +6,15 @@
font-size: smaller;
}
.rounded-entry {
border-radius: 999px;
padding-left: 12px;
padding-right: 12px;
padding-top: 3px;
padding-bottom: 3px;
.searchbar .searchtag {
background-color: alpha(currentColor, 0.1);
border-radius: 100px;
}
.searchbar .searchtag>button {
min-width: 24px;
min-height: 24px;
margin: 0px;
}
.tile {

View file

@ -65,15 +65,9 @@ template $MusicusAlbumEditor: Adw.NavigationPage {
margin-top: 24;
styles [
"boxed-list-separate",
"boxed-list",
]
Adw.SwitchRow enable_updates_row {
title: _("Enable updates");
subtitle: _("Keep this item up to date with the online metadata library");
active: true;
}
Adw.ButtonRow save_row {
title: _("_Create album");
use-underline: true;

View file

@ -50,24 +50,16 @@ template $MusicusEmptyPage: Adw.NavigationPage {
}
menu primary_menu {
section {
item {
label: _("_Import music");
action: "win.import";
}
item {
label: _("_Create album");
action: "win.create-album";
}
item {
label: _("_Library manager");
action: "win.library";
}
}
section {
item {
label: _("_Preferences");
action: "win.preferences";
@ -77,5 +69,4 @@ menu primary_menu {
label: _("_About Musicus");
action: "app.about";
}
}
}

View file

@ -78,10 +78,6 @@ template $MusicusSearchPage: Adw.NavigationPage {
placeholder-text: _("Enter composers, performers, works…");
margin-top: 24;
activate => $select() swapped;
styles [
"rounded-entry",
]
}
Gtk.Stack stack {
@ -264,24 +260,16 @@ template $MusicusSearchPage: Adw.NavigationPage {
}
menu primary_menu {
section {
item {
label: _("_Import music");
action: "win.import";
}
item {
label: _("_Create album");
action: "win.create-album";
}
item {
label: _("_Library manager");
action: "win.library";
}
}
section {
item {
label: _("_Preferences");
action: "win.preferences";
@ -291,7 +279,6 @@ menu primary_menu {
label: _("_About Musicus");
action: "app.about";
}
}
}
menu item_menu {

22
data/ui/search_tag.blp Normal file
View file

@ -0,0 +1,22 @@
using Gtk 4.0;
template $MusicusSearchTag : Gtk.Box {
styles ["searchtag"]
margin-start: 6;
margin-end: 6;
Gtk.Label label {
styles ["caption-heading"]
margin-start: 12;
margin-end: 6;
max-width-chars: 15;
ellipsize: end;
}
Gtk.Button button {
styles ["flat", "circular"]
icon-name: "window-close-symbolic";
clicked => $remove() swapped;
}
}

View file

@ -5,7 +5,7 @@
"sdk": "org.gnome.Sdk",
"sdk-extensions": [
"org.freedesktop.Sdk.Extension.rust-stable",
"org.freedesktop.Sdk.Extension.llvm20"
"org.freedesktop.Sdk.Extension.llvm18"
],
"command": "musicus",
"finish-args": [
@ -20,7 +20,7 @@
"--env=G_MESSAGES_DEBUG=none"
],
"build-options": {
"append-path": "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm20/bin",
"append-path": "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm18/bin",
"build-args": [
"--share=network"
],
@ -31,6 +31,17 @@
}
},
"modules": [
{
"name": "blueprint-compiler",
"buildsystem": "meson",
"sources": [
{
"type": "git",
"url": "https://gitlab.gnome.org/jwestman/blueprint-compiler.git",
"tag": "v0.16.0"
}
]
},
{
"name": "musicus",
"buildsystem": "meson",

View file

@ -5,7 +5,7 @@
"sdk": "org.gnome.Sdk",
"sdk-extensions": [
"org.freedesktop.Sdk.Extension.rust-stable",
"org.freedesktop.Sdk.Extension.llvm20"
"org.freedesktop.Sdk.Extension.llvm18"
],
"command": "musicus",
"finish-args": [
@ -20,7 +20,7 @@
"--env=G_MESSAGES_DEBUG=none"
],
"build-options": {
"append-path": "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm20/bin",
"append-path": "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm18/bin",
"build-args": [
"--share=network"
],

View file

@ -1,248 +0,0 @@
CREATE TABLE persons_old (
person_id TEXT NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_played_at TIMESTAMP,
enable_updates BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE roles_old (
role_id TEXT NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
enable_updates BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE instruments_old (
instrument_id TEXT NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_played_at TIMESTAMP,
enable_updates BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE works_old (
work_id TEXT NOT NULL PRIMARY KEY,
parent_work_id TEXT REFERENCES works(work_id),
sequence_number INTEGER,
name TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_played_at TIMESTAMP,
enable_updates BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE ensembles_old (
ensemble_id TEXT NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_played_at TIMESTAMP,
enable_updates BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE recordings_old (
recording_id TEXT NOT NULL PRIMARY KEY,
work_id TEXT NOT NULL REFERENCES works(work_id),
year INTEGER,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_played_at TIMESTAMP,
enable_updates BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE mediums_old (
medium_id TEXT NOT NULL PRIMARY KEY REFERENCES item_state(id),
discid TEXT NOT NULL,
enable_updates BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_played_at TIMESTAMP
);
CREATE TABLE albums_old (
album_id TEXT NOT NULL PRIMARY KEY REFERENCES item_state(id),
name TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_played_at TIMESTAMP
);
INSERT INTO persons_old (
person_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at,
enable_updates
)
SELECT person_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at,
enable_updates
FROM persons;
DROP TABLE persons;
ALTER TABLE persons_old
RENAME TO persons;
INSERT INTO roles_old (
role_id,
name,
created_at,
edited_at,
last_used_at,
enable_updates
)
SELECT role_id,
name,
created_at,
edited_at,
last_used_at,
enable_updates
FROM roles;
DROP TABLE roles;
ALTER TABLE roles_old
RENAME TO roles;
INSERT INTO instruments_old (
instrument_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at,
enable_updates
)
SELECT instrument_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at,
enable_updates
FROM instruments;
DROP TABLE instruments;
ALTER TABLE instruments_old
RENAME TO instruments;
INSERT INTO works_old (
work_id,
parent_work_id,
sequence_number,
name,
created_at,
edited_at,
last_used_at,
last_played_at,
enable_updates
)
SELECT work_id,
parent_work_id,
sequence_number,
name,
created_at,
edited_at,
last_used_at,
last_played_at,
enable_updates
FROM works;
DROP TABLE works;
ALTER TABLE works_old
RENAME TO works;
INSERT INTO ensembles_old (
ensemble_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at,
enable_updates
)
SELECT ensemble_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at,
enable_updates
FROM ensembles;
DROP TABLE ensembles;
ALTER TABLE ensembles_old
RENAME TO ensembles;
INSERT INTO recordings_old (
recording_id,
work_id,
year,
created_at,
edited_at,
last_used_at,
last_played_at,
enable_updates
)
SELECT recording_id,
work_id,
year,
created_at,
edited_at,
last_used_at,
last_played_at,
enable_updates
FROM recordings;
DROP TABLE recordings;
ALTER TABLE recordings_old
RENAME TO recordings;
INSERT INTO mediums_old (
medium_id,
discid,
created_at,
edited_at,
last_used_at,
last_played_at
)
SELECT medium_id,
discid,
created_at,
edited_at,
last_used_at,
last_played_at
FROM mediums;
DROP TABLE mediums;
ALTER TABLE mediums_old
RENAME TO mediums;
INSERT INTO albums_old (
album_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at
)
SELECT album_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at
FROM albums;
DROP TABLE albums;
ALTER TABLE albums_old
RENAME TO albums;

View file

@ -1,245 +0,0 @@
CREATE TABLE persons_new (
person_id TEXT NOT NULL PRIMARY KEY REFERENCES item_state(id),
name TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'user',
enable_updates BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_played_at TIMESTAMP
);
CREATE TABLE roles_new (
role_id TEXT NOT NULL PRIMARY KEY REFERENCES item_state(id),
name TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'user',
enable_updates BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime'))
);
CREATE TABLE instruments_new (
instrument_id TEXT NOT NULL PRIMARY KEY REFERENCES item_state(id),
name TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'user',
enable_updates BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_played_at TIMESTAMP
);
CREATE TABLE works_new (
work_id TEXT NOT NULL PRIMARY KEY REFERENCES item_state(id),
parent_work_id TEXT REFERENCES works(work_id),
sequence_number INTEGER,
name TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'user',
enable_updates BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_played_at TIMESTAMP
);
CREATE TABLE ensembles_new (
ensemble_id TEXT NOT NULL PRIMARY KEY REFERENCES item_state(id),
name TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'user',
enable_updates BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_played_at TIMESTAMP
);
CREATE TABLE recordings_new (
recording_id TEXT NOT NULL PRIMARY KEY REFERENCES item_state(id),
work_id TEXT NOT NULL REFERENCES works(work_id),
year INTEGER,
source TEXT NOT NULL DEFAULT 'user',
enable_updates BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_played_at TIMESTAMP
);
CREATE TABLE mediums_new (
medium_id TEXT NOT NULL PRIMARY KEY REFERENCES item_state(id),
discid TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'user',
enable_updates BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_played_at TIMESTAMP
);
CREATE TABLE albums_new (
album_id TEXT NOT NULL PRIMARY KEY REFERENCES item_state(id),
name TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'user',
enable_updates BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
edited_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_used_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now', 'localtime')),
last_played_at TIMESTAMP
);
INSERT INTO persons_new (
person_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at
)
SELECT person_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at
FROM persons;
DROP TABLE persons;
ALTER TABLE persons_new
RENAME TO persons;
INSERT INTO roles_new (
role_id,
name,
created_at,
edited_at,
last_used_at
)
SELECT role_id,
name,
created_at,
edited_at,
last_used_at
FROM roles;
DROP TABLE roles;
ALTER TABLE roles_new
RENAME TO roles;
INSERT INTO instruments_new (
instrument_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at
)
SELECT instrument_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at
FROM instruments;
DROP TABLE instruments;
ALTER TABLE instruments_new
RENAME TO instruments;
INSERT INTO works_new (
work_id,
parent_work_id,
sequence_number,
name,
created_at,
edited_at,
last_used_at,
last_played_at
)
SELECT work_id,
parent_work_id,
sequence_number,
name,
created_at,
edited_at,
last_used_at,
last_played_at
FROM works;
DROP TABLE works;
ALTER TABLE works_new
RENAME TO works;
INSERT INTO ensembles_new (
ensemble_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at
)
SELECT ensemble_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at
FROM ensembles;
DROP TABLE ensembles;
ALTER TABLE ensembles_new
RENAME TO ensembles;
INSERT INTO recordings_new (
recording_id,
work_id,
year,
created_at,
edited_at,
last_used_at,
last_played_at
)
SELECT recording_id,
work_id,
year,
created_at,
edited_at,
last_used_at,
last_played_at
FROM recordings;
DROP TABLE recordings;
ALTER TABLE recordings_new
RENAME TO recordings;
INSERT INTO mediums_new (
medium_id,
discid,
created_at,
edited_at,
last_used_at,
last_played_at
)
SELECT medium_id,
discid,
created_at,
edited_at,
last_used_at,
last_played_at
FROM mediums;
DROP TABLE mediums;
ALTER TABLE mediums_new
RENAME TO mediums;
INSERT INTO albums_new (
album_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at
)
SELECT album_id,
name,
created_at,
edited_at,
last_used_at,
last_played_at
FROM albums;
DROP TABLE albums;
ALTER TABLE albums_new
RENAME TO albums;

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-30 15:27+0200\n"
"POT-Creation-Date: 2025-04-27 17:54+0200\n"
"PO-Revision-Date: 2025-04-27 18:23+0200\n"
"Last-Translator: elias@johrpan.de\n"
"Language-Team: German <translation-team-de@lists.sourceforge.net>\n"
@ -818,7 +818,7 @@ msgstr "Bibliothek exportieren"
msgid "Exporting music library to {}"
msgstr "Bibliothek wird nach {} exportiert"
#: src/library_manager.rs:234 src/window.rs:305
#: src/library_manager.rs:234 src/window.rs:282
msgid "Updating metadata"
msgstr "Metadaten werden aktualisiert"
@ -826,23 +826,19 @@ msgstr "Metadaten werden aktualisiert"
msgid "Updating music library"
msgstr "Musikbibliothek wird aktualisiert"
#: src/window.rs:166
msgid "Currently playing music"
msgstr "Musik wird abgespielt"
#: src/window.rs:190
#: src/window.rs:167
msgid "Close window?"
msgstr "Fenster schließen?"
#: src/window.rs:192
#: src/window.rs:169
msgid "There are ongoing processes that will be canceled."
msgstr "Es gibt laufende Prozesse, die abgebrochen werden."
#: src/window.rs:197
#: src/window.rs:174
msgid "Keep open"
msgstr "Nicht schließen"
#: src/window.rs:198
#: src/window.rs:175
msgid "Close window"
msgstr "Fenster schließen"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-30 15:27+0200\n"
"POT-Creation-Date: 2025-04-27 17:54+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -784,7 +784,7 @@ msgstr ""
msgid "Exporting music library to {}"
msgstr ""
#: src/library_manager.rs:234 src/window.rs:305
#: src/library_manager.rs:234 src/window.rs:282
msgid "Updating metadata"
msgstr ""
@ -792,22 +792,18 @@ msgstr ""
msgid "Updating music library"
msgstr ""
#: src/window.rs:166
msgid "Currently playing music"
msgstr ""
#: src/window.rs:190
#: src/window.rs:167
msgid "Close window?"
msgstr ""
#: src/window.rs:192
#: src/window.rs:169
msgid "There are ongoing processes that will be canceled."
msgstr ""
#: src/window.rs:197
#: src/window.rs:174
msgid "Keep open"
msgstr ""
#: src/window.rs:198
#: src/window.rs:175
msgid "Close window"
msgstr ""

View file

@ -72,7 +72,8 @@ mod imp {
.unwrap()
.recordings
.iter()
.flat_map(|r| obj.player().recording_to_playlist(r))
.map(|r| obj.player().recording_to_playlist(r))
.flatten()
.collect::<Vec<PlaylistItem>>();
if let Err(err) = obj.player().append(playlist) {
@ -164,7 +165,8 @@ impl AlbumPage {
.unwrap()
.recordings
.iter()
.flat_map(|r| self.player().recording_to_playlist(r))
.map(|r| self.player().recording_to_playlist(r))
.flatten()
.collect::<Vec<PlaylistItem>>();
self.player().append_and_play(playlist);

View file

@ -45,7 +45,7 @@ impl AlbumTile {
pub fn new(album: &Album) -> Self {
let obj: Self = glib::Object::new();
obj.imp().title_label.set_label(album.name.get());
obj.imp().title_label.set_label(&album.name.get());
obj.imp().album.set(album.clone()).unwrap();
obj

View file

@ -74,7 +74,6 @@ pub struct Album {
pub album_id: String,
pub name: TranslatedString,
pub recordings: Vec<Recording>,
pub enable_updates: bool,
}
impl Eq for Person {}
@ -434,7 +433,6 @@ impl Album {
album_id: data.album_id,
name: data.name,
recordings,
enable_updates: data.enable_updates,
})
}

View file

@ -20,8 +20,6 @@ diesel::table! {
albums (album_id) {
album_id -> Text,
name -> Text,
source -> Text,
enable_updates -> Bool,
created_at -> Timestamp,
edited_at -> Timestamp,
last_used_at -> Timestamp,
@ -42,12 +40,11 @@ diesel::table! {
ensembles (ensemble_id) {
ensemble_id -> Text,
name -> Text,
source -> Text,
enable_updates -> Bool,
created_at -> Timestamp,
edited_at -> Timestamp,
last_used_at -> Timestamp,
last_played_at -> Nullable<Timestamp>,
enable_updates -> Bool,
}
}
@ -55,12 +52,11 @@ diesel::table! {
instruments (instrument_id) {
instrument_id -> Text,
name -> Text,
source -> Text,
enable_updates -> Bool,
created_at -> Timestamp,
edited_at -> Timestamp,
last_used_at -> Timestamp,
last_played_at -> Nullable<Timestamp>,
enable_updates -> Bool,
}
}
@ -68,8 +64,6 @@ diesel::table! {
mediums (medium_id) {
medium_id -> Text,
discid -> Text,
source -> Text,
enable_updates -> Bool,
created_at -> Timestamp,
edited_at -> Timestamp,
last_used_at -> Timestamp,
@ -81,17 +75,16 @@ diesel::table! {
persons (person_id) {
person_id -> Text,
name -> Text,
source -> Text,
enable_updates -> Bool,
created_at -> Timestamp,
edited_at -> Timestamp,
last_used_at -> Timestamp,
last_played_at -> Nullable<Timestamp>,
enable_updates -> Bool,
}
}
diesel::table! {
recording_ensembles (recording_id, ensemble_id, sequence_number) {
recording_ensembles (recording_id, ensemble_id) {
recording_id -> Text,
ensemble_id -> Text,
role_id -> Nullable<Text>,
@ -100,7 +93,7 @@ diesel::table! {
}
diesel::table! {
recording_persons (recording_id, person_id, sequence_number) {
recording_persons (recording_id, person_id) {
recording_id -> Text,
person_id -> Text,
role_id -> Nullable<Text>,
@ -114,12 +107,11 @@ diesel::table! {
recording_id -> Text,
work_id -> Text,
year -> Nullable<Integer>,
source -> Text,
enable_updates -> Bool,
created_at -> Timestamp,
edited_at -> Timestamp,
last_used_at -> Timestamp,
last_played_at -> Nullable<Timestamp>,
enable_updates -> Bool,
}
}
@ -127,11 +119,10 @@ diesel::table! {
roles (role_id) {
role_id -> Text,
name -> Text,
source -> Text,
enable_updates -> Bool,
created_at -> Timestamp,
edited_at -> Timestamp,
last_used_at -> Timestamp,
enable_updates -> Bool,
}
}
@ -167,7 +158,7 @@ diesel::table! {
}
diesel::table! {
work_persons (work_id, person_id, sequence_number) {
work_persons (work_id, person_id) {
work_id -> Text,
person_id -> Text,
role_id -> Nullable<Text>,
@ -181,12 +172,11 @@ diesel::table! {
parent_work_id -> Nullable<Text>,
sequence_number -> Nullable<Integer>,
name -> Text,
source -> Text,
enable_updates -> Bool,
created_at -> Timestamp,
edited_at -> Timestamp,
last_used_at -> Timestamp,
last_played_at -> Nullable<Timestamp>,
enable_updates -> Bool,
}
}

View file

@ -24,12 +24,11 @@ use super::{schema::*, TranslatedString};
pub struct Person {
pub person_id: String,
pub name: TranslatedString,
pub source: Source,
pub enable_updates: bool,
pub created_at: NaiveDateTime,
pub edited_at: NaiveDateTime,
pub last_used_at: NaiveDateTime,
pub last_played_at: Option<NaiveDateTime>,
pub enable_updates: bool,
}
#[derive(Boxed, Insertable, Queryable, Selectable, Clone, Debug)]
@ -38,11 +37,10 @@ pub struct Person {
pub struct Role {
pub role_id: String,
pub name: TranslatedString,
pub source: Source,
pub enable_updates: bool,
pub created_at: NaiveDateTime,
pub edited_at: NaiveDateTime,
pub last_used_at: NaiveDateTime,
pub enable_updates: bool,
}
#[derive(Boxed, Insertable, Queryable, Selectable, Clone, Debug)]
@ -51,12 +49,11 @@ pub struct Role {
pub struct Instrument {
pub instrument_id: String,
pub name: TranslatedString,
pub source: Source,
pub enable_updates: bool,
pub created_at: NaiveDateTime,
pub edited_at: NaiveDateTime,
pub last_used_at: NaiveDateTime,
pub last_played_at: Option<NaiveDateTime>,
pub enable_updates: bool,
}
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
@ -66,12 +63,11 @@ pub struct Work {
pub parent_work_id: Option<String>,
pub sequence_number: Option<i32>,
pub name: TranslatedString,
pub source: Source,
pub enable_updates: bool,
pub created_at: NaiveDateTime,
pub edited_at: NaiveDateTime,
pub last_used_at: NaiveDateTime,
pub last_played_at: Option<NaiveDateTime>,
pub enable_updates: bool,
}
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
@ -96,12 +92,11 @@ pub struct WorkInstrument {
pub struct Ensemble {
pub ensemble_id: String,
pub name: TranslatedString,
pub source: Source,
pub enable_updates: bool,
pub created_at: NaiveDateTime,
pub edited_at: NaiveDateTime,
pub last_used_at: NaiveDateTime,
pub last_played_at: Option<NaiveDateTime>,
pub enable_updates: bool,
}
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
@ -119,12 +114,11 @@ pub struct Recording {
pub recording_id: String,
pub work_id: String,
pub year: Option<i32>,
pub source: Source,
pub enable_updates: bool,
pub created_at: NaiveDateTime,
pub edited_at: NaiveDateTime,
pub last_used_at: NaiveDateTime,
pub last_played_at: Option<NaiveDateTime>,
pub enable_updates: bool,
}
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
@ -174,8 +168,6 @@ pub struct TrackWork {
pub struct Medium {
pub medium_id: String,
pub discid: String,
pub source: Source,
pub enable_updates: bool,
pub created_at: NaiveDateTime,
pub edited_at: NaiveDateTime,
pub last_used_at: NaiveDateTime,
@ -187,8 +179,6 @@ pub struct Medium {
pub struct Album {
pub album_id: String,
pub name: TranslatedString,
pub source: Source,
pub enable_updates: bool,
pub created_at: NaiveDateTime,
pub edited_at: NaiveDateTime,
pub last_used_at: NaiveDateTime,
@ -266,40 +256,3 @@ impl AsRef<Path> for PathBufWrapper {
self.0.as_ref()
}
}
#[derive(AsExpression, FromSqlRow, Copy, Clone, Debug)]
#[diesel(sql_type = Text)]
pub enum Source {
Metadata,
User,
Import,
Unknown,
}
impl ToSql<Text, Sqlite> for Source {
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> diesel::serialize::Result {
out.set_value(match self {
Source::Metadata => "metadata",
Source::User => "user",
Source::Import => "import",
Source::Unknown => "unknown",
});
Ok(IsNull::No)
}
}
impl<DB> FromSql<Text, DB> for Source
where
DB: Backend,
String: FromSql<Text, DB>,
{
fn from_sql(bytes: DB::RawValue<'_>) -> diesel::deserialize::Result<Self> {
Ok(match String::from_sql(bytes)?.as_str() {
"metadata" => Source::Metadata,
"user" => Source::User,
"import" => Source::Import,
_ => Source::Unknown,
})
}
}

View file

@ -39,8 +39,6 @@ mod imp {
#[template_child]
pub recordings_list: TemplateChild<gtk::ListBox>,
#[template_child]
pub enable_updates_row: TemplateChild<adw::SwitchRow>,
#[template_child]
pub save_row: TemplateChild<adw::ButtonRow>,
}
@ -128,10 +126,6 @@ impl AlbumEditor {
for recording in &album.recordings {
obj.add_recording(recording.to_owned());
}
obj.imp()
.enable_updates_row
.set_active(album.enable_updates);
}
obj
@ -197,16 +191,10 @@ impl AlbumEditor {
.map(|r| r.recording())
.collect::<Vec<Recording>>();
let enable_updates = self.imp().enable_updates_row.is_active();
if let Some(album_id) = self.imp().album_id.get() {
library
.update_album(album_id, name, recordings, enable_updates)
.unwrap();
library.update_album(album_id, name, recordings).unwrap();
} else {
let album = library
.create_album(name, recordings, enable_updates)
.unwrap();
let album = library.create_album(name, recordings).unwrap();
self.emit_by_name::<()>("created", &[&album]);
}

View file

@ -69,7 +69,7 @@ mod imp {
self.parent_constructed();
let set_design_action = gio::ActionEntry::builder("set-design")
.parameter_type(Some(glib::VariantTy::STRING))
.parameter_type(Some(&glib::VariantTy::STRING))
.state(glib::Variant::from("program-1"))
.build();

View file

@ -246,7 +246,7 @@ impl RecordingEditor {
}
fn set_work(&self, work: Work) {
self.imp().work_row.set_title(work.name.get());
self.imp().work_row.set_title(&work.name.get());
self.imp().work_row.set_subtitle(
&work
.composers_string()

View file

@ -245,7 +245,8 @@ impl TracksEditor {
.track_rows
.borrow()
.iter()
.flat_map(|t| t.track_data().parts.clone())
.map(|t| t.track_data().parts.clone())
.flatten()
.collect::<Vec<Work>>()
};

View file

@ -390,7 +390,8 @@ impl WorkEditor {
};
self.emit_by_name::<()>("created", &[&part]);
} else if let Some(work_id) = self.imp().work_id.get() {
} else {
if let Some(work_id) = self.imp().work_id.get() {
library
.update_work(work_id, name, parts, composers, instruments, enable_updates)
.unwrap();
@ -400,6 +401,7 @@ impl WorkEditor {
.unwrap();
self.emit_by_name::<()>("created", &[&work]);
}
}
self.imp().navigation.get().unwrap().pop();
}

View file

@ -145,7 +145,7 @@ impl WorkEditorPartRow {
}
fn set_part(&self, part: Work) {
self.set_title(part.name.get());
self.set_title(&part.name.get());
if !part.parts.is_empty() {
self.set_subtitle(

View file

@ -10,8 +10,8 @@ use gtk::{gio, glib, glib::subclass::Signal};
use once_cell::sync::Lazy;
use crate::{
config, db::tables::Source, library::Library, process::Process,
process_manager::ProcessManager, process_row::ProcessRow,
config, library::Library, process::Process, process_manager::ProcessManager,
process_row::ProcessRow,
};
mod imp {
@ -92,8 +92,8 @@ impl EmptyPage {
#[template_callback]
async fn download_library(&self) {
let dialog = adw::AlertDialog::builder()
.heading(gettext("Disclaimer"))
.body(gettext("You are about to download a library of audio files. These are from recordings that are in the public domain under EU law and are hosted on a server within the EU. Please ensure that you comply with the copyright laws of you country."))
.heading(&gettext("Disclaimer"))
.body(&gettext("You are about to download a library of audio files. These are from recordings that are in the public domain under EU law and are hosted on a server within the EU. Please ensure that you comply with the copyright laws of you country."))
.build();
dialog.add_response("continue", &gettext("Continue"));
@ -119,7 +119,7 @@ impl EmptyPage {
.library
.get()
.unwrap()
.import_library_from_url(&url, Source::Metadata)
.import_library_from_url(&url)
{
Ok(receiver) => {
let process = Process::new(&gettext("Downloading music library"), receiver);

View file

@ -10,13 +10,7 @@ use chrono::prelude::*;
use diesel::{prelude::*, QueryDsl, SqliteConnection};
use super::Library;
use crate::db::{
self,
models::*,
schema::*,
tables::{self, Source},
TranslatedString,
};
use crate::db::{self, models::*, schema::*, tables, TranslatedString};
impl Library {
pub fn create_person(&self, name: TranslatedString, enable_updates: bool) -> Result<Person> {
@ -27,7 +21,6 @@ impl Library {
let person = Person {
person_id: db::generate_id(),
name,
source: Source::User,
created_at: now,
edited_at: now,
last_used_at: now,
@ -93,7 +86,6 @@ impl Library {
let instrument = Instrument {
instrument_id: db::generate_id(),
name,
source: Source::User,
created_at: now,
edited_at: now,
last_used_at: now,
@ -155,7 +147,6 @@ impl Library {
let role = Role {
role_id: db::generate_id(),
name,
source: Source::User,
created_at: now,
edited_at: now,
last_used_at: now,
@ -218,7 +209,7 @@ impl Library {
) -> Result<Work> {
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
let work = Self::create_work_priv(
let work = self.create_work_priv(
connection,
name,
parts,
@ -235,6 +226,7 @@ impl Library {
}
fn create_work_priv(
&self,
connection: &mut SqliteConnection,
name: TranslatedString,
parts: Vec<Work>,
@ -250,9 +242,8 @@ impl Library {
let work_data = tables::Work {
work_id: work_id.clone(),
parent_work_id: parent_work_id.map(|w| w.to_string()),
sequence_number,
sequence_number: sequence_number,
name,
source: Source::User,
created_at: now,
edited_at: now,
last_used_at: now,
@ -265,7 +256,7 @@ impl Library {
.execute(connection)?;
for (index, part) in parts.into_iter().enumerate() {
Self::create_work_priv(
self.create_work_priv(
connection,
part.name,
part.parts,
@ -318,7 +309,7 @@ impl Library {
) -> Result<()> {
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
Self::update_work_priv(
self.update_work_priv(
connection,
work_id,
name,
@ -336,6 +327,7 @@ impl Library {
}
fn update_work_priv(
&self,
connection: &mut SqliteConnection,
work_id: &str,
name: TranslatedString,
@ -375,7 +367,7 @@ impl Library {
.optional()?
.is_some()
{
Self::update_work_priv(
self.update_work_priv(
connection,
&part.work_id,
part.name,
@ -389,7 +381,7 @@ impl Library {
} else {
// Note: The previously used ID is discarded. This should be OK, because
// at this point, the part ID should not have been used anywhere.
Self::create_work_priv(
self.create_work_priv(
connection,
part.name,
part.parts,
@ -462,7 +454,6 @@ impl Library {
let ensemble_data = tables::Ensemble {
ensemble_id: db::generate_id(),
name,
source: Source::User,
created_at: now,
edited_at: now,
last_used_at: now,
@ -539,7 +530,6 @@ impl Library {
recording_id: recording_id.clone(),
work_id: work.work_id.clone(),
year,
source: Source::User,
created_at: now,
edited_at: now,
last_used_at: now,
@ -703,7 +693,6 @@ impl Library {
&self,
name: TranslatedString,
recordings: Vec<Recording>,
enable_updates: bool,
) -> Result<Album> {
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
@ -713,8 +702,6 @@ impl Library {
let album_data = tables::Album {
album_id: album_id.clone(),
name,
source: Source::User,
enable_updates,
created_at: now,
edited_at: now,
last_used_at: now,
@ -749,7 +736,6 @@ impl Library {
album_id: &str,
name: TranslatedString,
recordings: Vec<Recording>,
enable_updates: bool,
) -> Result<()> {
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
@ -759,7 +745,6 @@ impl Library {
.filter(albums::album_id.eq(album_id))
.set((
albums::name.eq(name),
albums::enable_updates.eq(enable_updates),
albums::edited_at.eq(now),
albums::last_used_at.eq(now),
))

View file

@ -19,11 +19,7 @@ use zip::{write::SimpleFileOptions, ZipWriter};
use super::Library;
use crate::{
db::{
self,
schema::*,
tables::{self, Source},
},
db::{self, schema::*, tables},
process::ProcessMsg,
};
@ -32,27 +28,17 @@ impl Library {
pub fn import_library_from_zip(
&self,
path: impl AsRef<Path>,
source: Source,
) -> Result<async_channel::Receiver<ProcessMsg>> {
log::info!(
"Importing library from ZIP at {}",
path.as_ref().to_string_lossy()
);
log::info!("Importing library from ZIP at {}", path.as_ref().to_string_lossy());
let path = path.as_ref().to_owned();
let library_folder = PathBuf::from(&self.folder());
let this_connection = self.imp().connection.get().unwrap().clone();
let (sender, receiver) = async_channel::unbounded::<ProcessMsg>();
thread::spawn(move || {
if let Err(err) =
sender.send_blocking(ProcessMsg::Result(import_library_from_zip_priv(
path,
library_folder,
source,
this_connection,
&sender,
)))
{
if let Err(err) = sender.send_blocking(ProcessMsg::Result(
import_library_from_zip_priv(path, library_folder, this_connection, &sender),
)) {
log::error!("Failed to send library action result: {err:?}");
}
});
@ -66,10 +52,7 @@ impl Library {
&self,
path: impl AsRef<Path>,
) -> Result<async_channel::Receiver<ProcessMsg>> {
log::info!(
"Exporting library to ZIP at {}",
path.as_ref().to_string_lossy()
);
log::info!("Exporting library to ZIP at {}", path.as_ref().to_string_lossy());
let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap();
let path = path.as_ref().to_owned();
@ -95,7 +78,6 @@ impl Library {
pub fn import_library_from_url(
&self,
url: &str,
source: Source,
) -> Result<async_channel::Receiver<ProcessMsg>> {
log::info!("Importing library from URL {url}");
let url = url.to_owned();
@ -106,7 +88,7 @@ impl Library {
thread::spawn(move || {
if let Err(err) = sender.send_blocking(ProcessMsg::Result(
import_library_from_url_priv(url, library_folder, source, this_connection, &sender),
import_library_from_url_priv(url, library_folder, this_connection, &sender),
)) {
log::error!("Failed to send library action result: {err:?}");
}
@ -119,7 +101,6 @@ impl Library {
pub fn import_metadata_from_url(
&self,
url: &str,
source: Source,
) -> Result<async_channel::Receiver<ProcessMsg>> {
log::info!("Importing metadata from URL {url}");
@ -130,7 +111,7 @@ impl Library {
thread::spawn(move || {
if let Err(err) = sender.send_blocking(ProcessMsg::Result(
import_metadata_from_url_priv(url, source, this_connection, &sender),
import_metadata_from_url_priv(url, this_connection, &sender),
)) {
log::error!("Failed to send library action result: {err:?}");
}
@ -144,7 +125,6 @@ impl Library {
fn import_library_from_zip_priv(
zip_path: impl AsRef<Path>,
library_folder: impl AsRef<Path>,
source: Source,
this_connection: Arc<Mutex<SqliteConnection>>,
sender: &async_channel::Sender<ProcessMsg>,
) -> Result<()> {
@ -158,7 +138,7 @@ fn import_library_from_zip_priv(
)?;
// Import metadata.
let tracks = import_metadata_from_file(tmp_db_file.path(), source, this_connection, false)?;
let tracks = import_metadata_from_file(tmp_db_file.path(), this_connection, false)?;
// Import audio files.
let n_tracks = tracks.len();
@ -232,7 +212,6 @@ fn add_file_to_zip(
fn import_metadata_from_url_priv(
url: String,
source: Source,
this_connection: Arc<Mutex<SqliteConnection>>,
sender: &async_channel::Sender<ProcessMsg>,
) -> Result<()> {
@ -244,18 +223,20 @@ fn import_metadata_from_url_priv(
formatx!(gettext("Downloading {}"), &url).unwrap(),
));
match runtime.block_on(download_tmp_file(&url, sender)) {
match runtime.block_on(download_tmp_file(&url, &sender)) {
Ok(db_file) => {
let _ = sender.send_blocking(ProcessMsg::Message(
formatx!(gettext("Importing downloaded library"), &url).unwrap(),
));
let _ = sender.send_blocking(ProcessMsg::Result(
import_metadata_from_file(db_file.path(), source, this_connection, true).map(
import_metadata_from_file(db_file.path(), this_connection, true).and_then(
|tracks| {
if !tracks.is_empty() {
log::warn!("The metadata file at {url} contains tracks.");
}
Ok(())
},
),
));
@ -271,7 +252,6 @@ fn import_metadata_from_url_priv(
fn import_library_from_url_priv(
url: String,
library_folder: impl AsRef<Path>,
source: Source,
this_connection: Arc<Mutex<SqliteConnection>>,
sender: &async_channel::Sender<ProcessMsg>,
) -> Result<()> {
@ -283,7 +263,7 @@ fn import_library_from_url_priv(
formatx!(gettext("Downloading {}"), &url).unwrap(),
));
let archive_file = runtime.block_on(download_tmp_file(&url, sender));
let archive_file = runtime.block_on(download_tmp_file(&url, &sender));
match archive_file {
Ok(archive_file) => {
@ -294,9 +274,8 @@ fn import_library_from_url_priv(
let _ = sender.send_blocking(ProcessMsg::Result(import_library_from_zip_priv(
archive_file.path(),
library_folder,
source,
this_connection,
sender,
&sender,
)));
}
Err(err) => {
@ -314,7 +293,6 @@ fn import_library_from_url_priv(
/// In any case, tracks are returned.
fn import_metadata_from_file(
path: impl AsRef<Path>,
source: Source,
this_connection: Arc<Mutex<SqliteConnection>>,
ignore_tracks: bool,
) -> Result<Vec<tables::Track>> {
@ -349,7 +327,6 @@ fn import_metadata_from_file(
// Import metadata that is not already present.
for mut person in persons {
person.source = source;
person.created_at = now;
person.edited_at = now;
person.last_used_at = now;
@ -362,7 +339,6 @@ fn import_metadata_from_file(
}
for mut role in roles {
role.source = source;
role.created_at = now;
role.edited_at = now;
role.last_used_at = now;
@ -374,7 +350,6 @@ fn import_metadata_from_file(
}
for mut instrument in instruments {
instrument.source = source;
instrument.created_at = now;
instrument.edited_at = now;
instrument.last_used_at = now;
@ -387,7 +362,6 @@ fn import_metadata_from_file(
}
for mut work in works {
work.source = source;
work.created_at = now;
work.edited_at = now;
work.last_used_at = now;
@ -414,7 +388,6 @@ fn import_metadata_from_file(
}
for mut ensemble in ensembles {
ensemble.source = source;
ensemble.created_at = now;
ensemble.edited_at = now;
ensemble.last_used_at = now;
@ -434,7 +407,6 @@ fn import_metadata_from_file(
}
for mut recording in recordings {
recording.source = source;
recording.created_at = now;
recording.edited_at = now;
recording.last_used_at = now;
@ -494,7 +466,6 @@ fn import_metadata_from_file(
}
for mut album in albums {
album.source = source;
album.created_at = now;
album.edited_at = now;
album.last_used_at = now;

View file

@ -414,6 +414,7 @@ impl Library {
works,
recordings,
albums,
..Default::default()
}
}
LibraryQuery {

View file

@ -9,8 +9,8 @@ use gtk::{
};
use crate::{
config, db::tables::Source, library::Library, process::Process,
process_manager::ProcessManager, process_row::ProcessRow, window::Window,
config, library::Library, process::Process, process_manager::ProcessManager,
process_row::ProcessRow, window::Window,
};
mod imp {
@ -128,13 +128,7 @@ impl LibraryManager {
}
Ok(path) => {
if let Some(path) = path.path() {
match self
.imp()
.library
.get()
.unwrap()
.import_library_from_zip(&path, Source::Import)
{
match self.imp().library.get().unwrap().import_library_from_zip(&path) {
Ok(receiver) => {
let process = Process::new(
&formatx!(
@ -192,13 +186,7 @@ impl LibraryManager {
}
Ok(path) => {
if let Some(path) = path.path() {
match self
.imp()
.library
.get()
.unwrap()
.export_library_to_zip(&path)
{
match self.imp().library.get().unwrap().export_library_to_zip(&path) {
Ok(receiver) => {
let process = Process::new(
&formatx!(
@ -240,7 +228,7 @@ impl LibraryManager {
.library
.get()
.unwrap()
.import_metadata_from_url(&url, Source::Metadata)
.import_metadata_from_url(&url)
{
Ok(receiver) => {
let process = Process::new(&gettext("Updating metadata"), receiver);
@ -271,7 +259,7 @@ impl LibraryManager {
.library
.get()
.unwrap()
.import_library_from_url(&url, Source::Metadata)
.import_library_from_url(&url)
{
Ok(receiver) => {
let process = Process::new(&gettext("Updating music library"), receiver);

View file

@ -20,6 +20,7 @@ mod program;
mod program_tile;
mod recording_tile;
mod search_page;
mod search_tag;
mod selector;
mod slider_row;
mod tag_tile;
@ -46,7 +47,7 @@ fn main() -> glib::ExitCode {
gettextrs::textdomain(config::PKGNAME).unwrap();
gio::resources_register(
&gio::Resource::load(format!(
&gio::Resource::load(&format!(
"{}/{}/{}.gresource",
config::DATADIR,
config::PKGNAME,

View file

@ -221,14 +221,14 @@ impl Player {
items.push(PlaylistItem::new(
true,
recording.work.composers_string(),
recording.work.name.get(),
&recording.work.name.get(),
Some(&performances),
None,
self.library_path_to_file_path(&tracks[0].path),
&self.library_path_to_file_path(&tracks[0].path),
&tracks[0].track_id,
));
} else {
let mut tracks = tracks.iter();
let mut tracks = tracks.into_iter();
let first_track = tracks.next().unwrap();
let track_title = |track: &Track, number: usize| -> String {
@ -249,10 +249,10 @@ impl Player {
items.push(PlaylistItem::new(
true,
recording.work.composers_string(),
recording.work.name.get(),
&recording.work.name.get(),
Some(&performances),
Some(&track_title(first_track, 1)),
self.library_path_to_file_path(&first_track.path),
Some(&track_title(&first_track, 1)),
&self.library_path_to_file_path(&first_track.path),
&first_track.track_id,
));
@ -260,11 +260,11 @@ impl Player {
items.push(PlaylistItem::new(
false,
recording.work.composers_string(),
recording.work.name.get(),
&recording.work.name.get(),
Some(&performances),
// track number = track index + 1 (first track) + 1 (zero based)
Some(&track_title(track, index + 2)),
self.library_path_to_file_path(&track.path),
Some(&track_title(&track, index + 2)),
&self.library_path_to_file_path(&track.path),
&track.track_id,
));
}

View file

@ -83,7 +83,7 @@ impl Program {
}
pub fn from_query(query: LibraryQuery) -> Self {
let settings = gio::Settings::new(config::APP_ID);
let settings = gio::Settings::new(&config::APP_ID);
glib::Object::builder()
.property(

View file

@ -56,7 +56,7 @@ mod imp {
self.set_program_from_settings(&settings);
let obj = self.obj().to_owned();
settings.connect_changed(Some(self.key.get().unwrap()), move |settings, _| {
settings.connect_changed(Some(&self.key.get().unwrap()), move |settings, _| {
obj.imp().set_program_from_settings(settings);
});
}

View file

@ -69,7 +69,7 @@ mod imp {
.push(&RecordingEditor::new(
obj.imp().navigation.get().unwrap(),
obj.imp().library.get().unwrap(),
Some(obj.imp().recording.get().unwrap()),
Some(&obj.imp().recording.get().unwrap()),
));
})
.build();
@ -90,8 +90,8 @@ mod imp {
let delete_action = gio::ActionEntry::builder("delete")
.activate(move |_, _, _| {
let dialog = adw::AlertDialog::builder()
.heading(gettext("Delete recording?"))
.body(gettext("The recording will be removed from your music library and the corresponding audio files will be deleted. This action cannot be undone."))
.heading(&gettext("Delete recording?"))
.body(&gettext("The recording will be removed from your music library and the corresponding audio files will be deleted. This action cannot be undone."))
.build();
dialog.add_response("delete", &gettext("Delete"));
@ -142,7 +142,7 @@ impl RecordingTile {
let obj: Self = glib::Object::new();
let imp = obj.imp();
imp.work_label.set_label(recording.work.name.get());
imp.work_label.set_label(&recording.work.name.get());
imp.composer_label.set_label(
&recording
.work

View file

@ -22,7 +22,8 @@ use crate::{
program::Program,
program_tile::ProgramTile,
recording_tile::RecordingTile,
tag_tile::{Tag, TagTile},
search_tag::Tag,
tag_tile::TagTile,
util,
};
@ -391,7 +392,7 @@ impl SearchPage {
imp.header_box.set_visible(!query.is_empty());
let highlight = if let Some(work) = &query.work {
imp.title_label.set_text(work.name.get());
imp.title_label.set_text(&work.name.get());
if let Some(composers) = work.composers_string() {
imp.subtitle_label.set_text(&composers);
imp.subtitle_label.set_visible(true);
@ -400,15 +401,15 @@ impl SearchPage {
}
Some(Tag::Work(work.to_owned()))
} else if let Some(person) = &query.composer {
imp.title_label.set_text(person.name.get());
imp.title_label.set_text(&person.name.get());
imp.subtitle_label.set_visible(false);
Some(Tag::Composer(person.to_owned()))
} else if let Some(person) = &query.performer {
imp.title_label.set_text(person.name.get());
imp.title_label.set_text(&person.name.get());
imp.subtitle_label.set_visible(false);
Some(Tag::Performer(person.to_owned()))
} else if let Some(ensemble) = &query.ensemble {
imp.title_label.set_text(ensemble.name.get());
imp.title_label.set_text(&ensemble.name.get());
imp.subtitle_label.set_visible(false);
Some(Tag::Ensemble(ensemble.to_owned()))
} else if let Some(instrument) = &query.instrument {

98
src/search_tag.rs Normal file
View file

@ -0,0 +1,98 @@
use std::cell::OnceCell;
use adw::{glib, glib::subclass::Signal, prelude::*, subclass::prelude::*};
use once_cell::sync::Lazy;
use crate::db::models::{Ensemble, Instrument, Person, Work};
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(file = "data/ui/search_tag.blp")]
pub struct SearchTag {
#[template_child]
pub label: TemplateChild<gtk::Label>,
pub tag: OnceCell<Tag>,
}
#[glib::object_subclass]
impl ObjectSubclass for SearchTag {
const NAME: &'static str = "MusicusSearchTag";
type Type = super::SearchTag;
type ParentType = gtk::Box;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
klass.bind_template_instance_callbacks();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for SearchTag {
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> =
Lazy::new(|| vec![Signal::builder("remove").build()]);
SIGNALS.as_ref()
}
}
impl WidgetImpl for SearchTag {}
impl BoxImpl for SearchTag {}
}
glib::wrapper! {
pub struct SearchTag(ObjectSubclass<imp::SearchTag>)
@extends gtk::Widget;
}
#[gtk::template_callbacks]
impl SearchTag {
pub fn new(tag: Tag) -> Self {
let obj: SearchTag = glib::Object::new();
let label = match &tag {
Tag::Composer(person) => person.name.get(),
Tag::Performer(person) => person.name.get(),
Tag::Ensemble(ensemble) => ensemble.name.get(),
Tag::Instrument(instrument) => instrument.name.get(),
Tag::Work(work) => work.name.get(),
};
obj.imp().label.set_label(label);
obj.set_tooltip_text(Some(label));
obj.imp().tag.set(tag).unwrap();
obj
}
pub fn connect_remove<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
self.connect_local("remove", true, move |values| {
let obj = values[0].get::<Self>().unwrap();
f(&obj);
None
})
}
pub fn tag(&self) -> &Tag {
self.imp().tag.get().unwrap()
}
#[template_callback]
fn remove(&self) {
self.emit_by_name::<()>("remove", &[]);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Tag {
Composer(Person),
Performer(Person),
Ensemble(Ensemble),
Instrument(Instrument),
Work(Work),
}

View file

@ -294,7 +294,7 @@ impl RecordingSelectorPopover {
.build(),
);
row.set_tooltip_text(Some(work.name.get()));
row.set_tooltip_text(Some(&work.name.get()));
let work = work.clone();
let obj = self.clone();

View file

@ -256,7 +256,7 @@ impl WorkSelectorPopover {
.build(),
);
row.set_tooltip_text(Some(work.name.get()));
row.set_tooltip_text(Some(&work.name.get()));
let work = work.clone();
let obj = self.clone();

View file

@ -2,7 +2,7 @@ use std::cell::OnceCell;
use gtk::{glib, prelude::*, subclass::prelude::*};
use crate::db::models::{Ensemble, Instrument, Person, Work};
use crate::search_tag::Tag;
mod imp {
use super::*;
@ -78,12 +78,3 @@ impl TagTile {
self.imp().tag.get().unwrap()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Tag {
Composer(Person),
Performer(Person),
Ensemble(Ensemble),
Instrument(Instrument),
Work(Work),
}

View file

@ -2,15 +2,15 @@ pub mod activatable_row;
pub mod drag_widget;
pub mod error_dialog;
use std::sync::LazyLock;
use gettextrs::gettext;
use gtk::glib::{self, clone};
use lazy_static::lazy_static;
use error_dialog::ErrorDialog;
/// The user's language code.
pub static LANG: LazyLock<String> = LazyLock::new(|| {
lazy_static! {
/// The user's language code.
pub static ref LANG: String = {
let lang = match glib::language_names().first() {
Some(language_name) => match language_name.split('_').next() {
Some(lang) => lang.to_string(),
@ -21,14 +21,15 @@ pub static LANG: LazyLock<String> = LazyLock::new(|| {
log::info!("Intialized user language to '{lang}'.");
lang
});
};
}
/// Create and show an error toast. This will also log the error to the console.
pub fn error_toast(msgid: &str, err: anyhow::Error, toast_overlay: &adw::ToastOverlay) {
log::error!("{msgid}: {err:?}");
let toast = adw::Toast::builder()
.title(gettext(msgid))
.title(&gettext(msgid))
.button_label("Details")
.build();

View file

@ -61,7 +61,7 @@ glib::wrapper! {
impl ErrorDialog {
pub fn present(err: &anyhow::Error, parent: &impl IsA<gtk::Widget>) {
let obj: Self = glib::Object::builder()
.property("error-text", format!("{err:?}"))
.property("error-text", &format!("{err:?}"))
.build();
obj.present(Some(parent));

View file

@ -1,7 +1,4 @@
use std::{
cell::{Cell, RefCell},
path::Path,
};
use std::{cell::RefCell, path::Path};
use adw::{prelude::*, subclass::prelude::*};
use anyhow::{anyhow, Result};
@ -9,10 +6,8 @@ use gettextrs::gettext;
use gtk::{gio, glib, glib::clone};
use crate::{
album_page::AlbumPage,
config,
db::tables::Source,
editor::{album::AlbumEditor, tracks::TracksEditor},
editor::tracks::TracksEditor,
empty_page::EmptyPage,
library::{Library, LibraryQuery},
library_manager::LibraryManager,
@ -36,7 +31,6 @@ mod imp {
pub library: RefCell<Option<Library>>,
pub player: Player,
pub process_manager: ProcessManager,
pub inhibitor_cookie: Cell<Option<u32>>,
#[template_child]
pub toast_overlay: TemplateChild<adw::ToastOverlay>,
@ -89,16 +83,6 @@ mod imp {
})
.build();
let obj = self.obj().to_owned();
let create_album_action = gio::ActionEntry::builder("create-album")
.activate(move |_, _, _| {
if let Some(library) = &*obj.imp().library.borrow() {
let editor = AlbumEditor::new(&obj.imp().navigation_view, library, None);
obj.imp().navigation_view.push(&editor);
}
})
.build();
let obj = self.obj().to_owned();
let library_action = gio::ActionEntry::builder("library")
.activate(move |_, _, _| {
@ -120,12 +104,8 @@ mod imp {
})
.build();
self.obj().add_action_entries([
import_action,
create_album_action,
library_action,
preferences_action,
]);
self.obj()
.add_action_entries([import_action, library_action, preferences_action]);
let player_bar = PlayerBar::new(&self.player);
self.player_bar_revealer.set_child(Some(&player_bar));
@ -168,25 +148,6 @@ mod imp {
let obj = self.obj().to_owned();
self.player.connect_raise(move |_| obj.present());
let obj = self.obj().to_owned();
self.player.connect_playing_notify(move |player| {
if let Some(app) = obj.application() {
if let Some(cookie) = obj.imp().inhibitor_cookie.take() {
app.uninhibit(cookie);
};
if player.playing() {
let cookie = app.inhibit(
Some(&obj),
gtk::ApplicationInhibitFlags::SUSPEND,
Some(&gettext("Currently playing music")),
);
obj.imp().inhibitor_cookie.set(Some(cookie));
}
}
});
let settings = gio::Settings::new(config::APP_ID);
let library_path = settings.string("library-path").to_string();
if !library_path.is_empty() {
@ -203,8 +164,8 @@ mod imp {
fn close_request(&self) -> glib::signal::Propagation {
if self.process_manager.any_ongoing() {
let dialog = adw::AlertDialog::builder()
.heading(gettext("Close window?"))
.body(gettext(
.heading(&gettext("Close window?"))
.body(&gettext(
"There are ongoing processes that will be canceled.",
))
.build();
@ -316,7 +277,7 @@ impl Window {
config::METADATA_URL.to_string()
};
match library.import_metadata_from_url(&url, Source::Metadata) {
match library.import_metadata_from_url(&url) {
Ok(receiver) => {
let process = Process::new(&gettext("Updating metadata"), receiver);
self.imp().process_manager.add_process(&process);
@ -378,12 +339,17 @@ impl Window {
fn reset_view(&self) {
let navigation = self.imp().navigation_view.get();
// Get all pages that are not instances of SearchPage or AlbumPage.
// Get all pages that are not instances of SearchPage.
let mut navigation_stack = navigation
.navigation_stack()
.iter::<adw::NavigationPage>()
.filter_map(|page| page.ok())
.filter(|page| !page.is::<SearchPage>() && !page.is::<AlbumPage>())
.filter_map(|page| match page {
Ok(page) => match page.downcast_ref::<SearchPage>() {
Some(_) => None,
None => Some(page),
},
Err(_) => None,
})
.collect::<Vec<adw::NavigationPage>>();
navigation_stack.insert(