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

@ -29,9 +29,9 @@ void close_db() {
} }
void create_db_tables() { 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);" 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, path string UNIQUE, artist_id 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, path string UNIQUE, type int, artist_id int, album_id int, file_name string, name string, disc int, position int);"; "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; gchar *create_tables_errmsg = NULL;
int rc = sqlite3_exec(koto_db, tables_creation_queries, 0,0, &create_tables_errmsg); 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() { void open_db() {
const gchar *data_home = g_get_user_data_dir(); 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); 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; return;
} }
enable_foreign_keys(); // Enable FOREIGN KEY support
create_db_tables(); // Attempt to create our database tables create_db_tables(); // Attempt to create our database tables
} }

View file

@ -18,5 +18,6 @@
#include <sqlite3.h> #include <sqlite3.h>
void close_db(); void close_db();
void enable_foreign_keys();
void open_db(); void open_db();

View file

@ -17,13 +17,17 @@
#include <glib-2.0/glib.h> #include <glib-2.0/glib.h>
#include <magic.h> #include <magic.h>
#include <sqlite3.h>
#include <stdio.h> #include <stdio.h>
#include "album.h" #include "structs.h"
#include "file.h"
#include "koto-utils.h" #include "koto-utils.h"
extern sqlite3 *koto_db;
struct _KotoIndexedAlbum { struct _KotoIndexedAlbum {
GObject parent_instance; GObject parent_instance;
KotoIndexedArtist *artist;
gchar *uuid;
gchar *path; gchar *path;
gchar *name; gchar *name;
@ -31,12 +35,16 @@ struct _KotoIndexedAlbum {
GHashTable *files; GHashTable *files;
gboolean has_album_art; gboolean has_album_art;
gboolean do_initial_index;
}; };
G_DEFINE_TYPE(KotoIndexedAlbum, koto_indexed_album, G_TYPE_OBJECT); G_DEFINE_TYPE(KotoIndexedAlbum, koto_indexed_album, G_TYPE_OBJECT);
enum { enum {
PROP_0, PROP_0,
PROP_ARTIST,
PROP_UUID,
PROP_DO_INITIAL_INDEX,
PROP_PATH, PROP_PATH,
PROP_ALBUM_NAME, PROP_ALBUM_NAME,
PROP_ART_PATH, 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->set_property = koto_indexed_album_set_property;
gobject_class->get_property = koto_indexed_album_get_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( props[PROP_PATH] = g_param_spec_string(
"path", "path",
"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 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) { static void koto_indexed_album_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj); KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj);
switch (prop_id) { 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: case PROP_PATH:
g_value_set_string(val, self->path); g_value_set_string(val, self->path);
break; break;
@ -148,93 +371,11 @@ void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album
} }
self->art_path = g_strdup(album_art); self->art_path = g_strdup(album_art);
g_message("Set album art to %s", album_art);
self->has_album_art = TRUE; 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) { void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedFile *file) {
if (file == NULL) { // Not a file if (file == NULL) { // Not a file
return; 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); KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj);
switch (prop_id) { 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 case PROP_PATH: // Path to the album
koto_indexed_album_update_path(self, g_value_get_string(val)); koto_indexed_album_update_path(self, g_value_get_string(val));
break; break;
@ -294,12 +445,6 @@ void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar* new_pat
return; return;
} }
DIR *dir = opendir(new_path); // Attempt to open our directory
if (dir == NULL) {
return;
}
if (self->path != NULL) { if (self->path != NULL) {
g_free(self->path); 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 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 (!self->do_initial_index) { // Not doing our initial index
if (magic_cookie == NULL) {
return; return;
} }
if (magic_load(magic_cookie, NULL) != 0) { koto_indexed_album_find_album_art(self); // Update our path for the album art
magic_close(magic_cookie);
return;
} }
koto_indexed_album_read_path(self, magic_cookie, self->path); KotoIndexedAlbum* koto_indexed_album_new(KotoIndexedArtist *artist, const gchar *path) {
KotoIndexedAlbum* album = g_object_new(KOTO_TYPE_INDEXED_ALBUM,
magic_close(magic_cookie); "artist", artist,
} "uuid", g_strdup(g_uuid_string_random()),
"do-initial-index", TRUE,
KotoIndexedAlbum* koto_indexed_album_new(const gchar *path) {
return g_object_new(KOTO_TYPE_INDEXED_ALBUM,
"path", path, "path", path,
NULL 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
);
} }

View file

@ -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 <glib-2.0/glib-object.h>
#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

View file

@ -16,11 +16,15 @@
*/ */
#include <glib-2.0/glib.h> #include <glib-2.0/glib.h>
#include "album.h" #include <sqlite3.h>
#include "artist.h" #include "structs.h"
#include "../db/db.h"
extern sqlite3 *koto_db;
struct _KotoIndexedArtist { struct _KotoIndexedArtist {
GObject parent_instance; GObject parent_instance;
gchar *uuid;
gchar *path; gchar *path;
gboolean has_artist_art; gboolean has_artist_art;
@ -32,6 +36,7 @@ G_DEFINE_TYPE(KotoIndexedArtist, koto_indexed_artist, G_TYPE_OBJECT);
enum { enum {
PROP_0, PROP_0,
PROP_UUID,
PROP_PATH, PROP_PATH,
PROP_ARTIST_NAME, PROP_ARTIST_NAME,
N_PROPERTIES 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->set_property = koto_indexed_artist_set_property;
gobject_class->get_property = koto_indexed_artist_get_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( props[PROP_PATH] = g_param_spec_string(
"path", "path",
"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); 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) { static void koto_indexed_artist_init(KotoIndexedArtist *self) {
self->has_artist_art = FALSE; self->has_artist_art = FALSE;
self->albums = NULL; // Set to null initially maybe 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); KotoIndexedArtist *self = KOTO_INDEXED_ARTIST(obj);
switch (prop_id) { switch (prop_id) {
case PROP_UUID:
g_value_set_string(val, self->uuid);
break;
case PROP_PATH: case PROP_PATH:
g_value_set_string(val, self->path); g_value_set_string(val, self->path);
break; break;
@ -92,6 +136,10 @@ static void koto_indexed_artist_set_property(GObject *obj, guint prop_id, const
KotoIndexedArtist *self = KOTO_INDEXED_ARTIST(obj); KotoIndexedArtist *self = KOTO_INDEXED_ARTIST(obj);
switch (prop_id) { 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: case PROP_PATH:
koto_indexed_artist_update_path(self, g_value_get_string(val)); koto_indexed_artist_update_path(self, g_value_get_string(val));
break; 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) { 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, "path", path,
"name", g_path_get_basename(path), "name", g_path_get_basename(path),
NULL 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
);
} }

View file

@ -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 <glib-2.0/glib-object.h>
#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

View file

@ -20,10 +20,7 @@
#include <stdio.h> #include <stdio.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <taglib/tag_c.h> #include <taglib/tag_c.h>
#include "album.h" #include "structs.h"
#include "artist.h"
#include "file.h"
#include "file-indexer.h"
struct _KotoIndexedLibrary { struct _KotoIndexedLibrary {
GObject parent_instance; GObject parent_instance;
@ -205,7 +202,6 @@ void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth) {
g_free(artist_name); g_free(artist_name);
} else if (depth == 2) { // If we are following FOLDER/ARTIST/ALBUM then this would be album } 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 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 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; continue;
} }
KotoIndexedAlbum *album = koto_indexed_album_new(artist, full_path);
koto_indexed_artist_add_album(artist, album); // Add the album koto_indexed_artist_add_album(artist, album); // Add the album
g_free(artist_name); g_free(artist_name);
} }

View file

@ -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 <glib-2.0/glib-object.h>
#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

View file

@ -16,12 +16,18 @@
*/ */
#include <glib-2.0/glib.h> #include <glib-2.0/glib.h>
#include <sqlite3.h>
#include <taglib/tag_c.h> #include <taglib/tag_c.h>
#include "file.h" #include "structs.h"
#include "koto-utils.h" #include "koto-utils.h"
extern sqlite3 *koto_db;
struct _KotoIndexedFile { struct _KotoIndexedFile {
GObject parent_instance; GObject parent_instance;
gchar *artist_uuid;
gchar *album_uuid;
gchar *uuid;
gchar *path; gchar *path;
gchar *file_name; gchar *file_name;
@ -30,13 +36,19 @@ struct _KotoIndexedFile {
gchar *album; gchar *album;
guint *cd; guint *cd;
guint *position; guint *position;
gboolean acquired_metadata_from_id3; gboolean acquired_metadata_from_id3;
gboolean do_initial_index;
}; };
G_DEFINE_TYPE(KotoIndexedFile, koto_indexed_file, G_TYPE_OBJECT); G_DEFINE_TYPE(KotoIndexedFile, koto_indexed_file, G_TYPE_OBJECT);
enum { enum {
PROP_0, PROP_0,
PROP_ARTIST_UUID,
PROP_ALBUM_UUID,
PROP_UUID,
PROP_DO_INITIAL_INDEX,
PROP_PATH, PROP_PATH,
PROP_ARTIST, PROP_ARTIST,
PROP_ALBUM, 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->set_property = koto_indexed_file_set_property;
gobject_class->get_property = koto_indexed_file_get_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( props[PROP_PATH] = g_param_spec_string(
"path", "path",
"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); KotoIndexedFile *self = KOTO_INDEXED_FILE(obj);
switch (prop_id) { 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: case PROP_PATH:
g_value_set_string(val, self->path); g_value_set_string(val, self->path);
break; 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); KotoIndexedFile *self = KOTO_INDEXED_FILE(obj);
switch (prop_id) { 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: case PROP_PATH:
koto_indexed_file_update_path(self, g_value_get_string(val)); // Update the path koto_indexed_file_update_path(self, g_value_get_string(val)); // Update the path
break; 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) { void koto_indexed_file_parse_name(KotoIndexedFile *self) {
gchar *copied_file_name = g_strdelimit(g_strdup(self->file_name), "_", ' '); // Replace _ with whitespace for starters 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]); g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PATH]);
} }
KotoIndexedFile* koto_indexed_file_new(const gchar *path, guint *cd) { KotoIndexedFile* koto_indexed_file_new(KotoIndexedAlbum *album, const gchar *path, guint *cd) {
return g_object_new(KOTO_TYPE_INDEXED_FILE, 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, "path", path,
"cd", cd, "cd", cd,
NULL 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
);
} }

View file

@ -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 <glib-2.0/glib-object.h>
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

105
src/indexer/structs.h Normal file
View file

@ -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 <glib-2.0/glib-object.h>
#include <magic.h>
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

View file

@ -19,7 +19,7 @@
#include <glib-2.0/glib-object.h> #include <glib-2.0/glib-object.h>
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "indexer/file.h" #include "indexer/structs.h"
G_BEGIN_DECLS G_BEGIN_DECLS

View file

@ -16,7 +16,7 @@
*/ */
#include <gtk-4.0/gdk/x11/gdkx.h> #include <gtk-4.0/gdk/x11/gdkx.h>
#include "indexer/file-indexer.h" #include "indexer/structs.h"
#include "pages/music/music-local.h" #include "pages/music/music-local.h"
#include "koto-config.h" #include "koto-config.h"
#include "koto-nav.h" #include "koto-nav.h"

View file

@ -17,8 +17,7 @@
#include <glib-2.0/glib.h> #include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "../../indexer/album.h" #include "../../indexer/structs.h"
#include "../../indexer/artist.h"
#include "album-view.h" #include "album-view.h"
#include "disc-view.h" #include "disc-view.h"
#include "koto-config.h" #include "koto-config.h"

View file

@ -19,8 +19,7 @@
#include <glib-2.0/glib-object.h> #include <glib-2.0/glib-object.h>
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "../../indexer/album.h" #include "../../indexer/structs.h"
#include "../../indexer/artist.h"
G_BEGIN_DECLS G_BEGIN_DECLS

View file

@ -17,8 +17,7 @@
#include <glib-2.0/glib.h> #include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "../../indexer/album.h" #include "../../indexer/structs.h"
#include "../../indexer/artist.h"
#include "album-view.h" #include "album-view.h"
#include "artist-view.h" #include "artist-view.h"
#include "koto-config.h" #include "koto-config.h"

View file

@ -19,8 +19,7 @@
#include <glib-2.0/glib-object.h> #include <glib-2.0/glib-object.h>
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "../../indexer/album.h" #include "../../indexer/structs.h"
#include "../../indexer/artist.h"
G_BEGIN_DECLS G_BEGIN_DECLS

View file

@ -16,7 +16,7 @@
*/ */
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "../../indexer/album.h" #include "../../indexer/structs.h"
#include "../../koto-track-item.h" #include "../../koto-track-item.h"
#include "disc-view.h" #include "disc-view.h"

View file

@ -19,7 +19,7 @@
#include <glib-2.0/glib-object.h> #include <glib-2.0/glib-object.h>
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "../../indexer/album.h" #include "../../indexer/structs.h"
G_BEGIN_DECLS G_BEGIN_DECLS

View file

@ -17,7 +17,7 @@
#include <glib-2.0/glib.h> #include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "../../indexer/file-indexer.h" #include "../../indexer/structs.h"
#include "koto-button.h" #include "koto-button.h"
#include "koto-config.h" #include "koto-config.h"
#include "music-local.h" #include "music-local.h"

View file

@ -19,8 +19,7 @@
#include <glib-2.0/glib-object.h> #include <glib-2.0/glib-object.h>
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "../../indexer/artist.h" #include "../../indexer/structs.h"
#include "../../indexer/file-indexer.h"
#include "artist-view.h" #include "artist-view.h"
G_BEGIN_DECLS G_BEGIN_DECLS