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