Implemented clearer database functions and response codes so we know when to do library indexing.

Implement reading of artists, albums, and tracks. Fixed a bug related to unnecessary file name parsing. Fix SIGSEGV related to file name parsing and koto_utils_get_filename_without_extension.

Implemented an unquote string function for use when reading database entries. Try to fix some weird random rendering issues. Changed where we are doing some KotoPageMusicLocal widget construction.
This commit is contained in:
Joshua Strobl 2021-03-09 11:45:44 +02:00
parent 7566fb39f9
commit 35762ca233
12 changed files with 346 additions and 167 deletions

View file

@ -22,13 +22,19 @@
#include <unistd.h>
#include "db.h"
int KOTO_DB_SUCCESS = 0;
int KOTO_DB_NEW = 1;
int KOTO_DB_FAIL = 2;
sqlite3 *koto_db = NULL;
gchar *db_filepath = NULL;
gboolean created_new_db = FALSE;
void close_db() {
sqlite3_close(koto_db);
}
void create_db_tables() {
int create_db_tables() {
char *tables_creation_queries = "CREATE TABLE IF NOT EXISTS artists(id string UNIQUE PRIMARY KEY, path string, type int, name string, art_path string);"
"CREATE TABLE IF NOT EXISTS albums(id string UNIQUE PRIMARY KEY, path string, artist_id string, name string, art_path string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE PRIMARY KEY, path string, type int, artist_id string, album_id string, file_name string, name string, disc int, position int, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);";
@ -39,9 +45,11 @@ void create_db_tables() {
if (rc != SQLITE_OK) {
g_critical("Failed to create required tables: %s", create_tables_errmsg);
}
return (rc == SQLITE_OK) ? KOTO_DB_SUCCESS : KOTO_DB_FAIL;
}
void enable_foreign_keys() {
int enable_foreign_keys() {
gchar *enable_foreign_keys_err = NULL;
int rc = sqlite3_exec(koto_db, "PRAGMA foreign_keys = ON;", 0, 0, &enable_foreign_keys_err);
@ -50,24 +58,55 @@ void enable_foreign_keys() {
}
g_free(enable_foreign_keys_err);
return (rc == SQLITE_OK) ? KOTO_DB_SUCCESS : KOTO_DB_FAIL;
}
void open_db() {
const gchar *data_home = g_get_user_data_dir();
gchar *data_dir = g_build_path(G_DIR_SEPARATOR_S, data_home, "com.github.joshstrobl.koto", NULL);
mkdir(data_home, 0755);
mkdir(data_dir, 0755);
chown(data_dir, getuid(), getgid());
gchar *db_path = g_build_filename(data_dir, "db", NULL); // Build out our path using XDG_DATA_HOME (e.g. .local/share/) + our namespace + db as the file name
int rc = sqlite3_open(db_path, &koto_db);
if (rc) {
g_critical("Failed to open or create database: %s", sqlite3_errmsg(koto_db));
return;
gchar* get_db_path() {
if (db_filepath == NULL) {
const gchar *data_home = g_get_user_data_dir();
gchar *data_dir = g_build_path(G_DIR_SEPARATOR_S, data_home, "com.github.joshstrobl.koto", NULL);
db_filepath = g_build_filename(g_strdup(data_dir), "db", NULL); // Build out our path using XDG_DATA_HOME (e.g. .local/share/) + our namespace + db as the file name
g_free(data_dir);
}
enable_foreign_keys(); // Enable FOREIGN KEY support
create_db_tables(); // Attempt to create our database tables
return db_filepath;
}
int have_existing_db() {
struct stat db_stat;
int success = stat(get_db_path(), &db_stat);
return ((success == 0) && S_ISREG(db_stat.st_mode)) ? 0 : 1;
}
int open_db() {
int ret = KOTO_DB_SUCCESS; // Default to last return being SUCCESS
if (have_existing_db() == 1) { // If we do not have an existing DB
const gchar *data_home = g_get_user_data_dir();
const gchar *data_dir = g_path_get_dirname(db_filepath);
mkdir(data_home, 0755);
mkdir(data_dir, 0755);
chown(data_dir, getuid(), getgid());
ret = KOTO_DB_NEW;
}
if (sqlite3_open(db_filepath, &koto_db) != KOTO_DB_SUCCESS) { // If we failed to open the database file
g_critical("Failed to open or create database: %s", sqlite3_errmsg(koto_db));
return KOTO_DB_FAIL;
}
if (enable_foreign_keys() != KOTO_DB_SUCCESS) { // If we failed to enable foreign keys
return KOTO_DB_FAIL;
}
if (create_db_tables() != KOTO_DB_SUCCESS) {// Failed to create our database tables
return KOTO_DB_FAIL;
}
if (ret == KOTO_DB_NEW) {
created_new_db = TRUE;
}
return ret;
}

View file

@ -15,9 +15,17 @@
* limitations under the License.
*/
#include <glib-2.0/glib.h>
#include <sqlite3.h>
void close_db();
void enable_foreign_keys();
void open_db();
extern int KOTO_DB_SUCCESS;
extern int KOTO_DB_NEW;
extern int KOTO_DB_FAIL;
extern gboolean created_new_db;
void close_db();
int create_db_tables();
gchar* get_db_path();
int enable_foreign_keys();
int have_existing_db();
int open_db();

View file

@ -156,8 +156,6 @@ void koto_indexed_album_commit(KotoIndexedAlbum *self) {
self->art_path
);
g_debug("INSERT query for album: %s", commit_op);
gchar *commit_op_errmsg = NULL;
int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_op_errmsg);
@ -372,7 +370,6 @@ void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album
self->art_path = g_strdup(album_art);
g_message("Set album art to %s", album_art);
self->has_album_art = TRUE;
}
@ -478,7 +475,7 @@ KotoIndexedAlbum* koto_indexed_album_new(KotoIndexedArtist *artist, const gchar
KotoIndexedAlbum* koto_indexed_album_new_with_uuid(KotoIndexedArtist *artist, const gchar *uuid) {
return g_object_new(KOTO_TYPE_INDEXED_ALBUM,
"artist", artist,
"uuid", uuid,
"uuid", g_strdup(uuid),
"do-initial-index", FALSE,
NULL
);

View file

@ -95,8 +95,6 @@ void koto_indexed_artist_commit(KotoIndexedArtist *self) {
self->artist_name
);
g_debug("INSERT query for artist: %s", commit_op);
gchar *commit_opt_errmsg = NULL;
int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_opt_errmsg);
@ -161,20 +159,20 @@ void koto_indexed_artist_add_album(KotoIndexedArtist *self, KotoIndexedAlbum *al
self->albums = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
}
gchar *album_name;
g_object_get(album, "name", &album_name, NULL);
gchar *album_uuid;
g_object_get(album, "uuid", &album_uuid, NULL);
if (album_name == NULL) {
g_free(album_name);
if (album_uuid == NULL) {
g_free(album_uuid);
return;
}
if (g_hash_table_contains(self->albums, album_name)) { // If we have this album already
g_free(album_name);
if (g_hash_table_contains(self->albums, album_uuid)) { // If we have this album already
g_free(album_uuid);
return;
}
g_hash_table_insert(self->albums, album_name, album); // Add the album
g_hash_table_insert(self->albums, album_uuid, album); // Add the album
}
GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self) {
@ -185,6 +183,14 @@ GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self) {
return g_hash_table_get_values(self->albums);
}
KotoIndexedAlbum* koto_indexed_artist_get_album(KotoIndexedArtist *self, gchar *album_uuid) {
if (self->albums == NULL) {
return NULL;
}
return g_hash_table_lookup(self->albums, album_uuid);
}
void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album) {
if (album == NULL) { // No album defined
return;
@ -194,19 +200,10 @@ void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum
self->albums = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
}
gchar *album_name;
g_object_get(album, "name", &album_name, NULL);
gchar *album_uuid;
g_object_get(album, "uuid", &album_uuid, NULL);
g_hash_table_remove(self->albums, album_name);
}
void koto_indexed_artist_remove_album_by_name(KotoIndexedArtist *self, gchar *album_name) {
if (album_name == NULL) {
return;
}
KotoIndexedAlbum *album = g_hash_table_lookup(self->albums, album_name); // Get the album
koto_indexed_artist_remove_album(self, album);
g_hash_table_remove(self->albums, album_uuid);
}
void koto_indexed_artist_update_path(KotoIndexedArtist *self, const gchar *new_path) {
@ -249,7 +246,7 @@ KotoIndexedArtist* koto_indexed_artist_new(const gchar *path) {
KotoIndexedArtist* koto_indexed_artist_new_with_uuid(const gchar *uuid) {
return g_object_new(KOTO_TYPE_INDEXED_ARTIST,
"uuid", uuid,
"uuid", g_strdup(uuid),
NULL
);
}

View file

@ -20,7 +20,11 @@
#include <stdio.h>
#include <sys/stat.h>
#include <taglib/tag_c.h>
#include "../db/db.h"
#include "structs.h"
#include "../koto-utils.h"
extern sqlite3 *koto_db;
struct _KotoIndexedLibrary {
GObject parent_instance;
@ -79,7 +83,7 @@ void koto_indexed_library_add_artist(KotoIndexedLibrary *self, KotoIndexedArtist
return;
}
g_hash_table_insert(self->music_artists, artist_name, artist); // Add the artist
g_hash_table_insert(self->music_artists, artist_name, artist); // Add the artist by its name (this needs to be done so we can get the artist when doing the depth of 2 indexing for the album)
}
KotoIndexedArtist* koto_indexed_library_get_artist(KotoIndexedLibrary *self, gchar *artist_name) {
@ -131,8 +135,7 @@ static void koto_indexed_library_set_property(GObject *obj, guint prop_id, const
switch (prop_id) {
case PROP_PATH:
self->path = g_strdup(g_value_get_string(val));
start_indexing(self);
koto_indexed_library_set_path(self, g_strdup(g_value_get_string(val)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
@ -140,6 +143,140 @@ static void koto_indexed_library_set_property(GObject *obj, guint prop_id, const
}
}
void koto_indexed_library_set_path(KotoIndexedLibrary *self, gchar *path) {
if (path == NULL) {
return;
}
if (self->path != NULL) {
g_free(self->path);
}
self->path = path;
if (created_new_db) { // Created new database
start_indexing(self); // Start index operation
} else { // Have existing database
read_from_db(self); // Read from the database
}
}
int process_artists(void *data, int num_columns, char **fields, char **column_names) {
(void) num_columns; (void) column_names; // Don't need these
KotoIndexedLibrary *self = (KotoIndexedLibrary*) data;
gchar *artist_uuid = g_strdup(koto_utils_unquote_string(fields[0])); // First column is UUID
gchar *artist_path = g_strdup(koto_utils_unquote_string(fields[1])); // Second column is path
gchar *artist_name = g_strdup(koto_utils_unquote_string(fields[3])); // Fourth column is artist name
KotoIndexedArtist *artist = koto_indexed_artist_new_with_uuid(artist_uuid); // Create our artist with the UUID
g_object_set(artist,
"path", artist_path, // Set path
"name", artist_name, // Set name
NULL);
koto_indexed_library_add_artist(self, artist); // Add the artist
int albums_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM albums WHERE artist_id=\"%s\"", artist_uuid), process_albums, artist, NULL); // Process our albums
if (albums_rc != SQLITE_OK) { // Failed to get our albums
g_critical("Failed to read our albums: %s", sqlite3_errmsg(koto_db));
return 1;
}
g_free(artist_uuid);
g_free(artist_path);
g_free(artist_name);
return 0;
}
int process_albums(void *data, int num_columns, char **fields, char **column_names) {
(void) num_columns; (void) column_names; // Don't need these
KotoIndexedArtist *artist = (KotoIndexedArtist*) data;
gchar *album_uuid = g_strdup(koto_utils_unquote_string(fields[0]));
gchar *path = g_strdup(koto_utils_unquote_string(fields[1]));
gchar *artist_uuid = g_strdup(koto_utils_unquote_string(fields[2]));
gchar *album_name = g_strdup(koto_utils_unquote_string(fields[3]));
gchar *album_art = (fields[4] != NULL) ? g_strdup(koto_utils_unquote_string(fields[4])) : NULL;
KotoIndexedAlbum *album = koto_indexed_album_new_with_uuid(artist, album_uuid); // Create our album
g_object_set(album,
"path", path, // Set the path
"name", album_name, // Set name
"art-path", album_art, // Set art path if any
NULL);
koto_indexed_artist_add_album(artist, album); // Add the album
int tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM tracks WHERE album_id=\"%s\"", album_uuid), process_tracks, album, NULL); // Process our tracks
if (tracks_rc != SQLITE_OK) { // Failed to get our tracks
g_critical("Failed to read our tracks: %s", sqlite3_errmsg(koto_db));
return 1;
}
g_free(album_uuid);
g_free(path);
g_free(artist_uuid);
g_free(album_name);
if (album_art != NULL) {
g_free(album_art);
}
return 0;
}
int process_tracks(void *data, int num_columns, char **fields, char **column_names) {
(void) num_columns; (void) column_names; // Don't need these
KotoIndexedAlbum *album = (KotoIndexedAlbum*) data;
gchar *track_uuid = g_strdup(koto_utils_unquote_string(fields[0]));
gchar *path = g_strdup(koto_utils_unquote_string(fields[1]));
gchar *artist_uuid = g_strdup(koto_utils_unquote_string(fields[3]));
gchar *album_uuid = g_strdup(koto_utils_unquote_string(fields[4]));
gchar *file_name = g_strdup(koto_utils_unquote_string(fields[5]));
gchar *name = g_strdup(koto_utils_unquote_string(fields[6]));
guint *disc_num = (guint*) g_ascii_strtoull(fields[6], NULL, 10);
guint *position = (guint*) g_ascii_strtoull(fields[7], NULL, 10);
KotoIndexedFile *file = koto_indexed_file_new_with_uuid(track_uuid); // Create our file
g_object_set(file,
"artist-uuid", artist_uuid,
"album-uuid", album_uuid,
"path", path,
"file-name", file_name,
"parsed-name", name,
"cd", disc_num,
"position", position,
NULL);
koto_indexed_album_add_file(album, file); // Add the file
g_free(track_uuid);
g_free(path);
g_free(artist_uuid);
g_free(album_uuid);
g_free(file_name);
g_free(name);
return 0;
}
void read_from_db(KotoIndexedLibrary *self) {
int artists_rc = sqlite3_exec(koto_db, "SELECT * FROM artists", process_artists, self, NULL); // Process our artists
if (artists_rc != SQLITE_OK) { // Failed to get our artists
g_critical("Failed to read our artists: %s", sqlite3_errmsg(koto_db));
return;
}
g_hash_table_foreach(self->music_artists, output_artists, NULL);
}
void start_indexing(KotoIndexedLibrary *self) {
struct stat library_stat;
int success = stat(self->path, &library_stat);
@ -169,6 +306,13 @@ void start_indexing(KotoIndexedLibrary *self) {
g_hash_table_foreach(self->music_artists, output_artists, NULL);
}
KotoIndexedLibrary* koto_indexed_library_new(const gchar *path) {
return g_object_new(KOTO_TYPE_INDEXED_LIBRARY,
"path", path,
NULL
);
}
void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth) {
depth++;
@ -228,8 +372,8 @@ void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data) {
KotoIndexedArtist *artist = (KotoIndexedArtist*) artist_ptr;
gchar *artist_name;
g_object_get(artist, "name", &artist_name, NULL);
g_debug("Artist: %s", artist_name);
GList *albums = koto_indexed_artist_get_albums(artist); // Get the albums for this artist
if (albums != NULL) {
@ -267,10 +411,3 @@ void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data) {
}
}
}
KotoIndexedLibrary* koto_indexed_library_new(const gchar *path) {
return g_object_new(KOTO_TYPE_INDEXED_LIBRARY,
"path", path,
NULL
);
}

View file

@ -49,9 +49,9 @@ enum {
PROP_ALBUM_UUID,
PROP_UUID,
PROP_DO_INITIAL_INDEX,
PROP_PATH,
PROP_ARTIST,
PROP_ALBUM,
PROP_PATH,
PROP_FILE_NAME,
PROP_PARSED_NAME,
PROP_CD,
@ -182,15 +182,15 @@ static void koto_indexed_file_get_property(GObject *obj, guint prop_id, GValue *
case PROP_UUID:
g_value_set_string(val, self->uuid);
break;
case PROP_PATH:
g_value_set_string(val, self->path);
break;
case PROP_ARTIST:
g_value_set_string(val, self->artist);
break;
case PROP_ALBUM:
g_value_set_string(val, self->album);
break;
case PROP_PATH:
g_value_set_string(val, self->path);
break;
case PROP_FILE_NAME:
g_value_set_string(val, self->file_name);
break;
@ -228,15 +228,15 @@ static void koto_indexed_file_set_property(GObject *obj, guint prop_id, const GV
case PROP_DO_INITIAL_INDEX:
self->do_initial_index = g_value_get_boolean(val);
break;
case PROP_PATH:
koto_indexed_file_update_path(self, g_value_get_string(val)); // Update the path
break;
case PROP_ARTIST:
self->artist = g_strdup(g_value_get_string(val));
break;
case PROP_ALBUM:
self->album = g_strdup(g_value_get_string(val));
break;
case PROP_PATH:
koto_indexed_file_update_path(self, g_value_get_string(val)); // Update the path
break;
case PROP_FILE_NAME:
koto_indexed_file_set_file_name(self, g_strdup(g_value_get_string(val))); // Update the file name
break;
@ -279,8 +279,6 @@ void koto_indexed_file_commit(KotoIndexedFile *self) {
GPOINTER_TO_INT((int*) self->position)
);
g_debug("INSERT query for file: %s", commit_op);
gchar *commit_op_errmsg = NULL;
int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_op_errmsg);
@ -299,34 +297,16 @@ void koto_indexed_file_parse_name(KotoIndexedFile *self) {
gchar **split = g_strsplit(copied_file_name, self->artist, -1); // Split whenever we encounter the artist
copied_file_name = g_strjoinv("", split); // Remove the artist
g_strfreev(split);
}
if (self->artist != NULL && strcmp(self->album, "") != 0) { // If we have album set
gchar **split = g_strsplit(copied_file_name, self->album, -1); // Split whenever we encounter the album
copied_file_name = g_strjoinv("", split); // Remove the album
split = g_strsplit(copied_file_name, g_utf8_strdown(self->artist, -1), -1); // Lowercase album name and split by that
copied_file_name = g_strjoinv("", split); // Remove the artist
g_strfreev(split);
split = g_strsplit(copied_file_name, g_utf8_strdown(self->album, g_utf8_strlen(self->album, -1)), -1); // Split whenever we encounter the album lowercased
copied_file_name = g_strjoin("", split, NULL); // Remove the lowercased album
g_strfreev(split);
}
if (g_str_match_string(copied_file_name, "-", FALSE)) { // Contains - like a heathen
gchar **split = g_strsplit(copied_file_name, " - ", -1); // Split whenever we encounter " - "
copied_file_name = g_strjoin("", split, NULL); // Remove entirely
g_strfreev(split);
if (g_str_match_string(copied_file_name, "-", FALSE)) { // If we have any stragglers
split = g_strsplit(copied_file_name, "-", -1); // Split whenever we encounter -
copied_file_name = g_strjoin("", split, NULL); // Remove entirely
g_strfreev(split);
}
}
gchar *file_without_ext = koto_utils_get_filename_without_extension(copied_file_name);
g_free(copied_file_name);
gchar **split = g_regex_split_simple("^([\\d]+)\\s", file_without_ext, G_REGEX_JAVASCRIPT_COMPAT, 0);
gchar **split = g_regex_split_simple("^([\\d]+)", file_without_ext, G_REGEX_JAVASCRIPT_COMPAT, 0);
if (g_strv_length(split) > 1) { // Has positional info at the beginning of the file
gchar *num = split[1];
@ -347,6 +327,14 @@ void koto_indexed_file_parse_name(KotoIndexedFile *self) {
g_strfreev(split);
split = g_strsplit(file_without_ext, " - ", -1); // Split whenever we encounter " - "
file_without_ext = g_strjoinv("", split); // Remove entirely
g_strfreev(split);
split = g_strsplit(file_without_ext, "-", -1); // Split whenever we encounter -
file_without_ext = g_strjoinv("", split); // Remove entirely
g_strfreev(split);
koto_indexed_file_set_parsed_name(self, file_without_ext);
g_free(file_without_ext);
}
@ -367,7 +355,7 @@ void koto_indexed_file_set_file_name(KotoIndexedFile *self, gchar *new_file_name
self->file_name = g_strdup(new_file_name);
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_FILE_NAME]);
if (!self->acquired_metadata_from_id3) { // Haven't acquired our information from ID3
if (!self->acquired_metadata_from_id3 && self->do_initial_index) { // Haven't acquired our information from ID3
koto_indexed_file_parse_name(self); // Update our parsed name
}
}
@ -417,6 +405,8 @@ void koto_indexed_file_update_metadata(KotoIndexedFile *self) {
self->artist = g_strdup(taglib_tag_artist((tag))); // Set the artist
self->album = g_strdup(taglib_tag_album(tag)); // Set the album
koto_indexed_file_set_position(self, (uint) taglib_tag_track(tag)); // Get the track, convert to uint and cast as a pointer
} else {
koto_indexed_file_set_file_name(self, g_path_get_basename(self->path)); // Update our file name
}
taglib_tag_free_strings(); // Free strings
@ -433,8 +423,10 @@ void koto_indexed_file_update_path(KotoIndexedFile *self, const gchar *new_path)
}
self->path = g_strdup(new_path); // Duplicate the path and set it
koto_indexed_file_update_metadata(self); // Attempt to get ID3 info
koto_indexed_file_set_file_name(self, g_path_get_basename(self->path)); // Update our file name
if (self->do_initial_index) {
koto_indexed_file_update_metadata(self); // Attempt to get ID3 info
}
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PATH]);
}
@ -443,15 +435,27 @@ KotoIndexedFile* koto_indexed_file_new(KotoIndexedAlbum *album, const gchar *pat
KotoIndexedArtist *artist;
gchar *artist_uuid;
gchar *artist_name;
gchar *album_uuid;
gchar *album_name;
g_object_get(album, "artist", &artist, NULL); // Get the artist from our Album
g_object_get(artist, "uuid", &artist_uuid, NULL); // Get the artist UUID from the artist
g_object_get(album, "uuid", &album_uuid, NULL); // Get the album UUID from the album
g_object_get(artist,
"name", &artist_name,
"uuid", &artist_uuid, // Get the artist UUID from the artist
NULL);
g_object_get(album,
"name", &album_name,
"uuid", &album_uuid, // Get the album UUID from the album
NULL);
KotoIndexedFile *file = g_object_new(KOTO_TYPE_INDEXED_FILE,
"artist-uuid", artist_uuid,
"album-uuid", album_uuid,
"artist", artist_name,
"album", album_name,
"do-initial-index", TRUE,
"uuid", g_uuid_string_random(),
"path", path,
"cd", cd,
@ -464,7 +468,7 @@ KotoIndexedFile* koto_indexed_file_new(KotoIndexedAlbum *album, const gchar *pat
KotoIndexedFile* koto_indexed_file_new_with_uuid(const gchar *uuid) {
return g_object_new(KOTO_TYPE_INDEXED_FILE,
"uuid", uuid,
"uuid", g_strdup(uuid),
NULL
);
}

View file

@ -47,6 +47,11 @@ void koto_indexed_library_add_artist(KotoIndexedLibrary *self, KotoIndexedArtist
KotoIndexedArtist* koto_indexed_library_get_artist(KotoIndexedLibrary *self, gchar* artist_name);
GHashTable* koto_indexed_library_get_artists(KotoIndexedLibrary *self);
void koto_indexed_library_remove_artist(KotoIndexedLibrary *self, KotoIndexedArtist *artist);
void koto_indexed_library_set_path(KotoIndexedLibrary *self, gchar *path);
int process_artists(void *data, int num_columns, char **fields, char **column_names);
int process_albums(void *data, int num_columns, char **fields, char **column_names);
int process_tracks(void *data, int num_columns, char **fields, char **column_names);
void read_from_db(KotoIndexedLibrary *self);
void start_indexing(KotoIndexedLibrary *self);
void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth);
@ -61,6 +66,7 @@ void koto_indexed_artist_add_album(KotoIndexedArtist *self, KotoIndexedAlbum *al
void koto_indexed_artist_commit(KotoIndexedArtist *self);
guint koto_indexed_artist_find_album_with_name(gconstpointer *album_data, gconstpointer *album_name_data);
GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self);
KotoIndexedAlbum* koto_indexed_artist_get_album(KotoIndexedArtist *self, gchar *album_uuid);
void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album);
void koto_indexed_artist_remove_album_by_name(KotoIndexedArtist *self, gchar *album_name);
void koto_indexed_artist_set_artist_name(KotoIndexedArtist *self, const gchar *artist_name);

View file

@ -54,7 +54,7 @@ gchar* koto_utils_get_filename_without_extension(gchar *filename) {
} else {
gchar *tmp_copy = g_strdup(new_parsed_name);
g_free(new_parsed_name); // Free the old
new_parsed_name = g_strdup(g_strjoin(".", tmp_copy, split[i], NULL)); // Join the two strings with a . again and duplicate it, setting it to our new_parsed_name
new_parsed_name = g_strjoin(".", tmp_copy, split[i], NULL); // Join the two strings with a . again and duplicate it, setting it to our new_parsed_name
g_free(tmp_copy); // Free our temporary copy
}
}
@ -67,3 +67,19 @@ gchar* koto_utils_get_filename_without_extension(gchar *filename) {
g_free(trimmed_file_name);
return stripped_file_name;
}
gchar* koto_utils_unquote_string(gchar *s) {
gchar *new_s = NULL;
if (g_str_has_prefix(s, "'") && g_str_has_suffix(s, "'")) { // Begins and ends with '
new_s = g_utf8_substring(s, 1, g_utf8_strlen(s, -1)-1); // Start at 1 and end at n-1
} else {
new_s = g_strdup(s);
}
gchar **split_on_double_single = g_strsplit(new_s, "''", -1); // Split on instances of ''
new_s = g_strjoinv("'", split_on_double_single); // Rejoin as '
g_strfreev(split_on_double_single); // Free our array
return new_s;
}

View file

@ -23,5 +23,6 @@ G_BEGIN_DECLS
GtkWidget* koto_utils_create_image_from_filepath(gchar *filepath, gchar *fallback_icon, guint width, guint height);
gchar* koto_utils_get_filename_without_extension(gchar *filename);
gchar* koto_utils_unquote_string(gchar *s);
G_END_DECLS

View file

@ -73,6 +73,8 @@ static void koto_window_init (KotoWindow *self) {
if (GTK_IS_STACK(self->pages)) { // Created our stack successfully
gtk_stack_set_transition_type(GTK_STACK(self->pages), GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT);
gtk_box_append(GTK_BOX(self->content_layout), self->pages);
gtk_widget_set_hexpand(self->pages, TRUE);
gtk_widget_set_vexpand(self->pages, TRUE);
}
gtk_box_prepend(GTK_BOX(self->primary_layout), self->content_layout);
@ -87,13 +89,12 @@ static void koto_window_init (KotoWindow *self) {
gtk_window_set_child(GTK_WINDOW(self), self->primary_layout);
#ifdef GDK_WINDOWING_X11
set_optimal_default_window_size(self);
#else
gtk_widget_set_size_request(GTK_WIDGET(self), 1200, 675);
#endif
gtk_window_set_title(GTK_WINDOW(self), "Koto");
gtk_window_set_icon_name(GTK_WINDOW(self), "audio-headphones");
gtk_window_set_startup_id(GTK_WINDOW(self), "com.github.joshstrobl.koto");
gtk_widget_queue_draw(self->content_layout);
g_thread_new("load-library", (void*) load_library, self);
}
@ -124,13 +125,14 @@ void load_library(KotoWindow *self) {
self->library = lib;
KotoPageMusicLocal* l = koto_page_music_local_new();
if (GTK_IS_WIDGET(l)) { // Created our local library page
koto_page_music_local_set_library(l, self->library);
gtk_stack_add_named(GTK_STACK(self->pages), GTK_WIDGET(l), "music.local");
// TODO: Remove and do some fancy state loading
gtk_stack_set_visible_child_name(GTK_STACK(self->pages), "music.local");
}
// TODO: Remove and do some fancy state loading
gtk_stack_add_named(GTK_STACK(self->pages), GTK_WIDGET(l), "music.local");
gtk_stack_set_visible_child_name(GTK_STACK(self->pages), "music.local");
gtk_widget_show(self->pages); // Do not remove this. Will cause sporadic hiding of the local page content otherwise.
koto_page_music_local_set_library(l, self->library);
}
g_thread_exit(0);
}
void set_optimal_default_window_size(KotoWindow *self) {

View file

@ -30,7 +30,7 @@ static void on_activate (GtkApplication *app) {
window = gtk_application_get_active_window (app);
if (window == NULL) {
window = g_object_new(KOTO_TYPE_WINDOW, "application", app, "default-width", 600, "default-height", 300, NULL);
window = g_object_new(KOTO_TYPE_WINDOW, "application", app, "default-width", 1200, "default-height", 675, NULL);
}
gtk_window_present(window);
@ -38,11 +38,6 @@ static void on_activate (GtkApplication *app) {
static void on_shutdown(GtkApplication *app) {
(void) app;
if (koto_db != NULL) {
g_message("Have a db?");
}
close_db(); // Close the database
}

View file

@ -97,12 +97,35 @@ static void koto_page_music_local_set_property(GObject *obj, guint prop_id, cons
static void koto_page_music_local_init(KotoPageMusicLocal *self) {
self->lib = NULL;
self->constructed = FALSE;
gtk_widget_add_css_class(GTK_WIDGET(self), "page-music-local");
gtk_widget_set_hexpand(GTK_WIDGET(self), TRUE);
gtk_widget_set_vexpand(GTK_WIDGET(self), TRUE);
self->scrolled_window = gtk_scrolled_window_new();
gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(self->scrolled_window), 300);
gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(self->scrolled_window), FALSE);
gtk_widget_add_css_class(self->scrolled_window, "artist-list");
gtk_widget_set_vexpand(self->scrolled_window, TRUE); // Expand our scrolled window
gtk_box_prepend(GTK_BOX(self), self->scrolled_window);
self->artist_list = gtk_list_box_new(); // Create our artist list
g_signal_connect(GTK_LIST_BOX(self->artist_list), "row-activated", G_CALLBACK(koto_page_music_local_handle_artist_click), self);
gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(self->artist_list), TRUE);
gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->artist_list), GTK_SELECTION_BROWSE);
gtk_list_box_set_sort_func(GTK_LIST_BOX(self->artist_list), koto_page_music_local_sort_artists, NULL, NULL); // Add our sort function
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_window), self->artist_list);
self->stack = gtk_stack_new(); // Create a new stack
gtk_stack_set_transition_duration(GTK_STACK(self->stack), 400);
gtk_stack_set_transition_type(GTK_STACK(self->stack), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
gtk_widget_set_hexpand(self->stack, TRUE);
gtk_widget_set_vexpand(self->stack, TRUE);
gtk_box_append(GTK_BOX(self), self->stack);
}
static void koto_page_music_local_constructed(GObject *obj) {
KotoPageMusicLocal *self = KOTO_PAGE_MUSIC_LOCAL(obj);
gtk_widget_add_css_class(GTK_WIDGET(self), "page-music-local");
gtk_widget_set_hexpand(GTK_WIDGET(self), TRUE);
G_OBJECT_CLASS (koto_page_music_local_parent_class)->constructed (obj);
self->constructed = TRUE;
@ -144,63 +167,17 @@ void koto_page_music_local_set_library(KotoPageMusicLocal *self, KotoIndexedLibr
return;
}
if (!GTK_IS_SCROLLED_WINDOW(self->scrolled_window)) {
self->scrolled_window = gtk_scrolled_window_new();
gtk_widget_add_css_class(self->scrolled_window, "artist-list");
gtk_box_prepend(GTK_BOX(self), self->scrolled_window);
GHashTableIter artist_list_iter;
gpointer artist_key;
gpointer artist_data;
GHashTable *artists = koto_indexed_library_get_artists(self->lib); // Get the artists
g_hash_table_iter_init(&artist_list_iter, artists);
while (g_hash_table_iter_next(&artist_list_iter, &artist_key, &artist_data)) { // For each of the music artists
KotoIndexedArtist *artist = (KotoIndexedArtist*) artist_data; // Cast our data as a KotoIndexedArtist
koto_page_music_local_add_artist(self, artist);
}
if (GTK_IS_LIST_BOX(self->artist_list)) { // artist list is a list box
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_window), NULL); // Set to null to maybe clear?
g_object_unref(self->artist_list); // Unref
}
if (GTK_IS_STACK(self->stack)) { // Stack is a Stack
gtk_box_remove(GTK_BOX(self), GTK_WIDGET(self->stack)); // Destroy to free references
}
self->artist_list = gtk_list_box_new(); // Create our artist list
gboolean list_created = GTK_IS_LIST_BOX(self->artist_list);
if (list_created) { // Successfully created our list
g_signal_connect(GTK_LIST_BOX(self->artist_list), "row-activated", G_CALLBACK(koto_page_music_local_handle_artist_click), self);
gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(self->artist_list), TRUE);
gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->artist_list), GTK_SELECTION_BROWSE);
gtk_list_box_set_sort_func(GTK_LIST_BOX(self->artist_list), koto_page_music_local_sort_artists, NULL, NULL); // Add our sort function
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_window), self->artist_list);
gtk_widget_show(GTK_WIDGET(self->artist_list));
}
gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(self->scrolled_window), 300);
gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(self->scrolled_window), FALSE);
gtk_widget_set_size_request(GTK_WIDGET(self->artist_list), 300, -1);
self->stack = gtk_stack_new(); // Create a new stack
gtk_stack_set_transition_duration(GTK_STACK(self->stack), 400);
gtk_stack_set_transition_type(GTK_STACK(self->stack), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
gtk_widget_set_hexpand(self->stack, TRUE);
gboolean stack_created = GTK_IS_STACK(self->stack);
if (list_created && stack_created) {
GHashTableIter artist_list_iter;
gpointer artist_key;
gpointer artist_data;
GHashTable *artists = koto_indexed_library_get_artists(self->lib); // Get the artists
g_hash_table_iter_init(&artist_list_iter, artists);
while (g_hash_table_iter_next(&artist_list_iter, &artist_key, &artist_data)) { // For each of the music artists
KotoIndexedArtist *artist = (KotoIndexedArtist*) artist_data; // Cast our data as a KotoIndexedArtist
koto_page_music_local_add_artist(self, artist);
}
}
if (stack_created) { // Successfully created our stack
gtk_box_append(GTK_BOX(self), self->stack);
}
gtk_widget_show(GTK_WIDGET(self));
}
int koto_page_music_local_sort_artists(GtkListBoxRow *artist1, GtkListBoxRow *artist2, gpointer user_data) {