diff --git a/src/config/config.c b/src/config/config.c index b441cc3..21624b8 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -506,7 +506,12 @@ void koto_config_save(KotoConfig * self) { gchar * playback_hash = g_strdup("playback"); gchar * ui_hash = g_strdup("ui"); - gdouble current_playback_volume = koto_playback_engine_get_volume(playback_engine); // Get the last used volume in the playback engine + gdouble current_playback_volume = 1.0; + + if (KOTO_IS_PLAYBACK_ENGINE(playback_engine)) { // Have a playback engine (useful since it may not be initialized before the config performs saving on first application load) + current_playback_volume = koto_playback_engine_get_volume(playback_engine); // Get the last used volume in the playback engine + } + self->playback_last_used_volume = current_playback_volume; // Update our value so we have it during save int i; diff --git a/src/db/db.c b/src/db/db.c index 509feef..1c40b82 100644 --- a/src/db/db.c +++ b/src/db/db.c @@ -39,8 +39,8 @@ void close_db() { int create_db_tables() { gchar * tables_creation_queries = "CREATE TABLE IF NOT EXISTS artists(id string UNIQUE PRIMARY KEY, name string, art_path string);" - "CREATE TABLE IF NOT EXISTS albums(id string UNIQUE PRIMARY KEY, 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, artist_id string, album_id string, name string, disc int, position int, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);" + "CREATE TABLE IF NOT EXISTS albums(id string UNIQUE PRIMARY KEY, artist_id string, name string, art_path string, genres string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);" + "CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE PRIMARY KEY, artist_id string, album_id string, name string, disc int, position int, duration int, genres string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);" "CREATE TABLE IF NOT EXISTS libraries_albums(id string, album_id string, path string, PRIMARY KEY (id, album_id) FOREIGN KEY(album_id) REFERENCES albums(id) ON DELETE CASCADE);" "CREATE TABLE IF NOT EXISTS libraries_artists(id string, artist_id string, path string, PRIMARY KEY(id, artist_id) FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);" "CREATE TABLE IF NOT EXISTS libraries_tracks(id string, track_id string, path string, PRIMARY KEY(id, track_id) FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);" diff --git a/src/db/loaders.c b/src/db/loaders.c index 0bbda88..f12855f 100644 --- a/src/db/loaders.c +++ b/src/db/loaders.c @@ -116,6 +116,7 @@ int process_albums( gchar * artist_uuid = g_strdup(koto_utils_unquote_string(fields[1])); gchar * album_name = g_strdup(koto_utils_unquote_string(fields[2])); gchar * album_art = (fields[3] != NULL) ? g_strdup(koto_utils_unquote_string(fields[3])) : NULL; + gchar * album_genres = (fields[4] != NULL) ? g_strdup(koto_utils_unquote_string(fields[4])) : NULL; KotoAlbum * album = koto_album_new_with_uuid(artist, album_uuid); // Create our album @@ -125,6 +126,8 @@ int process_albums( album_name, // Set name "art-path", album_art, // Set art path if any + "preparsed-genres", + album_genres, NULL ); @@ -134,6 +137,7 @@ int process_albums( g_free(album_uuid); g_free(artist_uuid); g_free(album_name); + g_free(album_genres); if (album_art != NULL) { g_free(album_art); @@ -224,7 +228,9 @@ int process_tracks( gchar * album_uuid = g_strdup(koto_utils_unquote_string(fields[2])); gchar * name = g_strdup(koto_utils_unquote_string(fields[3])); guint * disc_num = (guint*) g_ascii_strtoull(fields[4], NULL, 10); - guint * position = (guint*) g_ascii_strtoull(fields[5], NULL, 10); + guint64 * position = (guint64*) g_ascii_strtoull(fields[5], NULL, 10); + guint64 * duration = (guint64*) g_ascii_strtoull(fields[6], NULL, 10); + gchar * genres = g_strdup(koto_utils_unquote_string(fields[7])); KotoTrack * track = koto_track_new_with_uuid(track_uuid); // Create our file @@ -240,13 +246,22 @@ int process_tracks( disc_num, "position", position, + "duration", + duration, + "preparsed-genres", + genres, NULL ); + g_free(name); + int track_paths = sqlite3_exec(koto_db, g_strdup_printf("SELECT id, path FROM libraries_tracks WHERE track_id=\"%s\"", track_uuid), process_track_paths, track, NULL); // Process all pathes associated with the track if (track_paths != SQLITE_OK) { // Failed to read the paths g_warning("Failed to read paths associated with track %s: %s", track_uuid, sqlite3_errmsg(koto_db)); + g_free(track_uuid); + g_free(artist_uuid); + g_free(album_uuid); return 1; } @@ -264,7 +279,6 @@ int process_tracks( g_free(track_uuid); g_free(artist_uuid); g_free(album_uuid); - g_free(name); return 0; } diff --git a/src/indexer/album.c b/src/indexer/album.c index 884b73d..2865a10 100644 --- a/src/indexer/album.c +++ b/src/indexer/album.c @@ -39,6 +39,7 @@ enum { PROP_ALBUM_NAME, PROP_ART_PATH, PROP_ARTIST_UUID, + PROP_ALBUM_PREPARED_GENRES, N_PROPERTIES }; @@ -64,6 +65,8 @@ struct _KotoAlbum { gchar * art_path; gchar * artist_uuid; + GList * genres; + GList * tracks; GHashTable * paths; @@ -147,6 +150,14 @@ static void koto_album_class_init(KotoAlbumClass * c) { G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE ); + props[PROP_ALBUM_PREPARED_GENRES] = g_param_spec_string( + "preparsed-genres", + "Preparsed Genres", + "Preparsed Genres", + NULL, + G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE + ); + g_object_class_install_properties(gobject_class, N_PROPERTIES, props); album_signals[SIGNAL_TRACK_ADDED] = g_signal_new( @@ -178,6 +189,7 @@ static void koto_album_class_init(KotoAlbumClass * c) { static void koto_album_init(KotoAlbum * self) { self->has_album_art = FALSE; + self->genres = NULL; self->tracks = NULL; self->paths = g_hash_table_new(g_str_hash, g_str_equal); } @@ -196,17 +208,34 @@ void koto_album_add_track( gchar * track_uuid = koto_track_get_uuid(track); - if (g_list_index(self->tracks, track_uuid) == -1) { // Haven't already added the track - koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer if necessary - self->tracks = g_list_insert_sorted_with_data(self->tracks, track_uuid, koto_track_helpers_sort_tracks_by_uuid, NULL); - - g_signal_emit( - self, - album_signals[SIGNAL_TRACK_ADDED], - 0, - track - ); + if (g_list_index(self->tracks, track_uuid) != -1) { // Have added it already + return; } + + koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer if necessary + self->tracks = g_list_insert_sorted_with_data(self->tracks, track_uuid, koto_track_helpers_sort_tracks_by_uuid, NULL); + + GList * track_genres = koto_track_get_genres(track); // Get the genres for the track + GList * current_genre_list; + + gchar * existing_genres_as_string = koto_utils_join_string_list(self->genres, ";"); + + for (current_genre_list = track_genres; current_genre_list != NULL; current_genre_list = current_genre_list->next) { // Iterate over each item in the track genres + gchar * track_genre = current_genre_list->data; // Get this genre + + if (koto_utils_string_contains_substring(existing_genres_as_string, track_genre)) { // Genres list contains this genre + continue; + } + + self->genres = g_list_append(self->genres, g_strdup(track_genre)); // Duplicate the genre and add it to our list + } + + g_signal_emit( + self, + album_signals[SIGNAL_TRACK_ADDED], + 0, + track + ); } void koto_album_commit(KotoAlbum * self) { @@ -214,18 +243,23 @@ void koto_album_commit(KotoAlbum * self) { koto_album_set_album_art(self, ""); // Set to an empty string } + gchar * genres_string = koto_utils_join_string_list(self->genres, ";"); + gchar * commit_op = g_strdup_printf( - "INSERT INTO albums(id, artist_id, name, art_path)" - "VALUES('%s', '%s', quote(\"%s\"), quote(\"%s\"))" - "ON CONFLICT(id) DO UPDATE SET artist_id=excluded.artist_id, name=excluded.name, art_path=excluded.art_path;", + "INSERT INTO albums(id, artist_id, name, art_path, genres)" + "VALUES('%s', '%s', quote(\"%s\"), quote(\"%s\"), '%s')" + "ON CONFLICT(id) DO UPDATE SET artist_id=excluded.artist_id, name=excluded.name, art_path=excluded.art_path, genres=excluded.genres;", self->uuid, self->artist_uuid, self->name, - self->art_path + self->art_path, + genres_string ); new_transaction(commit_op, "Failed to write our album to the database", FALSE); + g_free(genres_string); + GHashTableIter paths_iter; g_hash_table_iter_init(&paths_iter, self->paths); // Create an iterator for our paths gpointer lib_uuid_ptr, album_rel_path_ptr; @@ -302,6 +336,14 @@ void koto_album_find_album_art(KotoAlbum * self) { closedir(dir); } +GList * koto_album_get_genres(KotoAlbum * self) { + if (!KOTO_IS_ALBUM(self)) { // Not an album + return NULL; + } + + return self->genres; +} + static void koto_album_get_property( GObject * obj, guint prop_id, @@ -357,6 +399,9 @@ static void koto_album_set_property( case PROP_ARTIST_UUID: koto_album_set_artist_uuid(self, g_value_get_string(val)); break; + case PROP_ALBUM_PREPARED_GENRES: + koto_album_set_preparsed_genres(self, g_strdup(g_value_get_string(val))); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); break; @@ -573,6 +618,30 @@ void koto_album_set_as_current_playlist(KotoAlbum * self) { koto_current_playlist_set_playlist(current_playlist, new_album_playlist); // Set our new current playlist } +void koto_album_set_preparsed_genres( + KotoAlbum * self, + gchar * genrelist +) { + if (!KOTO_IS_ALBUM(self)) { // Not an album + return; + } + + if (!koto_utils_is_string_valid(genrelist)) { // If it is an empty string + return; + } + + GList * preparsed_genres_list = koto_utils_string_to_string_list(genrelist, ";"); + + if (g_list_length(preparsed_genres_list) == 0) { // No genres + g_list_free(preparsed_genres_list); + return; + } + + // TODO: Do a pass on in first memory optimization phase to ensure string elements are freed. + g_list_free_full(self->genres, NULL); // Free the existing genres list + self->genres = preparsed_genres_list; +}; + KotoAlbum * koto_album_new(gchar * artist_uuid) { if (!koto_utils_is_string_valid(artist_uuid)) { // Invalid artist UUID provided return NULL; diff --git a/src/indexer/file-indexer.c b/src/indexer/file-indexer.c index e916e97..4cfa0b1 100644 --- a/src/indexer/file-indexer.c +++ b/src/indexer/file-indexer.c @@ -72,12 +72,12 @@ void index_folder( KotoAlbum * album = koto_album_new(artist_uuid); koto_album_set_path(album, self, full_path); - koto_album_commit(album); // Save to database immediately koto_cartographer_add_album(koto_maps, album); // Add our album to the cartographer koto_artist_add_album(artist, album); // Add the album index_folder(self, full_path, depth); // Index inside the album + koto_album_commit(album); // Save to database immediately g_free(artist_name); } else if (depth == 3) { // Possibly CD within album gchar ** split = g_strsplit(full_path, G_DIR_SEPARATOR_S, -1); diff --git a/src/indexer/structs.h b/src/indexer/structs.h index d5951ef..c4b3951 100644 --- a/src/indexer/structs.h +++ b/src/indexer/structs.h @@ -250,6 +250,11 @@ void koto_album_set_path( const gchar * fixed_path ); +void koto_album_set_preparsed_genres( + KotoAlbum * self, + gchar * genrelist +); + /** * File / Track Functions **/ @@ -267,13 +272,17 @@ void koto_track_commit(KotoTrack * self); guint koto_track_get_disc_number(KotoTrack * self); +guint64 koto_track_get_duration(KotoTrack * self); + +GList * koto_track_get_genres(KotoTrack * self); + GVariant * koto_track_get_metadata_vardict(KotoTrack * self); gchar * koto_track_get_path(KotoTrack * self); gchar * koto_track_get_name(KotoTrack * self); -guint koto_track_get_position(KotoTrack * self); +guint64 koto_track_get_position(KotoTrack * self); gchar * koto_track_get_uniqueish_key(KotoTrack * self); @@ -305,6 +314,16 @@ void koto_track_set_cd( guint cd ); +void koto_track_set_duration( + KotoTrack * self, + guint64 duration +); + +void koto_track_set_genres( + KotoTrack * self, + char * genrelist +); + void koto_track_set_parsed_name( KotoTrack * self, gchar * new_parsed_name @@ -318,7 +337,12 @@ void koto_track_set_path( void koto_track_set_position( KotoTrack * self, - guint pos + guint64 pos +); + +void koto_track_set_preparsed_genres( + KotoTrack * self, + gchar * genrelist ); void koto_track_update_metadata(KotoTrack * self); diff --git a/src/indexer/track-helpers.c b/src/indexer/track-helpers.c index 0a3c4fb..0a7db6d 100644 --- a/src/indexer/track-helpers.c +++ b/src/indexer/track-helpers.c @@ -24,6 +24,25 @@ extern KotoCartographer * koto_maps; +GHashTable * genre_replacements; + +void koto_track_helpers_init() { + genre_replacements = g_hash_table_new(g_str_hash, g_str_equal); + + gchar * corrected_genre_hiphop = g_strdup("hip-hop"); + gchar * correct_genre_indie = g_strdup("indie"); + gchar * correct_genre_scifi = g_strdup("sci-fi"); + + g_hash_table_insert(genre_replacements, g_strdup("alternative/indie"), correct_genre_indie); // Change Alternative/Indie (lowercased) to indie + g_hash_table_insert(genre_replacements, g_strdup("rap-&-hip-hop"), corrected_genre_hiphop); // Change rap-&-hip-hop to just hip-hop + g_hash_table_insert(genre_replacements, g_strdup("science-fiction"), correct_genre_scifi); // Change science-fiction to sci-fi +} + +gchar * koto_track_helpers_get_corrected_genre(gchar * original_genre) { + gchar * lookedup_genre = g_hash_table_lookup(genre_replacements, original_genre); // Look up the genre + return koto_utils_is_string_valid(lookedup_genre) ? lookedup_genre : original_genre; +} + gchar * koto_track_helpers_get_name_for_file( const gchar * path, gchar * optional_artist_name diff --git a/src/indexer/track-helpers.h b/src/indexer/track-helpers.h index 09bd0fa..299131d 100644 --- a/src/indexer/track-helpers.h +++ b/src/indexer/track-helpers.h @@ -17,6 +17,10 @@ #include +void koto_track_helpers_init(); + +gchar * koto_track_helpers_get_corrected_genre(gchar * original_genre); + gchar * koto_track_helpers_get_name_for_file( const gchar * path, gchar * optional_artist_name diff --git a/src/indexer/track.c b/src/indexer/track.c index 8aa2925..8e5fb1d 100644 --- a/src/indexer/track.c +++ b/src/indexer/track.c @@ -37,8 +37,10 @@ struct _KotoTrack { gchar * parsed_name; guint cd; - guint position; - guint * playback_position; + guint64 position; + guint64 duration; + guint64 * playback_position; + GList * genres; gboolean do_initial_index; }; @@ -54,7 +56,9 @@ enum { PROP_PARSED_NAME, PROP_CD, PROP_POSITION, + PROP_DURATION, PROP_PLAYBACK_POSITION, + PROP_PREPARSED_GENRES, N_PROPERTIES }; @@ -133,31 +137,52 @@ static void koto_track_class_init(KotoTrackClass * c) { G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE ); - props[PROP_POSITION] = g_param_spec_uint( + props[PROP_POSITION] = g_param_spec_uint64( "position", "Position in Audiobook, Album, etc.", "Position in Audiobook, Album, etc.", 0, - G_MAXUINT16, + G_MAXUINT64, 0, G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE ); - props[PROP_PLAYBACK_POSITION] = g_param_spec_uint( + props[PROP_DURATION] = g_param_spec_uint64( + "duration", + "Duration of Track", + "Duration of Track", + 0, + G_MAXUINT64, + 0, + G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE + ); + + props[PROP_PLAYBACK_POSITION] = g_param_spec_uint64( "playback-position", "Current playback position", "Current playback position", 0, - G_MAXUINT16, + G_MAXUINT64, 0, G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE ); + props[PROP_PREPARSED_GENRES] = g_param_spec_string( + "preparsed-genres", + "Preparsed Genres", + "Preparsed Genres", + NULL, + G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE + ); + g_object_class_install_properties(gobject_class, N_PROPERTIES, props); } static void koto_track_init(KotoTrack * self) { + self->duration = 0; // Initialize our duration + self->genres = NULL; // Initialize our genres list self->paths = g_hash_table_new(g_str_hash, g_str_equal); // Create our hash table of paths + self->position = 0; // Initialize our duration } static void koto_track_get_property( @@ -226,10 +251,16 @@ static void koto_track_set_property( koto_track_set_cd(self, g_value_get_uint(val)); break; case PROP_POSITION: - koto_track_set_position(self, g_value_get_uint(val)); + koto_track_set_position(self, g_value_get_uint64(val)); break; case PROP_PLAYBACK_POSITION: - self->playback_position = GUINT_TO_POINTER(g_value_get_uint(val)); + self->playback_position = GUINT_TO_POINTER(g_value_get_uint64(val)); + break; + case PROP_DURATION: + koto_track_set_duration(self, g_value_get_uint64(val)); + break; + case PROP_PREPARSED_GENRES: + koto_track_set_preparsed_genres(self, g_strdup(g_value_get_string(val))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); @@ -250,9 +281,12 @@ void koto_track_commit(KotoTrack * self) { self->album_uuid = g_strdup(""); } - gchar * commit_msg = "INSERT INTO tracks(id, artist_id, album_id, name, disc, position)" \ - "VALUES('%s', '%s', '%s', quote(\"%s\"), %d, %d)" \ - "ON CONFLICT(id) DO UPDATE SET album_id=excluded.album_id, artist_id=excluded.artist_id, name=excluded.name, disc=excluded.disc, position=excluded.position;"; + gchar * commit_msg = "INSERT INTO tracks(id, artist_id, album_id, name, disc, position, duration, genres)" \ + "VALUES('%s', '%s', '%s', quote(\"%s\"), %d, %d, %d, '%s')" \ + "ON CONFLICT(id) DO UPDATE SET album_id=excluded.album_id, artist_id=excluded.artist_id, name=excluded.name, disc=excluded.disc, position=excluded.position, duration=excluded.duration, genres=excluded.genres;"; + + // Combine our list items into a semi-colon separated string + gchar * genres = koto_utils_join_string_list(self->genres, ";"); // Join our GList of strings into a single gchar * commit_op = g_strdup_printf( commit_msg, @@ -261,13 +295,17 @@ void koto_track_commit(KotoTrack * self) { self->album_uuid, g_strescape(self->parsed_name, NULL), (int) self->cd, - (int) self->position + (int) self->position, + (int) self->duration, + genres ); if (new_transaction(commit_op, "Failed to write our file to the database", FALSE) != SQLITE_OK) { - g_message("Failed with song: %s", self->parsed_name); + return; } + g_free(genres); // Free the genres string + GHashTableIter paths_iter; g_hash_table_iter_init(&paths_iter, self->paths); // Create an iterator for our paths gpointer lib_uuid_ptr, track_rel_path_ptr; @@ -292,6 +330,14 @@ guint koto_track_get_disc_number(KotoTrack * self) { return KOTO_IS_TRACK(self) ? self->cd : 1; } +guint64 koto_track_get_duration(KotoTrack * self) { + return KOTO_IS_TRACK(self) ? self->duration : 0; +} + +GList * koto_track_get_genres(KotoTrack * self) { + return KOTO_IS_TRACK(self) ? self->genres : NULL; +} + GVariant * koto_track_get_metadata_vardict(KotoTrack * self) { if (!KOTO_IS_TRACK(self)) { return NULL; @@ -374,7 +420,7 @@ gchar * koto_track_get_path(KotoTrack * self) { return path; } -guint koto_track_get_position(KotoTrack * self) { +guint64 koto_track_get_position(KotoTrack * self) { return KOTO_IS_TRACK(self) ? self->position : 0; } @@ -473,6 +519,10 @@ void koto_track_set_cd( KotoTrack * self, guint cd ) { + if (!KOTO_IS_TRACK(self)) { + return; + } + if (cd == 0) { // No change really return; } @@ -481,10 +531,68 @@ void koto_track_set_cd( g_object_notify_by_pspec(G_OBJECT(self), props[PROP_CD]); } +void koto_track_set_duration( + KotoTrack * self, + guint64 duration +) { + if (!KOTO_IS_TRACK(self)) { + return; + } + + if (duration == 0) { + return; + } + + self->duration = duration; +} + +void koto_track_set_genres( + KotoTrack * self, + char * genrelist +) { + if (!KOTO_IS_TRACK(self)) { + return; + } + + if (!koto_utils_is_string_valid((gchar*) genrelist)) { // If it is an empty string + return; + } + + gchar ** genres = g_strsplit(genrelist, ";", -1); // Split on semicolons for each genre, e.g. Electronic;Rock + guint len = g_strv_length(genres); + + if (len == 0) { // No genres + g_strfreev(genres); // Free the list + return; + } + + for (guint i = 0; i < len; i++) { // Iterate over each item + gchar * genre = genres[i]; // Get the genre + gchar * lowercased_genre = g_utf8_strdown(g_strstrip(genre), -1); // Lowercase the genre + + gchar * lowercased_hyphenated_genre = koto_utils_replace_string_all(lowercased_genre, " ", "-"); + g_free(lowercased_genre); // Free the lowercase genre string since we no longer need it + + gchar * corrected_genre = koto_track_helpers_get_corrected_genre(lowercased_hyphenated_genre); // Get any corrected genre + + if (g_list_index(self->genres, corrected_genre) == -1) { // Don't have this genre added + self->genres = g_list_append(self->genres, g_strdup(corrected_genre)); // Add the genre to our list + } + + g_free(lowercased_hyphenated_genre); // Free our remaining string + } + + g_strfreev(genres); // Free the list of genres locally +} + void koto_track_set_parsed_name( KotoTrack * self, gchar * new_parsed_name ) { + if (!KOTO_IS_TRACK(self)) { + return; + } + if (!koto_utils_is_string_valid(new_parsed_name)) { return; } @@ -529,7 +637,7 @@ void koto_track_set_path( void koto_track_set_position( KotoTrack * self, - guint pos + guint64 pos ) { if (pos == 0) { // No position change really return; @@ -550,7 +658,11 @@ void koto_track_update_metadata(KotoTrack * self) { if ((t_file != NULL) && taglib_file_is_valid(t_file)) { // If we got the taglib file and it is valid TagLib_Tag * tag = taglib_file_tag(t_file); // Get our tag + koto_track_set_genres(self, taglib_tag_genre(tag)); // Set our genres to any genres listed for the track koto_track_set_position(self, (uint) taglib_tag_track(tag)); // Get the track, convert to uint and cast as a pointer + + const TagLib_AudioProperties * tag_props = taglib_file_audioproperties(t_file); // Get the audio properties of the file + koto_track_set_duration(self, taglib_audioproperties_length(tag_props)); // Get the length of the track and set it as our duration } else { // Failed to get tag info guint64 position = koto_track_helpers_get_position_based_on_file_name(g_path_get_basename(optimal_track_path)); // Get the likely position koto_track_set_position(self, position); // Set our position @@ -560,6 +672,30 @@ void koto_track_update_metadata(KotoTrack * self) { taglib_file_free(t_file); // Free the file } +void koto_track_set_preparsed_genres( + KotoTrack * self, + gchar * genrelist +) { + if (!KOTO_IS_TRACK(self)) { + return; + } + + if (!koto_utils_is_string_valid(genrelist)) { // If it is an empty string + return; + } + + GList * preparsed_genres_list = koto_utils_string_to_string_list(genrelist, ";"); + + if (g_list_length(preparsed_genres_list) == 0) { // No genres + g_list_free(preparsed_genres_list); + return; + } + + // TODO: Do a pass on in first memory optimization phase to ensure string elements are freed. + g_list_free_full(self->genres, NULL); // Free the existing genres list + self->genres = preparsed_genres_list; +} + KotoTrack * koto_track_new( const gchar * artist_uuid, const gchar * album_uuid, diff --git a/src/koto-button.h b/src/koto-button.h index aa1fa6f..88825ba 100644 --- a/src/koto-button.h +++ b/src/koto-button.h @@ -133,6 +133,7 @@ void koto_button_show_image( ); void koto_button_unflatten(KotoButton * self); + void koto_button_unset_pseudoactive_styling(KotoButton * self); G_END_DECLS diff --git a/src/koto-expander.c b/src/koto-expander.c index d9595bc..14fb48a 100644 --- a/src/koto-expander.c +++ b/src/koto-expander.c @@ -205,7 +205,7 @@ static void koto_expander_init(KotoExpander * self) { // koto_expander_set_icon_name will set the icon for our inner KotoButton for this Expander void koto_expander_set_icon_name( - KotoExpander *self, + KotoExpander * self, gchar * icon ) { if (!KOTO_IS_EXPANDER(self)) { // Not a KotoExpander diff --git a/src/koto-expander.h b/src/koto-expander.h index 63618fb..48f6b40 100644 --- a/src/koto-expander.h +++ b/src/koto-expander.h @@ -36,7 +36,7 @@ KotoExpander * koto_expander_new_with_button( GtkWidget * koto_expander_get_content(KotoExpander * self); void koto_expander_set_icon_name( - KotoExpander *self, + KotoExpander * self, gchar * icon ); diff --git a/src/koto-utils.c b/src/koto-utils.c index b03a371..5391bf0 100644 --- a/src/koto-utils.c +++ b/src/koto-utils.c @@ -20,6 +20,7 @@ #include #include #include +#include "koto-utils.h" extern GtkWindow * main_window; @@ -102,6 +103,32 @@ gchar * koto_utils_get_filename_without_extension(gchar * filename) { return stripped_file_name; } +gchar * koto_utils_join_string_list ( + GList * list, + gchar * sep +) { + gchar * liststring = NULL; + GList * cur_list; + for (cur_list = list; cur_list != NULL; cur_list = cur_list->next) { // For each item in the list + gchar * current_item = cur_list->data; + if (!koto_utils_is_string_valid(current_item)) { // Not a valid string + continue; + } + + gchar * item_plus_sep = g_strdup_printf("%s%s", current_item, sep); + + if (koto_utils_is_string_valid(liststring)) { // Is a valid string + gchar * new_string = g_strconcat(liststring, item_plus_sep, NULL); + g_free(liststring); + liststring = new_string; + } else { // Don't have any content yet + liststring = item_plus_sep; + } + } + + return (liststring == NULL) ? g_strdup("") : liststring; +} + gboolean koto_utils_is_string_valid(gchar * str) { return ((str != NULL) && (g_strcmp0(str, "") != 0)); } @@ -123,15 +150,42 @@ gchar * koto_utils_replace_string_all( gchar * find, gchar * repl ) { - gchar * cleaned_string = ""; gchar ** split = g_strsplit(str, find, -1); // Split on find - for (guint i = 0; i < g_strv_length(split); i++) { // For each split - cleaned_string = g_strjoin(repl, cleaned_string, split[i], NULL); // Join the strings with our replace string + guint split_len = g_strv_length(split); + + if (split_len == 1) { // Only one item + g_strfreev(split); + return g_strdup(str); // Just set to the string we were provided } - g_strfreev(split); - return cleaned_string; + return g_strdup(g_strjoinv(repl, split)); +} + +gboolean koto_utils_string_contains_substring( + gchar * s, + gchar * sub +) { + gchar ** separated_string = g_strsplit(s, sub, -1); // Split on our substring + gboolean contains = (g_strv_length(separated_string) > 1); + g_strfreev(separated_string); + return contains; +} + +GList * koto_utils_string_to_string_list( + gchar * s, + gchar * sep +) { + GList * list = NULL; + gchar ** separated_strings = g_strsplit(s, sep, -1); // Split on separator for the string + + for (guint i = 0; i < g_strv_length(separated_strings); i++) { // Iterate over each item + gchar * item = separated_strings[i]; + list = g_list_append(list, g_strdup(item)); + } + + g_strfreev(separated_strings); // Free our strings + return list; } gchar * koto_utils_unquote_string(gchar * s) { diff --git a/src/koto-utils.h b/src/koto-utils.h index e1317fb..cff0b46 100644 --- a/src/koto-utils.h +++ b/src/koto-utils.h @@ -34,6 +34,11 @@ gchar * koto_utils_gboolean_to_string(gboolean b); gchar * koto_utils_get_filename_without_extension(gchar * filename); +gchar * koto_utils_join_string_list( + GList * list, + gchar * sep +); + gboolean koto_utils_is_string_valid(gchar * str); void koto_utils_mkdir(gchar * path); @@ -49,6 +54,16 @@ gchar * koto_utils_replace_string_all( gchar * repl ); +gboolean koto_utils_string_contains_substring( + gchar * s, + gchar * sub +); + +GList * koto_utils_string_to_string_list( + gchar * s, + gchar * sep +); + gchar * koto_utils_unquote_string(gchar * s); G_END_DECLS diff --git a/src/main.c b/src/main.c index 3c65e12..e1cc3b6 100644 --- a/src/main.c +++ b/src/main.c @@ -22,6 +22,7 @@ #include "db/cartographer.h" #include "db/db.h" #include "db/loaders.h" +#include "indexer/track-helpers.h" #include "playback/engine.h" #include "playback/media-keys.h" #include "playback/mimes.h" @@ -88,6 +89,7 @@ int main ( gtk_init(); gst_init(&argc, &argv); + koto_track_helpers_init(); // Init our track helpers (primarily our genre replacement hashtable) koto_paths_setup(); // Set up our required paths supported_mimes_hash = g_hash_table_new(g_str_hash, g_str_equal);