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.
This commit is contained in:
Joshua Strobl 2021-03-02 19:12:12 +02:00
parent 8f50d388bd
commit 7566fb39f9
21 changed files with 585 additions and 282 deletions

View file

@ -17,13 +17,17 @@
#include <glib-2.0/glib.h>
#include <magic.h>
#include <sqlite3.h>
#include <stdio.h>
#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
);
}