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:
parent
8d823dbbec
commit
44e4564f1c
38 changed files with 2408 additions and 1072 deletions
14
.vscode/settings.json
vendored
14
.vscode/settings.json
vendored
|
@ -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
2
.vscode/tasks.json
vendored
|
@ -51,7 +51,7 @@
|
|||
"args": [
|
||||
"compile",
|
||||
"-C",
|
||||
"builddir"
|
||||
"builddir",
|
||||
],
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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(
|
||||
|
|
52
src/db/db.c
52
src/db/db.c
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
137
src/db/loaders.c
137
src/db/loaders.c
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
511
src/indexer/library.c
Normal 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");
|
||||
}
|
||||
}
|
|
@ -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
149
src/indexer/track-helpers.c
Normal 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);
|
||||
}
|
43
src/indexer/track-helpers.h
Normal file
43
src/indexer/track-helpers.h
Normal 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
|
||||
);
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
26
src/main.c
26
src/main.c
|
@ -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);
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue