From 35762ca23363fe63ea79e745870d4255e0ac5b5b Mon Sep 17 00:00:00 2001 From: Joshua Strobl Date: Tue, 9 Mar 2021 11:45:44 +0200 Subject: [PATCH] Implemented clearer database functions and response codes so we know when to do library indexing. Implement reading of artists, albums, and tracks. Fixed a bug related to unnecessary file name parsing. Fix SIGSEGV related to file name parsing and koto_utils_get_filename_without_extension. Implemented an unquote string function for use when reading database entries. Try to fix some weird random rendering issues. Changed where we are doing some KotoPageMusicLocal widget construction. --- src/db/db.c | 75 ++++++++++++---- src/db/db.h | 14 ++- src/indexer/album.c | 5 +- src/indexer/artist.c | 41 ++++----- src/indexer/file-indexer.c | 159 +++++++++++++++++++++++++++++++--- src/indexer/file.c | 76 ++++++++-------- src/indexer/structs.h | 6 ++ src/koto-utils.c | 18 +++- src/koto-utils.h | 1 + src/koto-window.c | 18 ++-- src/main.c | 7 +- src/pages/music/music-local.c | 93 ++++++++------------ 12 files changed, 346 insertions(+), 167 deletions(-) diff --git a/src/db/db.c b/src/db/db.c index f1341aa..e8c75a4 100644 --- a/src/db/db.c +++ b/src/db/db.c @@ -22,13 +22,19 @@ #include #include "db.h" +int KOTO_DB_SUCCESS = 0; +int KOTO_DB_NEW = 1; +int KOTO_DB_FAIL = 2; + sqlite3 *koto_db = NULL; +gchar *db_filepath = NULL; +gboolean created_new_db = FALSE; void close_db() { sqlite3_close(koto_db); } -void create_db_tables() { +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);"; @@ -39,9 +45,11 @@ void create_db_tables() { 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; } -void enable_foreign_keys() { +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); @@ -50,24 +58,55 @@ void enable_foreign_keys() { } g_free(enable_foreign_keys_err); + return (rc == SQLITE_OK) ? KOTO_DB_SUCCESS : KOTO_DB_FAIL; } -void open_db() { - const gchar *data_home = g_get_user_data_dir(); - gchar *data_dir = g_build_path(G_DIR_SEPARATOR_S, data_home, "com.github.joshstrobl.koto", NULL); - mkdir(data_home, 0755); - mkdir(data_dir, 0755); - chown(data_dir, getuid(), getgid()); - - gchar *db_path = g_build_filename(data_dir, "db", NULL); // Build out our path using XDG_DATA_HOME (e.g. .local/share/) + our namespace + db as the file name - int rc = sqlite3_open(db_path, &koto_db); - - if (rc) { - g_critical("Failed to open or create database: %s", sqlite3_errmsg(koto_db)); - return; +gchar* get_db_path() { + if (db_filepath == NULL) { + const gchar *data_home = g_get_user_data_dir(); + gchar *data_dir = g_build_path(G_DIR_SEPARATOR_S, data_home, "com.github.joshstrobl.koto", NULL); + db_filepath = g_build_filename(g_strdup(data_dir), "db", NULL); // Build out our path using XDG_DATA_HOME (e.g. .local/share/) + our namespace + db as the file name + g_free(data_dir); } - enable_foreign_keys(); // Enable FOREIGN KEY support - - create_db_tables(); // Attempt to create our database tables + return db_filepath; +} + +int have_existing_db() { + struct stat db_stat; + int success = stat(get_db_path(), &db_stat); + + return ((success == 0) && S_ISREG(db_stat.st_mode)) ? 0 : 1; +} + +int open_db() { + int ret = KOTO_DB_SUCCESS; // Default to last return being SUCCESS + + if (have_existing_db() == 1) { // If we do not have an existing DB + const gchar *data_home = g_get_user_data_dir(); + const gchar *data_dir = g_path_get_dirname(db_filepath); + mkdir(data_home, 0755); + mkdir(data_dir, 0755); + chown(data_dir, getuid(), getgid()); + ret = KOTO_DB_NEW; + } + + if (sqlite3_open(db_filepath, &koto_db) != KOTO_DB_SUCCESS) { // If we failed to open the database file + g_critical("Failed to open or create database: %s", sqlite3_errmsg(koto_db)); + return KOTO_DB_FAIL; + } + + if (enable_foreign_keys() != KOTO_DB_SUCCESS) { // If we failed to enable foreign keys + return KOTO_DB_FAIL; + } + + if (create_db_tables() != KOTO_DB_SUCCESS) {// Failed to create our database tables + return KOTO_DB_FAIL; + } + + if (ret == KOTO_DB_NEW) { + created_new_db = TRUE; + } + + return ret; } diff --git a/src/db/db.h b/src/db/db.h index 30ed2af..dd8d83f 100644 --- a/src/db/db.h +++ b/src/db/db.h @@ -15,9 +15,17 @@ * limitations under the License. */ +#include #include -void close_db(); -void enable_foreign_keys(); -void open_db(); +extern int KOTO_DB_SUCCESS; +extern int KOTO_DB_NEW; +extern int KOTO_DB_FAIL; +extern gboolean created_new_db; +void close_db(); +int create_db_tables(); +gchar* get_db_path(); +int enable_foreign_keys(); +int have_existing_db(); +int open_db(); diff --git a/src/indexer/album.c b/src/indexer/album.c index c30c3ad..02a72c9 100644 --- a/src/indexer/album.c +++ b/src/indexer/album.c @@ -156,8 +156,6 @@ void koto_indexed_album_commit(KotoIndexedAlbum *self) { self->art_path ); - g_debug("INSERT query for album: %s", commit_op); - gchar *commit_op_errmsg = NULL; int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_op_errmsg); @@ -372,7 +370,6 @@ void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album self->art_path = g_strdup(album_art); - g_message("Set album art to %s", album_art); self->has_album_art = TRUE; } @@ -478,7 +475,7 @@ KotoIndexedAlbum* koto_indexed_album_new(KotoIndexedArtist *artist, const gchar KotoIndexedAlbum* koto_indexed_album_new_with_uuid(KotoIndexedArtist *artist, const gchar *uuid) { return g_object_new(KOTO_TYPE_INDEXED_ALBUM, "artist", artist, - "uuid", uuid, + "uuid", g_strdup(uuid), "do-initial-index", FALSE, NULL ); diff --git a/src/indexer/artist.c b/src/indexer/artist.c index 41cd320..a3b0133 100644 --- a/src/indexer/artist.c +++ b/src/indexer/artist.c @@ -95,8 +95,6 @@ void koto_indexed_artist_commit(KotoIndexedArtist *self) { self->artist_name ); - g_debug("INSERT query for artist: %s", commit_op); - gchar *commit_opt_errmsg = NULL; int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_opt_errmsg); @@ -161,20 +159,20 @@ void koto_indexed_artist_add_album(KotoIndexedArtist *self, KotoIndexedAlbum *al self->albums = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable } - gchar *album_name; - g_object_get(album, "name", &album_name, NULL); + gchar *album_uuid; + g_object_get(album, "uuid", &album_uuid, NULL); - if (album_name == NULL) { - g_free(album_name); + if (album_uuid == NULL) { + g_free(album_uuid); return; } - if (g_hash_table_contains(self->albums, album_name)) { // If we have this album already - g_free(album_name); + if (g_hash_table_contains(self->albums, album_uuid)) { // If we have this album already + g_free(album_uuid); return; } - g_hash_table_insert(self->albums, album_name, album); // Add the album + g_hash_table_insert(self->albums, album_uuid, album); // Add the album } GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self) { @@ -185,6 +183,14 @@ GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self) { return g_hash_table_get_values(self->albums); } +KotoIndexedAlbum* koto_indexed_artist_get_album(KotoIndexedArtist *self, gchar *album_uuid) { + if (self->albums == NULL) { + return NULL; + } + + return g_hash_table_lookup(self->albums, album_uuid); +} + void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album) { if (album == NULL) { // No album defined return; @@ -194,19 +200,10 @@ void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum self->albums = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable } - gchar *album_name; - g_object_get(album, "name", &album_name, NULL); + gchar *album_uuid; + g_object_get(album, "uuid", &album_uuid, NULL); - g_hash_table_remove(self->albums, album_name); -} - -void koto_indexed_artist_remove_album_by_name(KotoIndexedArtist *self, gchar *album_name) { - if (album_name == NULL) { - return; - } - - KotoIndexedAlbum *album = g_hash_table_lookup(self->albums, album_name); // Get the album - koto_indexed_artist_remove_album(self, album); + g_hash_table_remove(self->albums, album_uuid); } void koto_indexed_artist_update_path(KotoIndexedArtist *self, const gchar *new_path) { @@ -249,7 +246,7 @@ KotoIndexedArtist* koto_indexed_artist_new(const gchar *path) { KotoIndexedArtist* koto_indexed_artist_new_with_uuid(const gchar *uuid) { return g_object_new(KOTO_TYPE_INDEXED_ARTIST, - "uuid", uuid, + "uuid", g_strdup(uuid), NULL ); } diff --git a/src/indexer/file-indexer.c b/src/indexer/file-indexer.c index 949c804..5e449ef 100644 --- a/src/indexer/file-indexer.c +++ b/src/indexer/file-indexer.c @@ -20,7 +20,11 @@ #include #include #include +#include "../db/db.h" #include "structs.h" +#include "../koto-utils.h" + +extern sqlite3 *koto_db; struct _KotoIndexedLibrary { GObject parent_instance; @@ -79,7 +83,7 @@ void koto_indexed_library_add_artist(KotoIndexedLibrary *self, KotoIndexedArtist return; } - g_hash_table_insert(self->music_artists, artist_name, artist); // Add the artist + g_hash_table_insert(self->music_artists, artist_name, artist); // Add the artist by its name (this needs to be done so we can get the artist when doing the depth of 2 indexing for the album) } KotoIndexedArtist* koto_indexed_library_get_artist(KotoIndexedLibrary *self, gchar *artist_name) { @@ -131,8 +135,7 @@ static void koto_indexed_library_set_property(GObject *obj, guint prop_id, const switch (prop_id) { case PROP_PATH: - self->path = g_strdup(g_value_get_string(val)); - start_indexing(self); + koto_indexed_library_set_path(self, g_strdup(g_value_get_string(val))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); @@ -140,6 +143,140 @@ static void koto_indexed_library_set_property(GObject *obj, guint prop_id, const } } +void koto_indexed_library_set_path(KotoIndexedLibrary *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(self); // Read from the database + } +} + +int process_artists(void *data, int num_columns, char **fields, char **column_names) { + (void) num_columns; (void) column_names; // Don't need these + + KotoIndexedLibrary *self = (KotoIndexedLibrary*) data; + + 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 + + KotoIndexedArtist *artist = koto_indexed_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); + + koto_indexed_library_add_artist(self, artist); // Add the artist + + 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 + if (albums_rc != SQLITE_OK) { // Failed to get our albums + g_critical("Failed to read our albums: %s", sqlite3_errmsg(koto_db)); + return 1; + } + + g_free(artist_uuid); + g_free(artist_path); + g_free(artist_name); + + return 0; +} + +int process_albums(void *data, int num_columns, char **fields, char **column_names) { + (void) num_columns; (void) column_names; // Don't need these + + KotoIndexedArtist *artist = (KotoIndexedArtist*) 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; + + KotoIndexedAlbum *album = koto_indexed_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); + + koto_indexed_artist_add_album(artist, album); // 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; + } + + g_free(album_uuid); + g_free(path); + g_free(artist_uuid); + g_free(album_name); + + if (album_art != NULL) { + g_free(album_art); + } + + return 0; +} + +int process_tracks(void *data, int num_columns, char **fields, char **column_names) { + (void) num_columns; (void) column_names; // Don't need these + + KotoIndexedAlbum *album = (KotoIndexedAlbum*) 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[6], NULL, 10); + guint *position = (guint*) g_ascii_strtoull(fields[7], NULL, 10); + + KotoIndexedFile *file = koto_indexed_file_new_with_uuid(track_uuid); // Create our file + g_object_set(file, + "artist-uuid", artist_uuid, + "album-uuid", album_uuid, + "path", path, + "file-name", file_name, + "parsed-name", name, + "cd", disc_num, + "position", position, + NULL); + + koto_indexed_album_add_file(album, file); // Add the file + + g_free(track_uuid); + g_free(path); + g_free(artist_uuid); + g_free(album_uuid); + g_free(file_name); + g_free(name); + + return 0; +} + +void read_from_db(KotoIndexedLibrary *self) { + int artists_rc = sqlite3_exec(koto_db, "SELECT * FROM artists", process_artists, self, NULL); // Process our artists + if (artists_rc != SQLITE_OK) { // Failed to get our artists + g_critical("Failed to read our artists: %s", sqlite3_errmsg(koto_db)); + return; + } + + g_hash_table_foreach(self->music_artists, output_artists, NULL); +} + void start_indexing(KotoIndexedLibrary *self) { struct stat library_stat; int success = stat(self->path, &library_stat); @@ -169,6 +306,13 @@ void start_indexing(KotoIndexedLibrary *self) { g_hash_table_foreach(self->music_artists, output_artists, NULL); } +KotoIndexedLibrary* koto_indexed_library_new(const gchar *path) { + return g_object_new(KOTO_TYPE_INDEXED_LIBRARY, + "path", path, + NULL + ); +} + void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth) { depth++; @@ -228,8 +372,8 @@ void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data) { KotoIndexedArtist *artist = (KotoIndexedArtist*) artist_ptr; gchar *artist_name; g_object_get(artist, "name", &artist_name, NULL); - g_debug("Artist: %s", artist_name); + GList *albums = koto_indexed_artist_get_albums(artist); // Get the albums for this artist if (albums != NULL) { @@ -267,10 +411,3 @@ void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data) { } } } - -KotoIndexedLibrary* koto_indexed_library_new(const gchar *path) { - return g_object_new(KOTO_TYPE_INDEXED_LIBRARY, - "path", path, - NULL - ); -} diff --git a/src/indexer/file.c b/src/indexer/file.c index 4b1bba2..d1224af 100644 --- a/src/indexer/file.c +++ b/src/indexer/file.c @@ -49,9 +49,9 @@ enum { PROP_ALBUM_UUID, PROP_UUID, PROP_DO_INITIAL_INDEX, - PROP_PATH, PROP_ARTIST, PROP_ALBUM, + PROP_PATH, PROP_FILE_NAME, PROP_PARSED_NAME, PROP_CD, @@ -182,15 +182,15 @@ static void koto_indexed_file_get_property(GObject *obj, guint prop_id, GValue * 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: g_value_set_string(val, self->artist); break; case PROP_ALBUM: g_value_set_string(val, self->album); 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; @@ -228,15 +228,15 @@ static void koto_indexed_file_set_property(GObject *obj, guint prop_id, const GV case PROP_DO_INITIAL_INDEX: self->do_initial_index = g_value_get_boolean(val); break; - case PROP_PATH: - koto_indexed_file_update_path(self, g_value_get_string(val)); // Update the path - break; case PROP_ARTIST: self->artist = g_strdup(g_value_get_string(val)); break; case PROP_ALBUM: self->album = g_strdup(g_value_get_string(val)); break; + case PROP_PATH: + koto_indexed_file_update_path(self, g_value_get_string(val)); // Update the path + break; case PROP_FILE_NAME: koto_indexed_file_set_file_name(self, g_strdup(g_value_get_string(val))); // Update the file name break; @@ -279,8 +279,6 @@ void koto_indexed_file_commit(KotoIndexedFile *self) { GPOINTER_TO_INT((int*) self->position) ); - g_debug("INSERT query for file: %s", commit_op); - gchar *commit_op_errmsg = NULL; int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_op_errmsg); @@ -299,34 +297,16 @@ void koto_indexed_file_parse_name(KotoIndexedFile *self) { gchar **split = g_strsplit(copied_file_name, self->artist, -1); // Split whenever we encounter the artist copied_file_name = g_strjoinv("", split); // Remove the artist g_strfreev(split); - } - if (self->artist != NULL && strcmp(self->album, "") != 0) { // If we have album set - gchar **split = g_strsplit(copied_file_name, self->album, -1); // Split whenever we encounter the album - copied_file_name = g_strjoinv("", split); // Remove the album + split = g_strsplit(copied_file_name, g_utf8_strdown(self->artist, -1), -1); // Lowercase album name and split by that + copied_file_name = g_strjoinv("", split); // Remove the artist g_strfreev(split); - - split = g_strsplit(copied_file_name, g_utf8_strdown(self->album, g_utf8_strlen(self->album, -1)), -1); // Split whenever we encounter the album lowercased - copied_file_name = g_strjoin("", split, NULL); // Remove the lowercased album - g_strfreev(split); - } - - if (g_str_match_string(copied_file_name, "-", FALSE)) { // Contains - like a heathen - gchar **split = g_strsplit(copied_file_name, " - ", -1); // Split whenever we encounter " - " - copied_file_name = g_strjoin("", split, NULL); // Remove entirely - g_strfreev(split); - - if (g_str_match_string(copied_file_name, "-", FALSE)) { // If we have any stragglers - split = g_strsplit(copied_file_name, "-", -1); // Split whenever we encounter - - copied_file_name = g_strjoin("", split, NULL); // Remove entirely - 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]+)\\s", file_without_ext, G_REGEX_JAVASCRIPT_COMPAT, 0); + 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]; @@ -347,6 +327,14 @@ void koto_indexed_file_parse_name(KotoIndexedFile *self) { 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_indexed_file_set_parsed_name(self, file_without_ext); g_free(file_without_ext); } @@ -367,7 +355,7 @@ void koto_indexed_file_set_file_name(KotoIndexedFile *self, gchar *new_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) { // Haven't acquired our information from ID3 + if (!self->acquired_metadata_from_id3 && self->do_initial_index) { // Haven't acquired our information from ID3 koto_indexed_file_parse_name(self); // Update our parsed name } } @@ -417,6 +405,8 @@ void koto_indexed_file_update_metadata(KotoIndexedFile *self) { self->artist = g_strdup(taglib_tag_artist((tag))); // Set the artist self->album = g_strdup(taglib_tag_album(tag)); // Set the album koto_indexed_file_set_position(self, (uint) taglib_tag_track(tag)); // Get the track, convert to uint and cast as a pointer + } else { + koto_indexed_file_set_file_name(self, g_path_get_basename(self->path)); // Update our file name } taglib_tag_free_strings(); // Free strings @@ -433,8 +423,10 @@ void koto_indexed_file_update_path(KotoIndexedFile *self, const gchar *new_path) } self->path = g_strdup(new_path); // Duplicate the path and set it - koto_indexed_file_update_metadata(self); // Attempt to get ID3 info - koto_indexed_file_set_file_name(self, g_path_get_basename(self->path)); // Update our file name + + if (self->do_initial_index) { + koto_indexed_file_update_metadata(self); // Attempt to get ID3 info + } g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PATH]); } @@ -443,15 +435,27 @@ KotoIndexedFile* koto_indexed_file_new(KotoIndexedAlbum *album, const gchar *pat KotoIndexedArtist *artist; gchar *artist_uuid; + gchar *artist_name; gchar *album_uuid; + gchar *album_name; g_object_get(album, "artist", &artist, NULL); // Get the artist from our Album - g_object_get(artist, "uuid", &artist_uuid, NULL); // Get the artist UUID from the artist - g_object_get(album, "uuid", &album_uuid, NULL); // Get the album UUID from the album + g_object_get(artist, + "name", &artist_name, + "uuid", &artist_uuid, // Get the artist UUID from the artist + NULL); + + g_object_get(album, + "name", &album_name, + "uuid", &album_uuid, // Get the album UUID from the album + NULL); KotoIndexedFile *file = g_object_new(KOTO_TYPE_INDEXED_FILE, "artist-uuid", artist_uuid, "album-uuid", album_uuid, + "artist", artist_name, + "album", album_name, + "do-initial-index", TRUE, "uuid", g_uuid_string_random(), "path", path, "cd", cd, @@ -464,7 +468,7 @@ KotoIndexedFile* koto_indexed_file_new(KotoIndexedAlbum *album, const gchar *pat KotoIndexedFile* koto_indexed_file_new_with_uuid(const gchar *uuid) { return g_object_new(KOTO_TYPE_INDEXED_FILE, - "uuid", uuid, + "uuid", g_strdup(uuid), NULL ); } diff --git a/src/indexer/structs.h b/src/indexer/structs.h index 1de7a2c..321d896 100644 --- a/src/indexer/structs.h +++ b/src/indexer/structs.h @@ -47,6 +47,11 @@ void koto_indexed_library_add_artist(KotoIndexedLibrary *self, KotoIndexedArtist KotoIndexedArtist* koto_indexed_library_get_artist(KotoIndexedLibrary *self, gchar* artist_name); GHashTable* koto_indexed_library_get_artists(KotoIndexedLibrary *self); void koto_indexed_library_remove_artist(KotoIndexedLibrary *self, KotoIndexedArtist *artist); +void koto_indexed_library_set_path(KotoIndexedLibrary *self, gchar *path); +int process_artists(void *data, int num_columns, char **fields, char **column_names); +int process_albums(void *data, int num_columns, char **fields, char **column_names); +int process_tracks(void *data, int num_columns, char **fields, char **column_names); +void read_from_db(KotoIndexedLibrary *self); void start_indexing(KotoIndexedLibrary *self); void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth); @@ -61,6 +66,7 @@ void koto_indexed_artist_add_album(KotoIndexedArtist *self, KotoIndexedAlbum *al void koto_indexed_artist_commit(KotoIndexedArtist *self); guint koto_indexed_artist_find_album_with_name(gconstpointer *album_data, gconstpointer *album_name_data); GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self); +KotoIndexedAlbum* koto_indexed_artist_get_album(KotoIndexedArtist *self, gchar *album_uuid); void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album); void koto_indexed_artist_remove_album_by_name(KotoIndexedArtist *self, gchar *album_name); void koto_indexed_artist_set_artist_name(KotoIndexedArtist *self, const gchar *artist_name); diff --git a/src/koto-utils.c b/src/koto-utils.c index 214abc5..3999b79 100644 --- a/src/koto-utils.c +++ b/src/koto-utils.c @@ -54,7 +54,7 @@ gchar* koto_utils_get_filename_without_extension(gchar *filename) { } else { gchar *tmp_copy = g_strdup(new_parsed_name); g_free(new_parsed_name); // Free the old - new_parsed_name = g_strdup(g_strjoin(".", tmp_copy, split[i], NULL)); // Join the two strings with a . again and duplicate it, setting it to our new_parsed_name + new_parsed_name = g_strjoin(".", tmp_copy, split[i], NULL); // Join the two strings with a . again and duplicate it, setting it to our new_parsed_name g_free(tmp_copy); // Free our temporary copy } } @@ -67,3 +67,19 @@ gchar* koto_utils_get_filename_without_extension(gchar *filename) { g_free(trimmed_file_name); return stripped_file_name; } + +gchar* koto_utils_unquote_string(gchar *s) { + gchar *new_s = NULL; + + if (g_str_has_prefix(s, "'") && g_str_has_suffix(s, "'")) { // Begins and ends with ' + new_s = g_utf8_substring(s, 1, g_utf8_strlen(s, -1)-1); // Start at 1 and end at n-1 + } else { + new_s = g_strdup(s); + } + + gchar **split_on_double_single = g_strsplit(new_s, "''", -1); // Split on instances of '' + new_s = g_strjoinv("'", split_on_double_single); // Rejoin as ' + g_strfreev(split_on_double_single); // Free our array + + return new_s; +} diff --git a/src/koto-utils.h b/src/koto-utils.h index 6e124e9..9cf7a79 100644 --- a/src/koto-utils.h +++ b/src/koto-utils.h @@ -23,5 +23,6 @@ G_BEGIN_DECLS GtkWidget* koto_utils_create_image_from_filepath(gchar *filepath, gchar *fallback_icon, guint width, guint height); gchar* koto_utils_get_filename_without_extension(gchar *filename); +gchar* koto_utils_unquote_string(gchar *s); G_END_DECLS diff --git a/src/koto-window.c b/src/koto-window.c index fe3e1a7..36b4659 100644 --- a/src/koto-window.c +++ b/src/koto-window.c @@ -73,6 +73,8 @@ static void koto_window_init (KotoWindow *self) { if (GTK_IS_STACK(self->pages)) { // Created our stack successfully gtk_stack_set_transition_type(GTK_STACK(self->pages), GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT); gtk_box_append(GTK_BOX(self->content_layout), self->pages); + gtk_widget_set_hexpand(self->pages, TRUE); + gtk_widget_set_vexpand(self->pages, TRUE); } gtk_box_prepend(GTK_BOX(self->primary_layout), self->content_layout); @@ -87,13 +89,12 @@ static void koto_window_init (KotoWindow *self) { gtk_window_set_child(GTK_WINDOW(self), self->primary_layout); #ifdef GDK_WINDOWING_X11 set_optimal_default_window_size(self); -#else - gtk_widget_set_size_request(GTK_WIDGET(self), 1200, 675); #endif gtk_window_set_title(GTK_WINDOW(self), "Koto"); gtk_window_set_icon_name(GTK_WINDOW(self), "audio-headphones"); gtk_window_set_startup_id(GTK_WINDOW(self), "com.github.joshstrobl.koto"); + gtk_widget_queue_draw(self->content_layout); g_thread_new("load-library", (void*) load_library, self); } @@ -124,13 +125,14 @@ void load_library(KotoWindow *self) { self->library = lib; KotoPageMusicLocal* l = koto_page_music_local_new(); - if (GTK_IS_WIDGET(l)) { // Created our local library page - koto_page_music_local_set_library(l, self->library); - gtk_stack_add_named(GTK_STACK(self->pages), GTK_WIDGET(l), "music.local"); - // TODO: Remove and do some fancy state loading - gtk_stack_set_visible_child_name(GTK_STACK(self->pages), "music.local"); - } + // TODO: Remove and do some fancy state loading + gtk_stack_add_named(GTK_STACK(self->pages), GTK_WIDGET(l), "music.local"); + gtk_stack_set_visible_child_name(GTK_STACK(self->pages), "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_set_library(l, self->library); } + + g_thread_exit(0); } void set_optimal_default_window_size(KotoWindow *self) { diff --git a/src/main.c b/src/main.c index 0e28674..d5d27d6 100644 --- a/src/main.c +++ b/src/main.c @@ -30,7 +30,7 @@ static void on_activate (GtkApplication *app) { window = gtk_application_get_active_window (app); if (window == NULL) { - window = g_object_new(KOTO_TYPE_WINDOW, "application", app, "default-width", 600, "default-height", 300, NULL); + window = g_object_new(KOTO_TYPE_WINDOW, "application", app, "default-width", 1200, "default-height", 675, NULL); } gtk_window_present(window); @@ -38,11 +38,6 @@ static void on_activate (GtkApplication *app) { static void on_shutdown(GtkApplication *app) { (void) app; - - if (koto_db != NULL) { - g_message("Have a db?"); - } - close_db(); // Close the database } diff --git a/src/pages/music/music-local.c b/src/pages/music/music-local.c index 3aae182..ce5a693 100644 --- a/src/pages/music/music-local.c +++ b/src/pages/music/music-local.c @@ -97,12 +97,35 @@ static void koto_page_music_local_set_property(GObject *obj, guint prop_id, cons static void koto_page_music_local_init(KotoPageMusicLocal *self) { self->lib = NULL; self->constructed = FALSE; + + gtk_widget_add_css_class(GTK_WIDGET(self), "page-music-local"); + gtk_widget_set_hexpand(GTK_WIDGET(self), TRUE); + gtk_widget_set_vexpand(GTK_WIDGET(self), TRUE); + + self->scrolled_window = gtk_scrolled_window_new(); + gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(self->scrolled_window), 300); + gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(self->scrolled_window), FALSE); + gtk_widget_add_css_class(self->scrolled_window, "artist-list"); + gtk_widget_set_vexpand(self->scrolled_window, TRUE); // Expand our scrolled window + gtk_box_prepend(GTK_BOX(self), self->scrolled_window); + + self->artist_list = gtk_list_box_new(); // Create our artist list + g_signal_connect(GTK_LIST_BOX(self->artist_list), "row-activated", G_CALLBACK(koto_page_music_local_handle_artist_click), self); + gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(self->artist_list), TRUE); + gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->artist_list), GTK_SELECTION_BROWSE); + gtk_list_box_set_sort_func(GTK_LIST_BOX(self->artist_list), koto_page_music_local_sort_artists, NULL, NULL); // Add our sort function + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_window), self->artist_list); + + self->stack = gtk_stack_new(); // Create a new stack + gtk_stack_set_transition_duration(GTK_STACK(self->stack), 400); + gtk_stack_set_transition_type(GTK_STACK(self->stack), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT); + gtk_widget_set_hexpand(self->stack, TRUE); + gtk_widget_set_vexpand(self->stack, TRUE); + gtk_box_append(GTK_BOX(self), self->stack); } static void koto_page_music_local_constructed(GObject *obj) { KotoPageMusicLocal *self = KOTO_PAGE_MUSIC_LOCAL(obj); - gtk_widget_add_css_class(GTK_WIDGET(self), "page-music-local"); - gtk_widget_set_hexpand(GTK_WIDGET(self), TRUE); G_OBJECT_CLASS (koto_page_music_local_parent_class)->constructed (obj); self->constructed = TRUE; @@ -144,63 +167,17 @@ void koto_page_music_local_set_library(KotoPageMusicLocal *self, KotoIndexedLibr return; } - if (!GTK_IS_SCROLLED_WINDOW(self->scrolled_window)) { - self->scrolled_window = gtk_scrolled_window_new(); - gtk_widget_add_css_class(self->scrolled_window, "artist-list"); - gtk_box_prepend(GTK_BOX(self), self->scrolled_window); + GHashTableIter artist_list_iter; + gpointer artist_key; + gpointer artist_data; + + GHashTable *artists = koto_indexed_library_get_artists(self->lib); // Get the artists + + 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 + KotoIndexedArtist *artist = (KotoIndexedArtist*) artist_data; // Cast our data as a KotoIndexedArtist + koto_page_music_local_add_artist(self, artist); } - - if (GTK_IS_LIST_BOX(self->artist_list)) { // artist list is a list box - gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_window), NULL); // Set to null to maybe clear? - g_object_unref(self->artist_list); // Unref - } - - if (GTK_IS_STACK(self->stack)) { // Stack is a Stack - gtk_box_remove(GTK_BOX(self), GTK_WIDGET(self->stack)); // Destroy to free references - } - - self->artist_list = gtk_list_box_new(); // Create our artist list - - gboolean list_created = GTK_IS_LIST_BOX(self->artist_list); - - if (list_created) { // Successfully created our list - g_signal_connect(GTK_LIST_BOX(self->artist_list), "row-activated", G_CALLBACK(koto_page_music_local_handle_artist_click), self); - gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(self->artist_list), TRUE); - gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->artist_list), GTK_SELECTION_BROWSE); - gtk_list_box_set_sort_func(GTK_LIST_BOX(self->artist_list), koto_page_music_local_sort_artists, NULL, NULL); // Add our sort function - gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_window), self->artist_list); - gtk_widget_show(GTK_WIDGET(self->artist_list)); - } - - gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(self->scrolled_window), 300); - gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(self->scrolled_window), FALSE); - gtk_widget_set_size_request(GTK_WIDGET(self->artist_list), 300, -1); - - self->stack = gtk_stack_new(); // Create a new stack - gtk_stack_set_transition_duration(GTK_STACK(self->stack), 400); - gtk_stack_set_transition_type(GTK_STACK(self->stack), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT); - gtk_widget_set_hexpand(self->stack, TRUE); - gboolean stack_created = GTK_IS_STACK(self->stack); - - if (list_created && stack_created) { - GHashTableIter artist_list_iter; - gpointer artist_key; - gpointer artist_data; - - GHashTable *artists = koto_indexed_library_get_artists(self->lib); // Get the artists - - 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 - KotoIndexedArtist *artist = (KotoIndexedArtist*) artist_data; // Cast our data as a KotoIndexedArtist - koto_page_music_local_add_artist(self, artist); - } - } - - if (stack_created) { // Successfully created our stack - gtk_box_append(GTK_BOX(self), self->stack); - } - - gtk_widget_show(GTK_WIDGET(self)); } int koto_page_music_local_sort_artists(GtkListBoxRow *artist1, GtkListBoxRow *artist2, gpointer user_data) {