Implement mutli-Library support.

Honestly, not going to bother summarizing this massive changeset. You are welcome to look it over in your own free time.

Fixes #10. Fixes #11
This commit is contained in:
Joshua Strobl 2021-06-22 16:48:13 +03:00
parent 8d823dbbec
commit 44e4564f1c
38 changed files with 2408 additions and 1072 deletions

14
.vscode/settings.json vendored
View file

@ -5,6 +5,18 @@
"__node_handle": "c",
"gtk.h": "c",
"gtktreeview.h": "c",
"cartographer.h": "c"
"cartographer.h": "c",
"structs.h": "c",
"gst.h": "c",
"player.h": "c",
"config.h": "c",
"toml.h": "c",
"chrono": "c",
"sqlite3.h": "c",
"unistd.h": "c",
"ui.h": "c",
"koto-utils.h": "c",
"random": "c",
"add-remove-track-popover.h": "c"
}
}

2
.vscode/tasks.json vendored
View file

@ -51,7 +51,7 @@
"args": [
"compile",
"-C",
"builddir"
"builddir",
],
"problemMatcher": [],
"group": {

View file

@ -27,7 +27,7 @@ struct _KotoCoverArtButton {
GtkWidget * art;
GtkWidget * main;
GtkWidget * revealer;
KotoButton * play_pause_button;
KotoButton * play_button;
guint height;
guint width;
@ -107,8 +107,8 @@ static void koto_cover_art_button_init(KotoCoverArtButton * self) {
GtkWidget * controls = gtk_center_box_new(); // Create a center box for the controls
self->play_pause_button = koto_button_new_with_icon("", "media-playback-start-symbolic", "media-playback-pause-symbolic", KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
gtk_center_box_set_center_widget(GTK_CENTER_BOX(controls), GTK_WIDGET(self->play_pause_button));
self->play_button = koto_button_new_with_icon("", "media-playback-start-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
gtk_center_box_set_center_widget(GTK_CENTER_BOX(controls), GTK_WIDGET(self->play_button));
gtk_revealer_set_child(GTK_REVEALER(self->revealer), controls);
koto_cover_art_button_hide_overlay_controls(NULL, self); // Hide by default
@ -175,7 +175,7 @@ KotoButton * koto_cover_art_button_get_button(KotoCoverArtButton * self) {
return NULL;
}
return self->play_pause_button;
return self->play_button;
}
GtkWidget * koto_cover_art_button_get_main(KotoCoverArtButton * self) {

View file

@ -20,6 +20,7 @@
#include <errno.h>
#include <toml.h>
#include "../db/cartographer.h"
#include "../playback/engine.h"
#include "../koto-paths.h"
#include "../koto-utils.h"
@ -28,6 +29,7 @@
extern int errno;
extern const gchar * koto_config_template;
extern KotoCartographer * koto_maps;
extern KotoPlaybackEngine * playback_engine;
enum {
@ -46,6 +48,7 @@ static GParamSpec * config_props[N_PROPS] = {
struct _KotoConfig {
GObject parent_instance;
toml_table_t * toml_ref;
GFile * config_file;
GFileMonitor * config_file_monitor;
@ -53,6 +56,13 @@ struct _KotoConfig {
gchar * path;
gboolean finalized;
/* Library Attributes */
// These are useful for when we need to determine separately if we need to index initial builtin folders that did not exist previously (during load)
gboolean has_type_audiobook;
gboolean has_type_music;
gboolean has_type_podcast;
/* Playback Settings */
gboolean playback_continue_on_playlist;
@ -143,6 +153,9 @@ static void koto_config_class_init(KotoConfigClass * c) {
static void koto_config_init(KotoConfig * self) {
self->finalized = FALSE;
self->has_type_audiobook = FALSE;
self->has_type_music = FALSE;
self->has_type_podcast = FALSE;
}
static void koto_config_constructed(GObject * obj) {
@ -214,6 +227,25 @@ static void koto_config_set_property(
}
}
toml_table_t * koto_config_get_library(
KotoConfig * self,
gchar * library_uuid
) {
toml_array_t * libraries = toml_array_in(self->toml_ref, "library"); // Get the array of tables
for (int i = 0; i < toml_array_nelem(libraries); i++) { // For each library
toml_table_t * library = toml_table_at(libraries, i); // Get this library
toml_datum_t uuid_datum = toml_string_in(library, "uuid"); // Get the datum for the uuid
gchar * lib_uuid = (uuid_datum.ok) ? (gchar*) uuid_datum.u.s : NULL;
if (koto_utils_is_string_valid(lib_uuid) && (g_strcmp0(library_uuid, lib_uuid) == 0)) { // Is a valid string and the libraries match
return library;
}
}
return NULL;
}
/**
* Load our TOML file from the specified path into our KotoConfig
**/
@ -288,25 +320,29 @@ void koto_config_load(
return;
}
self->toml_ref = conf;
/** Supplemental Libraries (Excludes Built-in) */
toml_table_t * libraries_section = toml_table_in(conf, "libraries");
toml_array_t * libraries = toml_array_in(conf, "library"); // Get all of our libraries
if (libraries) { // If we have libraries already
for (int i = 0; i < toml_array_nelem(libraries); i++) { // Iterate over each library
toml_table_t * lib = toml_table_at(libraries, i); // Get the library datum
KotoLibrary * koto_lib = koto_library_new_from_toml_table(lib); // Get the library based on the TOML data for this specific type
if (libraries_section) { // Have supplemental libraries
toml_array_t * library_uuids = toml_array_in(libraries_section, "uuids");
if (!KOTO_IS_LIBRARY(koto_lib)) { // Something wrong with it, not a library
continue;
}
if (library_uuids && (toml_array_nelem(library_uuids) != 0)) { // Have UUIDs
for (int i = 0; i < toml_array_nelem(library_uuids); i++) { // Iterate over each UUID
toml_datum_t uuid = toml_string_at(library_uuids, i); // Get the UUID
koto_cartographer_add_library(koto_maps, koto_lib); // Add library to Cartographer
KotoLibraryType lib_type = koto_library_get_lib_type(koto_lib); // Get the type
if (!uuid.ok) { // Not a UUID string
continue; // Skip this entry in the array
}
g_message("UUID: %s", uuid.u.s);
// TODO: Implement Koto library creation
free(uuid.u.s);
toml_free(conf);
if (lib_type == KOTO_LIBRARY_TYPE_AUDIOBOOK) { // Is an audiobook lib
self->has_type_audiobook = TRUE;
} else if (lib_type == KOTO_LIBRARY_TYPE_MUSIC) { // Is a music lib
self->has_type_music = TRUE;
} else if (lib_type == KOTO_LIBRARY_TYPE_PODCAST) { // Is a podcast lib
self->has_type_podcast = TRUE;
}
}
}
@ -371,6 +407,51 @@ monitor:
}
}
void koto_config_load_libs(KotoConfig * self) {
gchar * home_dir = g_strdup(g_get_home_dir()); // Get the home directory
if (!self->has_type_audiobook) { // If we do not have a KotoLibrary for Audiobooks
gchar * audiobooks_path = g_build_path(G_DIR_SEPARATOR_S, home_dir, "Audiobooks", NULL);
koto_utils_mkdir(audiobooks_path); // Create the directory just in case
KotoLibrary * lib = koto_library_new(KOTO_LIBRARY_TYPE_AUDIOBOOK, NULL, audiobooks_path); // Audiobooks relative to home directory
if (KOTO_IS_LIBRARY(lib)) { // Created built-in audiobooks lib successfully
koto_cartographer_add_library(koto_maps, lib);
koto_config_save(config);
koto_library_index(lib); // Index this library
}
g_free(audiobooks_path);
}
if (!self->has_type_music) { // If we do not have a KotoLibrary for Music
KotoLibrary * lib = koto_library_new(KOTO_LIBRARY_TYPE_MUSIC, NULL, g_get_user_special_dir(G_USER_DIRECTORY_MUSIC)); // Create a library using the user's MUSIC directory defined
if (KOTO_IS_LIBRARY(lib)) { // Created built-in music lib successfully
koto_cartographer_add_library(koto_maps, lib);
koto_config_save(config);
koto_library_index(lib); // Index this library
}
}
if (!self->has_type_podcast) { // If we do not have a KotoLibrary for Podcasts
gchar * podcasts_path = g_build_path(G_DIR_SEPARATOR_S, home_dir, "Podcasts", NULL);
koto_utils_mkdir(podcasts_path); // Create the directory just in case
KotoLibrary * lib = koto_library_new(KOTO_LIBRARY_TYPE_PODCAST, NULL, podcasts_path); // Podcasts relative to home dir
if (KOTO_IS_LIBRARY(lib)) { // Created built-in podcasts lib successfully
koto_cartographer_add_library(koto_maps, lib);
koto_config_save(config);
koto_library_index(lib); // Index this library
}
g_free(podcasts_path);
}
g_free(home_dir);
g_thread_exit(0);
}
void koto_config_monitor_handle_changed(
GFileMonitor * monitor,
GFile * file,
@ -404,6 +485,18 @@ void koto_config_refresh(KotoConfig * self) {
void koto_config_save(KotoConfig * self) {
GStrvBuilder * root_builder = g_strv_builder_new(); // Create a new strv builder
/* Iterate over our libraries */
GList * libs = koto_cartographer_get_libraries(koto_maps); // Get our libraries
GList * current_libs;
for (current_libs = libs; current_libs != NULL; current_libs = current_libs->next) { // Iterate over our libraries
KotoLibrary * lib = current_libs->data;
gchar * lib_config = koto_library_to_config_string(lib); // Get the config string
g_strv_builder_add(root_builder, lib_config); // Add the config to our string builder
g_free(lib_config);
}
GParamSpec ** props_list = g_object_class_list_properties(G_OBJECT_GET_CLASS(self), NULL); // Get the propreties associated with our settings
GHashTable * sections_to_prop_keys = g_hash_table_new(g_str_hash, g_str_equal); // Create our section to hold our various sections based on props

View file

@ -35,6 +35,8 @@ void koto_config_load(
gchar * path
);
void koto_config_load_libs(KotoConfig * self);
void koto_config_monitor_handle_changed(
GFileMonitor * monitor,
GFile * file,

View file

@ -46,6 +46,7 @@ struct _KotoCartographer {
GHashTable * libraries;
GHashTable * playlists;
GHashTable * tracks;
GHashTable * tracks_by_uniqueish_key;
};
struct _KotoCartographerClass {
@ -150,7 +151,8 @@ static void koto_cartographer_class_init(KotoCartographerClass * c) {
NULL,
NULL,
G_TYPE_NONE,
1,
2,
G_TYPE_CHAR,
G_TYPE_CHAR
);
@ -240,6 +242,7 @@ static void koto_cartographer_init(KotoCartographer * self) {
self->libraries = g_hash_table_new(g_str_hash, g_str_equal);
self->playlists = g_hash_table_new(g_str_hash, g_str_equal);
self->tracks = g_hash_table_new(g_str_hash, g_str_equal);
self->tracks_by_uniqueish_key = g_hash_table_new(g_str_hash, g_str_equal);
}
void koto_cartographer_add_album(
@ -256,7 +259,7 @@ void koto_cartographer_add_album(
gchar * album_uuid = koto_album_get_uuid(album); // Get the album UUID
if (!koto_utils_is_string_valid(album_uuid)|| koto_cartographer_has_album_by_uuid(self, album_uuid)) { // Have the album or invalid UUID
if (!koto_utils_is_string_valid(album_uuid) || koto_cartographer_has_album_by_uuid(self, album_uuid)) { // Have the album or invalid UUID
return;
}
@ -288,8 +291,7 @@ void koto_cartographer_add_artist(
return;
}
g_hash_table_replace(self->artists_name_to_uuid, koto_artist_get_name(artist), koto_artist_get_uuid(artist)); // Add the UUID as a value with the key being the name of the artist
g_hash_table_replace(self->artists_name_to_uuid, koto_artist_get_name(artist), artist_uuid); // Add the UUID as a value with the key being the name of the artist
g_hash_table_replace(self->artists, artist_uuid, artist);
g_signal_emit(
@ -312,15 +314,15 @@ void koto_cartographer_add_library(
return;
}
gchar * library_uuid = NULL;
g_object_get(library, "uuid", &library_uuid, NULL);
gchar * library_uuid = koto_library_get_uuid(library);
if (!koto_utils_is_string_valid(library_uuid)|| koto_cartographer_has_library_by_uuid(self, library_uuid)) { // Have the library or invalid UUID
if (!koto_utils_is_string_valid(library_uuid) || koto_cartographer_has_library_by_uuid(self, library_uuid)) { // Have the library or invalid UUID
return;
}
g_hash_table_replace(self->libraries, library_uuid, library); // Add the library
g_signal_emit( // Emit our library added signal
g_signal_emit(
// Emit our library added signal
self,
cartographer_signals[SIGNAL_LIBRARY_ADDED],
0,
@ -445,7 +447,7 @@ KotoArtist * koto_cartographer_get_artist_by_uuid(
}
KotoLibrary * koto_cartographer_get_library_by_uuid(
KotoCartographer *self,
KotoCartographer * self,
gchar * library_uuid
) {
if (!KOTO_IS_CARTOGRAPHER(self)) {
@ -459,8 +461,40 @@ KotoLibrary * koto_cartographer_get_library_by_uuid(
return g_hash_table_lookup(self->libraries, library_uuid);
}
KotoLibrary * koto_cartographer_get_library_containing_path(
KotoCartographer * self,
gchar * relative_path
) {
if (!KOTO_IS_CARTOGRAPHER(self)) {
return NULL;
}
if (!koto_utils_is_string_valid(relative_path)) { // Not a valid string
return NULL;
}
GList * libs = koto_cartographer_get_libraries(self); // Get all the libraries, sorted based on priority
GList * current;
for (current = libs; current != NULL; current = current->next) { // For each library
KotoLibrary * lib = (KotoLibrary*) current->data;
gchar * lib_path = koto_library_get_path(lib); // Get the path for the library
GFile * track_file = g_file_new_build_filename(lib_path, relative_path, NULL); // Build a path from storage to file
if (g_file_query_exists(track_file, NULL)) { // If this library contains this file
g_object_unref(track_file);
return lib;
}
g_object_unref(track_file);
}
return NULL;
}
GList * koto_cartographer_get_libraries_for_storage_uuid(
KotoCartographer *self,
KotoCartographer * self,
gchar * storage_uuid
) {
if (!KOTO_IS_CARTOGRAPHER(self)) {
@ -477,6 +511,12 @@ GList * koto_cartographer_get_libraries_for_storage_uuid(
return libraries;
}
GList * koto_cartographer_get_libraries(KotoCartographer * self) {
GList * libraries = g_hash_table_get_values(self->libraries);
// TODO: Implement priority mechanism
return libraries;
}
GHashTable * koto_cartographer_get_playlists(KotoCartographer * self) {
if (!KOTO_IS_CARTOGRAPHER(self)) {
return NULL;
@ -507,6 +547,21 @@ KotoTrack * koto_cartographer_get_track_by_uuid(
return g_hash_table_lookup(self->tracks, track_uuid);
}
KotoTrack * koto_cartographer_get_track_by_uniqueish_key(
KotoCartographer * self,
gchar * key
) {
if (!KOTO_IS_CARTOGRAPHER(self)) {
return NULL;
}
if (!koto_utils_is_string_valid(key)) {
return NULL;
}
return g_hash_table_lookup(self->tracks_by_uniqueish_key, key);
}
gboolean koto_cartographer_has_album(
KotoCartographer * self,
KotoAlbum * album
@ -572,7 +627,7 @@ gboolean koto_cartographer_has_artist_by_uuid(
gboolean koto_cartographer_has_library(
KotoCartographer * self,
KotoLibrary *library
KotoLibrary * library
) {
if (!KOTO_IS_CARTOGRAPHER(self)) {
return FALSE;
@ -714,8 +769,19 @@ void koto_cartographer_remove_artist(
return;
}
koto_cartographer_remove_artist_by_uuid(self, koto_artist_get_uuid(artist));
g_hash_table_remove(self->artists_name_to_uuid, koto_artist_get_name(artist)); // Add the UUID as a value with the key being the name of the artist
gchar * artist_uuid = koto_artist_get_uuid(artist);
gchar * artist_name = koto_artist_get_name(artist);
g_hash_table_remove(self->artists_name_to_uuid, artist_name); // Add the UUID as a value with the key being the name of the artist
g_hash_table_remove(self->artists, artist_uuid);
g_signal_emit(
self,
cartographer_signals[SIGNAL_ARTIST_REMOVED],
0,
artist_uuid,
artist_name
);
}
void koto_cartographer_remove_artist_by_uuid(
@ -734,13 +800,23 @@ void koto_cartographer_remove_artist_by_uuid(
return;
}
KotoArtist * artist = koto_cartographer_get_artist_by_uuid(self, artist_uuid);
if (!KOTO_IS_ARTIST(artist)) {
return;
}
gchar * artist_name = koto_artist_get_name(artist);
g_hash_table_remove(self->artists_name_to_uuid, artist_name); // Add the UUID as a value with the key being the name of the artist
g_hash_table_remove(self->artists, artist_uuid);
g_signal_emit(
self,
cartographer_signals[SIGNAL_ARTIST_REMOVED],
0,
artist_uuid
artist_uuid,
artist_name
);
}
@ -792,7 +868,7 @@ void koto_cartographer_remove_track(
return;
}
if (!KOTO_IS_TRACK(track)) { // Not a track
if (!KOTO_IS_TRACK(track)) { // Not a track
return;
}
@ -815,14 +891,14 @@ void koto_cartographer_remove_track_by_uuid(
return;
}
g_hash_table_remove(self->tracks, track_uuid);
g_hash_table_remove(self->tracks, track_uuid);
g_signal_emit(
self,
cartographer_signals[SIGNAL_TRACK_REMOVED],
0,
track_uuid
);
g_signal_emit(
self,
cartographer_signals[SIGNAL_TRACK_REMOVED],
0,
track_uuid
);
}
KotoCartographer * koto_cartographer_new() {

View file

@ -90,12 +90,19 @@ KotoArtist * koto_cartographer_get_artist_by_uuid(
);
KotoLibrary * koto_cartographer_get_library_by_uuid(
KotoCartographer *self,
KotoCartographer * self,
gchar * library_uuid
);
KotoLibrary * koto_cartographer_get_library_containing_path(
KotoCartographer * self,
gchar * path
);
GList * koto_cartographer_get_libraries(KotoCartographer * self);
GList * koto_cartographer_get_libraries_for_storage_uuid(
KotoCartographer *self,
KotoCartographer * self,
gchar * storage_uuid
);
@ -111,6 +118,11 @@ KotoTrack * koto_cartographer_get_track_by_uuid(
gchar * track_uuid
);
KotoTrack * koto_cartographer_get_track_by_uniqueish_key(
KotoCartographer * self,
gchar * key
);
gboolean koto_cartographer_has_album(
KotoCartographer * self,
KotoAlbum * album
@ -133,7 +145,7 @@ gboolean koto_cartographer_has_artist_by_uuid(
gboolean koto_cartographer_has_library(
KotoCartographer * self,
KotoLibrary *library
KotoLibrary * library
);
gboolean koto_cartographer_has_library_by_uuid(

View file

@ -38,32 +38,23 @@ void close_db() {
}
int create_db_tables() {
char * tables_creation_queries = "CREATE TABLE IF NOT EXISTS artists(id string UNIQUE PRIMARY KEY, path string, type int, name string, art_path string);"
"CREATE TABLE IF NOT EXISTS albums(id string UNIQUE PRIMARY KEY, path string, artist_id string, name string, art_path string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE PRIMARY KEY, path string, type int, artist_id string, album_id string, file_name string, name string, disc int, position int, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS playlist_meta(id string UNIQUE PRIMARY KEY, name string, art_path string, preferred_model int);"
"CREATE TABLE IF NOT EXISTS playlist_tracks(position INTEGER PRIMARY KEY AUTOINCREMENT, playlist_id string, track_id string, current int, FOREIGN KEY(playlist_id) REFERENCES playlist_meta(id), FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);";
gchar * tables_creation_queries = "CREATE TABLE IF NOT EXISTS artists(id string UNIQUE PRIMARY KEY, name string, art_path string);"
"CREATE TABLE IF NOT EXISTS albums(id string UNIQUE PRIMARY KEY, artist_id string, name string, art_path string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE PRIMARY KEY, artist_id string, album_id string, name string, disc int, position int, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS libraries_albums(id string, album_id string, path string, PRIMARY KEY (id, album_id) FOREIGN KEY(album_id) REFERENCES albums(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS libraries_artists(id string, artist_id string, path string, PRIMARY KEY(id, artist_id) FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS libraries_tracks(id string, track_id string, path string, PRIMARY KEY(id, track_id) FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS playlist_meta(id string UNIQUE PRIMARY KEY, name string, art_path string, preferred_model int);"
"CREATE TABLE IF NOT EXISTS playlist_tracks(position INTEGER PRIMARY KEY AUTOINCREMENT, playlist_id string, track_id string, current int, FOREIGN KEY(playlist_id) REFERENCES playlist_meta(id), FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);";
gchar * create_tables_errmsg = NULL;
int rc = sqlite3_exec(koto_db, tables_creation_queries, 0, 0, &create_tables_errmsg);
if (rc != SQLITE_OK) {
g_critical("Failed to create required tables: %s", create_tables_errmsg);
}
return (rc == SQLITE_OK) ? KOTO_DB_SUCCESS : KOTO_DB_FAIL;
return (new_transaction(tables_creation_queries, "Failed to create required tables", TRUE) == SQLITE_OK) ? KOTO_DB_SUCCESS : KOTO_DB_FAIL;
}
int enable_foreign_keys() {
gchar * enable_foreign_keys_err = NULL;
int rc = sqlite3_exec(koto_db, "PRAGMA foreign_keys = ON;", 0, 0, &enable_foreign_keys_err);
gchar * commit_op = g_strdup("PRAGMA foreign_keys = ON;");
const gchar * transaction_err_msg = "Failed to enable foreign key support. Ensure your sqlite3 is compiled with neither SQLITE_OMIT_FOREIGN_KEY or SQLITE_OMIT_TRIGGER defined";
if (rc != SQLITE_OK) {
g_critical("Failed to enable foreign key support. Ensure your sqlite3 is compiled with neither SQLITE_OMIT_FOREIGN_KEY or SQLITE_OMIT_TRIGGER defined: %s", enable_foreign_keys_err);
}
g_free(enable_foreign_keys_err);
return (rc == SQLITE_OK) ? KOTO_DB_SUCCESS : KOTO_DB_FAIL;
return (new_transaction(commit_op, transaction_err_msg, FALSE) == SQLITE_OK) ? KOTO_DB_SUCCESS : KOTO_DB_FAIL;
}
int have_existing_db() {
@ -72,6 +63,25 @@ int have_existing_db() {
return ((success == 0) && S_ISREG(db_stat.st_mode)) ? 0 : 1;
}
int new_transaction(
gchar * operation,
const gchar * transaction_err_msg,
gboolean fatal
) {
gchar * commit_op_errmsg = NULL;
int rc = sqlite3_exec(koto_db, operation, 0, 0, &commit_op_errmsg);
if (rc != SQLITE_OK) {
(fatal) ? g_critical("%s: %s", transaction_err_msg, commit_op_errmsg) : g_warning("%s: %s", transaction_err_msg, commit_op_errmsg);
}
if (commit_op_errmsg == NULL) {
g_free(commit_op_errmsg);
}
return rc;
}
int open_db() {
int ret = KOTO_DB_SUCCESS; // Default to last return being SUCCESS

View file

@ -21,7 +21,6 @@
extern int KOTO_DB_SUCCESS;
extern int KOTO_DB_NEW;
extern int KOTO_DB_FAIL;
extern gboolean created_new_db;
void close_db();
@ -33,4 +32,10 @@ int enable_foreign_keys();
int have_existing_db();
int new_transaction(
gchar * operation,
const gchar * transaction_err_msg,
gboolean fatal
);
int open_db();

View file

@ -35,19 +35,23 @@ int process_artists(
(void) column_names; // Don't need any of the params
gchar * artist_uuid = g_strdup(koto_utils_unquote_string(fields[0])); // First column is UUID
gchar * artist_path = g_strdup(koto_utils_unquote_string(fields[1])); // Second column is path
gchar * artist_name = g_strdup(koto_utils_unquote_string(fields[3])); // Fourth column is artist name
gchar * artist_name = g_strdup(koto_utils_unquote_string(fields[1])); // Second column is artist name
KotoArtist * artist = koto_artist_new_with_uuid(artist_uuid); // Create our artist with the UUID
g_object_set(
artist,
"path",
artist_path, // Set path
"name",
artist_name, // Set name
NULL);
int artist_paths = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM libraries_artists WHERE artist_id=\"%s\"", artist_uuid), process_artist_paths, artist, NULL); // Process all the paths for this given artist
if (artist_paths != SQLITE_OK) { // Failed to get our artists_paths
g_critical("Failed to read our paths for this artist: %s", sqlite3_errmsg(koto_db));
return 1;
}
koto_cartographer_add_artist(koto_maps, artist); // Add the artist to our global cartographer
int albums_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM albums WHERE artist_id=\"%s\"", artist_uuid), process_albums, artist, NULL); // Process our albums
@ -57,13 +61,44 @@ int process_artists(
return 1;
}
int tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM tracks WHERE artist_id=\"%s\"", artist_uuid), process_tracks, NULL, NULL); // Process our tracks by artist uuid
if (tracks_rc != SQLITE_OK) { // Failed to get our tracks
g_critical("Failed to read our tracks: %s", sqlite3_errmsg(koto_db));
return 1;
}
g_free(artist_uuid);
g_free(artist_path);
g_free(artist_name);
return 0;
}
int process_artist_paths(
void * data,
int num_columns,
char ** fields,
char ** column_names
) {
(void) num_columns;
(void) column_names; // Don't need these
KotoArtist * artist = (KotoArtist*) data;
gchar * library_uuid = g_strdup(koto_utils_unquote_string(fields[0]));
gchar * relative_path = g_strdup(koto_utils_unquote_string(fields[2]));
KotoLibrary * lib = koto_cartographer_get_library_by_uuid(koto_maps, library_uuid); // Get the library for this artist
if (!KOTO_IS_LIBRARY(lib)) { // Failed to get the library for this UUID
return 0;
}
koto_artist_set_path(artist, lib, relative_path, FALSE); // Add the relative path from the db for this artist and lib to the Artist, do not commit
return 0;
}
int process_albums(
void * data,
int num_columns,
@ -76,35 +111,25 @@ int process_albums(
KotoArtist * artist = (KotoArtist*) data;
gchar * album_uuid = g_strdup(koto_utils_unquote_string(fields[0]));
gchar * path = g_strdup(koto_utils_unquote_string(fields[1]));
gchar * artist_uuid = g_strdup(koto_utils_unquote_string(fields[2]));
gchar * album_name = g_strdup(koto_utils_unquote_string(fields[3]));
gchar * album_art = (fields[4] != NULL) ? g_strdup(koto_utils_unquote_string(fields[4])) : NULL;
gchar * artist_uuid = g_strdup(koto_utils_unquote_string(fields[1]));
gchar * album_name = g_strdup(koto_utils_unquote_string(fields[2]));
gchar * album_art = (fields[3] != NULL) ? g_strdup(koto_utils_unquote_string(fields[3])) : NULL;
KotoAlbum * album = koto_album_new_with_uuid(artist, album_uuid); // Create our album
g_object_set(
album,
"path",
path, // Set the path
"name",
album_name, // Set name
"art-path",
album_art, // Set art path if any
NULL);
NULL
);
koto_cartographer_add_album(koto_maps, album); // Add the album to our global cartographer
koto_artist_add_album(artist, album_uuid); // Add the album
int tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM tracks WHERE album_id=\"%s\"", album_uuid), process_tracks, album, NULL); // Process our tracks
if (tracks_rc != SQLITE_OK) { // Failed to get our tracks
g_critical("Failed to read our tracks: %s", sqlite3_errmsg(koto_db));
return 1;
}
koto_artist_add_album(artist, album); // Add the album
g_free(album_uuid);
g_free(path);
g_free(artist_uuid);
g_free(album_name);
@ -188,35 +213,81 @@ int process_tracks(
char ** fields,
char ** column_names
) {
(void) data;
(void) num_columns;
(void) column_names; // Don't need these
KotoAlbum * album = (KotoAlbum*) data;
gchar * track_uuid = g_strdup(koto_utils_unquote_string(fields[0]));
gchar * path = g_strdup(koto_utils_unquote_string(fields[1]));
gchar * artist_uuid = g_strdup(koto_utils_unquote_string(fields[3]));
gchar * album_uuid = g_strdup(koto_utils_unquote_string(fields[4]));
gchar * file_name = g_strdup(koto_utils_unquote_string(fields[5]));
gchar * name = g_strdup(koto_utils_unquote_string(fields[6]));
guint * disc_num = (guint*) g_ascii_strtoull(fields[7], NULL, 10);
guint * position = (guint*) g_ascii_strtoull(fields[8], NULL, 10);
gchar * artist_uuid = g_strdup(koto_utils_unquote_string(fields[1]));
gchar * album_uuid = g_strdup(koto_utils_unquote_string(fields[2]));
gchar * name = g_strdup(koto_utils_unquote_string(fields[3]));
guint * disc_num = (guint*) g_ascii_strtoull(fields[4], NULL, 10);
guint * position = (guint*) g_ascii_strtoull(fields[5], NULL, 10);
KotoTrack * track = koto_track_new_with_uuid(track_uuid); // Create our file
g_object_set(track, "artist-uuid", artist_uuid, "album-uuid", album_uuid, "path", path, "file-name", file_name, "parsed-name", name, "cd", disc_num, "position", position, NULL);
g_object_set(
track,
"artist-uuid",
artist_uuid,
"album-uuid",
album_uuid,
"parsed-name",
name,
"cd",
disc_num,
"position",
position,
NULL
);
koto_album_add_track(album, track); // Add the track
int track_paths = sqlite3_exec(koto_db, g_strdup_printf("SELECT id, path FROM libraries_tracks WHERE track_id=\"%s\"", track_uuid), process_track_paths, track, NULL); // Process all pathes associated with the track
if (track_paths != SQLITE_OK) { // Failed to read the paths
g_warning("Failed to read paths associated with track %s: %s", track_uuid, sqlite3_errmsg(koto_db));
return 1;
}
KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); // Get the artist
koto_artist_add_track(artist, track); // Add the track for the artist
if (koto_utils_is_string_valid(album_uuid)) { // If we have an album UUID
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid); // Attempt to get album
if (KOTO_IS_ALBUM(album)) { // This is an album
koto_album_add_track(album, track); // Add the track
}
}
g_free(track_uuid);
g_free(path);
g_free(artist_uuid);
g_free(album_uuid);
g_free(file_name);
g_free(name);
return 0;
}
int process_track_paths(
void * data,
int num_columns,
char ** fields,
char ** column_names
) {
KotoTrack * track = (KotoTrack*) data;
(void) num_columns;
(void) column_names; // Don't need these
KotoLibrary * library = koto_cartographer_get_library_by_uuid(koto_maps, koto_utils_unquote_string(fields[0]));
if (!KOTO_IS_LIBRARY(library)) { // Not a library
return 1;
}
koto_track_set_path(track, library, koto_utils_unquote_string(fields[1]));
return 0;
}
void read_from_db() {
int artists_rc = sqlite3_exec(koto_db, "SELECT * FROM artists", process_artists, NULL, NULL); // Process our artists

View file

@ -22,6 +22,13 @@ int process_artists(
char ** column_names
);
int process_artist_paths(
void * data,
int num_columns,
char ** fields,
char ** column_names
);
int process_albums(
void * data,
int num_columns,
@ -50,4 +57,11 @@ int process_tracks(
char ** column_names
);
int process_track_paths(
void * data,
int num_columns,
char ** fields,
char ** column_names
);
void read_from_db();

View file

@ -20,36 +20,22 @@
#include <sqlite3.h>
#include <stdio.h>
#include "../db/cartographer.h"
#include "../db/db.h"
#include "../playlist/current.h"
#include "../playlist/playlist.h"
#include "../koto-utils.h"
#include "structs.h"
#include "koto-utils.h"
#include "track-helpers.h"
extern KotoCartographer * koto_maps;
extern KotoCurrentPlaylist * current_playlist;
extern magic_t magic_cookie;
extern sqlite3 * koto_db;
struct _KotoAlbum {
GObject parent_instance;
gchar * uuid;
gchar * path;
gchar * name;
gchar * art_path;
gchar * artist_uuid;
GList * tracks;
gboolean has_album_art;
gboolean do_initial_index;
};
G_DEFINE_TYPE(KotoAlbum, koto_album, G_TYPE_OBJECT);
enum {
PROP_0,
PROP_UUID,
PROP_DO_INITIAL_INDEX,
PROP_PATH,
PROP_ALBUM_NAME,
PROP_ART_PATH,
PROP_ARTIST_UUID,
@ -60,6 +46,46 @@ static GParamSpec * props[N_PROPERTIES] = {
NULL,
};
enum {
SIGNAL_TRACK_ADDED,
SIGNAL_TRACK_REMOVED,
N_SIGNALS
};
static guint album_signals[N_SIGNALS] = {
0
};
struct _KotoAlbum {
GObject parent_instance;
gchar * uuid;
gchar * name;
gchar * art_path;
gchar * artist_uuid;
GList * tracks;
GHashTable * paths;
gboolean has_album_art;
gboolean do_initial_index;
};
struct _KotoAlbumClass {
GObjectClass parent_class;
void (* track_added) (
KotoAlbum * album,
KotoTrack * track
);
void (* track_removed) (
KotoAlbum * album,
KotoTrack * track
);
};
G_DEFINE_TYPE(KotoAlbum, koto_album, G_TYPE_OBJECT);
static void koto_album_get_property(
GObject * obj,
guint prop_id,
@ -97,14 +123,6 @@ static void koto_album_class_init(KotoAlbumClass * c) {
G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_PATH] = g_param_spec_string(
"path",
"Path",
"Path to Album",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_ALBUM_NAME] = g_param_spec_string(
"name",
"Name",
@ -130,28 +148,64 @@ static void koto_album_class_init(KotoAlbumClass * c) {
);
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
album_signals[SIGNAL_TRACK_ADDED] = g_signal_new(
"track-added",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(KotoAlbumClass, track_added),
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
KOTO_TYPE_TRACK
);
album_signals[SIGNAL_TRACK_REMOVED] = g_signal_new(
"track-removed",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(KotoAlbumClass, track_removed),
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
KOTO_TYPE_TRACK
);
}
static void koto_album_init(KotoAlbum * self) {
self->has_album_art = FALSE;
self->tracks = NULL;
self->paths = g_hash_table_new(g_str_hash, g_str_equal);
}
void koto_album_add_track(
KotoAlbum * self,
KotoTrack * track
) {
if (track == NULL) { // Not a file
if (!KOTO_IS_ALBUM(self)) { // Not an album
return;
}
gchar * track_uuid;
if (!KOTO_IS_TRACK(track)) { // Not a track
return;
}
g_object_get(track, "uuid", &track_uuid, NULL);
gchar * track_uuid = koto_track_get_uuid(track);
if (g_list_index(self->tracks, track_uuid) == -1) {
koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer
self->tracks = g_list_insert_sorted_with_data(self->tracks, track_uuid, koto_album_sort_tracks, NULL);
if (g_list_index(self->tracks, track_uuid) == -1) { // Haven't already added the track
koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer if necessary
self->tracks = g_list_insert_sorted_with_data(self->tracks, track_uuid, koto_track_helpers_sort_tracks_by_uuid, NULL);
g_signal_emit(
self,
album_signals[SIGNAL_TRACK_ADDED],
0,
track
);
}
}
@ -161,40 +215,44 @@ void koto_album_commit(KotoAlbum * self) {
}
gchar * commit_op = g_strdup_printf(
"INSERT INTO albums(id, path, artist_id, name, art_path)"
"VALUES('%s', quote(\"%s\"), '%s', quote(\"%s\"), quote(\"%s\"))"
"ON CONFLICT(id) DO UPDATE SET path=excluded.path, name=excluded.name, art_path=excluded.art_path;",
"INSERT INTO albums(id, artist_id, name, art_path)"
"VALUES('%s', '%s', quote(\"%s\"), quote(\"%s\"))"
"ON CONFLICT(id) DO UPDATE SET artist_id=excluded.artist_id, name=excluded.name, art_path=excluded.art_path;",
self->uuid,
self->path,
self->artist_uuid,
self->name,
self->art_path
);
gchar * commit_op_errmsg = NULL;
int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_op_errmsg);
new_transaction(commit_op, "Failed to write our album to the database", FALSE);
if (rc != SQLITE_OK) {
g_warning("Failed to write our album to the database: %s", commit_op_errmsg);
GHashTableIter paths_iter;
g_hash_table_iter_init(&paths_iter, self->paths); // Create an iterator for our paths
gpointer lib_uuid_ptr, album_rel_path_ptr;
while (g_hash_table_iter_next(&paths_iter, &lib_uuid_ptr, &album_rel_path_ptr)) {
gchar * lib_uuid = lib_uuid_ptr;
gchar * album_rel_path = album_rel_path_ptr;
gchar * commit_op = g_strdup_printf(
"INSERT INTO libraries_albums(id, album_id, path)"
"VALUES ('%s', '%s', quote(\"%s\"))"
"ON CONFLICT(id, album_id) DO UPDATE SET path=excluded.path;",
lib_uuid,
self->uuid,
album_rel_path
);
new_transaction(commit_op, "Failed to add this path for the album", FALSE);
}
g_free(commit_op);
g_free(commit_op_errmsg);
}
void koto_album_find_album_art(KotoAlbum * self) {
magic_t magic_cookie = magic_open(MAGIC_MIME);
if (magic_cookie == NULL) {
if (self->has_album_art) { // If we already have album art
return;
}
if (magic_load(magic_cookie, NULL) != 0) {
magic_close(magic_cookie);
return;
}
DIR * dir = opendir(self->path); // Attempt to open our directory
gchar * optimal_album_path = koto_album_get_path(self);
DIR * dir = opendir(optimal_album_path); // Attempt to open our directory
if (dir == NULL) {
return;
@ -211,131 +269,37 @@ void koto_album_find_album_art(KotoAlbum * self) {
continue; // Skip
}
gchar * full_path = g_strdup_printf("%s%s%s", self->path, G_DIR_SEPARATOR_S, entry->d_name);
gchar * full_path = g_strdup_printf("%s%s%s", optimal_album_path, G_DIR_SEPARATOR_S, entry->d_name);
const char * mime_type = magic_file(magic_cookie, full_path);
if (mime_type == NULL) { // Failed to get the mimetype
if (
(mime_type == NULL) || // Failed to get the mimetype
((mime_type != NULL) && !g_str_has_prefix(mime_type, "image/")) // Got the mimetype but it is not an image
) {
g_free(full_path);
continue; // Skip
}
if (g_str_has_prefix(mime_type, "image/") && !self->has_album_art) { // Is an image file and doesn't have album art yet
gchar * album_art_no_ext = g_strdup(koto_utils_get_filename_without_extension(entry->d_name)); // Get the name of the file without the extension
gchar * lower_art = g_strdup(g_utf8_strdown(album_art_no_ext, -1)); // Lowercase
gchar * album_art_no_ext = g_strdup(koto_utils_get_filename_without_extension(entry->d_name)); // Get the name of the file without the extension
if (
(g_strrstr(lower_art, "Small") == NULL) && // Not Small
(g_strrstr(lower_art, "back") == NULL) // Not back
) {
koto_album_set_album_art(self, full_path);
g_free(album_art_no_ext);
g_free(lower_art);
break;
}
gchar * lower_art = g_strdup(g_utf8_strdown(album_art_no_ext, -1)); // Lowercase
g_free(album_art_no_ext);
g_free(album_art_no_ext);
g_free(lower_art);
gboolean should_set = (g_strrstr(lower_art, "Small") == NULL) && (g_strrstr(lower_art, "back") == NULL); // Not back or small
g_free(lower_art);
if (should_set) {
koto_album_set_album_art(self, full_path);
g_free(full_path);
break;
}
g_free(full_path);
}
closedir(dir);
magic_close(magic_cookie);
}
void koto_album_find_tracks(
KotoAlbum * self,
magic_t magic_cookie,
const gchar * path
) {
if (magic_cookie == NULL) { // No cookie provided
magic_cookie = magic_open(MAGIC_MIME);
}
if (magic_cookie == NULL) {
return;
}
if (path == NULL) {
path = self->path;
}
if (magic_load(magic_cookie, NULL) != 0) {
magic_close(magic_cookie);
return;
}
DIR * dir = opendir(path); // Attempt to open our directory
if (dir == NULL) {
return;
}
struct dirent * entry;
while ((entry = readdir(dir))) {
if (g_str_has_prefix(entry->d_name, ".")) { // Reference to parent dir, self, or a hidden item
continue; // Skip
}
gchar * full_path = g_strdup_printf("%s%s%s", path, G_DIR_SEPARATOR_S, entry->d_name);
if (entry->d_type == DT_DIR) { // If this is a directory
koto_album_find_tracks(self, magic_cookie, full_path); // Recursively find tracks
g_free(full_path);
continue;
}
if (entry->d_type != DT_REG) { // Not a regular file
continue; // SKIP
}
const char * mime_type = magic_file(magic_cookie, full_path);
if (mime_type == NULL) { // Failed to get the mimetype
g_free(full_path);
continue; // Skip
}
if (g_str_has_prefix(mime_type, "audio/") || g_str_has_prefix(mime_type, "video/ogg")) { // Is an audio file or ogg because it is special
gchar * appended_slash_to_path = g_strdup_printf("%s%s", g_strdup(self->path), G_DIR_SEPARATOR_S);
gchar ** possible_cd_split = g_strsplit(full_path, appended_slash_to_path, -1); // Split based on the album path
guint * cd = (guint*) 1;
gchar * track_with_cd_sep = g_strdup(possible_cd_split[1]); // Duplicate
gchar ** split_on_cd = g_strsplit(track_with_cd_sep, G_DIR_SEPARATOR_S, -1); // Split based on separator (e.g. / )
if (g_strv_length(split_on_cd) > 1) {
gchar * cdd = g_strdup(split_on_cd[0]);
gchar ** cd_sep = g_strsplit(g_utf8_strdown(cdd, -1), "cd", -1);
if (g_strv_length(cd_sep) > 1) {
gchar * pos_str = g_strdup(cd_sep[1]);
cd = (guint*) g_ascii_strtoull(pos_str, NULL, 10); // Attempt to convert
g_free(pos_str);
}
g_strfreev(cd_sep);
g_free(cdd);
}
g_strfreev(split_on_cd);
g_free(track_with_cd_sep);
g_strfreev(possible_cd_split);
g_free(appended_slash_to_path);
KotoTrack * track = koto_track_new(self, full_path, cd);
if (track != NULL) { // Is a file
koto_album_add_track(self, track); // Add our file
}
}
g_free(full_path);
}
}
static void koto_album_get_property(
@ -353,14 +317,11 @@ static void koto_album_get_property(
case PROP_DO_INITIAL_INDEX:
g_value_set_boolean(val, self->do_initial_index);
break;
case PROP_PATH:
g_value_set_string(val, self->path);
break;
case PROP_ALBUM_NAME:
g_value_set_string(val, self->name);
break;
case PROP_ART_PATH:
g_value_set_string(val, koto_album_get_album_art(self));
g_value_set_string(val, koto_album_get_art(self));
break;
case PROP_ARTIST_UUID:
g_value_set_string(val, self->artist_uuid);
@ -387,9 +348,6 @@ static void koto_album_set_property(
case PROP_DO_INITIAL_INDEX:
self->do_initial_index = g_value_get_boolean(val);
break;
case PROP_PATH: // Path to the album
koto_album_update_path(self, (gchar*) g_value_get_string(val));
break;
case PROP_ALBUM_NAME: // Name of album
koto_album_set_album_name(self, g_value_get_string(val));
break;
@ -405,7 +363,7 @@ static void koto_album_set_property(
}
}
gchar * koto_album_get_album_art(KotoAlbum * self) {
gchar * koto_album_get_art(KotoAlbum * self) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return g_strdup("");
}
@ -413,7 +371,7 @@ gchar * koto_album_get_album_art(KotoAlbum * self) {
return g_strdup((self->has_album_art && koto_utils_is_string_valid(self->art_path)) ? self->art_path : "");
}
gchar * koto_album_get_album_name(KotoAlbum * self) {
gchar * koto_album_get_name(KotoAlbum * self) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return NULL;
}
@ -437,6 +395,28 @@ gchar * koto_album_get_album_uuid(KotoAlbum * self) {
return g_strdup(self->uuid); // Return a duplicate of the UUID
}
gchar * koto_album_get_path(KotoAlbum * self) {
if (!KOTO_IS_ALBUM(self) || (KOTO_IS_ALBUM(self) && (g_list_length(g_hash_table_get_keys(self->paths)) == 0))) { // If this is not an album or is but we have no paths associated with it
return NULL;
}
GList * libs = koto_cartographer_get_libraries(koto_maps); // Get all of our libraries
GList * cur_lib_list;
for (cur_lib_list = libs; cur_lib_list != NULL; cur_lib_list = libs->next) { // Iterate over our libraries
KotoLibrary * cur_library = libs->data; // Get this as a KotoLibrary
gchar * library_relative_path = g_hash_table_lookup(self->paths, koto_library_get_uuid(cur_library)); // Get any relative path in our paths based on the current UUID
if (!koto_utils_is_string_valid(library_relative_path)) { // Not a valid path
continue;
}
return g_strdup(g_build_path(G_DIR_SEPARATOR_S, koto_library_get_path(cur_library), library_relative_path, NULL)); // Build our full library path using library's path and our file relative path
}
return NULL;
}
GList * koto_album_get_tracks(KotoAlbum * self) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return NULL;
@ -445,7 +425,7 @@ GList * koto_album_get_tracks(KotoAlbum * self) {
return self->tracks; // Return the tracks
}
gchar * koto_album_get_uuid(KotoAlbum *self) {
gchar * koto_album_get_uuid(KotoAlbum * self) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return NULL;
}
@ -474,7 +454,32 @@ void koto_album_set_album_art(
self->has_album_art = TRUE;
}
void koto_album_remove_file(
void koto_album_set_path(
KotoAlbum * self,
KotoLibrary * lib,
const gchar * fixed_path
) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return;
}
gchar * path = g_strdup(fixed_path); // Duplicate our fixed_path
gchar * relative_path = koto_library_get_relative_path_to_file(lib, path); // Get the relative path to the file for the given library
gchar * library_uuid = koto_library_get_uuid(lib); // Get the library for this path
g_hash_table_replace(self->paths, library_uuid, relative_path); // Replace any existing value or add this one
koto_album_set_album_name(self, g_path_get_basename(relative_path)); // Update our album name based on the base name
if (!self->do_initial_index) { // Not doing our initial index
return;
}
koto_album_find_album_art(self); // Update our path for the album art
self->do_initial_index = FALSE;
}
void koto_album_remove_track(
KotoAlbum * self,
KotoTrack * track
) {
@ -482,14 +487,17 @@ void koto_album_remove_file(
return;
}
if (track == NULL) { // Not a file
if (!KOTO_IS_TRACK(track)) { // Not a track
return;
}
gchar * track_uuid;
g_object_get(track, "parsed-name", &track_uuid, NULL);
self->tracks = g_list_remove(self->tracks, track_uuid);
self->tracks = g_list_remove(self->tracks, koto_track_get_uuid(track));
g_signal_emit(
self,
album_signals[SIGNAL_TRACK_REMOVED],
0,
track
);
}
void koto_album_set_album_name(
@ -565,90 +573,11 @@ void koto_album_set_as_current_playlist(KotoAlbum * self) {
koto_current_playlist_set_playlist(current_playlist, new_album_playlist); // Set our new current playlist
}
gint koto_album_sort_tracks(
gconstpointer track1_uuid,
gconstpointer track2_uuid,
gpointer user_data
) {
(void) user_data;
KotoTrack * track1 = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) track1_uuid);
KotoTrack * track2 = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) track2_uuid);
if ((track1 == NULL) && (track2 == NULL)) { // Neither tracks actually exist
return 0;
} else if ((track1 != NULL) && (track2 == NULL)) { // Only track2 does not exist
return -1;
} else if ((track1 == NULL) && (track2 != NULL)) { // Only track1 does not exist
return 1;
KotoAlbum * koto_album_new(gchar * artist_uuid) {
if (!koto_utils_is_string_valid(artist_uuid)) { // Invalid artist UUID provided
return NULL;
}
guint * track1_disc = (guint*) 1;
guint * track2_disc = (guint*) 2;
g_object_get(track1, "cd", &track1_disc, NULL);
g_object_get(track2, "cd", &track2_disc, NULL);
if (track1_disc < track2_disc) { // Track 2 is in a later CD / Disc
return -1;
} else if (track1_disc > track2_disc) { // Track1 is later
return 1;
}
guint16 * track1_pos;
guint16 * track2_pos;
g_object_get(track1, "position", &track1_pos, NULL);
g_object_get(track2, "position", &track2_pos, NULL);
if (track1_pos == track2_pos) { // Identical positions (like reported as 0)
gchar * track1_name;
gchar * track2_name;
g_object_get(track1, "parsed-name", &track1_name, NULL);
g_object_get(track2, "parsed-name", &track2_name, NULL);
return g_utf8_collate(track1_name, track2_name);
} else if (track1_pos < track2_pos) {
return -1;
} else {
return 1;
}
}
void koto_album_update_path(
KotoAlbum * self,
gchar * new_path
) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return;
}
if (!koto_utils_is_string_valid(new_path)) {
return;
}
if (koto_utils_is_string_valid(self->path)) { // Path is currently set
g_free(self->path);
}
self->path = g_strdup(new_path);
koto_album_set_album_name(self, g_path_get_basename(self->path)); // Update our album name based on the base name
if (!self->do_initial_index) { // Not doing our initial index
return;
}
koto_album_find_album_art(self); // Update our path for the album art
}
KotoAlbum * koto_album_new(
KotoArtist * artist,
const gchar * path
) {
gchar * artist_uuid = NULL;
g_object_get(artist, "uuid", &artist_uuid, NULL);
KotoAlbum * album = g_object_new(
KOTO_TYPE_ALBUM,
"artist-uuid",
@ -657,14 +586,9 @@ KotoAlbum * koto_album_new(
g_strdup(g_uuid_string_random()),
"do-initial-index",
TRUE,
"path",
path,
NULL
);
koto_album_commit(album);
koto_album_find_tracks(album, NULL, NULL); // Scan for tracks now that we committed to the database (hopefully)
return album;
}

View file

@ -17,28 +17,17 @@
#include <glib-2.0/glib.h>
#include <sqlite3.h>
#include "structs.h"
#include "../db/db.h"
#include "../db/cartographer.h"
#include "../koto-utils.h"
#include "structs.h"
#include "track-helpers.h"
extern sqlite3 * koto_db;
struct _KotoArtist {
GObject parent_instance;
gchar * uuid;
gchar * path;
gboolean has_artist_art;
gchar * artist_name;
GList * albums;
};
G_DEFINE_TYPE(KotoArtist, koto_artist, G_TYPE_OBJECT);
extern KotoCartographer * koto_maps;
enum {
PROP_0,
PROP_UUID,
PROP_PATH,
PROP_ARTIST_NAME,
N_PROPERTIES
};
@ -47,6 +36,53 @@ static GParamSpec * props[N_PROPERTIES] = {
NULL,
};
enum {
SIGNAL_ALBUM_ADDED,
SIGNAL_ALBUM_REMOVED,
SIGNAL_TRACK_ADDED,
SIGNAL_TRACK_REMOVED,
N_SIGNALS
};
static guint artist_signals[N_SIGNALS] = {
0
};
struct _KotoArtist {
GObject parent_instance;
gchar * uuid;
gboolean has_artist_art;
gchar * artist_name;
GList * albums;
GList * tracks;
GHashTable * paths;
KotoLibraryType type;
};
struct _KotoArtistClass {
GObjectClass parent_class;
void (* album_added) (
KotoArtist * artist,
KotoAlbum * album
);
void (* album_removed) (
KotoArtist * artist,
KotoAlbum * album
);
void (* track_added) (
KotoArtist * artist,
KotoTrack * track
);
void (* track_removed) (
KotoArtist * artist,
KotoTrack * track
);
};
G_DEFINE_TYPE(KotoArtist, koto_artist, G_TYPE_OBJECT);
static void koto_artist_get_property(
GObject * obj,
guint prop_id,
@ -68,6 +104,58 @@ static void koto_artist_class_init(KotoArtistClass * c) {
gobject_class->set_property = koto_artist_set_property;
gobject_class->get_property = koto_artist_get_property;
artist_signals[SIGNAL_ALBUM_ADDED] = g_signal_new(
"album-added",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(KotoArtistClass, album_added),
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
KOTO_TYPE_ALBUM
);
artist_signals[SIGNAL_ALBUM_REMOVED] = g_signal_new(
"album-removed",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(KotoArtistClass, album_removed),
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
KOTO_TYPE_ALBUM
);
artist_signals[SIGNAL_TRACK_ADDED] = g_signal_new(
"track-added",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(KotoArtistClass, track_added),
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
KOTO_TYPE_TRACK
);
artist_signals[SIGNAL_TRACK_REMOVED] = g_signal_new(
"track-removed",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(KotoArtistClass, track_removed),
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
KOTO_TYPE_TRACK
);
props[PROP_UUID] = g_param_spec_string(
"uuid",
"UUID to Artist in database",
@ -76,14 +164,6 @@ static void koto_artist_class_init(KotoArtistClass * c) {
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_PATH] = g_param_spec_string(
"path",
"Path",
"Path to Artist",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_ARTIST_NAME] = g_param_spec_string(
"name",
"Name",
@ -102,28 +182,41 @@ void koto_artist_commit(KotoArtist * self) {
// TODO: Support multiple types instead of just local music artist
gchar * commit_op = g_strdup_printf(
"INSERT INTO artists(id,path,type,name,art_path)"
"VALUES ('%s', quote(\"%s\"), 0, quote(\"%s\"), NULL)"
"ON CONFLICT(id) DO UPDATE SET path=excluded.path, type=excluded.type, name=excluded.name, art_path=excluded.art_path;",
"INSERT INTO artists(id , name, art_path)"
"VALUES ('%s', quote(\"%s\"), NULL)"
"ON CONFLICT(id) DO UPDATE SET name=excluded.name, art_path=excluded.art_path;",
self->uuid,
self->path,
self->artist_name
);
gchar * commit_opt_errmsg = NULL;
int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_opt_errmsg);
new_transaction(commit_op, "Failed to write our artist to the database", FALSE);
if (rc != SQLITE_OK) {
g_warning("Failed to write our artist to the database: %s", commit_opt_errmsg);
GHashTableIter paths_iter;
g_hash_table_iter_init(&paths_iter, self->paths); // Create an iterator for our paths
gpointer lib_uuid_ptr, artist_rel_path_ptr;
while (g_hash_table_iter_next(&paths_iter, &lib_uuid_ptr, &artist_rel_path_ptr)) {
gchar * lib_uuid = lib_uuid_ptr;
gchar * artist_rel_path = artist_rel_path_ptr;
gchar * commit_op = g_strdup_printf(
"INSERT INTO libraries_artists(id, artist_id, path)"
"VALUES ('%s', '%s', quote(\"%s\"))"
"ON CONFLICT(id, artist_id) DO UPDATE SET path=excluded.path;",
lib_uuid,
self->uuid,
artist_rel_path
);
new_transaction(commit_op, "Failed to add this path for the artist", FALSE);
}
g_free(commit_op);
g_free(commit_opt_errmsg);
}
static void koto_artist_init(KotoArtist * self) {
self->has_artist_art = FALSE;
self->albums = NULL; // Create a new GList
self->has_artist_art = FALSE;
self->paths = g_hash_table_new(g_str_hash, g_str_equal);
self->tracks = NULL;
self->type = KOTO_LIBRARY_TYPE_UNKNOWN;
}
static void koto_artist_get_property(
@ -138,9 +231,6 @@ static void koto_artist_get_property(
case PROP_UUID:
g_value_set_string(val, self->uuid);
break;
case PROP_PATH:
g_value_set_string(val, self->path);
break;
case PROP_ARTIST_NAME:
g_value_set_string(val, self->artist_name);
break;
@ -163,9 +253,6 @@ static void koto_artist_set_property(
self->uuid = g_strdup(g_value_get_string(val));
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UUID]);
break;
case PROP_PATH:
koto_artist_update_path(self, (gchar*) g_value_get_string(val));
break;
case PROP_ARTIST_NAME:
koto_artist_set_artist_name(self, (gchar*) g_value_get_string(val));
break;
@ -177,21 +264,59 @@ static void koto_artist_set_property(
void koto_artist_add_album(
KotoArtist * self,
gchar * album_uuid
KotoAlbum * album
) {
if (!KOTO_IS_ARTIST(self)) { // Not an artist
return;
}
if (!koto_utils_is_string_valid(album_uuid)) { // No album UUID really defined
if (!KOTO_IS_ALBUM(album)) { // Album provided is not an album
return;
}
gchar * uuid = g_strdup(album_uuid); // Duplicate our UUID
gchar * album_uuid = koto_album_get_uuid(album);
if (g_list_index(self->albums, uuid) == -1) {
self->albums = g_list_append(self->albums, uuid); // Push to end of list
if (g_list_index(self->albums, album_uuid) != -1) { // If we have already added the album
return;
}
self->albums = g_list_append(self->albums, album_uuid); // Push to our album's UUID to the end of the list
g_signal_emit(
self,
artist_signals[SIGNAL_ALBUM_ADDED],
0,
album
);
}
void koto_artist_add_track(
KotoArtist * self,
KotoTrack * track
) {
if (!KOTO_IS_ARTIST(self)) { // Not an artist
return;
}
if (!KOTO_IS_TRACK(track)) { // Not a track
return;
}
gchar * track_uuid = koto_track_get_uuid(track);
if (g_list_index(self->tracks, track_uuid) != -1) { // If we have already added the track
return;
}
koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer if necessary
self->tracks = g_list_insert_sorted_with_data(self->tracks, track_uuid, koto_track_helpers_sort_tracks_by_uuid, NULL);
g_signal_emit(
self,
artist_signals[SIGNAL_TRACK_ADDED],
0,
track
);
}
GList * koto_artist_get_albums(KotoArtist * self) {
@ -202,6 +327,33 @@ GList * koto_artist_get_albums(KotoArtist * self) {
return g_list_copy(self->albums);
}
KotoAlbum * koto_artist_get_album_by_name(
KotoArtist * self,
gchar * album_name
) {
if (!KOTO_IS_ARTIST(self)) { // Not an artist
return NULL;
}
KotoAlbum * album = NULL;
GList * cur_list_iter;
for (cur_list_iter = self->albums; cur_list_iter != NULL; cur_list_iter = cur_list_iter->next) { // Iterate through our albums by their UUIDs
KotoAlbum * album_for_uuid = koto_cartographer_get_album_by_uuid(koto_maps, cur_list_iter->data); // Get the album
if (!KOTO_IS_ALBUM(album_for_uuid)) { // Not an album
continue;
}
if (g_strcmp0(koto_album_get_name(album_for_uuid), album_name) == 0) { // These album names match
album = album_for_uuid;
break;
}
}
return album;
}
gchar * koto_artist_get_name(KotoArtist * self) {
if (!KOTO_IS_ARTIST(self)) { // Not an artist
return g_strdup("");
@ -210,8 +362,16 @@ gchar * koto_artist_get_name(KotoArtist * self) {
return g_strdup(koto_utils_is_string_valid(self->artist_name) ? self->artist_name : ""); // Return artist name if set
}
GList * koto_artist_get_tracks(KotoArtist * self) {
return KOTO_IS_ARTIST(self) ? self->tracks : NULL;
}
KotoLibraryType koto_artist_get_lib_type(KotoArtist * self) {
return KOTO_IS_ARTIST(self) ? self->type : KOTO_LIBRARY_TYPE_UNKNOWN;
}
gchar * koto_artist_get_uuid(KotoArtist * self) {
return self->uuid;
return KOTO_IS_ARTIST(self) ? self->uuid : NULL;
}
void koto_artist_remove_album(
@ -226,30 +386,36 @@ void koto_artist_remove_album(
return;
}
gchar * album_uuid;
self->albums = g_list_remove(self->albums, koto_album_get_uuid(album));
g_object_get(album, "uuid", &album_uuid, NULL);
self->albums = g_list_remove(self->albums, album_uuid);
g_signal_emit(
self,
artist_signals[SIGNAL_ALBUM_REMOVED],
0,
album
);
}
void koto_artist_update_path(
void koto_artist_remove_track(
KotoArtist * self,
gchar * new_path
KotoTrack * track
) {
if (!KOTO_IS_ARTIST(self)) { // Not an artist
return;
}
if (!koto_utils_is_string_valid(new_path)) { // No path really
if (!KOTO_IS_TRACK(track)) { // Not a track
return;
}
if (koto_utils_is_string_valid(self->path)) { // Already have a path set
g_free(self->path); // Free
}
self->tracks = g_list_remove(self->tracks, koto_track_get_uuid(track));
self->path = g_strdup(new_path);
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PATH]);
g_signal_emit(
self,
artist_signals[SIGNAL_TRACK_ADDED],
0,
track
);
}
void koto_artist_set_artist_name(
@ -272,19 +438,39 @@ void koto_artist_set_artist_name(
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ARTIST_NAME]);
}
KotoArtist * koto_artist_new(gchar * path) {
void koto_artist_set_path(
KotoArtist * self,
KotoLibrary * lib,
const gchar * fixed_path,
gboolean should_commit
) {
if (!KOTO_IS_ARTIST(self)) { // Not an artist
return;
}
gchar * path = g_strdup(fixed_path); // Duplicate our fixed_path
gchar * relative_path = koto_library_get_relative_path_to_file(lib, path); // Get the relative path to the file for the given library
gchar * library_uuid = koto_library_get_uuid(lib); // Get the library for this path
g_hash_table_replace(self->paths, library_uuid, relative_path); // Replace any existing value or add this one
if (should_commit) { // Should commit to the DB
koto_artist_commit(self); // Save the artist
}
self->type = koto_library_get_lib_type(lib); // Define our artist type as the type from the library
}
KotoArtist * koto_artist_new(gchar * artist_name) {
KotoArtist * artist = g_object_new(
KOTO_TYPE_ARTIST,
"uuid",
g_uuid_string_random(),
"path",
path,
"name",
g_path_get_basename(path),
artist_name,
NULL
);
koto_artist_commit(artist); // Commit the artist immediately to the database
return artist;
}

View file

@ -19,173 +19,13 @@
#include <magic.h>
#include <stdio.h>
#include <sys/stat.h>
#include <taglib/tag_c.h>
#include "../db/cartographer.h"
#include "../db/db.h"
#include "../db/loaders.h"
#include "../playlist/playlist.h"
#include "../koto-utils.h"
#include "structs.h"
#include "track-helpers.h"
extern KotoCartographer * koto_maps;
extern sqlite3 * koto_db;
struct _KotoLibrary {
GObject parent_instance;
gchar * path; // Compat
gchar * directory;
gchar * name;
KotoLibraryType type;
gchar * uuid;
gboolean override_builtin;
magic_t magic_cookie;
};
G_DEFINE_TYPE(KotoLibrary, koto_library, G_TYPE_OBJECT);
enum {
PROP_0,
PROP_PATH,
N_PROPERTIES
};
static GParamSpec * props[N_PROPERTIES] = {
NULL,
};
static void koto_library_get_property(
GObject * obj,
guint prop_id,
GValue * val,
GParamSpec * spec
);
static void koto_library_set_property(
GObject * obj,
guint prop_id,
const GValue * val,
GParamSpec * spec
);
static void koto_library_class_init(KotoLibraryClass * c) {
GObjectClass * gobject_class;
gobject_class = G_OBJECT_CLASS(c);
gobject_class->set_property = koto_library_set_property;
gobject_class->get_property = koto_library_get_property;
props[PROP_PATH] = g_param_spec_string(
"path",
"Path",
"Path to Music",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
taglib_id3v2_set_default_text_encoding(TagLib_ID3v2_UTF8); // Ensure our id3v2 text encoding is UTF-8
}
static void koto_library_init(KotoLibrary * self) {
(void) self;
}
static void koto_library_get_property(
GObject * obj,
guint prop_id,
GValue * val,
GParamSpec * spec
) {
KotoLibrary * self = KOTO_LIBRARY(obj);
switch (prop_id) {
case PROP_PATH:
g_value_set_string(val, self->path);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
static void koto_library_set_property(
GObject * obj,
guint prop_id,
const GValue * val,
GParamSpec * spec
) {
KotoLibrary * self = KOTO_LIBRARY(obj);
switch (prop_id) {
case PROP_PATH:
koto_library_set_path(self, g_strdup(g_value_get_string(val)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
void koto_library_set_path(
KotoLibrary * self,
gchar * path
) {
if (path == NULL) {
return;
}
if (self->path != NULL) {
g_free(self->path);
}
self->path = path;
if (created_new_db) { // Created new database
start_indexing(self); // Start index operation
} else { // Have existing database
read_from_db(NULL); // Read from the database
}
}
void start_indexing(KotoLibrary * self) {
struct stat library_stat;
int success = stat(self->path, &library_stat);
if (success != 0) { // Failed to read the library path
return;
}
if (!S_ISDIR(library_stat.st_mode)) { // Is not a directory
g_warning("%s is not a directory", self->path);
return;
}
self->magic_cookie = magic_open(MAGIC_MIME);
if (self->magic_cookie == NULL) {
return;
}
if (magic_load(self->magic_cookie, NULL) != 0) {
magic_close(self->magic_cookie);
return;
}
index_folder(self, self->path, 0);
magic_close(self->magic_cookie);
}
KotoLibrary * koto_library_new(const gchar * path) {
return g_object_new(
KOTO_TYPE_LIBRARY,
"path",
path,
NULL
);
}
extern magic_t magic_cookie;
void index_folder(
KotoLibrary * self,
@ -210,20 +50,14 @@ void index_folder(
gchar * full_path = g_strdup_printf("%s%s%s", path, G_DIR_SEPARATOR_S, entry->d_name);
if (entry->d_type == DT_DIR) { // Directory
if (depth == 1) { // If we are following FOLDER/ARTIST/ALBUM then this would be artist
KotoArtist * artist = koto_artist_new(full_path); // Attempt to get the artist
gchar * artist_name;
if (depth == 1) { // If we are following (ARTIST,AUTHOR,PODCAST)/ALBUM then this would be artist
KotoArtist * artist = koto_artist_new(entry->d_name); // Attempt to get the artist
g_object_get(
artist,
"name",
&artist_name,
NULL
);
koto_cartographer_add_artist(koto_maps, artist); // Add the artist to cartographer
index_folder(self, full_path, depth); // Index this directory
g_free(artist_name);
if (KOTO_IS_ARTIST(artist)) {
koto_artist_set_path(artist, self, full_path, TRUE); // Add the path for this library on this Artist and commit immediately
koto_cartographer_add_artist(koto_maps, artist); // Add the artist to cartographer
index_folder(self, full_path, depth); // Index this directory
}
} else if (depth == 2) { // If we are following FOLDER/ARTIST/ALBUM then this would be album
gchar * artist_name = g_path_get_basename(path); // Get the last entry from our path which is probably the artist
KotoArtist * artist = koto_cartographer_get_artist_by_name(koto_maps, artist_name);
@ -232,18 +66,155 @@ void index_folder(
continue;
}
KotoAlbum * album = koto_album_new(artist, full_path);
koto_cartographer_add_album(koto_maps, album); // Add our album to the cartographer
gchar * artist_uuid = koto_artist_get_uuid(artist); // Get the artist's UUID
gchar * album_uuid = NULL;
g_object_get(album, "uuid", &album_uuid, NULL);
koto_artist_add_album(artist, album_uuid); // Add the album
KotoAlbum * album = koto_album_new(artist_uuid);
koto_album_set_path(album, self, full_path);
koto_album_commit(album); // Save to database immediately
koto_cartographer_add_album(koto_maps, album); // Add our album to the cartographer
koto_artist_add_album(artist, album); // Add the album
index_folder(self, full_path, depth); // Index inside the album
g_free(artist_name);
} else if (depth == 3) { // Possibly CD within album
gchar ** split = g_strsplit(full_path, G_DIR_SEPARATOR_S, -1);
guint split_len = g_strv_length(split);
if (split_len < 4) {
g_strfreev(split);
continue;
}
gchar * album_name = g_strdup(split[split_len - 2]);
gchar * artist_name = g_strdup(split[split_len - 3]);
g_strfreev(split);
if (!koto_utils_is_string_valid(album_name)) {
g_free(album_name);
continue;
}
if (!koto_utils_is_string_valid(artist_name)) {
g_free(album_name);
g_free(artist_name);
continue;
}
KotoArtist * artist = koto_cartographer_get_artist_by_name(koto_maps, artist_name);
g_free(artist_name);
if (!KOTO_IS_ARTIST(artist)) {
continue;
}
KotoAlbum * album = koto_artist_get_album_by_name(artist, album_name); // Get the album
g_free(album_name);
if (!KOTO_IS_ALBUM(album)) {
continue;
}
index_folder(self, full_path, depth); // Index inside the album
}
} else if ((entry->d_type == DT_REG)) { // Is a file in artist folder or lower in FS hierarchy
index_file(self, full_path); // Index this audio file or weird ogg thing
}
g_free(full_path);
}
closedir(dir); // Close the directory
}
void index_file(
KotoLibrary * lib,
const gchar * path
) {
const char * mime_type = magic_file(magic_cookie, path);
if (mime_type == NULL) { // Failed to get the mimetype
return;
}
if (!g_str_has_prefix(mime_type, "audio/") && !g_str_has_prefix(mime_type, "video/ogg")) { // Is not an audio file or ogg
return;
}
gchar * relative_path_to_file = koto_library_get_relative_path_to_file(lib, g_strdup(path)); // Strip out library path so we have a relative path to the file
gchar ** split_on_relative_slashes = g_strsplit(relative_path_to_file, G_DIR_SEPARATOR_S, -1); // Split based on separator (e.g. / )
guint slash_sep_count = g_strv_length(split_on_relative_slashes);
gchar * artist_author_podcast_name = g_strdup(split_on_relative_slashes[0]); // No matter what, artist should be first
gchar * album_or_audiobook_name = NULL;
gchar * file_name = koto_track_helpers_get_name_for_file(path, artist_author_podcast_name); // Get the name of the file
guint cd = (guint) 1;
if (slash_sep_count >= 3) { // If this it is at least "artist" + "album" + "file" (or with CD)
album_or_audiobook_name = g_strdup(split_on_relative_slashes[1]); // Duplicate the second item as the album or audiobook name
}
// #region CD parsing logic
if ((slash_sep_count == 4)) { // If is at least "artist" + "album" + "cd" + "file"
gchar * cd_str = g_strdup(g_strstrip(koto_utils_replace_string_all(g_utf8_strdown(split_on_relative_slashes[2], -1), "cd", ""))); // Replace a lowercased version of our CD ("cd") and trim any whitespace
cd = (guint) g_ascii_strtoull(cd_str, NULL, 10); // Attempt to convert
if (cd == 0) { // Had an error during conversion
cd = 1; // Set back to 1
}
}
// #endregion
g_strfreev(split_on_relative_slashes);
gchar * sorta_uniqueish_key = NULL;
if (koto_utils_is_string_valid(album_or_audiobook_name)) { // Have audiobook or album name
sorta_uniqueish_key = g_strdup_printf("%s-%s-%s", artist_author_podcast_name, album_or_audiobook_name, file_name);
} else { // No audiobook or album name
sorta_uniqueish_key = g_strdup_printf("%s-%s", artist_author_podcast_name, file_name);
}
KotoTrack * track = koto_cartographer_get_track_by_uniqueish_key(koto_maps, sorta_uniqueish_key); // Attempt to get any existing KotoTrack
if (KOTO_IS_TRACK(track)) { // Got a track already
koto_track_set_path(track, lib, relative_path_to_file); // Add this path, which will determine the associated library within that function
} else { // Don't already have a track for this file
KotoArtist * artist = koto_cartographer_get_artist_by_name(koto_maps, artist_author_podcast_name); // Get the possible artist
if (!KOTO_IS_ARTIST(artist)) { // Have an artist for this already
return;
}
KotoAlbum * album = NULL;
if (koto_utils_is_string_valid(album_or_audiobook_name)) { // Have an album or audiobook name
KotoAlbum * possible_album = koto_artist_get_album_by_name(artist, album_or_audiobook_name);
album = KOTO_IS_ALBUM(possible_album) ? possible_album : NULL;
}
if (!KOTO_IS_ALBUM(album)) {
return;
}
gchar * album_uuid = KOTO_IS_ALBUM(album) ? koto_album_get_uuid(album) : NULL;
track = koto_track_new(koto_artist_get_uuid(artist), album_uuid, file_name, cd);
koto_track_set_path(track, lib, relative_path_to_file); // Immediately add the path to this file, for this Library
koto_artist_add_track(artist, track); // Add the track to the artist in the event this is a podcast (no album) or the track is directly in the artist directory
if (KOTO_IS_ALBUM(album)) { // Have an album
koto_album_add_track(album, track); // Add this track since we haven't yet
}
koto_cartographer_add_track(koto_maps, track); // Add to our cartographer tracks hashtable
}
if (KOTO_IS_TRACK(track)) { // Is a track
koto_track_commit(track); // Save the track immediately
}
}

511
src/indexer/library.c Normal file
View file

@ -0,0 +1,511 @@
/* library.c
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "../config/config.h"
#include "../koto-utils.h"
#include "structs.h"
extern KotoConfig * config;
extern GVolumeMonitor * volume_monitor;
enum {
PROP_0,
PROP_TYPE,
PROP_UUID,
PROP_STORAGE_UUID,
PROP_CONSTRUCTION_PATH,
PROP_NAME,
N_PROPERTIES
};
static GParamSpec * props[N_PROPERTIES] = {
NULL,
};
enum {
SIGNAL_NOW_AVAILABLE,
SIGNAL_NOW_UNAVAILABLE,
N_SIGNALS
};
static guint library_signals[N_SIGNALS] = {
0
};
struct _KotoLibrary {
GObject parent_instance;
gchar * uuid;
KotoLibraryType type;
gchar * directory;
gchar * storage_uuid;
GMount * mount;
gulong mount_unmounted_handler;
gchar * mount_path;
gboolean should_index;
gchar * path;
gchar * relative_path;
gchar * name;
};
struct _KotoLibraryClass {
GObjectClass parent_class;
void (* now_available) (KotoLibrary * library);
void (* now_unavailable) (KotoLibrary * library);
};
G_DEFINE_TYPE(KotoLibrary, koto_library, G_TYPE_OBJECT);
static void koto_library_get_property(
GObject * obj,
guint prop_id,
GValue * val,
GParamSpec * spec
);
static void koto_library_set_property(
GObject * obj,
guint prop_id,
const GValue * val,
GParamSpec * spec
);
static void koto_library_class_init(KotoLibraryClass * c) {
GObjectClass * gobject_class;
gobject_class = G_OBJECT_CLASS(c);
gobject_class->set_property = koto_library_set_property;
gobject_class->get_property = koto_library_get_property;
library_signals[SIGNAL_NOW_AVAILABLE] = g_signal_new(
"now-available",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(KotoLibraryClass, now_available),
NULL,
NULL,
NULL,
G_TYPE_NONE,
0
);
library_signals[SIGNAL_NOW_UNAVAILABLE] = g_signal_new(
"now-unavailable",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(KotoLibraryClass, now_unavailable),
NULL,
NULL,
NULL,
G_TYPE_NONE,
0
);
props[PROP_UUID] = g_param_spec_string(
"uuid",
"UUID of Library",
"UUID of Library",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_TYPE] = g_param_spec_string(
"type",
"Type of Library",
"Type of Library",
koto_library_type_to_string(KOTO_LIBRARY_TYPE_MUSIC),
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_STORAGE_UUID] = g_param_spec_string(
"storage-uuid",
"Storage UUID to associated Mount of Library",
"Storage UUID to associated Mount of Library",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_CONSTRUCTION_PATH] = g_param_spec_string(
"construction-path",
"Construction Path",
"Path to this library during construction",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE
);
props[PROP_NAME] = g_param_spec_string(
"name",
"Name of the Library",
"Name of the Library",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE
);
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
}
static void koto_library_init(KotoLibrary * self) {
(void) self;
}
static void koto_library_get_property(
GObject * obj,
guint prop_id,
GValue * val,
GParamSpec * spec
) {
KotoLibrary * self = KOTO_LIBRARY(obj);
switch (prop_id) {
case PROP_NAME:
g_value_set_string(val, g_strdup(self->name));
break;
case PROP_UUID:
g_value_set_string(val, self->uuid);
break;
case PROP_STORAGE_UUID:
g_value_set_string(val, self->storage_uuid);
break;
case PROP_TYPE:
g_value_set_string(val, koto_library_type_to_string(self->type));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
static void koto_library_set_property(
GObject * obj,
guint prop_id,
const GValue * val,
GParamSpec * spec
) {
KotoLibrary * self = KOTO_LIBRARY(obj);
switch (prop_id) {
case PROP_UUID:
self->uuid = g_strdup(g_value_get_string(val));
break;
case PROP_TYPE:
self->type = koto_library_type_from_string(g_strdup(g_value_get_string(val)));
break;
case PROP_STORAGE_UUID:
koto_library_set_storage_uuid(self, g_strdup(g_value_get_string(val)));
break;
case PROP_CONSTRUCTION_PATH:
koto_library_set_path(self, g_strdup(g_value_get_string(val)));
break;
case PROP_NAME:
koto_library_set_name(self, g_strdup(g_value_get_string(val)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
gchar * koto_library_get_path(KotoLibrary * self) {
if (!KOTO_IS_LIBRARY(self)) {
return NULL;
}
if (G_IS_MOUNT(self->mount)) {
}
return self->path;
}
gchar * koto_library_get_relative_path_to_file(
KotoLibrary * self,
gchar * full_path
) {
if (!KOTO_IS_LIBRARY(self)) {
return NULL;
}
gchar * appended_slash_to_library_path = g_str_has_suffix(self->path, G_DIR_SEPARATOR_S) ? g_strdup(self->path) : g_strdup_printf("%s%s", g_strdup(self->path), G_DIR_SEPARATOR_S);
gchar * cleaned_path = koto_utils_replace_string_all(full_path, appended_slash_to_library_path, ""); // Replace the full path
g_free(appended_slash_to_library_path);
return cleaned_path;
}
gchar * koto_library_get_storage_uuid(KotoLibrary * self) {
if (!KOTO_IS_LIBRARY(self)) {
return NULL;
}
return self->storage_uuid;
}
KotoLibraryType koto_library_get_lib_type(KotoLibrary * self) {
if (!KOTO_IS_LIBRARY(self)) {
return KOTO_LIBRARY_TYPE_UNKNOWN;
}
return self->type;
}
gchar * koto_library_get_uuid(KotoLibrary * self) {
if (!KOTO_IS_LIBRARY(self)) {
return NULL;
}
return self->uuid;
}
void koto_library_index(KotoLibrary * self) {
if (!KOTO_IS_LIBRARY(self) || !self->should_index) { // Not a library or should not index
return;
}
index_folder(self, self->path, 0); // Start index operation at the top
}
gboolean koto_library_is_available(KotoLibrary * self) {
if (!KOTO_IS_LIBRARY(self)) {
return FALSE;
}
return FALSE;
}
void koto_library_set_name(
KotoLibrary * self,
gchar * library_name
) {
if (!KOTO_IS_LIBRARY(self)) {
return;
}
if (!koto_utils_is_string_valid(library_name)) { // Not a string
return;
}
if (koto_utils_is_string_valid(self->name)) { // Name already set
g_free(self->name); // Free the existing value
}
self->name = g_strdup(library_name);
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_NAME]);
}
void koto_library_set_path(
KotoLibrary * self,
gchar * path
) {
if (!KOTO_IS_LIBRARY(self)) {
return;
}
if (!koto_utils_is_string_valid(path)) { // Not a valid string
return;
}
if (koto_utils_is_string_valid(self->path)) {
g_free(self->path);
}
self->relative_path = g_path_is_absolute(path) ? koto_utils_replace_string_all(path, self->mount_path, "") : path; // Ensure path is relative to our mount, even if the mount is really our own system partition
self->path = g_build_path(G_DIR_SEPARATOR_S, self->mount_path, self->relative_path, NULL); // Ensure our path is to whatever the current path of the mount + relative path is
}
void koto_library_set_storage_uuid(
KotoLibrary * self,
gchar * storage_uuid
) {
if (!KOTO_IS_LIBRARY(self)) {
return;
}
if (G_IS_MOUNT(self->mount)) { // Already have a mount
g_signal_handler_disconnect(self->mount, self->mount_unmounted_handler); // Stop listening to the unmounted signal for this existing mount
g_object_unref(self->mount); // Dereference the mount
g_free(self->mount_path);
}
if (!koto_utils_is_string_valid(storage_uuid)) { // Not a valid string, which actually is allowed for built-ins
self->mount = NULL;
self->mount_path = g_strdup_printf("%s%s", g_get_home_dir(), G_DIR_SEPARATOR_S); // Set mount path to user's home directory
self->storage_uuid = NULL;
return;
}
GMount * mount = g_volume_monitor_get_mount_for_uuid(volume_monitor, storage_uuid); // Attempt to get the mount by this UUID
if (!G_IS_MOUNT(mount)) {
g_warning("Failed to get mount for UUID: %s", storage_uuid);
self->mount = NULL;
return;
}
if (g_mount_is_shadowed(mount)) { // Is shadowed and should not use
g_message("This mount is considered \"shadowed\" and will not be used.");
return;
}
GFile * mount_file = g_mount_get_default_location(mount); // Get the file for the entry location of the mount
self->mount = mount;
self->mount_path = g_strdup(g_file_get_path(mount_file)); // Set the mount path to the path defined for the Mount File
self->storage_uuid = g_strdup(storage_uuid);
}
gchar * koto_library_to_config_string(KotoLibrary * self) {
GStrvBuilder * lib_builder = g_strv_builder_new(); // Create new strv builder
g_strv_builder_add(lib_builder, g_strdup("[[library]]")); // Add our library array header
g_strv_builder_add(lib_builder, g_strdup_printf("\tdirectory=\"%s\"", self->relative_path)); // Add the directory
if (koto_utils_is_string_valid(self->name)) { // Have a library name
g_strv_builder_add(lib_builder, g_strdup_printf("\tname=\"%s\"", self->name)); // Add the name
}
if (koto_utils_is_string_valid(self->storage_uuid)) { // Have a storage UUID (not applicable to built-ins)
g_strv_builder_add(lib_builder, g_strdup_printf("\tstorage_uuid=\"%s\"", self->storage_uuid)); // Add the storage UUID
}
g_strv_builder_add(lib_builder, g_strdup_printf("\ttype=\"%s\"", koto_library_type_to_string(self->type))); // Add the type
g_strv_builder_add(lib_builder, g_strdup_printf("\tuuid=\"%s\"", self->uuid));
GStrv lines = g_strv_builder_end(lib_builder); // Get all the lines as a GStrv which is a gchar **
gchar * content = g_strjoinv("\n", lines); // Separate all lines with newline
g_strfreev(lines); // Free our lines
g_strv_builder_unref(lib_builder); // Unref our builder
return g_strdup(content);
}
KotoLibrary * koto_library_new(
KotoLibraryType type,
const gchar * storage_uuid,
const gchar * path
) {
KotoLibrary * lib = g_object_new(
KOTO_TYPE_LIBRARY,
"type",
koto_library_type_to_string(type),
"uuid",
g_uuid_string_random(), // Create a new Library with a new UUID
"storage-uuid",
storage_uuid,
"construction-path",
path,
NULL
);
lib->should_index = TRUE;
return lib;
}
KotoLibrary * koto_library_new_from_toml_table(toml_table_t * lib_datum) {
toml_datum_t uuid_datum = toml_string_in(lib_datum, "uuid"); // Get the library UUID
if (!uuid_datum.ok) { // No UUID defined
g_warning("No UUID set for this library. Ignoring");
return NULL;
}
gchar * uuid = g_strdup(uuid_datum.u.s); // Duplicate our UUID
toml_datum_t type_datum = toml_string_in(lib_datum, "type");
if (!type_datum.ok) { // No type defined
g_warning("Unknown type for library with UUID of %s", uuid);
return NULL;
}
gchar * lib_type_as_str = g_strdup(type_datum.u.s);
KotoLibraryType lib_type = koto_library_type_from_string(lib_type_as_str); // Get the library's type
if (lib_type == KOTO_LIBRARY_TYPE_UNKNOWN) { // Known type
return NULL;
}
toml_datum_t dir_datum = toml_string_in(lib_datum, "directory");
if (!dir_datum.ok) {
g_critical("Failed to get directory path for library with UUID of %s", uuid);
return NULL;
}
gchar * path = g_strdup(dir_datum.u.s); // Duplicate the path string
toml_datum_t storage_uuid_datum = toml_string_in(lib_datum, "storage_uuid"); // Get the datum for the storage UUID
gchar * storage_uuid = g_strdup((storage_uuid_datum.ok) ? storage_uuid_datum.u.s : "");
toml_datum_t name_datum = toml_string_in(lib_datum, "name"); // Get the datum for the name
gchar * name = g_strdup((name_datum.ok) ? name_datum.u.s : "");
KotoLibrary * lib = g_object_new(
KOTO_TYPE_LIBRARY,
"type",
lib_type_as_str,
"uuid",
uuid,
"storage-uuid",
storage_uuid,
"construction-path",
path,
"name",
name,
NULL
);
lib->should_index = FALSE;
return lib;
}
KotoLibraryType koto_library_type_from_string(gchar * t) {
if (
(g_strcmp0(t, "audiobooks") == 0) ||
(g_strcmp0(t, "audiobook") == 0)
) {
return KOTO_LIBRARY_TYPE_AUDIOBOOK;
} else if (g_strcmp0(t, "music") == 0) {
return KOTO_LIBRARY_TYPE_MUSIC;
} else if (
(g_strcmp0(t, "podcasts") == 0) ||
(g_strcmp0(t, "podcast") == 0)
) {
return KOTO_LIBRARY_TYPE_PODCAST;
}
g_warning("Invalid type provided for koto_library_type_from_string: %s", t);
return KOTO_LIBRARY_TYPE_UNKNOWN;
}
gchar * koto_library_type_to_string(KotoLibraryType t) {
switch (t) {
case KOTO_LIBRARY_TYPE_AUDIOBOOK:
return g_strdup("audiobook");
case KOTO_LIBRARY_TYPE_MUSIC:
return g_strdup("music");
case KOTO_LIBRARY_TYPE_PODCAST:
return g_strdup("podcast");
default:
return g_strdup("UNKNOWN");
}
}

View file

@ -18,11 +18,13 @@
#pragma once
#include <glib-2.0/glib-object.h>
#include <magic.h>
#include <toml.h>
typedef enum {
KOTO_LIBRARY_TYPE_AUDIOBOOK = 1,
KOTO_LIBRARY_TYPE_MUSIC = 2,
KOTO_LIBRARY_TYPE_PODCAST = 3
KOTO_LIBRARY_TYPE_PODCAST = 3,
KOTO_LIBRARY_TYPE_UNKNOWN = 4
} KotoLibraryType;
G_BEGIN_DECLS
@ -32,15 +34,33 @@ G_BEGIN_DECLS
**/
#define KOTO_TYPE_LIBRARY koto_library_get_type()
G_DECLARE_FINAL_TYPE(KotoLibrary, koto_library, KOTO, LIBRARY, GObject);
#define KOTO_LIBRARY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_LIBRARY, KotoLibrary))
typedef struct _KotoLibrary KotoLibrary;
typedef struct _KotoLibraryClass KotoLibraryClass;
GLIB_AVAILABLE_IN_ALL
GType koto_library_get_type(void) G_GNUC_CONST;
#define KOTO_IS_LIBRARY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_LIBRARY))
#define KOTO_TYPE_ARTIST koto_artist_get_type()
G_DECLARE_FINAL_TYPE(KotoArtist, koto_artist, KOTO, ARTIST, GObject);
#define KOTO_ARTIST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_ARTIST, KotoArtist))
typedef struct _KotoArtist KotoArtist;
typedef struct _KotoArtistClass KotoArtistClass;
GLIB_AVAILABLE_IN_ALL
GType koto_artist_get_type(void) G_GNUC_CONST;
#define KOTO_IS_ARTIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_ARTIST))
#define KOTO_TYPE_ALBUM koto_album_get_type()
G_DECLARE_FINAL_TYPE(KotoAlbum, koto_album, KOTO, ALBUM, GObject);
#define KOTO_ALBUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_ALBUM, KotoAlbum))
typedef struct _KotoAlbum KotoAlbum;
typedef struct _KotoAlbumClass KotoAlbumClass;
GLIB_AVAILABLE_IN_ALL
GType koto_album_get_type(void) G_GNUC_CONST;
#define KOTO_IS_ALBUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_ALBUM))
#define KOTO_TYPE_TRACK koto_track_get_type()
@ -51,14 +71,53 @@ G_DECLARE_FINAL_TYPE(KotoTrack, koto_track, KOTO, TRACK, GObject);
* Library Functions
**/
KotoLibrary * koto_library_new(const gchar * path);
KotoLibrary * koto_library_new(
KotoLibraryType type,
const gchar * storage_uuid,
const gchar * path
);
KotoLibrary * koto_library_new_from_toml_table(toml_table_t * lib_datum);
gchar * koto_library_get_path(KotoLibrary * self);
gchar * koto_library_get_relative_path_to_file(
KotoLibrary * self,
gchar * full_path
);
gchar * koto_library_get_storage_uuid(KotoLibrary * self);
KotoLibraryType koto_library_get_lib_type(KotoLibrary * self);
gchar * koto_library_get_uuid(KotoLibrary * self);
void koto_library_index(KotoLibrary * self);
gboolean koto_library_is_available(KotoLibrary * self);
gchar * koto_library_get_storage_uuid(KotoLibrary * self);
void koto_library_set_name(
KotoLibrary * self,
gchar * library_name
);
void koto_library_set_path(
KotoLibrary * self,
gchar * path
);
void start_indexing(KotoLibrary * self);
void koto_library_set_storage_uuid(
KotoLibrary * self,
gchar * uuid
);
gchar * koto_library_to_config_string(KotoLibrary * self);
KotoLibraryType koto_library_type_from_string(gchar * t);
gchar * koto_library_type_to_string(KotoLibraryType t);
void index_folder(
KotoLibrary * self,
@ -66,40 +125,54 @@ void index_folder(
guint depth
);
void index_file(
KotoLibrary * lib,
const gchar * path
);
/**
* Artist Functions
**/
KotoArtist * koto_artist_new(gchar * path);
KotoArtist * koto_artist_new(gchar * artist_name);
KotoArtist * koto_artist_new_with_uuid(const gchar * uuid);
void koto_artist_add_album(
KotoArtist * self,
gchar * album_uuid
KotoAlbum * album
);
void koto_artist_add_track(
KotoArtist * self,
KotoTrack * track
);
void koto_artist_commit(KotoArtist * self);
guint koto_artist_find_album_with_name(
gconstpointer * album_data,
gconstpointer * album_name_data
);
GList * koto_artist_get_albums(KotoArtist * self);
KotoAlbum * koto_artist_get_album_by_name(
KotoArtist * self,
gchar * album_name
);
gchar * koto_artist_get_name(KotoArtist * self);
GList * koto_artist_get_tracks(KotoArtist * self);
gchar * koto_artist_get_uuid(KotoArtist * self);
KotoLibraryType koto_artist_get_lib_type(KotoArtist * self);
void koto_artist_remove_album(
KotoArtist * self,
KotoAlbum * album
);
void koto_artist_remove_album_by_name(
void koto_artist_remove_track(
KotoArtist * self,
gchar * album_name
KotoTrack * track
);
void koto_artist_set_artist_name(
@ -107,19 +180,18 @@ void koto_artist_set_artist_name(
gchar * artist_name
);
void koto_artist_update_path(
void koto_artist_set_path(
KotoArtist * self,
gchar * new_path
KotoLibrary * lib,
const gchar * fixed_path,
gboolean should_commit
);
/**
* Album Functions
**/
KotoAlbum * koto_album_new(
KotoArtist * artist,
const gchar * path
);
KotoAlbum * koto_album_new(gchar * artist_uuid);
KotoAlbum * koto_album_new_with_uuid(
KotoArtist * artist,
@ -135,23 +207,19 @@ void koto_album_commit(KotoAlbum * self);
void koto_album_find_album_art(KotoAlbum * self);
void koto_album_find_tracks(
KotoAlbum * self,
magic_t magic_cookie,
const gchar * path
);
gchar * koto_album_get_art(KotoAlbum * self);
gchar * koto_album_get_album_art(KotoAlbum * self);
gchar * koto_album_get_album_name(KotoAlbum * self);
gchar * koto_album_get_name(KotoAlbum * self);
gchar * koto_album_get_album_uuid(KotoAlbum * self);
gchar * koto_album_get_path(KotoAlbum * self);
GList * koto_album_get_tracks(KotoAlbum * self);
gchar * koto_album_get_uuid(KotoAlbum *self);
gchar * koto_album_get_uuid(KotoAlbum * self);
void koto_album_remove_file(
void koto_album_remove_track(
KotoAlbum * self,
KotoTrack * track
);
@ -173,15 +241,11 @@ void koto_album_set_artist_uuid(
void koto_album_set_as_current_playlist(KotoAlbum * self);
void koto_album_update_path(
KotoAlbum * self,
gchar * path
);
gint koto_album_sort_tracks(
gconstpointer track1_uuid,
gconstpointer track2_uuid,
gpointer user_data
void koto_album_set_path(
KotoAlbum * self,
KotoLibrary * lib,
const gchar * fixed_path
);
/**
@ -189,20 +253,29 @@ gint koto_album_sort_tracks(
**/
KotoTrack * koto_track_new(
KotoAlbum * album,
const gchar * path,
guint * cd
const gchar * artist_uuid,
const gchar * album_uuid,
const gchar * parsed_name,
guint cd
);
KotoTrack * koto_track_new_with_uuid(const gchar * uuid);
void koto_track_commit(KotoTrack * self);
guint koto_track_get_disc_number(KotoTrack * self);
GVariant * koto_track_get_metadata_vardict(KotoTrack * self);
gchar * koto_track_get_uuid(KotoTrack * self);
gchar * koto_track_get_path(KotoTrack * self);
void koto_track_parse_name(KotoTrack * self);
gchar * koto_track_get_name(KotoTrack * self);
guint koto_track_get_position(KotoTrack * self);
gchar * koto_track_get_uniqueish_key(KotoTrack * self);
gchar * koto_track_get_uuid(KotoTrack * self);
void koto_track_remove_from_playlist(
KotoTrack * self,
@ -230,6 +303,12 @@ void koto_track_set_parsed_name(
gchar * new_parsed_name
);
void koto_track_set_path(
KotoTrack * self,
KotoLibrary * lib,
gchar * fixed_path
);
void koto_track_set_position(
KotoTrack * self,
guint pos
@ -237,9 +316,4 @@ void koto_track_set_position(
void koto_track_update_metadata(KotoTrack * self);
void koto_track_update_path(
KotoTrack * self,
const gchar * new_path
);
G_END_DECLS

149
src/indexer/track-helpers.c Normal file
View file

@ -0,0 +1,149 @@
/* track-helpers.c
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <glib-2.0/glib.h>
#include <taglib/tag_c.h>
#include "../db/cartographer.h"
#include "../koto-track-item.h"
#include "../koto-utils.h"
#include "structs.h"
extern KotoCartographer * koto_maps;
gchar * koto_track_helpers_get_name_for_file(
const gchar * path,
gchar * optional_artist_name
) {
gchar * file_name = NULL;
TagLib_File * t_file = taglib_file_new(path); // Get a taglib file for this file
if ((t_file != NULL) && taglib_file_is_valid(t_file)) { // If we got the taglib file and it is valid
TagLib_Tag * tag = taglib_file_tag(t_file); // Get our tag
file_name = g_strdup(taglib_tag_title(tag)); // Get the tag title and duplicate it
}
taglib_tag_free_strings(); // Free strings
taglib_file_free(t_file); // Free the file
if (koto_utils_is_string_valid(file_name)) { // File name not set yet
return file_name;
}
gchar * file_name_without_path = koto_utils_get_filename_without_extension((gchar*) path); // Get the "default" file name without the extension or path (strips all but basename)
if (koto_utils_is_string_valid(optional_artist_name)) { // Was provided an optional artist name
gchar * removed_artist_name = koto_utils_replace_string_all(file_name_without_path, optional_artist_name, ""); // Remove the artist
g_free(file_name_without_path);
file_name_without_path = removed_artist_name;
}
gchar ** split = g_regex_split_simple("^([\\d]+)", file_name_without_path, G_REGEX_JAVASCRIPT_COMPAT, 0); // Split based on any possible position
if (g_strv_length(split) > 1) { // Has positional info at the beginning of the file name
g_free(file_name_without_path); // Free the prior name
file_name_without_path = g_strdup(split[2]); // Set to our second item which is the rest of the song name without the prefixed numbers
}
g_strfreev(split);
split = g_strsplit(file_name_without_path, " - ", -1); // Split whenever we encounter " - "
file_name_without_path = g_strjoinv("", split); // Remove entirely
g_strfreev(split);
split = g_strsplit(file_name_without_path, "-", -1); // Split whenever we encounter -
file_name_without_path = g_strjoinv("", split); // Remove entirely
g_strfreev(split);
file_name = g_strdup(file_name_without_path); // Set file_name to the duplicate of the file name without the path, now all cleaned up
g_free(file_name_without_path); // Free old reference
return file_name;
}
guint64 koto_track_helpers_get_position_based_on_file_name(const gchar * file_name) {
guint64 position = 0; // Default to position 0
gchar ** split = g_regex_split_simple("^([\\d]+)", file_name, G_REGEX_JAVASCRIPT_COMPAT, 0);
if (g_strv_length(split) > 1) { // Has positional info at the beginning of the file
gchar * num = split[1];
if ((strcmp(num, "0") != 0) && (strcmp(num, "00") != 0)) { // Is not zero
guint64 potential_pos = g_ascii_strtoull(num, NULL, 10); // Attempt to convert
if (potential_pos != 0) { // Got a legitimate position
position = potential_pos; // Update position
}
}
}
return position;
}
gint koto_track_helpers_sort_tracks(
gconstpointer track1,
gconstpointer track2,
gpointer user_data
) {
(void) user_data;
KotoTrack * track1_real = (KotoTrack*) track1;
KotoTrack * track2_real = (KotoTrack*) track2;
if (!KOTO_IS_TRACK(track1_real) && !KOTO_IS_TRACK(track2_real)) { // Neither tracks actually exist
return 0;
} else if (KOTO_IS_TRACK(track1_real) && !KOTO_IS_TRACK(track2_real)) { // Only track2 does not exist
return -1;
} else if (!KOTO_IS_TRACK(track1_real) && KOTO_IS_TRACK(track2_real)) { // Only track1 does not exist
return 1;
}
guint track1_disc = koto_track_get_disc_number(track1_real);
guint track2_disc = koto_track_get_disc_number(track2_real);
if (track1_disc < track2_disc) { // Track 2 is in a later CD / Disc
return -1;
} else if (track1_disc > track2_disc) { // Track1 is later
return 1;
}
guint track1_pos = koto_track_get_position(track1_real);
guint track2_pos = koto_track_get_position(track2_real);
if (track1_pos == track2_pos) { // Identical positions (like reported as 0)
return g_utf8_collate(koto_track_get_name(track1_real), koto_track_get_name(track2_real));
} else if (track1_pos < track2_pos) {
return -1;
} else {
return 1;
}
}
gint koto_track_helpers_sort_track_items(
gconstpointer track1_item,
gconstpointer track2_item,
gpointer user_data
) {
KotoTrack * track1 = koto_track_item_get_track((KotoTrackItem*) track1_item);
KotoTrack * track2 = koto_track_item_get_track((KotoTrackItem*) track2_item);
return koto_track_helpers_sort_tracks(track1, track2, user_data);
}
gint koto_track_helpers_sort_tracks_by_uuid(
gconstpointer track1_uuid,
gconstpointer track2_uuid,
gpointer user_data
) {
KotoTrack * track1 = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) track1_uuid);
KotoTrack * track2 = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) track2_uuid);
return koto_track_helpers_sort_tracks(track1, track2, user_data);
}

View file

@ -0,0 +1,43 @@
/* track-helpers.h
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <glib-2.0/glib.h>
gchar * koto_track_helpers_get_name_for_file(
const gchar * path,
gchar * optional_artist_name
);
guint64 koto_track_helpers_get_position_based_on_file_name(const gchar * file_name);
gint koto_track_helpers_sort_track_items(
gconstpointer track1_item,
gconstpointer track2_item,
gpointer user_data
);
gint koto_track_helpers_sort_tracks(
gconstpointer track1,
gconstpointer track2,
gpointer user_data
);
gint koto_track_helpers_sort_tracks_by_uuid(
gconstpointer track1_uuid,
gconstpointer track2_uuid,
gpointer user_data
);

View file

@ -1,4 +1,4 @@
/* file.c
/* track.c
*
* Copyright 2021 Joshua Strobl
*
@ -18,8 +18,10 @@
#include <glib-2.0/glib.h>
#include <sqlite3.h>
#include <taglib/tag_c.h>
#include "../db/db.h"
#include "../db/cartographer.h"
#include "structs.h"
#include "track-helpers.h"
#include "koto-utils.h"
extern KotoCartographer * koto_maps;
@ -30,15 +32,14 @@ struct _KotoTrack {
gchar * artist_uuid;
gchar * album_uuid;
gchar * uuid;
gchar * path;
gchar * file_name;
GHashTable * paths;
gchar * parsed_name;
guint * cd;
guint * position;
guint cd;
guint position;
guint * playback_position;
gboolean acquired_metadata_from_id3;
gboolean do_initial_index;
};
@ -50,8 +51,6 @@ enum {
PROP_ALBUM_UUID,
PROP_UUID,
PROP_DO_INITIAL_INDEX,
PROP_PATH,
PROP_FILE_NAME,
PROP_PARSED_NAME,
PROP_CD,
PROP_POSITION,
@ -116,22 +115,6 @@ static void koto_track_class_init(KotoTrackClass * c) {
G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_PATH] = g_param_spec_string(
"path",
"Path",
"Path to File",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_FILE_NAME] = g_param_spec_string(
"file-name",
"Name of File",
"Name of File",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_PARSED_NAME] = g_param_spec_string(
"parsed-name",
"Parsed Name of File",
@ -174,7 +157,7 @@ static void koto_track_class_init(KotoTrackClass * c) {
}
static void koto_track_init(KotoTrack * self) {
self->acquired_metadata_from_id3 = FALSE;
self->paths = g_hash_table_new(g_str_hash, g_str_equal); // Create our hash table of paths
}
static void koto_track_get_property(
@ -195,20 +178,14 @@ static void koto_track_get_property(
case PROP_UUID:
g_value_set_string(val, self->uuid);
break;
case PROP_PATH:
g_value_set_string(val, self->path);
break;
case PROP_FILE_NAME:
g_value_set_string(val, self->file_name);
break;
case PROP_PARSED_NAME:
g_value_set_string(val, self->parsed_name);
break;
case PROP_CD:
g_value_set_uint(val, GPOINTER_TO_UINT(self->cd));
g_value_set_uint(val, self->cd);
break;
case PROP_POSITION:
g_value_set_uint(val, GPOINTER_TO_UINT(self->position));
g_value_set_uint(val, self->position);
break;
case PROP_PLAYBACK_POSITION:
g_value_set_uint(val, GPOINTER_TO_UINT(self->playback_position));
@ -243,12 +220,6 @@ static void koto_track_set_property(
case PROP_DO_INITIAL_INDEX:
self->do_initial_index = g_value_get_boolean(val);
break;
case PROP_PATH:
koto_track_update_path(self, g_value_get_string(val)); // Update the path
break;
case PROP_FILE_NAME:
koto_track_set_file_name(self, g_strdup(g_value_get_string(val))); // Update the file name
break;
case PROP_PARSED_NAME:
koto_track_set_parsed_name(self, g_strdup(g_value_get_string(val)));
break;
@ -268,37 +239,56 @@ static void koto_track_set_property(
}
void koto_track_commit(KotoTrack * self) {
if ((self->artist_uuid == NULL) || (strcmp(self->artist_uuid, "") == 0)) { // No valid required artist UUID
if (!KOTO_IS_TRACK(self)) {
return;
}
if (self->album_uuid == NULL) {
g_object_set(self, "album-uuid", "", NULL); // Set to an empty string
if (!koto_utils_is_string_valid(self->artist_uuid)) { // No valid required artist UUID
return;
}
if (!koto_utils_is_string_valid(self->album_uuid)) { // If we do not have a valid album UUID
self->album_uuid = g_strdup("");
}
gchar * commit_msg = "INSERT INTO tracks(id, artist_id, album_id, name, disc, position)" \
"VALUES('%s', '%s', '%s', quote(\"%s\"), %d, %d)" \
"ON CONFLICT(id) DO UPDATE SET album_id=excluded.album_id, artist_id=excluded.artist_id, name=excluded.name, disc=excluded.disc, position=excluded.position;";
gchar * commit_op = g_strdup_printf(
"INSERT INTO tracks(id, path, type, artist_id, album_id, file_name, name, disc, position)"
"VALUES('%s', quote(\"%s\"), 0, '%s', '%s', quote(\"%s\"), quote(\"%s\"), %d, %d)"
"ON CONFLICT(id) DO UPDATE SET path=excluded.path, type=excluded.type, album_id=excluded.album_id, file_name=excluded.file_name, name=excluded.file_name, disc=excluded.disc, position=excluded.position;",
commit_msg,
self->uuid,
self->path,
self->artist_uuid,
self->album_uuid,
self->file_name,
self->parsed_name,
GPOINTER_TO_INT((int*) self->cd),
GPOINTER_TO_INT((int*) self->position)
(int) self->cd,
(int) self->position
);
gchar * commit_op_errmsg = NULL;
int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_op_errmsg);
new_transaction(commit_op, "Failed to write our file to the database", FALSE);
if (rc != SQLITE_OK) {
g_warning("Failed to write our file to the database: %s", commit_op_errmsg);
GHashTableIter paths_iter;
g_hash_table_iter_init(&paths_iter, self->paths); // Create an iterator for our paths
gpointer lib_uuid_ptr, track_rel_path_ptr;
while (g_hash_table_iter_next(&paths_iter, &lib_uuid_ptr, &track_rel_path_ptr)) {
gchar * lib_uuid = lib_uuid_ptr;
gchar * track_rel_path = track_rel_path_ptr;
gchar * commit_op = g_strdup_printf(
"INSERT INTO libraries_tracks(id, track_id, path)"
"VALUES ('%s', '%s', quote(\"%s\"))"
"ON CONFLICT(id, track_id) DO UPDATE SET path=excluded.path;",
lib_uuid,
self->uuid,
track_rel_path
);
new_transaction(commit_op, "Failed to add this path for the track", FALSE);
}
}
g_free(commit_op);
g_free(commit_op_errmsg);
guint koto_track_get_disc_number(KotoTrack * self) {
return KOTO_IS_TRACK(self) ? self->cd : 1;
}
GVariant * koto_track_get_metadata_vardict(KotoTrack * self) {
@ -307,27 +297,28 @@ GVariant * koto_track_get_metadata_vardict(KotoTrack * self) {
}
GVariantBuilder * builder = g_variant_builder_new(G_VARIANT_TYPE_VARDICT);
gchar * album_art_path = NULL;
gchar * album_name = NULL;
gchar * artist_name = NULL;
KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, self->artist_uuid);
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid);
gchar * artist_name = koto_artist_get_name(artist);
g_object_get(album, "art-path", &album_art_path, "name", &album_name, NULL);
if (koto_utils_is_string_valid(self->album_uuid)) { // Have an album associated
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid);
g_object_get(artist, "name", &artist_name, NULL);
if (KOTO_IS_ALBUM(album)) {
gchar * album_art_path = koto_album_get_art(album);
gchar * album_name = koto_album_get_name(album);
if (koto_utils_is_string_valid(album_art_path)) { // Valid album art path
album_art_path = g_strconcat("file://", album_art_path, NULL); // Prepend with file://
g_variant_builder_add(builder, "{sv}", "mpris:artUrl", g_variant_new_string(album_art_path));
}
g_variant_builder_add(builder, "{sv}", "xesam:album", g_variant_new_string(album_name));
}
} else {
} // TODO: Implement artist artwork fetching here
g_variant_builder_add(builder, "{sv}", "mpris:trackid", g_variant_new_string(self->uuid));
if (koto_utils_is_string_valid(album_art_path)) { // Valid album art path
album_art_path = g_strconcat("file://", album_art_path, NULL); // Prepend with file://
g_variant_builder_add(builder, "{sv}", "mpris:artUrl", g_variant_new_string(album_art_path));
}
g_variant_builder_add(builder, "{sv}", "xesam:album", g_variant_new_string(album_name));
if (koto_utils_is_string_valid(artist_name)) { // Valid artist name
GVariant * artist_name_variant;
GVariantBuilder * artist_list_builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
@ -339,16 +330,74 @@ GVariant * koto_track_get_metadata_vardict(KotoTrack * self) {
g_variant_builder_add(builder, "{sv}", "playbackengine:artist", g_variant_new_string(artist_name)); // Add a sort of "meta" string val for our playback engine so we don't need to mess about with the array
}
g_variant_builder_add(builder, "{sv}", "xesam:discNumber", g_variant_new_uint64(GPOINTER_TO_UINT(self->cd)));
g_variant_builder_add(builder, "{sv}", "xesam:discNumber", g_variant_new_uint64(self->cd));
g_variant_builder_add(builder, "{sv}", "xesam:title", g_variant_new_string(self->parsed_name));
g_variant_builder_add(builder, "{sv}", "xesam:url", g_variant_new_string(self->path));
g_variant_builder_add(builder, "{sv}", "xesam:trackNumber", g_variant_new_uint64(GPOINTER_TO_UINT(self->position)));
g_variant_builder_add(builder, "{sv}", "xesam:url", g_variant_new_string(koto_track_get_path(self)));
g_variant_builder_add(builder, "{sv}", "xesam:trackNumber", g_variant_new_uint64(self->position));
GVariant * metadata_ret = g_variant_builder_end(builder);
return metadata_ret;
}
gchar * koto_track_get_name(KotoTrack * self) {
if (!KOTO_IS_TRACK(self)) { // Not a track
return NULL;
}
return g_strdup(self->parsed_name);
}
gchar * koto_track_get_path(KotoTrack * self) {
if (!KOTO_IS_TRACK(self) || (KOTO_IS_TRACK(self) && (g_list_length(g_hash_table_get_keys(self->paths)) == 0))) { // If this is not a track or is but we have no paths associated with it
return NULL;
}
GList * libs = koto_cartographer_get_libraries(koto_maps); // Get all of our libraries
GList * cur_lib_list;
for (cur_lib_list = libs; cur_lib_list != NULL; cur_lib_list = libs->next) { // Iterate over our libraries
KotoLibrary * cur_library = libs->data; // Get this as a KotoLibrary
gchar * library_relative_path = g_hash_table_lookup(self->paths, koto_library_get_uuid(cur_library)); // Get any relative path in our paths based on the current UUID
if (!koto_utils_is_string_valid(library_relative_path)) { // Not a valid path
continue;
}
return g_strdup(g_build_path(G_DIR_SEPARATOR_S, koto_library_get_path(cur_library), library_relative_path, NULL)); // Build our full library path using library's path and our file relative path
}
return NULL;
}
guint koto_track_get_position(KotoTrack * self) {
return KOTO_IS_TRACK(self) ? self->position : 0;
}
gchar * koto_track_get_uniqueish_key(KotoTrack * self) {
KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, self->artist_uuid); // Get the artist associated with this track
if (!KOTO_IS_ARTIST(artist)) { // Don't have an artist
return g_strdup(self->parsed_name); // Just return the name of the file, which is very likely not unique
}
gchar * artist_name = koto_artist_get_name(artist); // Get the artist name
if (koto_utils_is_string_valid(self->album_uuid)) { // If we have an album associated with this track (not necessarily guaranteed)
KotoAlbum * possible_album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid);
if (KOTO_IS_ALBUM(possible_album)) { // Album exists
gchar * album_name = koto_album_get_name(possible_album); // Get the name of the album
if (koto_utils_is_string_valid(album_name)) {
return g_strdup_printf("%s-%s-%s", artist_name, album_name, self->parsed_name); // Create a key of (ARTIST/WRITER)-(ALBUM/AUDIOBOOK)-(CHAPTER/TRACK)
}
}
}
return g_strdup_printf("%s-%s", artist_name, self->parsed_name); // Create a key of just (ARTIST/WRITER)-(CHAPTER/TRACK)
}
gchar * koto_track_get_uuid(KotoTrack * self) {
if (!KOTO_IS_TRACK(self)) {
return NULL;
@ -357,65 +406,6 @@ gchar * koto_track_get_uuid(KotoTrack * self) {
return self->uuid; // Do not return a duplicate since otherwise comparison refs fail due to pointer positions being different
}
void koto_track_parse_name(KotoTrack * self) {
gchar * copied_file_name = g_strdelimit(g_strdup(self->file_name), "_", ' '); // Replace _ with whitespace for starters
KotoArtist * artist = NULL;
artist = koto_cartographer_get_artist_by_uuid(koto_maps, self->artist_uuid);
if (artist != NULL) { // If we have artist
gchar * artist_name = NULL;
g_object_get(artist, "name", &artist_name, NULL);
if (artist_name != NULL && (strcmp(artist_name, "") != 0)) {
gchar ** split = g_strsplit(copied_file_name, artist_name, -1); // Split whenever we encounter the artist
copied_file_name = g_strjoinv("", split); // Remove the artist
g_strfreev(split);
split = g_strsplit(copied_file_name, g_utf8_strdown(artist_name, -1), -1); // Lowercase album name and split by that
copied_file_name = g_strjoinv("", split); // Remove the artist
g_strfreev(split);
}
}
gchar * file_without_ext = koto_utils_get_filename_without_extension(copied_file_name);
g_free(copied_file_name);
gchar ** split = g_regex_split_simple("^([\\d]+)", file_without_ext, G_REGEX_JAVASCRIPT_COMPAT, 0);
if (g_strv_length(split) > 1) { // Has positional info at the beginning of the file
gchar * num = split[1];
g_free(file_without_ext); // Free the prior name
file_without_ext = g_strdup(split[2]); // Set to our second item which is the rest of the song name without the prefixed numbers
if ((strcmp(num, "0") == 0) || (strcmp(num, "00") == 0)) { // Is exactly zero
koto_track_set_position(self, 0); // Set position to 0
} else { // Either starts with 0 (like 09) or doesn't start with it at all
guint64 potential_pos = g_ascii_strtoull(num, NULL, 10); // Attempt to convert
if (potential_pos != 0) { // Got a legitimate position
koto_track_set_position(self, potential_pos);
}
}
}
g_strfreev(split);
split = g_strsplit(file_without_ext, " - ", -1); // Split whenever we encounter " - "
file_without_ext = g_strjoinv("", split); // Remove entirely
g_strfreev(split);
split = g_strsplit(file_without_ext, "-", -1); // Split whenever we encounter -
file_without_ext = g_strjoinv("", split); // Remove entirely
g_strfreev(split);
koto_track_set_parsed_name(self, file_without_ext);
g_free(file_without_ext);
}
void koto_track_remove_from_playlist(
KotoTrack * self,
gchar * playlist_uuid
@ -430,15 +420,7 @@ void koto_track_remove_from_playlist(
playlist_uuid
);
gchar * commit_op_errmsg = NULL;
int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_op_errmsg);
if (rc != SQLITE_OK) {
g_warning("Failed to remove track from playlist: %s", commit_op_errmsg);
}
g_free(commit_op);
g_free(commit_op_errmsg);
new_transaction(commit_op, "Failed to remove track from playlist", FALSE);
}
void koto_track_save_to_playlist(
@ -458,39 +440,7 @@ void koto_track_save_to_playlist(
current
);
gchar * commit_op_errmsg = NULL;
int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_op_errmsg);
if (rc != SQLITE_OK) {
g_warning("Failed to save track to playlist: %s", commit_op_errmsg);
}
g_free(commit_op);
g_free(commit_op_errmsg);
}
void koto_track_set_file_name(
KotoTrack * self,
gchar * new_file_name
) {
if (new_file_name == NULL) {
return;
}
if ((self->file_name != NULL) && (strcmp(self->file_name, new_file_name) == 0)) { // Not null and the same
return; // Don't do anything
}
if (self->file_name != NULL) { // If it is defined
g_free(self->file_name);
}
self->file_name = g_strdup(new_file_name);
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_FILE_NAME]);
if (!self->acquired_metadata_from_id3 && self->do_initial_index) { // Haven't acquired our information from ID3
koto_track_parse_name(self); // Update our parsed name
}
new_transaction(commit_op, "Failed to save track to playlist", FALSE);
}
void koto_track_set_cd(
@ -501,7 +451,7 @@ void koto_track_set_cd(
return;
}
self->cd = GUINT_TO_POINTER(cd);
self->cd = cd;
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_CD]);
}
@ -509,15 +459,17 @@ void koto_track_set_parsed_name(
KotoTrack * self,
gchar * new_parsed_name
) {
if (new_parsed_name == NULL) {
if (!koto_utils_is_string_valid(new_parsed_name)) {
return;
}
if ((self->parsed_name != NULL) && (strcmp(self->parsed_name, new_parsed_name) == 0)) { // Not null and the same
gboolean have_existing_name = koto_utils_is_string_valid(self->parsed_name);
if (have_existing_name && (strcmp(self->parsed_name, new_parsed_name) == 0)) { // Have existing name that matches one provided
return; // Don't do anything
}
if (self->parsed_name != NULL) {
if (have_existing_name) {
g_free(self->parsed_name);
}
@ -525,6 +477,30 @@ void koto_track_set_parsed_name(
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PARSED_NAME]);
}
void koto_track_set_path(
KotoTrack * self,
KotoLibrary * lib,
gchar * fixed_path
) {
if (!KOTO_IS_TRACK(self)) {
return;
}
if (!koto_utils_is_string_valid(fixed_path)) { // Not a valid path
return;
}
gchar * path = g_strdup(fixed_path); // Duplicate our fixed_path
gchar * relative_path = koto_library_get_relative_path_to_file(lib, path); // Get the relative path to the file for the given library
gchar * library_uuid = koto_library_get_uuid(lib); // Get the library for this path
g_hash_table_replace(self->paths, library_uuid, relative_path); // Replace any existing value or add this one
if (self->do_initial_index) {
koto_track_update_metadata(self); // Attempt to get ID3 info
}
}
void koto_track_set_position(
KotoTrack * self,
guint pos
@ -533,58 +509,37 @@ void koto_track_set_position(
return;
}
self->position = GUINT_TO_POINTER(pos);
self->position = pos;
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_POSITION]);
}
void koto_track_update_metadata(KotoTrack * self) {
TagLib_File * t_file = taglib_file_new(self->path); // Get a taglib file for this file
if (!KOTO_IS_TRACK(self)) { // Not a track
return;
}
gchar * optimal_track_path = koto_track_get_path(self); // Check all the libraries associated with this track, based on priority, return a built path using lib path + relative file path
TagLib_File * t_file = taglib_file_new(optimal_track_path); // Get a taglib file for this file
g_free(optimal_track_path);
if ((t_file != NULL) && taglib_file_is_valid(t_file)) { // If we got the taglib file and it is valid
self->acquired_metadata_from_id3 = TRUE;
TagLib_Tag * tag = taglib_file_tag(t_file); // Get our tag
koto_track_set_parsed_name(self, taglib_tag_title(tag)); // Set the title of the file
koto_track_set_position(self, (uint) taglib_tag_track(tag)); // Get the track, convert to uint and cast as a pointer
koto_track_set_file_name(self, g_path_get_basename(self->path)); // Update our file name
} else {
koto_track_set_file_name(self, g_path_get_basename(self->path)); // Update our file name
} else { // Failed to get tag info
guint64 position = koto_track_helpers_get_position_based_on_file_name(g_path_get_basename(optimal_track_path)); // Get the likely position
koto_track_set_position(self, position); // Set our position
}
taglib_tag_free_strings(); // Free strings
taglib_file_free(t_file); // Free the file
}
void koto_track_update_path(
KotoTrack * self,
const gchar * new_path
) {
if (new_path == NULL) {
return;
}
if (self->path != NULL) { // Already have a path
g_free(self->path); // Free it
}
self->path = g_strdup(new_path); // Duplicate the path and set it
if (self->do_initial_index) {
koto_track_update_metadata(self); // Attempt to get ID3 info
}
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PATH]);
}
KotoTrack * koto_track_new(
KotoAlbum * album,
const gchar * path,
guint * cd
const gchar * artist_uuid,
const gchar * album_uuid,
const gchar * parsed_name,
guint cd
) {
gchar * artist_uuid;
gchar * album_uuid;
g_object_get(album, "artist-uuid", &artist_uuid, "uuid", &album_uuid, NULL); // Get the artist and album uuids from our Album
KotoTrack * track = g_object_new(
KOTO_TYPE_TRACK,
"artist-uuid",
@ -595,14 +550,13 @@ KotoTrack * koto_track_new(
TRUE,
"uuid",
g_uuid_string_random(),
"path",
path,
"cd",
cd,
"parsed-name",
parsed_name,
NULL
);
koto_track_commit(track); // Immediately commit to the database
return track;
}

View file

@ -20,6 +20,7 @@
#include <sys/types.h>
#include <unistd.h>
#include "koto-paths.h"
#include "koto-utils.h"
gchar * koto_rev_dns;
gchar * koto_path_cache;
@ -30,21 +31,16 @@ gchar * koto_path_to_db;
void koto_paths_setup() {
koto_rev_dns = "com.github.joshstrobl.koto";
const gchar * user_cache_dir = g_get_user_data_dir();
const gchar * user_config_dir = g_get_user_config_dir();
gchar * user_cache_dir = g_strdup(g_get_user_data_dir());
gchar * user_config_dir = g_strdup(g_get_user_config_dir());
koto_path_cache = g_build_path(G_DIR_SEPARATOR_S, user_cache_dir, koto_rev_dns, NULL);
koto_path_config = g_build_path(G_DIR_SEPARATOR_S, user_config_dir, koto_rev_dns, NULL);
koto_path_to_conf = g_build_filename(koto_path_config, "config.toml", NULL);
koto_path_to_db = g_build_filename( koto_path_cache, "db", NULL);
mkdir(user_cache_dir, 0755);
mkdir(user_config_dir, 0755);
mkdir(koto_path_cache, 0755);
mkdir(koto_path_config, 0755);
chown(user_cache_dir, getuid(), getgid());
chown(user_config_dir, getuid(), getgid());
chown(koto_path_cache, getuid(), getgid());
chown(koto_path_config, getuid(), getgid());
koto_utils_mkdir(user_cache_dir);
koto_utils_mkdir(user_config_dir);
koto_utils_mkdir(koto_path_cache);
koto_utils_mkdir(koto_path_config);
}

View file

@ -126,6 +126,10 @@ static void koto_track_item_init(KotoTrackItem * self) {
}
KotoTrack * koto_track_item_get_track(KotoTrackItem * self) {
if (!KOTO_IS_TRACK_ITEM(self)) {
return NULL;
}
return self->track;
}

View file

@ -17,6 +17,9 @@
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
extern GtkWindow * main_window;
@ -68,8 +71,8 @@ gchar * koto_utils_gboolean_to_string(gboolean b) {
}
gchar * koto_utils_get_filename_without_extension(gchar * filename) {
gchar * trimmed_file_name = g_strdup(filename);
gchar ** split = g_strsplit(filename, ".", -1); // Split every time we see .
gchar * trimmed_file_name = g_strdup(g_path_get_basename(filename)); // Ensure the filename provided is the base name of any possible path and duplicate it
gchar ** split = g_strsplit(trimmed_file_name, ".", -1); // Split every time we see .
g_free(trimmed_file_name);
guint len_of_extension_split = g_strv_length(split);
@ -103,6 +106,11 @@ gboolean koto_utils_is_string_valid(gchar * str) {
return ((str != NULL) && (g_strcmp0(str, "") != 0));
}
void koto_utils_mkdir(gchar * path) {
mkdir(path, 0755);
chown(path, getuid(), getgid());
}
void koto_utils_push_queue_element_to_store(
gpointer data,
gpointer user_data

View file

@ -36,6 +36,8 @@ gchar * koto_utils_get_filename_without_extension(gchar * filename);
gboolean koto_utils_is_string_valid(gchar * str);
void koto_utils_mkdir(gchar * path);
void koto_utils_push_queue_element_to_store(
gpointer data,
gpointer user_data

View file

@ -23,7 +23,6 @@
#include "pages/playlist/list.h"
#include "playback/engine.h"
#include "playlist/add-remove-track-popover.h"
#include "playlist/current.h"
#include "playlist/create-modify-dialog.h"
#include "config/config.h"
#include "koto-dialog-container.h"
@ -37,16 +36,13 @@ extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup;
extern KotoCartographer * koto_maps;
extern KotoCreateModifyPlaylistDialog * playlist_create_modify_dialog;
extern KotoConfig * config;
extern KotoCurrentPlaylist * current_playlist;
extern KotoPageMusicLocal * music_local_page;
extern KotoPlaybackEngine * playback_engine;
extern gchar * koto_rev_dns;
struct _KotoWindow {
GtkApplicationWindow parent_instance;
KotoLibrary * library;
KotoCurrentPlaylist * current_playlist;
KotoDialogContainer * dialogs;
@ -72,9 +68,6 @@ static void koto_window_class_init (KotoWindowClass * klass) {
}
static void koto_window_init (KotoWindow * self) {
current_playlist = koto_current_playlist_new();
playback_engine = koto_playback_engine_new();
koto_window_manage_style(config, 0, self); // Immediately apply the theme
g_signal_connect(config, "notify::ui-theme-desired", G_CALLBACK(koto_window_manage_style), self); // Handle changes to desired theme
g_signal_connect(config, "notify::ui-theme-override", G_CALLBACK(koto_window_manage_style), self); // Handle changes to theme overriding
@ -145,7 +138,12 @@ static void koto_window_init (KotoWindow * self) {
gtk_widget_queue_draw(self->content_layout);
g_thread_new("load-library", (void*) load_library, self);
music_local_page = koto_page_music_local_new();
// TODO: Remove and do some fancy state loading
koto_window_add_page(self, "music.local", GTK_WIDGET(music_local_page));
koto_window_go_to_page(self, "music.local");
gtk_widget_show(self->pages); // Do not remove this. Will cause sporadic hiding of the local page content otherwise.
}
void koto_window_add_page(
@ -153,6 +151,18 @@ void koto_window_add_page(
gchar * page_name,
GtkWidget * page
) {
if (!KOTO_IS_WINDOW(self)) {
return;
}
if (!GTK_IS_STACK(self->pages)) {
return;
}
if (!GTK_IS_WIDGET(page)) {
return;
}
gtk_stack_add_named(GTK_STACK(self->pages), page, page_name);
}
@ -285,23 +295,6 @@ void create_new_headerbar(KotoWindow * self) {
gtk_window_set_titlebar(GTK_WINDOW(self), self->header_bar);
}
void load_library(KotoWindow * self) {
KotoLibrary * lib = koto_library_new(g_get_user_special_dir(G_USER_DIRECTORY_MUSIC));
if (lib != NULL) {
self->library = lib;
music_local_page = koto_page_music_local_new();
// TODO: Remove and do some fancy state loading
koto_window_add_page(self, "music.local", GTK_WIDGET(music_local_page));
koto_window_go_to_page(self, "music.local");
gtk_widget_show(self->pages); // Do not remove this. Will cause sporadic hiding of the local page content otherwise.
koto_page_music_local_build_ui(music_local_page);
}
g_thread_exit(0);
}
void set_optimal_default_window_size(KotoWindow * self) {
GdkDisplay * default_display = gdk_display_get_default();

View file

@ -16,9 +16,13 @@
*/
#include <glib.h>
#include <gstreamer-1.0/gst/gst.h>
#include <magic.h>
#include <taglib/tag_c.h>
#include "config/config.h"
#include "db/cartographer.h"
#include "db/db.h"
#include "db/loaders.h"
#include "playback/engine.h"
#include "playback/media-keys.h"
#include "playback/mimes.h"
#include "playback/mpris.h"
@ -33,6 +37,7 @@ extern KotoConfig * config;
extern guint mpris_bus_id;
extern GDBusNodeInfo * introspection_data;
extern KotoPlaybackEngine * playback_engine;
extern KotoCartographer * koto_maps;
extern sqlite3 * koto_db;
@ -41,9 +46,10 @@ extern GList * supported_mimes;
extern gchar * koto_path_to_conf;
extern gchar * koto_rev_dns;
GVolumeMonitor * volume_monitor = NULL;
GtkApplication * app = NULL;
GtkWindow * main_window;
magic_t magic_cookie;
static void on_activate (GtkApplication * app) {
g_assert(GTK_IS_APPLICATION(app));
@ -53,6 +59,7 @@ static void on_activate (GtkApplication * app) {
main_window = g_object_new(KOTO_TYPE_WINDOW, "application", app, "default-width", 1200, "default-height", 675, NULL);
setup_mpris_interfaces(); // Set up our MPRIS interfaces
setup_mediakeys_interface(); // Set up our media key support
read_from_db(); // Read the database, allowing us to propagate the UI with various data such as artists and playlist navigation elements
}
gtk_window_present(main_window);
@ -83,9 +90,26 @@ int main (
koto_maps = koto_cartographer_new(); // Create our new cartographer and their collection of maps
volume_monitor = g_volume_monitor_get(); // Get a VolumeMonitor
config = koto_config_new(); // Set our config
koto_config_load(config, koto_path_to_conf);
playback_engine = koto_playback_engine_new(); // Initialize the engine now that the config is available, since it listens on various config signals
taglib_id3v2_set_default_text_encoding(TagLib_ID3v2_UTF8); // Ensure our id3v2 text encoding is UTF-8
magic_cookie = magic_open(MAGIC_MIME);
if (magic_cookie == NULL) { // Failed to open
g_critical("Failed to allocate a cookie pointer from libmagic.");
}
if (magic_load(magic_cookie, NULL) != 0) { // Failed to load data
magic_close(magic_cookie);
g_critical("Failed to load the system magic database.");
}
open_db(); // Open our database
g_thread_new("indexing-any-necessary-libs", (void*) koto_config_load_libs, config); // Load our libraries, now that our database is set up. Note that read_from_db is called in koto-window.c
app = gtk_application_new(koto_rev_dns, G_APPLICATION_FLAGS_NONE);
g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);

View file

@ -14,6 +14,8 @@ koto_sources = [
'indexer/album.c',
'indexer/artist.c',
'indexer/file-indexer.c',
'indexer/library.c',
'indexer/track-helpers.c',
'indexer/track.c',
'pages/music/album-view.c',
'pages/music/artist-view.c',

View file

@ -38,7 +38,7 @@ struct _KotoAlbumView {
KotoCoverArtButton * album_cover;
GtkWidget * album_label;
GHashTable * cd_to_track_listbox;
GHashTable * cd_to_disc_views;
};
G_DEFINE_TYPE(KotoAlbumView, koto_album_view, G_TYPE_OBJECT);
@ -85,7 +85,7 @@ static void koto_album_view_class_init(KotoAlbumViewClass * c) {
}
static void koto_album_view_init(KotoAlbumView * self) {
self->cd_to_track_listbox = g_hash_table_new(g_str_hash, g_str_equal);
self->cd_to_disc_views = g_hash_table_new(g_str_hash, g_str_equal);
self->main = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_add_css_class(self->main, "album-view");
gtk_widget_set_can_focus(self->main, FALSE);
@ -153,66 +153,87 @@ static void koto_album_view_set_property(
}
}
void koto_album_view_add_track_to_listbox(
KotoAlbum * self,
void koto_album_view_add_track(
KotoAlbumView * self,
KotoTrack * track
) {
(void) self;
(void) track;
if (!KOTO_IS_ALBUM_VIEW(self)) { // Not an album view
return;
}
if (!KOTO_IS_ALBUM(self->album)) { // Somehow don't have an album set
return;
}
if (!KOTO_IS_TRACK(track)) { // Track doesn't exist
return;
}
guint disc_number = koto_track_get_disc_number(track); // Get the disc number
gchar * disc_num_as_str = g_strdup_printf("%u", disc_number);
KotoDiscView * disc_view;
if (g_hash_table_contains(self->cd_to_disc_views, disc_num_as_str)) { // Already have this added this disc and its disc view
disc_view = g_hash_table_lookup(self->cd_to_disc_views, disc_num_as_str); // Get the disc view
} else {
disc_view = koto_disc_view_new(self->album, disc_number); // Build a new disc view
gtk_list_box_append(GTK_LIST_BOX(self->discs), GTK_WIDGET(disc_view)); // Add the Disc View to the List Box
g_hash_table_replace(self->cd_to_disc_views, disc_num_as_str, disc_view); // Add the new Disc View to our hash table
}
if (!KOTO_IS_DISC_VIEW(disc_view)) { // If this is not a Disc View
return;
}
koto_disc_view_add_track(disc_view, track); // Add the track to the disc view
koto_album_view_update_disc_labels(self); // Update our disc labels
}
void koto_album_view_handle_track_added(
KotoAlbum * album,
KotoTrack * track,
gpointer user_data
) {
if (!KOTO_IS_ALBUM(album)) { // If not an album
return;
}
if (!KOTO_IS_TRACK(track)) { // If not a track
return;
}
KotoAlbumView * self = KOTO_ALBUM_VIEW(user_data); // Define as an album view
if (!KOTO_IS_ALBUM_VIEW(self)) {
return;
}
koto_album_view_add_track(self, track); // Add the track
}
void koto_album_view_set_album(
KotoAlbumView * self,
KotoAlbum * album
) {
if (album == NULL) {
if (!KOTO_IS_ALBUM_VIEW(self)) {
return;
}
if (!KOTO_IS_ALBUM(album)) {
return;
}
self->album = album;
gchar * album_art = koto_album_get_album_art(self->album); // Get the art for the album
gchar * album_art = koto_album_get_art(self->album); // Get the art for the album
koto_cover_art_button_set_art_path(self->album_cover, album_art);
gchar * album_name;
g_object_get(album, "name", &album_name, NULL); // Get the album name
self->album_label = gtk_label_new(album_name);
self->album_label = gtk_label_new(koto_album_get_name(album));
gtk_widget_set_halign(self->album_label, GTK_ALIGN_START);
gtk_box_prepend(GTK_BOX(self->album_tracks_box), self->album_label); // Prepend our new label to the album + tracks box
GHashTable * discs = g_hash_table_new(g_str_hash, g_str_equal);
GList * tracks = koto_album_get_tracks(album); // Get the tracks for this album
for (guint i = 0; i < g_list_length(tracks); i++) {
KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) g_list_nth_data(tracks, i)); // Get the track by its UUID
if (!KOTO_IS_TRACK(track)) { // Track doesn't exist
continue;
}
guint * disc_number;
g_object_get(track, "cd", &disc_number, NULL);
gchar * disc_num_as_str = g_strdup_printf("%u", GPOINTER_TO_UINT(disc_number));
if (g_hash_table_contains(discs, disc_num_as_str)) { // Already have this added
continue; // Skip
}
g_hash_table_insert(discs, disc_num_as_str, "0"); // Mark this disc number in the hash table
KotoDiscView * disc_view = koto_disc_view_new(album, disc_number);
gtk_list_box_append(GTK_LIST_BOX(self->discs), GTK_WIDGET(disc_view)); // Add the Disc View to the List Box
}
if (g_list_length(g_hash_table_get_keys(discs)) == 1) { // Only have one album
GtkListBoxRow * row = gtk_list_box_get_row_at_index(GTK_LIST_BOX(self->discs), 0); // Get the first item
if (GTK_IS_LIST_BOX_ROW(row)) { // Is a row
koto_disc_view_set_disc_label_visible((KotoDiscView*) gtk_list_box_row_get_child(row), FALSE); // Get
}
}
g_hash_table_destroy(discs);
g_signal_connect(self->album, "track-added", G_CALLBACK(koto_album_view_handle_track_added), self); // Handle track added on our album
}
int koto_album_view_sort_discs(
@ -255,6 +276,23 @@ void koto_album_view_toggle_album_playback(
koto_album_set_as_current_playlist(self->album); // Set as the current playlist
}
void koto_album_view_update_disc_labels(KotoAlbumView * self) {
gboolean show_disc_labels = g_hash_table_size(self->cd_to_disc_views) > 1;
gpointer disc_num_ptr;
gpointer disc_view_ptr;
GHashTableIter disc_view_iter;
g_hash_table_iter_init(&disc_view_iter, self->cd_to_disc_views);
while (g_hash_table_iter_next(&disc_view_iter, &disc_num_ptr, &disc_view_ptr)) {
(void) disc_num_ptr;
KotoDiscView * view = (KotoDiscView*) disc_view_ptr;
if (KOTO_IS_DISC_VIEW(view)) {
koto_disc_view_set_disc_label_visible(view, show_disc_labels);
}
}
}
KotoAlbumView * koto_album_view_new(KotoAlbum * album) {
return g_object_new(KOTO_TYPE_ALBUM_VIEW, "album", album, NULL);
}

View file

@ -30,16 +30,28 @@ G_DECLARE_FINAL_TYPE(KotoAlbumView, koto_album_view, KOTO, ALBUM_VIEW, GObject)
KotoAlbumView* koto_album_view_new(KotoAlbum * album);
GtkWidget * koto_album_view_get_main(KotoAlbumView * self);
void koto_album_view_add_track_to_listbox(
KotoAlbum * self,
void koto_album_view_add_track(
KotoAlbumView * self,
KotoTrack * track
);
void koto_album_view_handle_track_added(
KotoAlbum * album,
KotoTrack * track,
gpointer user_data
);
void koto_album_view_set_album(
KotoAlbumView * self,
KotoAlbum * album
);
int koto_album_view_sort_discs(
GtkListBoxRow * track1,
GtkListBoxRow * track2,
gpointer user_data
);
void koto_album_view_toggle_album_playback(
GtkGestureClick * gesture,
int n_press,
@ -48,10 +60,6 @@ void koto_album_view_toggle_album_playback(
gpointer data
);
int koto_album_view_sort_discs(
GtkListBoxRow * track1,
GtkListBoxRow * track2,
gpointer user_data
);
void koto_album_view_update_disc_labels(KotoAlbumView * self);
G_END_DECLS

View file

@ -31,12 +31,9 @@ struct _KotoArtistView {
KotoArtist * artist;
GtkWidget * scrolled_window;
GtkWidget * content;
GtkWidget * favorites_list;
GtkWidget * album_list;
GHashTable * albums_to_component;
gboolean constructed;
};
G_DEFINE_TYPE(KotoArtistView, koto_artist_view, G_TYPE_OBJECT);
@ -113,7 +110,7 @@ static void koto_artist_view_set_property(
switch (prop_id) {
case PROP_ARTIST:
koto_artist_view_add_artist(self, (KotoArtist*) g_value_get_object(val));
koto_artist_view_set_artist(self, (KotoArtist*) g_value_get_object(val));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
@ -123,13 +120,12 @@ static void koto_artist_view_set_property(
static void koto_artist_view_init(KotoArtistView * self) {
self->artist = NULL;
self->constructed = FALSE;
self->albums_to_component = g_hash_table_new(g_str_hash, g_str_equal);
}
static void koto_artist_view_constructed(GObject * obj) {
KotoArtistView * self = KOTO_ARTIST_VIEW(obj);
self->albums_to_component = g_hash_table_new(g_str_hash, g_str_equal);
self->scrolled_window = gtk_scrolled_window_new(); // Create our scrolled window
self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); // Create our content as a GtkBox
@ -139,77 +135,119 @@ static void koto_artist_view_constructed(GObject * obj) {
gtk_widget_add_css_class(GTK_WIDGET(self->scrolled_window), "artist-view");
gtk_widget_add_css_class(GTK_WIDGET(self->content), "artist-view-content");
self->favorites_list = gtk_flow_box_new(); // Create our favorites list
gtk_flow_box_set_activate_on_single_click(GTK_FLOW_BOX(self->favorites_list), TRUE);
gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->favorites_list), GTK_SELECTION_NONE);
gtk_flow_box_set_min_children_per_line(GTK_FLOW_BOX(self->favorites_list), 6);
gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(self->favorites_list), 6);
gtk_widget_add_css_class(GTK_WIDGET(self->favorites_list), "album-strip");
gtk_widget_set_halign(self->favorites_list, GTK_ALIGN_START);
self->album_list = gtk_flow_box_new(); // Create our list of our albums
//gtk_flow_box_set_activate_on_single_click(GTK_FLOW_BOX(self->album_list), FALSE);
gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->album_list), GTK_SELECTION_NONE);
gtk_widget_add_css_class(self->album_list, "album-list");
gtk_box_prepend(GTK_BOX(self->content), self->favorites_list); // Add the strip
gtk_box_append(GTK_BOX(self->content), self->album_list); // Add the list
gtk_widget_set_hexpand(GTK_WIDGET(self->favorites_list), TRUE);
gtk_widget_set_hexpand(GTK_WIDGET(self->album_list), TRUE);
G_OBJECT_CLASS(koto_artist_view_parent_class)->constructed(obj);
self->constructed = TRUE;
}
void koto_artist_view_add_album(
KotoArtistView * self,
KotoAlbum * album
) {
gchar * album_art = koto_album_get_album_art(album); // Get the art for the album
if (!KOTO_IS_ARTIST_VIEW(self)) {
return;
}
GtkWidget * art_image = koto_utils_create_image_from_filepath(album_art, "audio-x-generic-symbolic", 220, 220);
if (!KOTO_IS_ALBUM(album)) {
return;
}
gtk_widget_set_halign(art_image, GTK_ALIGN_START); // Align to start
gtk_flow_box_insert(GTK_FLOW_BOX(self->favorites_list), art_image, -1); // Append the album art
gchar * album_uuid = koto_album_get_uuid(album); // Get the album UUID
if (g_hash_table_contains(self->albums_to_component, album_uuid)) { // Already added this album
return;
}
KotoAlbumView * album_view = koto_album_view_new(album); // Create our new album view
GtkWidget * album_view_main = koto_album_view_get_main(album_view);
gtk_flow_box_insert(GTK_FLOW_BOX(self->album_list), album_view_main, -1); // Append the album view to the album list
g_hash_table_replace(self->albums_to_component, album_uuid, album_view_main);
}
void koto_artist_view_add_artist(
void koto_artist_view_handle_album_added(
KotoArtist * artist,
KotoAlbum * album,
gpointer user_data
) {
KotoArtistView * self = user_data;
if (!KOTO_IS_ARTIST_VIEW(self)) {
return;
}
if (!KOTO_IS_ARTIST(artist)) {
return;
}
if (!KOTO_IS_ALBUM(album)) {
return;
}
if (g_strcmp0(koto_artist_get_uuid(self->artist), koto_artist_get_uuid(artist)) != 0) { // Not the same artist
return;
}
koto_artist_view_add_album(self, album); // Add the album if necessary
}
void koto_artist_view_handle_album_removed(
KotoArtist * artist,
gchar * album_uuid,
gpointer user_data
) {
KotoArtistView * self = user_data;
if (!KOTO_IS_ARTIST_VIEW(self)) {
return;
}
if (g_strcmp0(koto_artist_get_uuid(self->artist), koto_artist_get_uuid(artist)) != 0) { // Not the same artist
return;
}
GtkWidget * album_view = g_hash_table_lookup(self->albums_to_component, album_uuid); // Get the album view if it exists
if (!GTK_IS_WIDGET(album_view)) { // Not a widget
return;
}
gtk_flow_box_remove(GTK_FLOW_BOX(self->album_list), album_view); // Remove the album
g_hash_table_remove(self->albums_to_component, album_uuid); // Remove the album from our hash table
}
void koto_artist_view_set_artist(
KotoArtistView * self,
KotoArtist * artist
) {
if (artist == NULL) {
if (!KOTO_IS_ARTIST_VIEW(self)) { // Not an Artist view
return;
}
if (!KOTO_IS_ARTIST(artist)) {
return;
}
self->artist = artist;
if (!self->constructed) {
return;
}
GList * albums = koto_artist_get_albums(self->artist); // Get the albums
GList * a;
for (a = albums; a != NULL; a = a->next) {
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, (gchar*) a->data);
if (album != NULL) {
koto_artist_view_add_album(self, album); // Add the album
}
}
g_signal_connect(artist, "album-added", G_CALLBACK(koto_artist_view_handle_album_added), self);
g_signal_connect(artist, "album-removed", G_CALLBACK(koto_artist_view_handle_album_removed), self);
}
GtkWidget * koto_artist_view_get_main(KotoArtistView * self) {
return self->scrolled_window;
}
KotoArtistView * koto_artist_view_new() {
return g_object_new(KOTO_TYPE_ARTIST_VIEW, NULL);
KotoArtistView * koto_artist_view_new(KotoArtist * artist) {
return g_object_new(
KOTO_TYPE_ARTIST_VIEW,
"artist",
artist,
NULL
);
}

View file

@ -27,13 +27,26 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(KotoArtistView, koto_artist_view, KOTO, ARTIST_VIEW, GObject)
KotoArtistView* koto_artist_view_new();
KotoArtistView* koto_artist_view_new(KotoArtist * artist);
void koto_artist_view_add_album(
KotoArtistView * self,
KotoAlbum * album
);
void koto_artist_view_add_artist(
void koto_artist_view_handle_album_added(
KotoArtist * artist,
KotoAlbum * album,
gpointer user_data
);
void koto_artist_view_handle_album_removed(
KotoArtist * artist,
gchar * album_uuid,
gpointer user_data
);
void koto_artist_view_set_artist(
KotoArtistView * self,
KotoArtist * artist
);

View file

@ -18,6 +18,7 @@
#include <gtk-4.0/gtk/gtk.h>
#include "../../components/koto-action-bar.h"
#include "../../db/cartographer.h"
#include "../../indexer/track-helpers.h"
#include "../../indexer/structs.h"
#include "../../koto-track-item.h"
#include "../../koto-utils.h"
@ -33,7 +34,9 @@ struct _KotoDiscView {
GtkWidget * label;
GtkWidget * list;
guint * disc_number;
GHashTable * tracks_to_items;
guint disc_number;
};
G_DEFINE_TYPE(KotoDiscView, koto_disc_view, GTK_TYPE_BOX);
@ -48,6 +51,7 @@ enum {
static GParamSpec * props[N_PROPERTIES] = {
NULL,
};
static void koto_disc_view_get_property(
GObject * obj,
guint prop_id,
@ -133,6 +137,8 @@ static void koto_disc_view_set_property(
}
static void koto_disc_view_init(KotoDiscView * self) {
self->tracks_to_items = g_hash_table_new(g_str_hash, g_str_equal);
gtk_widget_add_css_class(GTK_WIDGET(self), "disc-view");
gtk_widget_set_can_focus(GTK_WIDGET(self), FALSE);
self->header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
@ -153,6 +159,7 @@ static void koto_disc_view_init(KotoDiscView * self) {
self->list = gtk_list_box_new(); // Create our list of our tracks
gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(self->list), FALSE);
gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->list), GTK_SELECTION_MULTIPLE);
gtk_list_box_set_sort_func(GTK_LIST_BOX(self->list), koto_disc_view_sort_list_box_rows, self, NULL);
gtk_widget_add_css_class(self->list, "track-list");
gtk_widget_set_can_focus(self->list, FALSE);
gtk_widget_set_focusable(self->list, FALSE);
@ -162,24 +169,37 @@ static void koto_disc_view_init(KotoDiscView * self) {
g_signal_connect(self->list, "selected-rows-changed", G_CALLBACK(koto_disc_view_handle_selected_rows_changed), self);
}
void koto_disc_view_list_tracks(
gpointer data,
gpointer selfptr
void koto_disc_view_add_track(
KotoDiscView * self,
KotoTrack * track
) {
KotoDiscView * self = (KotoDiscView*) selfptr;
KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) data); // Get the track by its UUID
if (!KOTO_IS_DISC_VIEW(self)) { // If this is not a disc view
return;
}
guint * disc_number;
if (!KOTO_IS_TRACK(track)) { // If this is not a track
return;
}
g_object_get(track, "cd", &disc_number, NULL); // get the disc number
if (self->disc_number != koto_track_get_disc_number(track)) { // Not the same disc
return;
}
if (GPOINTER_TO_UINT(self->disc_number) != GPOINTER_TO_UINT(disc_number)) { // Track does not belong to this CD
gchar * track_uuid = koto_track_get_uuid(track); // Get the track UUID
if (g_hash_table_contains(self->tracks_to_items, track_uuid)) { // Already have added this track
return;
}
KotoTrackItem * track_item = koto_track_item_new(track); // Create our new track item
if (!KOTO_IS_TRACK_ITEM(track_item)) { // Failed to create a track item for this track
g_warning("Failed to build track item for track with UUID %s", track_uuid);
return;
}
gtk_list_box_append(GTK_LIST_BOX(self->list), GTK_WIDGET(track_item)); // Add to our tracks list box
g_hash_table_replace(self->tracks_to_items, track_uuid, track_item); // Add to our hash table so we know we have added this
}
void koto_disc_view_handle_selected_rows_changed(
@ -215,6 +235,28 @@ void koto_disc_view_handle_selected_rows_changed(
koto_action_bar_toggle_reveal(action_bar, TRUE); // Show the action bar
}
void koto_disc_view_remove_track(
KotoDiscView * self,
gchar * track_uuid
) {
if (!KOTO_IS_DISC_VIEW(self)) { // If this is not a disc view
return;
}
if (!koto_utils_is_string_valid(track_uuid)) { // If our UUID is not a valid
return;
}
KotoTrackItem * track_item = g_hash_table_lookup(self->tracks_to_items, track_uuid); // Get the track item
if (!KOTO_IS_TRACK_ITEM(track_item)) { // Track item not valid
return;
}
gtk_list_box_remove(GTK_LIST_BOX(self->list), GTK_WIDGET(track_item)); // Remove the item from the listbox
g_hash_table_remove(self->tracks_to_items, track_uuid); // Remove the track from the hashtable
}
void koto_disc_view_set_album(
KotoDiscView * self,
KotoAlbum * album
@ -228,8 +270,6 @@ void koto_disc_view_set_album(
}
self->album = album;
g_list_foreach(koto_album_get_tracks(self->album), koto_disc_view_list_tracks, self);
}
void koto_disc_view_set_disc_number(
@ -240,7 +280,7 @@ void koto_disc_view_set_disc_number(
return;
}
self->disc_number = GUINT_TO_POINTER(disc_number);
self->disc_number = disc_number;
gchar * disc_label = g_strdup_printf("Disc %u", disc_number);
@ -256,9 +296,19 @@ void koto_disc_view_set_disc_label_visible(
(visible) ? gtk_widget_show(self->header) : gtk_widget_hide(self->header);
}
gint koto_disc_view_sort_list_box_rows(
GtkListBoxRow * row1,
GtkListBoxRow * row2,
gpointer user_data
) {
KotoTrackItem * track1_item = KOTO_TRACK_ITEM(gtk_list_box_row_get_child(row1));
KotoTrackItem * track2_item = KOTO_TRACK_ITEM(gtk_list_box_row_get_child(row2));
return koto_track_helpers_sort_track_items(track1_item, track2_item, user_data);
}
KotoDiscView * koto_disc_view_new(
KotoAlbum * album,
guint * disc_number
guint disc_number
) {
return g_object_new(
KOTO_TYPE_DISC_VIEW,

View file

@ -27,17 +27,21 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(KotoDiscView, koto_disc_view, KOTO, DISC_VIEW, GtkBox)
KotoDiscView* koto_disc_view_new(KotoAlbum * album, guint * disc);
KotoDiscView* koto_disc_view_new(
KotoAlbum * album,
guint disc
);
void koto_disc_view_add_track(
KotoDiscView * self,
KotoTrack * track
);
void koto_disc_view_handle_selected_rows_changed(
GtkListBox * box,
gpointer user_data
);
void koto_disc_view_list_tracks(
gpointer data,
gpointer selfptr
);
void koto_disc_view_set_album(
KotoDiscView * self,
KotoAlbum * album
@ -53,4 +57,10 @@ void koto_disc_view_set_disc_number(
guint disc_number
);
gint koto_disc_view_sort_list_box_rows(
GtkListBoxRow * row1,
GtkListBoxRow * row2,
gpointer user_data
);
G_END_DECLS

View file

@ -31,6 +31,7 @@ struct _KotoPageMusicLocal {
GtkWidget * scrolled_window;
GtkWidget * artist_list;
GtkWidget * stack;
GHashTable * artist_name_to_buttons;
gboolean constructed;
};
@ -54,6 +55,7 @@ static void koto_page_music_local_class_init(KotoPageMusicLocalClass * c) {
static void koto_page_music_local_init(KotoPageMusicLocal * self) {
self->constructed = FALSE;
self->artist_name_to_buttons = g_hash_table_new(g_str_hash, g_str_equal);
gtk_widget_add_css_class(GTK_WIDGET(self), "page-music-local");
gtk_widget_set_hexpand(GTK_WIDGET(self), TRUE);
@ -79,6 +81,9 @@ static void koto_page_music_local_init(KotoPageMusicLocal * self) {
gtk_widget_set_hexpand(self->stack, TRUE);
gtk_widget_set_vexpand(self->stack, TRUE);
gtk_box_append(GTK_BOX(self), self->stack);
g_signal_connect(koto_maps, "artist-added", G_CALLBACK(koto_page_music_local_handle_artist_added), self);
g_signal_connect(koto_maps, "artist-removed", G_CALLBACK(koto_page_music_local_handle_artist_removed), self);
}
static void koto_page_music_local_constructed(GObject * obj) {
@ -92,17 +97,35 @@ void koto_page_music_local_add_artist(
KotoPageMusicLocal * self,
KotoArtist * artist
) {
gchar * artist_name;
if (!KOTO_IS_PAGE_MUSIC_LOCAL(self)) { // Not the local page
return;
}
if (!KOTO_IS_ARTIST(artist)) { // Not an artist
return;
}
gchar * artist_name = koto_artist_get_name(artist); // Get the artist name
if (!GTK_IS_STACK(self->stack)) {
return;
}
GtkWidget * stack_child = gtk_stack_get_child_by_name(GTK_STACK(self->stack), artist_name);
if (GTK_IS_WIDGET(stack_child)) { // Already have a page for this artist
g_free(artist_name);
return;
}
g_object_get(artist, "name", &artist_name, NULL);
KotoButton * artist_button = koto_button_new_plain(artist_name);
gtk_list_box_prepend(GTK_LIST_BOX(self->artist_list), GTK_WIDGET(artist_button));
g_hash_table_replace(self->artist_name_to_buttons, artist_name, artist_button); // Add the KotoButton for this artist to the hash table, that way we can reference and remove it when we remove the artist
KotoArtistView * artist_view = koto_artist_view_new(); // Create our new artist view
KotoArtistView * artist_view = koto_artist_view_new(artist); // Create our new artist view
koto_artist_view_add_artist(artist_view, artist); // Add the artist
gtk_stack_add_named(GTK_STACK(self->stack), koto_artist_view_get_main(artist_view), artist_name);
g_free(artist_name);
}
void koto_page_music_local_go_to_artist_by_name(
@ -153,24 +176,51 @@ void koto_page_music_local_handle_artist_click(
koto_page_music_local_go_to_artist_by_name(self, artist_name);
}
void koto_page_music_local_build_ui(KotoPageMusicLocal * self) {
if (!self->constructed) {
void koto_page_music_local_handle_artist_added(
KotoCartographer * carto,
KotoArtist * artist,
gpointer user_data
) {
(void) carto;
KotoPageMusicLocal * self = user_data;
if (!KOTO_IS_PAGE_MUSIC_LOCAL(self)) { // Not the page
return;
}
GHashTableIter artist_list_iter;
gpointer artist_key;
gpointer artist_data;
if (!KOTO_IS_ARTIST(artist)) { // Not an artist
return;
}
GHashTable * artists = koto_cartographer_get_artists(koto_maps); // Get all the artists
if (koto_artist_get_lib_type(artist) != KOTO_LIBRARY_TYPE_MUSIC) { // Not in our music library
return;
}
g_hash_table_iter_init(&artist_list_iter, artists);
while (g_hash_table_iter_next(&artist_list_iter, &artist_key, &artist_data)) { // For each of the music artists
KotoArtist * artist = artist_data; // Cast our artist_data as an artist
koto_page_music_local_add_artist(self, artist); // Add the artist if needed
}
if (KOTO_IS_ARTIST(artist)) { // Is an artist
koto_page_music_local_add_artist(self, artist);
}
void koto_page_music_local_handle_artist_removed(
KotoCartographer * carto,
gchar * artist_uuid,
gchar * artist_name,
gpointer user_data
) {
(void) carto;
(void) artist_uuid;
KotoPageMusicLocal * self = user_data;
GtkWidget * existing_artist_page = gtk_stack_get_child_by_name(GTK_STACK(self->stack), artist_name);
// TODO: Navigate away from artist if we are currently looking at it
if (GTK_IS_WIDGET(existing_artist_page)) { // Page exists
gtk_stack_remove(GTK_STACK(self->stack), existing_artist_page); // Remove the artist page
}
KotoButton * btn = g_hash_table_lookup(self->artist_name_to_buttons, artist_name);
if (KOTO_IS_BUTTON(btn)) { // If we have a button for this artist
gtk_list_box_remove(GTK_LIST_BOX(self->artist_list), GTK_WIDGET(btn)); // Remove the button
}
}

View file

@ -40,6 +40,19 @@ void koto_page_music_local_handle_artist_click(
gpointer data
);
void koto_page_music_local_handle_artist_added(
KotoCartographer * carto,
KotoArtist * artist,
gpointer user_data
);
void koto_page_music_local_handle_artist_removed(
KotoCartographer * carto,
gchar * artist_uuid,
gchar * artist_name,
gpointer user_data
);
void koto_page_music_local_go_to_artist_by_name(
KotoPageMusicLocal * self,
gchar * artist_name
@ -50,8 +63,6 @@ void koto_page_music_local_go_to_artist_by_uuid(
gchar * artist_uuid
);
void koto_page_music_local_build_ui(KotoPageMusicLocal * self);
int koto_page_music_local_sort_artists(
GtkListBoxRow * artist1,
GtkListBoxRow * artist2,

View file

@ -269,7 +269,7 @@ void koto_playlist_page_bind_track_item(
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid);
if (KOTO_IS_ALBUM(album)) {
gtk_label_set_label(GTK_LABEL(track_album_label), koto_album_get_album_name(album)); // Get the name of the album and set it to the label
gtk_label_set_label(GTK_LABEL(track_album_label), koto_album_get_name(album)); // Get the name of the album and set it to the label
}
KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid);

View file

@ -200,6 +200,7 @@ static void koto_playback_engine_class_init(KotoPlaybackEngineClass * c) {
static void koto_playback_engine_init(KotoPlaybackEngine * self) {
self->current_track = NULL;
current_playlist = koto_current_playlist_new(); // Ensure our global current playlist is set
self->player = gst_pipeline_new("player");
self->playbin = gst_element_factory_make("playbin", NULL);
@ -480,7 +481,7 @@ void koto_playback_engine_set_track_by_uuid(
gchar * track_uuid,
gboolean playing_specific_track
) {
if (track_uuid == NULL) {
if (!koto_utils_is_string_valid(track_uuid)) { // If this is not a valid track uuid string
return;
}
@ -492,9 +493,7 @@ void koto_playback_engine_set_track_by_uuid(
self->current_track = track;
gchar * track_file_path = NULL;
g_object_get(track, "path", &track_file_path, NULL); // Get the path to the track
gchar * track_file_path = koto_track_get_path(self->current_track); // Get the most optimal path for the track given the libraries it is in
koto_playback_engine_stop(self); // Stop current track

View file

@ -18,8 +18,8 @@
#include <glib-2.0/glib.h>
#include <glib-2.0/gio/gio.h>
#include <magic.h>
#include <sqlite3.h>
#include "../db/cartographer.h"
#include "../db/db.h"
#include "../koto-utils.h"
#include "playlist.h"
@ -372,17 +372,7 @@ void koto_playlist_commit(KotoPlaylist * self) {
self->art_path
);
gchar * commit_op_errmsg = NULL;
int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_op_errmsg);
if (rc != SQLITE_OK) {
g_warning("Failed to save playlist: %s", commit_op_errmsg);
} else { // Successfully saved our playlist
g_queue_foreach(self->tracks, koto_playlist_commit_tracks, self); // Iterate over each track to save it
}
g_free(commit_op);
g_free(commit_op_errmsg);
new_transaction(commit_op, "Failed to save playlist", FALSE);
}
void koto_playlist_commit_tracks(
@ -660,7 +650,7 @@ gint koto_playlist_model_sort_by_track(
return 0; // Just consider them as equal
}
return g_utf8_collate(koto_album_get_album_name(first_album), koto_album_get_album_name(second_album));
return g_utf8_collate(koto_album_get_name(first_album), koto_album_get_name(second_album));
}
if (model == KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST) { // Sort by artist name

View file

@ -16,13 +16,6 @@
& > stack {
& > .artist-view {
& > viewport > .artist-view-content {
& > .album-strip {
margin-bottom: $itempadding;
& > flowboxchild {
margin-right: $itempadding;
}
}
& > .album-list {
& > flowboxchild > .album-view {
& > overlay {