From 7566fb39f95310a1d676c34a2a92fed8fa2e904d Mon Sep 17 00:00:00 2001 From: Joshua Strobl Date: Tue, 2 Mar 2021 19:12:12 +0200 Subject: [PATCH] Consolidate indexer struct header definitions, implement artist, album, and file inserts. Tweaked database schema. Ensure we enable FOREIGN KEYS. Refactored how and when we are doing cover art and track fetching for an album. --- src/db/db.c | 19 +- src/db/db.h | 1 + src/indexer/album.c | 363 ++++++++++++++++++++++++---------- src/indexer/album.h | 38 ---- src/indexer/artist.c | 65 +++++- src/indexer/artist.h | 38 ---- src/indexer/file-indexer.c | 8 +- src/indexer/file-indexer.h | 36 ---- src/indexer/file.c | 133 ++++++++++++- src/indexer/file.h | 36 ---- src/indexer/structs.h | 105 ++++++++++ src/koto-track-item.h | 2 +- src/koto-window.c | 2 +- src/pages/music/album-view.c | 3 +- src/pages/music/album-view.h | 3 +- src/pages/music/artist-view.c | 3 +- src/pages/music/artist-view.h | 3 +- src/pages/music/disc-view.c | 2 +- src/pages/music/disc-view.h | 2 +- src/pages/music/music-local.c | 2 +- src/pages/music/music-local.h | 3 +- 21 files changed, 585 insertions(+), 282 deletions(-) delete mode 100644 src/indexer/album.h delete mode 100644 src/indexer/artist.h delete mode 100644 src/indexer/file-indexer.h delete mode 100644 src/indexer/file.h create mode 100644 src/indexer/structs.h diff --git a/src/db/db.c b/src/db/db.c index 8bbce74..f1341aa 100644 --- a/src/db/db.c +++ b/src/db/db.c @@ -29,9 +29,9 @@ void close_db() { } void create_db_tables() { - char *tables_creation_queries = "CREATE TABLE IF NOT EXISTS artists(id string UNIQUE, path string UNIQUE, type int, name string, art_path string);" - "CREATE TABLE IF NOT EXISTS albums(id string UNIQUE, path string UNIQUE, artist_id int, name string, art_path string);" - "CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE, path string UNIQUE, type int, artist_id int, album_id int, file_name string, name string, disc int, position int);"; + 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);"; gchar *create_tables_errmsg = NULL; int rc = sqlite3_exec(koto_db, tables_creation_queries, 0,0, &create_tables_errmsg); @@ -41,6 +41,17 @@ void create_db_tables() { } } +void 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); + + 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); +} + 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); @@ -56,5 +67,7 @@ void open_db() { return; } + enable_foreign_keys(); // Enable FOREIGN KEY support + create_db_tables(); // Attempt to create our database tables } diff --git a/src/db/db.h b/src/db/db.h index a7cf546..30ed2af 100644 --- a/src/db/db.h +++ b/src/db/db.h @@ -18,5 +18,6 @@ #include void close_db(); +void enable_foreign_keys(); void open_db(); diff --git a/src/indexer/album.c b/src/indexer/album.c index ae671d3..c30c3ad 100644 --- a/src/indexer/album.c +++ b/src/indexer/album.c @@ -17,13 +17,17 @@ #include #include +#include #include -#include "album.h" -#include "file.h" +#include "structs.h" #include "koto-utils.h" +extern sqlite3 *koto_db; + struct _KotoIndexedAlbum { GObject parent_instance; + KotoIndexedArtist *artist; + gchar *uuid; gchar *path; gchar *name; @@ -31,12 +35,16 @@ struct _KotoIndexedAlbum { GHashTable *files; gboolean has_album_art; + gboolean do_initial_index; }; G_DEFINE_TYPE(KotoIndexedAlbum, koto_indexed_album, G_TYPE_OBJECT); enum { PROP_0, + PROP_ARTIST, + PROP_UUID, + PROP_DO_INITIAL_INDEX, PROP_PATH, PROP_ALBUM_NAME, PROP_ART_PATH, @@ -54,6 +62,30 @@ static void koto_indexed_album_class_init(KotoIndexedAlbumClass *c) { gobject_class->set_property = koto_indexed_album_set_property; gobject_class->get_property = koto_indexed_album_get_property; + props[PROP_ARTIST] = g_param_spec_object( + "artist", + "Artist associated with Album", + "Artist associated with Album", + KOTO_TYPE_INDEXED_ARTIST, + G_PARAM_CONSTRUCT_ONLY|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_UUID] = g_param_spec_string( + "uuid", + "UUID to Album in database", + "UUID to Album in database", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_DO_INITIAL_INDEX] = g_param_spec_boolean( + "do-initial-index", + "Do an initial indexing operating instead of pulling from the database", + "Do an initial indexing operating instead of pulling from the database", + FALSE, + G_PARAM_CONSTRUCT_ONLY|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + props[PROP_PATH] = g_param_spec_string( "path", "Path", @@ -105,12 +137,203 @@ void koto_indexed_album_add_file(KotoIndexedAlbum *self, KotoIndexedFile *file) g_hash_table_insert(self->files, file_name, file); // Add the file } +void koto_indexed_album_commit(KotoIndexedAlbum *self) { + gchar *artist_uuid; + g_object_get(self->artist, "uuid", &artist_uuid, NULL); // Get the artist UUID + if (self->art_path == NULL) { // If art_path isn't defined when committing + koto_indexed_album_set_album_art(self, ""); // Set to an empty string + } + + 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;", + self->uuid, + self->path, + artist_uuid, + self->name, + 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); + + if (rc != SQLITE_OK) { + g_warning("Failed to write our album to the database: %s", commit_op_errmsg); + } + + g_free(commit_op); + g_free(commit_op_errmsg); +} + +void koto_indexed_album_find_album_art(KotoIndexedAlbum *self) { + magic_t magic_cookie = magic_open(MAGIC_MIME); + + if (magic_cookie == NULL) { + return; + } + + if (magic_load(magic_cookie, NULL) != 0) { + magic_close(magic_cookie); + return; + } + + DIR *dir = opendir(self->path); // Attempt to open our directory + + if (dir == NULL) { + return; + } + + struct dirent *entry; + + while ((entry = readdir(dir))) { + if (entry->d_type != DT_REG) { // Not a regular file + continue; // SKIP + } + + 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", self->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 + 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 + + if ( + (g_strrstr(lower_art, "Small") == NULL) && // Not Small + (g_strrstr(lower_art, "back") == NULL) // Not back + ) { + koto_indexed_album_set_album_art(self, full_path); + g_free(album_art_no_ext); + g_free(lower_art); + break; + } + + g_free(album_art_no_ext); + g_free(lower_art); + } + + g_free(full_path); + } + + closedir(dir); + magic_close(magic_cookie); +} + +void koto_indexed_album_find_tracks(KotoIndexedAlbum *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_indexed_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 *file_with_cd_sep = g_strdup(possible_cd_split[1]); // Duplicate + gchar **split_on_cd = g_strsplit(file_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(file_with_cd_sep); + + g_strfreev(possible_cd_split); + g_free(appended_slash_to_path); + + KotoIndexedFile *file = koto_indexed_file_new(self, full_path, cd); + + if (file != NULL) { // Is a file + koto_indexed_album_add_file(self, file); // Add our file + } + } + + g_free(full_path); + } +} static void koto_indexed_album_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) { KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj); switch (prop_id) { + case PROP_ARTIST: + g_value_set_object(val, self->artist); + break; + case PROP_UUID: + g_value_set_string(val, self->uuid); + break; + 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; @@ -148,93 +371,11 @@ 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; } -void koto_indexed_album_read_path(KotoIndexedAlbum *self, magic_t cookie, const gchar* path) { - 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_indexed_album_read_path(self, cookie, full_path); // Read this directory as well - continue; - } - - if (entry->d_type != DT_REG) { // If this is not a regular file - continue; // Skip - } - - const char *mime_type = magic_file(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, "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 - - if ( - (g_strrstr(lower_art, "Small") == NULL) && // Not Small - (g_strrstr(lower_art, "back") == NULL) // Not back - ) { - koto_indexed_album_set_album_art(self, full_path); - } - - g_free(album_art_no_ext); - g_free(lower_art); - } else 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 *file_with_cd_sep = g_strdup(possible_cd_split[1]); // Duplicate - gchar **split_on_cd = g_strsplit(file_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(file_with_cd_sep); - - g_strfreev(possible_cd_split); - g_free(appended_slash_to_path); - - KotoIndexedFile *file = koto_indexed_file_new(full_path, cd); - - if (file != NULL) { // Is a file - koto_indexed_album_add_file(self, file); // Add our file - } - } - - g_free(full_path); - } -} - void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedFile *file) { if (file == NULL) { // Not a file return; @@ -274,6 +415,16 @@ static void koto_indexed_album_set_property(GObject *obj, guint prop_id, const G KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj); switch (prop_id) { + case PROP_ARTIST: + self->artist = (KotoIndexedArtist*) g_value_get_object(val); + break; + case PROP_UUID: + self->uuid = g_strdup(g_value_get_string(val)); + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UUID]); + break; + case PROP_DO_INITIAL_INDEX: + self->do_initial_index = g_value_get_boolean(val); + break; case PROP_PATH: // Path to the album koto_indexed_album_update_path(self, g_value_get_string(val)); break; @@ -294,12 +445,6 @@ void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar* new_pat return; } - DIR *dir = opendir(new_path); // Attempt to open our directory - - if (dir == NULL) { - return; - } - if (self->path != NULL) { g_free(self->path); } @@ -308,25 +453,33 @@ void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar* new_pat koto_indexed_album_set_album_name(self, g_path_get_basename(self->path)); // Update our album name based on the base name - magic_t magic_cookie = magic_open(MAGIC_MIME); - - if (magic_cookie == NULL) { + if (!self->do_initial_index) { // Not doing our initial index return; } - if (magic_load(magic_cookie, NULL) != 0) { - magic_close(magic_cookie); - return; - } - - koto_indexed_album_read_path(self, magic_cookie, self->path); - - magic_close(magic_cookie); + koto_indexed_album_find_album_art(self); // Update our path for the album art } -KotoIndexedAlbum* koto_indexed_album_new(const gchar *path) { - return g_object_new(KOTO_TYPE_INDEXED_ALBUM, +KotoIndexedAlbum* koto_indexed_album_new(KotoIndexedArtist *artist, const gchar *path) { + KotoIndexedAlbum* album = g_object_new(KOTO_TYPE_INDEXED_ALBUM, + "artist", artist, + "uuid", g_strdup(g_uuid_string_random()), + "do-initial-index", TRUE, "path", path, NULL ); + + koto_indexed_album_commit(album); + koto_indexed_album_find_tracks(album, NULL, NULL); // Scan for tracks now that we committed to the database (hopefully) + + return album; +} + +KotoIndexedAlbum* koto_indexed_album_new_with_uuid(KotoIndexedArtist *artist, const gchar *uuid) { + return g_object_new(KOTO_TYPE_INDEXED_ALBUM, + "artist", artist, + "uuid", uuid, + "do-initial-index", FALSE, + NULL + ); } diff --git a/src/indexer/album.h b/src/indexer/album.h deleted file mode 100644 index 3ff9001..0000000 --- a/src/indexer/album.h +++ /dev/null @@ -1,38 +0,0 @@ -/* album.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. - */ - -#pragma once -#include -#include "file.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_INDEXED_ALBUM koto_indexed_album_get_type() -G_DECLARE_FINAL_TYPE (KotoIndexedAlbum, koto_indexed_album, KOTO, INDEXED_ALBUM, GObject); - -KotoIndexedAlbum* koto_indexed_album_new(const gchar *path); - -void koto_indexed_album_add_file(KotoIndexedAlbum *self, KotoIndexedFile *file); -gchar* koto_indexed_album_get_album_art(KotoIndexedAlbum *self); -GList* koto_indexed_album_get_files(KotoIndexedAlbum *self); -void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedFile *file); -void koto_indexed_album_remove_file_by_name(KotoIndexedAlbum *self, const gchar *file_name); -void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album_art); -void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *album_name); -void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar *path); - -G_END_DECLS diff --git a/src/indexer/artist.c b/src/indexer/artist.c index 6985a31..41cd320 100644 --- a/src/indexer/artist.c +++ b/src/indexer/artist.c @@ -16,11 +16,15 @@ */ #include -#include "album.h" -#include "artist.h" +#include +#include "structs.h" +#include "../db/db.h" + +extern sqlite3 *koto_db; struct _KotoIndexedArtist { GObject parent_instance; + gchar *uuid; gchar *path; gboolean has_artist_art; @@ -32,6 +36,7 @@ G_DEFINE_TYPE(KotoIndexedArtist, koto_indexed_artist, G_TYPE_OBJECT); enum { PROP_0, + PROP_UUID, PROP_PATH, PROP_ARTIST_NAME, N_PROPERTIES @@ -48,6 +53,14 @@ static void koto_indexed_artist_class_init(KotoIndexedArtistClass *c) { gobject_class->set_property = koto_indexed_artist_set_property; gobject_class->get_property = koto_indexed_artist_get_property; + props[PROP_UUID] = g_param_spec_string( + "uuid", + "UUID to Artist in database", + "UUID to Artist in database", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + props[PROP_PATH] = g_param_spec_string( "path", "Path", @@ -67,6 +80,34 @@ static void koto_indexed_artist_class_init(KotoIndexedArtistClass *c) { g_object_class_install_properties(gobject_class, N_PROPERTIES, props); } +void koto_indexed_artist_commit(KotoIndexedArtist *self) { + if ((self->uuid == NULL) || strcmp(self->uuid, "")) { // UUID not set + self->uuid = g_strdup(g_uuid_string_random()); + } + + // 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;", + self->uuid, + self->path, + 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); + + if (rc != SQLITE_OK) { + g_warning("Failed to write our artist to the database: %s", commit_opt_errmsg); + } + + g_free(commit_op); + g_free(commit_opt_errmsg); +} + static void koto_indexed_artist_init(KotoIndexedArtist *self) { self->has_artist_art = FALSE; self->albums = NULL; // Set to null initially maybe @@ -76,6 +117,9 @@ static void koto_indexed_artist_get_property(GObject *obj, guint prop_id, GValue KotoIndexedArtist *self = KOTO_INDEXED_ARTIST(obj); switch (prop_id) { + case PROP_UUID: + g_value_set_string(val, self->uuid); + break; case PROP_PATH: g_value_set_string(val, self->path); break; @@ -92,6 +136,10 @@ static void koto_indexed_artist_set_property(GObject *obj, guint prop_id, const KotoIndexedArtist *self = KOTO_INDEXED_ARTIST(obj); switch (prop_id) { + case PROP_UUID: + 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_indexed_artist_update_path(self, g_value_get_string(val)); break; @@ -188,9 +236,20 @@ void koto_indexed_artist_set_artist_name(KotoIndexedArtist *self, const gchar *a } KotoIndexedArtist* koto_indexed_artist_new(const gchar *path) { - return g_object_new(KOTO_TYPE_INDEXED_ARTIST, + KotoIndexedArtist* artist = g_object_new(KOTO_TYPE_INDEXED_ARTIST, + "uuid", g_uuid_string_random(), "path", path, "name", g_path_get_basename(path), NULL ); + + koto_indexed_artist_commit(artist); // Commit the artist immediately to the database + return artist; +} + +KotoIndexedArtist* koto_indexed_artist_new_with_uuid(const gchar *uuid) { + return g_object_new(KOTO_TYPE_INDEXED_ARTIST, + "uuid", uuid, + NULL + ); } diff --git a/src/indexer/artist.h b/src/indexer/artist.h deleted file mode 100644 index df88435..0000000 --- a/src/indexer/artist.h +++ /dev/null @@ -1,38 +0,0 @@ -/* artist.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. - */ - -#pragma once -#include -#include "album.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_INDEXED_ARTIST koto_indexed_artist_get_type() -G_DECLARE_FINAL_TYPE (KotoIndexedArtist, koto_indexed_artist, KOTO, INDEXED_ARTIST, GObject); - -KotoIndexedArtist* koto_indexed_artist_new(const gchar *path); - -void koto_indexed_artist_add_album(KotoIndexedArtist *self, KotoIndexedAlbum *album); -guint koto_indexed_artist_find_album_with_name(gconstpointer *album_data, gconstpointer *album_name_data); -GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self); -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); -void koto_indexed_artist_update_path(KotoIndexedArtist *self, const gchar *new_path); -void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data); - -G_END_DECLS diff --git a/src/indexer/file-indexer.c b/src/indexer/file-indexer.c index e8ea155..949c804 100644 --- a/src/indexer/file-indexer.c +++ b/src/indexer/file-indexer.c @@ -20,10 +20,7 @@ #include #include #include -#include "album.h" -#include "artist.h" -#include "file.h" -#include "file-indexer.h" +#include "structs.h" struct _KotoIndexedLibrary { GObject parent_instance; @@ -205,7 +202,6 @@ void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth) { g_free(artist_name); } 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 - KotoIndexedAlbum *album = koto_indexed_album_new(full_path); KotoIndexedArtist *artist = koto_indexed_library_get_artist(self, artist_name); // Get the artist @@ -213,6 +209,8 @@ void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth) { continue; } + KotoIndexedAlbum *album = koto_indexed_album_new(artist, full_path); + koto_indexed_artist_add_album(artist, album); // Add the album g_free(artist_name); } diff --git a/src/indexer/file-indexer.h b/src/indexer/file-indexer.h deleted file mode 100644 index 59af814..0000000 --- a/src/indexer/file-indexer.h +++ /dev/null @@ -1,36 +0,0 @@ -/* file-indexer.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. - */ - -#pragma once -#include -#include "artist.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_INDEXED_LIBRARY koto_indexed_library_get_type() -G_DECLARE_FINAL_TYPE(KotoIndexedLibrary, koto_indexed_library, KOTO, INDEXED_LIBRARY, GObject); - -KotoIndexedLibrary* koto_indexed_library_new(const gchar *path); - -void koto_indexed_library_add_artist(KotoIndexedLibrary *self, KotoIndexedArtist *artist); -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 start_indexing(KotoIndexedLibrary *self); -void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth); - -G_END_DECLS diff --git a/src/indexer/file.c b/src/indexer/file.c index c436cec..4b1bba2 100644 --- a/src/indexer/file.c +++ b/src/indexer/file.c @@ -16,12 +16,18 @@ */ #include +#include #include -#include "file.h" +#include "structs.h" #include "koto-utils.h" +extern sqlite3 *koto_db; + struct _KotoIndexedFile { GObject parent_instance; + gchar *artist_uuid; + gchar *album_uuid; + gchar *uuid; gchar *path; gchar *file_name; @@ -30,13 +36,19 @@ struct _KotoIndexedFile { gchar *album; guint *cd; guint *position; + gboolean acquired_metadata_from_id3; + gboolean do_initial_index; }; G_DEFINE_TYPE(KotoIndexedFile, koto_indexed_file, G_TYPE_OBJECT); enum { PROP_0, + PROP_ARTIST_UUID, + PROP_ALBUM_UUID, + PROP_UUID, + PROP_DO_INITIAL_INDEX, PROP_PATH, PROP_ARTIST, PROP_ALBUM, @@ -58,6 +70,38 @@ static void koto_indexed_file_class_init(KotoIndexedFileClass *c) { gobject_class->set_property = koto_indexed_file_set_property; gobject_class->get_property = koto_indexed_file_get_property; + props[PROP_ARTIST_UUID] = g_param_spec_string( + "artist-uuid", + "UUID to Artist associated with the File", + "UUID to Artist associated with the File", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_ALBUM_UUID] = g_param_spec_string( + "album-uuid", + "UUID to Album associated with the File", + "UUID to Album associated with the File", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_UUID] = g_param_spec_string( + "uuid", + "UUID to File in database", + "UUID to File in database", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_DO_INITIAL_INDEX] = g_param_spec_boolean( + "do-initial-index", + "Do an initial indexing operating instead of pulling from the database", + "Do an initial indexing operating instead of pulling from the database", + FALSE, + G_PARAM_CONSTRUCT_ONLY|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + props[PROP_PATH] = g_param_spec_string( "path", "Path", @@ -129,6 +173,15 @@ static void koto_indexed_file_get_property(GObject *obj, guint prop_id, GValue * KotoIndexedFile *self = KOTO_INDEXED_FILE(obj); switch (prop_id) { + case PROP_ARTIST_UUID: + g_value_set_string(val, self->artist_uuid); + break; + case PROP_ALBUM_UUID: + g_value_set_string(val, self->album_uuid); + break; + case PROP_UUID: + g_value_set_string(val, self->uuid); + break; case PROP_PATH: g_value_set_string(val, self->path); break; @@ -160,6 +213,21 @@ static void koto_indexed_file_set_property(GObject *obj, guint prop_id, const GV KotoIndexedFile *self = KOTO_INDEXED_FILE(obj); switch (prop_id) { + case PROP_ARTIST_UUID: + self->artist_uuid = g_strdup(g_value_get_string(val)); + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ARTIST_UUID]); + break; + case PROP_ALBUM_UUID: + self->album_uuid = g_strdup(g_value_get_string(val)); + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ALBUM_UUID]); + break; + case PROP_UUID: + self->uuid = g_strdup(g_value_get_string(val)); + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UUID]); + break; + 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; @@ -187,6 +255,43 @@ static void koto_indexed_file_set_property(GObject *obj, guint prop_id, const GV } } + +void koto_indexed_file_commit(KotoIndexedFile *self) { + if ((self->artist_uuid == NULL) || (strcmp(self->artist_uuid, "") == 0)) { // No valid required artist UUID + return; + } + + if (self->album_uuid == NULL) { + g_object_set(self, "album-uuid", "", NULL); // Set to an empty string + } + + 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;", + 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) + ); + + 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); + + if (rc != SQLITE_OK) { + g_warning("Failed to write our file to the database: %s", commit_op_errmsg); + } + + g_free(commit_op); + g_free(commit_op_errmsg); +} + void koto_indexed_file_parse_name(KotoIndexedFile *self) { gchar *copied_file_name = g_strdelimit(g_strdup(self->file_name), "_", ' '); // Replace _ with whitespace for starters @@ -334,10 +439,32 @@ void koto_indexed_file_update_path(KotoIndexedFile *self, const gchar *new_path) g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PATH]); } -KotoIndexedFile* koto_indexed_file_new(const gchar *path, guint *cd) { - return g_object_new(KOTO_TYPE_INDEXED_FILE, +KotoIndexedFile* koto_indexed_file_new(KotoIndexedAlbum *album, const gchar *path, guint *cd) { + KotoIndexedArtist *artist; + + gchar *artist_uuid; + gchar *album_uuid; + + 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 + + KotoIndexedFile *file = g_object_new(KOTO_TYPE_INDEXED_FILE, + "artist-uuid", artist_uuid, + "album-uuid", album_uuid, + "uuid", g_uuid_string_random(), "path", path, "cd", cd, NULL ); + + koto_indexed_file_commit(file); // Immediately commit to the database + return file; +} + +KotoIndexedFile* koto_indexed_file_new_with_uuid(const gchar *uuid) { + return g_object_new(KOTO_TYPE_INDEXED_FILE, + "uuid", uuid, + NULL + ); } diff --git a/src/indexer/file.h b/src/indexer/file.h deleted file mode 100644 index bb28202..0000000 --- a/src/indexer/file.h +++ /dev/null @@ -1,36 +0,0 @@ -/* file.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. - */ - -#pragma once -#include - -G_BEGIN_DECLS - -#define KOTO_TYPE_INDEXED_FILE koto_indexed_file_get_type() -G_DECLARE_FINAL_TYPE(KotoIndexedFile, koto_indexed_file, KOTO, INDEXED_FILE, GObject); - -KotoIndexedFile* koto_indexed_file_new(const gchar *path, guint *cd); - -void koto_indexed_file_parse_name(KotoIndexedFile *self); -void koto_indexed_file_set_file_name(KotoIndexedFile *self, gchar *new_file_name); -void koto_indexed_file_set_cd(KotoIndexedFile *self, guint cd); -void koto_indexed_file_set_parsed_name(KotoIndexedFile *self, gchar *new_parsed_name); -void koto_indexed_file_set_position(KotoIndexedFile *self, guint pos); -void koto_indexed_file_update_metadata(KotoIndexedFile *self); -void koto_indexed_file_update_path(KotoIndexedFile *self, const gchar *new_path); - -G_END_DECLS diff --git a/src/indexer/structs.h b/src/indexer/structs.h new file mode 100644 index 0000000..1de7a2c --- /dev/null +++ b/src/indexer/structs.h @@ -0,0 +1,105 @@ +/* structs.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. + */ + +#pragma once +#include +#include + +G_BEGIN_DECLS + +/** + * Type Definition +**/ + +#define KOTO_TYPE_INDEXED_LIBRARY koto_indexed_library_get_type() +G_DECLARE_FINAL_TYPE(KotoIndexedLibrary, koto_indexed_library, KOTO, INDEXED_LIBRARY, GObject); + +#define KOTO_TYPE_INDEXED_ARTIST koto_indexed_artist_get_type() +G_DECLARE_FINAL_TYPE (KotoIndexedArtist, koto_indexed_artist, KOTO, INDEXED_ARTIST, GObject); + +#define KOTO_TYPE_INDEXED_ALBUM koto_indexed_album_get_type() +G_DECLARE_FINAL_TYPE (KotoIndexedAlbum, koto_indexed_album, KOTO, INDEXED_ALBUM, GObject); + +#define KOTO_TYPE_INDEXED_FILE koto_indexed_file_get_type() +G_DECLARE_FINAL_TYPE(KotoIndexedFile, koto_indexed_file, KOTO, INDEXED_FILE, GObject); + +/** + * Library Functions +**/ + +KotoIndexedLibrary* koto_indexed_library_new(const gchar *path); + +void koto_indexed_library_add_artist(KotoIndexedLibrary *self, KotoIndexedArtist *artist); +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 start_indexing(KotoIndexedLibrary *self); +void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth); + +/** + * Artist Functions +**/ + +KotoIndexedArtist* koto_indexed_artist_new(const gchar *path); +KotoIndexedArtist* koto_indexed_artist_new_with_uuid(const gchar *uuid); + +void koto_indexed_artist_add_album(KotoIndexedArtist *self, KotoIndexedAlbum *album); +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); +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); +void koto_indexed_artist_update_path(KotoIndexedArtist *self, const gchar *new_path); +void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data); + +/** + * Album Functions +**/ + +KotoIndexedAlbum* koto_indexed_album_new(KotoIndexedArtist *artist, const gchar *path); +KotoIndexedAlbum* koto_indexed_album_new_with_uuid(KotoIndexedArtist *artist, const gchar *uuid); + +void koto_indexed_album_add_file(KotoIndexedAlbum *self, KotoIndexedFile *file); +void koto_indexed_album_commit(KotoIndexedAlbum *self); +void koto_indexed_album_find_album_art(KotoIndexedAlbum *self); +void koto_indexed_album_find_tracks(KotoIndexedAlbum *self, magic_t magic_cookie, const gchar *path); +gchar* koto_indexed_album_get_album_art(KotoIndexedAlbum *self); +GList* koto_indexed_album_get_files(KotoIndexedAlbum *self); +void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedFile *file); +void koto_indexed_album_remove_file_by_name(KotoIndexedAlbum *self, const gchar *file_name); +void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album_art); +void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *album_name); +void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar *path); + +/** + * File / Track Functions +**/ + +KotoIndexedFile* koto_indexed_file_new(KotoIndexedAlbum *album, const gchar *path, guint *cd); +KotoIndexedFile* koto_indexed_file_new_with_uuid(const gchar *uuid); + +void koto_indexed_file_commit(KotoIndexedFile *self); +void koto_indexed_file_parse_name(KotoIndexedFile *self); +void koto_indexed_file_set_file_name(KotoIndexedFile *self, gchar *new_file_name); +void koto_indexed_file_set_cd(KotoIndexedFile *self, guint cd); +void koto_indexed_file_set_parsed_name(KotoIndexedFile *self, gchar *new_parsed_name); +void koto_indexed_file_set_position(KotoIndexedFile *self, guint pos); +void koto_indexed_file_update_metadata(KotoIndexedFile *self); +void koto_indexed_file_update_path(KotoIndexedFile *self, const gchar *new_path); + +G_END_DECLS diff --git a/src/koto-track-item.h b/src/koto-track-item.h index a165710..b2fbf6b 100644 --- a/src/koto-track-item.h +++ b/src/koto-track-item.h @@ -19,7 +19,7 @@ #include #include -#include "indexer/file.h" +#include "indexer/structs.h" G_BEGIN_DECLS diff --git a/src/koto-window.c b/src/koto-window.c index 4404b81..fe3e1a7 100644 --- a/src/koto-window.c +++ b/src/koto-window.c @@ -16,7 +16,7 @@ */ #include -#include "indexer/file-indexer.h" +#include "indexer/structs.h" #include "pages/music/music-local.h" #include "koto-config.h" #include "koto-nav.h" diff --git a/src/pages/music/album-view.c b/src/pages/music/album-view.c index 1b1e0fd..449e962 100644 --- a/src/pages/music/album-view.c +++ b/src/pages/music/album-view.c @@ -17,8 +17,7 @@ #include #include -#include "../../indexer/album.h" -#include "../../indexer/artist.h" +#include "../../indexer/structs.h" #include "album-view.h" #include "disc-view.h" #include "koto-config.h" diff --git a/src/pages/music/album-view.h b/src/pages/music/album-view.h index c415b8b..c43b617 100644 --- a/src/pages/music/album-view.h +++ b/src/pages/music/album-view.h @@ -19,8 +19,7 @@ #include #include -#include "../../indexer/album.h" -#include "../../indexer/artist.h" +#include "../../indexer/structs.h" G_BEGIN_DECLS diff --git a/src/pages/music/artist-view.c b/src/pages/music/artist-view.c index 1da0342..8ad4501 100644 --- a/src/pages/music/artist-view.c +++ b/src/pages/music/artist-view.c @@ -17,8 +17,7 @@ #include #include -#include "../../indexer/album.h" -#include "../../indexer/artist.h" +#include "../../indexer/structs.h" #include "album-view.h" #include "artist-view.h" #include "koto-config.h" diff --git a/src/pages/music/artist-view.h b/src/pages/music/artist-view.h index 9cbcfa3..800ecea 100644 --- a/src/pages/music/artist-view.h +++ b/src/pages/music/artist-view.h @@ -19,8 +19,7 @@ #include #include -#include "../../indexer/album.h" -#include "../../indexer/artist.h" +#include "../../indexer/structs.h" G_BEGIN_DECLS diff --git a/src/pages/music/disc-view.c b/src/pages/music/disc-view.c index 9a1cc73..dbd7c84 100644 --- a/src/pages/music/disc-view.c +++ b/src/pages/music/disc-view.c @@ -16,7 +16,7 @@ */ #include -#include "../../indexer/album.h" +#include "../../indexer/structs.h" #include "../../koto-track-item.h" #include "disc-view.h" diff --git a/src/pages/music/disc-view.h b/src/pages/music/disc-view.h index 464ade5..c392aa2 100644 --- a/src/pages/music/disc-view.h +++ b/src/pages/music/disc-view.h @@ -19,7 +19,7 @@ #include #include -#include "../../indexer/album.h" +#include "../../indexer/structs.h" G_BEGIN_DECLS diff --git a/src/pages/music/music-local.c b/src/pages/music/music-local.c index ddb40eb..3aae182 100644 --- a/src/pages/music/music-local.c +++ b/src/pages/music/music-local.c @@ -17,7 +17,7 @@ #include #include -#include "../../indexer/file-indexer.h" +#include "../../indexer/structs.h" #include "koto-button.h" #include "koto-config.h" #include "music-local.h" diff --git a/src/pages/music/music-local.h b/src/pages/music/music-local.h index 20ccf56..dc80510 100644 --- a/src/pages/music/music-local.h +++ b/src/pages/music/music-local.h @@ -19,8 +19,7 @@ #include #include -#include "../../indexer/artist.h" -#include "../../indexer/file-indexer.h" +#include "../../indexer/structs.h" #include "artist-view.h" G_BEGIN_DECLS