diff --git a/src/indexer/album.c b/src/indexer/album.c new file mode 100644 index 0000000..589c7eb --- /dev/null +++ b/src/indexer/album.c @@ -0,0 +1,289 @@ +/* album.c + * + * 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. + */ + +#include +#include +#include +#include "album.h" +#include "file.h" +#include "koto-utils.h" + +struct _KotoIndexedAlbum { + GObject parent_instance; + gchar *path; + + gchar *name; + gchar *art_path; + GHashTable *files; + + gboolean has_album_art; +}; + +G_DEFINE_TYPE(KotoIndexedAlbum, koto_indexed_album, G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_PATH, + PROP_ALBUM_NAME, + PROP_ART_PATH, + N_PROPERTIES +}; + +static GParamSpec *props[N_PROPERTIES] = { NULL, }; + +static void koto_indexed_album_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec); +static void koto_indexed_album_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec); + +static void koto_indexed_album_class_init(KotoIndexedAlbumClass *c) { + GObjectClass *gobject_class; + gobject_class = G_OBJECT_CLASS(c); + gobject_class->set_property = koto_indexed_album_set_property; + gobject_class->get_property = koto_indexed_album_get_property; + + props[PROP_PATH] = g_param_spec_string( + "path", + "Path", + "Path to Album", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_ALBUM_NAME] = g_param_spec_string( + "name", + "Name", + "Name of Album", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_ART_PATH] = g_param_spec_string( + "art-path", + "Path to Artwork", + "Path to Artwork", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + g_object_class_install_properties(gobject_class, N_PROPERTIES, props); +} + +static void koto_indexed_album_init(KotoIndexedAlbum *self) { + self->has_album_art = FALSE; +} + +void koto_indexed_album_add_file(KotoIndexedAlbum *self, KotoIndexedFile *file) { + if (file == NULL) { // Not a file + return; + } + + if (self->files == NULL) { // No HashTable yet + self->files = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable + } + + gchar *file_name; + g_object_get(file, "parsed-name", &file_name, NULL); + + if (g_hash_table_contains(self->files, file_name)) { // If we have this file already + g_free(file_name); + return; + } + + g_hash_table_insert(self->files, file_name, file); // Add the file +} + + + +static void koto_indexed_album_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) { + KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj); + + switch (prop_id) { + case PROP_PATH: + g_value_set_string(val, self->path); + break; + case PROP_ALBUM_NAME: + g_value_set_string(val, self->name); + break; + case PROP_ART_PATH: + g_value_set_string(val, koto_indexed_album_get_album_art(self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); + break; + } +} + +gchar* koto_indexed_album_get_album_art(KotoIndexedAlbum *self) { + return g_strdup((self->has_album_art) ? self->art_path : ""); +} + +GList* koto_indexed_album_get_files(KotoIndexedAlbum *self) { + if (self->files == NULL) { // No HashTable yet + self->files = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable + } + + return g_hash_table_get_values(self->files); +} + +void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album_art) { + if (album_art == NULL) { // Not valid album art + return; + } + + if (self->art_path != NULL) { + g_free(self->art_path); + } + + self->art_path = g_strdup(album_art); + self->has_album_art = TRUE; +} + +void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedFile *file) { + if (file == NULL) { // Not a file + return; + } + + if (self->files == NULL) { // No HashTable yet + self->files = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable + } + + gchar *file_name; + g_object_get(file, "parsed-name", &file_name, NULL); + g_hash_table_remove(self->files, file_name); +} + +void koto_indexed_album_remove_file_by_name(KotoIndexedAlbum *self, const gchar *file_name) { + if (file_name == NULL) { + return; + } + + KotoIndexedFile *file = g_hash_table_lookup(self->files, file_name); // Get the files + koto_indexed_album_remove_file(self, file); +} + +void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *album_name) { + if (album_name == NULL) { // Not valid album name + return; + } + + if (self->name != NULL) { + g_free(self->name); + } + + self->name = g_strdup(album_name); +} + +static void koto_indexed_album_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec){ + KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj); + + switch (prop_id) { + case PROP_PATH: // Path to the album + koto_indexed_album_update_path(self, g_value_get_string(val)); + break; + case PROP_ALBUM_NAME: // Name of album + koto_indexed_album_set_album_name(self, g_value_get_string(val)); + break; + case PROP_ART_PATH: // Path to art + koto_indexed_album_set_album_art(self, g_value_get_string(val)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); + break; + } +} + +void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar* new_path) { + if (new_path == NULL) { + return; + } + + DIR *dir = opendir(new_path); // Attempt to open our directory + + if (dir == NULL) { + return; + } + + if (self->path != NULL) { + g_free(self->path); + } + + self->path = g_strdup(new_path); + + koto_indexed_album_set_album_name(self, g_path_get_basename(self->path)); // Update our album name based on the base name + + magic_t magic_cookie = magic_open(MAGIC_MIME); + + if (magic_cookie == NULL) { + return; + } + + if (magic_load(magic_cookie, NULL) != 0) { + magic_close(magic_cookie); + 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 + } + + if (entry->d_type != DT_REG) { // If this is not a regular file + 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); + } else if (g_str_has_prefix(mime_type, "audio/")) { // Is an audio file + KotoIndexedFile *file = koto_indexed_file_new(full_path); + + if (file != NULL) { // Is a file + koto_indexed_album_add_file(self, file); // Add our file + } + } + + g_free(full_path); + } + + magic_close(magic_cookie); +} + +KotoIndexedAlbum* koto_indexed_album_new(const gchar *path) { + return g_object_new(KOTO_TYPE_INDEXED_ALBUM, + "path", path, + NULL + ); +} diff --git a/src/indexer/album.h b/src/indexer/album.h index f9cc23b..3ff9001 100644 --- a/src/indexer/album.h +++ b/src/indexer/album.h @@ -17,17 +17,22 @@ #pragma once #include +#include "file.h" G_BEGIN_DECLS -#define KOTO_INDEXED_ALBUM_TYPE koto_indexed_album_get_type() +#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 add_song(KotoIndexedAlbum *self, KotoIndexedFile *file); -KotoIndexedFile** get_songs(KotoIndexedAlbum *self); -void remove_song(KotoIndexedAlbum *self, KotoIndexedFile *file); -void remove_song_by_name(KotoIndexedAlbum *self, gchar *file_name); +void koto_indexed_album_add_file(KotoIndexedAlbum *self, KotoIndexedFile *file); +gchar* koto_indexed_album_get_album_art(KotoIndexedAlbum *self); +GList* koto_indexed_album_get_files(KotoIndexedAlbum *self); +void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedFile *file); +void koto_indexed_album_remove_file_by_name(KotoIndexedAlbum *self, const gchar *file_name); +void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album_art); +void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *album_name); +void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar *path); G_END_DECLS diff --git a/src/indexer/artist.c b/src/indexer/artist.c new file mode 100644 index 0000000..6985a31 --- /dev/null +++ b/src/indexer/artist.c @@ -0,0 +1,196 @@ +/* artist.c + * + * 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. + */ + +#include +#include "album.h" +#include "artist.h" + +struct _KotoIndexedArtist { + GObject parent_instance; + gchar *path; + + gboolean has_artist_art; + gchar *artist_name; + GHashTable *albums; +}; + +G_DEFINE_TYPE(KotoIndexedArtist, koto_indexed_artist, G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_PATH, + PROP_ARTIST_NAME, + N_PROPERTIES +}; + +static GParamSpec *props[N_PROPERTIES] = { NULL, }; + +static void koto_indexed_artist_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec); +static void koto_indexed_artist_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec); + +static void koto_indexed_artist_class_init(KotoIndexedArtistClass *c) { + GObjectClass *gobject_class; + gobject_class = G_OBJECT_CLASS(c); + gobject_class->set_property = koto_indexed_artist_set_property; + gobject_class->get_property = koto_indexed_artist_get_property; + + props[PROP_PATH] = g_param_spec_string( + "path", + "Path", + "Path to Artist", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_ARTIST_NAME] = g_param_spec_string( + "name", + "Name", + "Name of Artist", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + g_object_class_install_properties(gobject_class, N_PROPERTIES, props); +} + +static void koto_indexed_artist_init(KotoIndexedArtist *self) { + self->has_artist_art = FALSE; + self->albums = NULL; // Set to null initially maybe +} + +static void koto_indexed_artist_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) { + KotoIndexedArtist *self = KOTO_INDEXED_ARTIST(obj); + + switch (prop_id) { + case PROP_PATH: + g_value_set_string(val, self->path); + break; + case PROP_ARTIST_NAME: + g_value_set_string(val, self->artist_name); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); + break; + } +} + +static void koto_indexed_artist_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec){ + KotoIndexedArtist *self = KOTO_INDEXED_ARTIST(obj); + + switch (prop_id) { + case PROP_PATH: + koto_indexed_artist_update_path(self, g_value_get_string(val)); + break; + case PROP_ARTIST_NAME: + koto_indexed_artist_set_artist_name(self, g_value_get_string(val)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); + break; + } +} + +void koto_indexed_artist_add_album(KotoIndexedArtist *self, KotoIndexedAlbum *album) { + if (album == NULL) { // No album really defined + return; + } + + if (self->albums == NULL) { // No HashTable yet + 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); + + if (album_name == NULL) { + g_free(album_name); + return; + } + + if (g_hash_table_contains(self->albums, album_name)) { // If we have this album already + g_free(album_name); + return; + } + + g_hash_table_insert(self->albums, album_name, album); // Add the album +} + +GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self) { + if (self->albums == NULL) { // No HashTable yet + self->albums = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable + } + + return g_hash_table_get_values(self->albums); +} + +void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album) { + if (album == NULL) { // No album defined + return; + } + + if (self->albums == NULL) { // No HashTable yet + 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); + + 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); +} + +void koto_indexed_artist_update_path(KotoIndexedArtist *self, const gchar *new_path) { + if (new_path == NULL) { // No path really + return; + } + + if (self->path != NULL) { // Already have a path set + g_free(self->path); // Free + } + + self->path = g_strdup(new_path); + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PATH]); +} + +void koto_indexed_artist_set_artist_name(KotoIndexedArtist *self, const gchar *artist_name) { + if (artist_name == NULL) { // No artist name + return; + } + + if (self->artist_name != NULL) { // Has artist name + g_free(self->artist_name); + } + + self->artist_name = g_strdup(artist_name); + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ARTIST_NAME]); +} + +KotoIndexedArtist* koto_indexed_artist_new(const gchar *path) { + return g_object_new(KOTO_TYPE_INDEXED_ARTIST, + "path", path, + "name", g_path_get_basename(path), + NULL + ); +} diff --git a/src/indexer/artist.h b/src/indexer/artist.h index e87fa2f..df88435 100644 --- a/src/indexer/artist.h +++ b/src/indexer/artist.h @@ -17,17 +17,22 @@ #pragma once #include +#include "album.h" G_BEGIN_DECLS -#define KOTO_INDEXED_ARTIST_TYPE koto_indexed_artist_get_type() +#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 add_album(KotoIndexedArtist *self, KotoIndexedAlbum *album); -KotoIndexedAlbum** get_albums(KotoIndexedArtist *self); -void remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album); -void remove_album_by_name(KotoIndexedArtist *self, gchar *album_name); +void koto_indexed_artist_add_album(KotoIndexedArtist *self, KotoIndexedAlbum *album); +guint koto_indexed_artist_find_album_with_name(gconstpointer *album_data, gconstpointer *album_name_data); +GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self); +void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album); +void koto_indexed_artist_remove_album_by_name(KotoIndexedArtist *self, gchar *album_name); +void koto_indexed_artist_set_artist_name(KotoIndexedArtist *self, const gchar *artist_name); +void koto_indexed_artist_update_path(KotoIndexedArtist *self, const gchar *new_path); +void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data); G_END_DECLS diff --git a/src/indexer/file-indexer.c b/src/indexer/file-indexer.c index 5a9b55b..7609485 100644 --- a/src/indexer/file-indexer.c +++ b/src/indexer/file-indexer.c @@ -17,27 +17,14 @@ #include #include +#include #include +#include +#include "album.h" +#include "artist.h" #include "file.h" #include "file-indexer.h" -struct KotoIndexedAlbum { - GObject parent_instance; - gboolean has_album_art; - gchar *album_name; - gchar *art_path; - gchar *path; - GHashTable *songs; -}; - -struct _KotoIndexedArtist { - GObject parent_instance; - gboolean has_artist_art; - gchar *artist_name; - GHashTable *albums; - gchar *path; -}; - struct _KotoIndexedLibrary { GObject parent_instance; gchar *path; @@ -73,12 +60,60 @@ static void koto_indexed_library_class_init(KotoIndexedLibraryClass *c) { ); g_object_class_install_properties(gobject_class, N_PROPERTIES, props); + taglib_id3v2_set_default_text_encoding(TagLib_ID3v2_UTF8); // Ensure our id3v2 text encoding is UTF-8 } static void koto_indexed_library_init(KotoIndexedLibrary *self) { self->music_artists = g_hash_table_new(g_str_hash, g_str_equal); } +void koto_indexed_library_add_artist(KotoIndexedLibrary *self, KotoIndexedArtist *artist) { + if (artist == NULL) { // No artist + return; + } + + if (self->music_artists == NULL) { // Not a HashTable + self->music_artists = g_hash_table_new(g_str_hash, g_str_equal); + } + + gchar *artist_name; + g_object_get(artist, "name", &artist_name, NULL); + + if (g_hash_table_contains(self->music_artists, artist_name)) { // Already have the artist + g_free(artist_name); + return; + } + + g_hash_table_insert(self->music_artists, artist_name, artist); // Add the artist +} + +KotoIndexedArtist* koto_indexed_library_get_artist(KotoIndexedLibrary *self, gchar *artist_name) { + if (artist_name == NULL) { + return NULL; + } + + if (self->music_artists == NULL) { // Not a HashTable + return NULL; + } + + return g_hash_table_lookup(self->music_artists, (KotoIndexedArtist*) artist_name); +} + +void koto_indexed_library_remove_artist(KotoIndexedLibrary *self, KotoIndexedArtist *artist) { + if (artist == NULL) { + return; + } + + if (self->music_artists == NULL) { // Not a HashTable + return; + } + + gchar *artist_name; + g_object_get(artist, "name", &artist_name, NULL); + + g_hash_table_remove(self->music_artists, artist_name); // Remove the artist +} + static void koto_indexed_library_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) { KotoIndexedLibrary *self = KOTO_INDEXED_LIBRARY(obj); @@ -98,7 +133,6 @@ 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)); - g_message("Set to %s", self->path); start_indexing(self); break; default: @@ -131,11 +165,14 @@ void start_indexing(KotoIndexedLibrary *self) { return; } - index_folder(self, self->path); + index_folder(self, self->path, 0); magic_close(self->magic_cookie); + g_hash_table_foreach(self->music_artists, output_artists, NULL); } -void index_folder(KotoIndexedLibrary *self, gchar *path) { +void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth) { + depth++; + DIR *dir = opendir(path); // Attempt to open our directory if (dir == NULL) { @@ -145,54 +182,88 @@ void index_folder(KotoIndexedLibrary *self, gchar *path) { struct dirent *entry; while ((entry = readdir(dir))) { - if (!g_str_has_prefix(entry->d_name, ".")) { // Not a reference to parent dir, self, or a hidden item - gchar *full_path = g_strdup_printf("%s%s%s", path, G_DIR_SEPARATOR_S, entry->d_name); - - if (entry->d_type == DT_DIR) { // Directory - index_folder(self, full_path); // Index this directory - } else if (entry->d_type == DT_REG) { // Regular file - index_file(self, full_path); // Index the file - } - - g_free(full_path); + if (g_str_has_prefix(entry->d_name, ".")) { // A reference to parent dir, self, or a hidden item + continue; } + + gchar *full_path = g_strdup_printf("%s%s%s", path, G_DIR_SEPARATOR_S, entry->d_name); + + if (entry->d_type == DT_DIR) { // Directory + if (depth == 1) { // If we are following FOLDER/ARTIST/ALBUM then this would be artist + KotoIndexedArtist *artist = koto_indexed_artist_new(full_path); // Attempt to get the artist + gchar *artist_name; + + g_object_get(artist, + "name", &artist_name, + NULL + ); + + koto_indexed_library_add_artist(self, artist); // Add the artist + index_folder(self, full_path, depth); // Index this directory + g_free(artist_name); + } else if (depth == 2) { // If we are following FOLDER/ARTIST/ALBUM then this would be album + gchar *artist_name = g_path_get_basename(path); // Get the last entry from our path which is probably the artist + KotoIndexedAlbum *album = koto_indexed_album_new(full_path); + + KotoIndexedArtist *artist = koto_indexed_library_get_artist(self, artist_name); // Get the artist + + if (artist == NULL) { + continue; + } + + koto_indexed_artist_add_album(artist, album); // Add the album + g_free(artist_name); + } + } + + g_free(full_path); } closedir(dir); // Close the directory } -void index_file(KotoIndexedLibrary *self, gchar *path) { - const char *mime_type = magic_file(self->magic_cookie, path); +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); - if (mime_type == NULL) { // Failed to get the mimetype - return; + g_debug("Artist: %s", artist_name); + GList *albums = koto_indexed_artist_get_albums(artist); // Get the albums for this artist + + if (albums != NULL) { + g_debug("Length of Albums: %d", g_list_length(albums)); } - gchar** mime_info = g_strsplit(mime_type, ";", 2); // Only care about our first item + GList *a; + for (a = albums; a != NULL; a = a->next) { + KotoIndexedAlbum *album = (KotoIndexedAlbum*) a->data; + gchar *artwork = koto_indexed_album_get_album_art(album); + gchar *album_name; + g_object_get(album, "name", &album_name, NULL); + g_debug("Album Art: %s", artwork); + g_debug("Album Name: %s", album_name); - if ( - g_str_has_prefix(mime_info[0], "audio/") || // Is audio - g_str_has_prefix(mime_info[0], "image/") // Is image - ) { - //g_message("File Name: %s", path); + GList *files = koto_indexed_album_get_files(album); // Get the files for the album + GList *f; + + for (f = files; f != NULL; f = f->next) { + KotoIndexedFile *file = (KotoIndexedFile*) f->data; + gchar *filepath; + gchar *parsed_name; + guint *pos; + + g_object_get(file, + "path", &filepath, + "parsed-name", &parsed_name, + "position", &pos, + NULL); + g_debug("File Path: %s", filepath); + g_debug("Parsed Name: %s", parsed_name); + g_debug("Position: %d", GPOINTER_TO_INT(pos)); + g_free(filepath); + g_free(parsed_name); + } } - - if (g_str_has_prefix(mime_info[0], "audio/")) { // Is an audio file - KotoIndexedFile *file = koto_indexed_file_new(path); - gchar *filepath; - gchar *parsed_name; - - g_object_get(file, - "path", &filepath, - "parsed-name", &parsed_name, - NULL); - - g_free(filepath); - g_free(parsed_name); - g_object_unref(file); - } - - g_strfreev(mime_info); // Free our mimeinfo } KotoIndexedLibrary* koto_indexed_library_new(const gchar *path) { diff --git a/src/indexer/file-indexer.h b/src/indexer/file-indexer.h index f5a5155..97b25cc 100644 --- a/src/indexer/file-indexer.h +++ b/src/indexer/file-indexer.h @@ -17,6 +17,7 @@ #pragma once #include +#include "artist.h" G_BEGIN_DECLS @@ -25,8 +26,10 @@ G_DECLARE_FINAL_TYPE(KotoIndexedLibrary, koto_indexed_library, KOTO, INDEXED_LIB 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); +void koto_indexed_library_remove_artist(KotoIndexedLibrary *self, KotoIndexedArtist *artist); void start_indexing(KotoIndexedLibrary *self); -void index_folder(KotoIndexedLibrary *self, gchar *path); -void index_file(KotoIndexedLibrary *self, gchar *path); +void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth); G_END_DECLS diff --git a/src/indexer/file.c b/src/indexer/file.c index c9052b5..043dd7a 100644 --- a/src/indexer/file.c +++ b/src/indexer/file.c @@ -16,17 +16,19 @@ */ #include +#include #include "file.h" +#include "koto-utils.h" struct _KotoIndexedFile { GObject parent_instance; gchar *path; + gchar *file_name; gchar *parsed_name; gchar *artist; gchar *album; - - guint position; + guint *position; gboolean acquired_metadata_from_id3; }; @@ -131,7 +133,7 @@ static void koto_indexed_file_get_property(GObject *obj, guint prop_id, GValue * g_value_set_string(val, self->parsed_name); break; case PROP_POSITION: - g_value_set_uint(val, self->position); + g_value_set_uint(val, GPOINTER_TO_UINT(self->position)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); @@ -144,8 +146,7 @@ static void koto_indexed_file_set_property(GObject *obj, guint prop_id, const GV switch (prop_id) { case PROP_PATH: - self->path = g_strdup(g_value_get_string(val)); // Update our path - koto_indexed_file_set_file_name(self, g_path_get_basename(self->path)); // Update our file name + 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)); @@ -160,7 +161,7 @@ static void koto_indexed_file_set_property(GObject *obj, guint prop_id, const GV koto_indexed_file_set_parsed_name(self, g_strdup(g_value_get_string(val))); break; case PROP_POSITION: - self->position = g_value_get_uint(val); + koto_indexed_file_set_position(self, g_value_get_uint(val)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); @@ -168,6 +169,65 @@ static void koto_indexed_file_set_property(GObject *obj, guint prop_id, const GV } } +void koto_indexed_file_parse_name(KotoIndexedFile *self) { + gchar *copied_file_name = g_strdelimit(g_strdup(self->file_name), "_", ' '); // Replace _ with whitespace for starters + + if (self->artist != NULL && strcmp(self->artist, "") != 0) { // If we have artist set + 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 + 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); + + if (g_strv_length(split) > 1) { // Has positional info at the beginning of the file + gchar *num = split[1]; + + g_free(file_without_ext); // Free the prior name + file_without_ext = g_strdup(split[2]); // Set to our second item which is the rest of the song name without the prefixed numbers + + if ((strcmp(num, "0") == 0) || (strcmp(num, "00") == 0)) { // Is exactly zero + koto_indexed_file_set_position(self, 0); // Set position to 0 + } else { // Either starts with 0 (like 09) or doesn't start with it at all + guint64 potential_pos = g_ascii_strtoull(num, NULL, 10); // Attempt to convert + + if (potential_pos != 0) { // Got a legitimate position + koto_indexed_file_set_position(self, potential_pos); + } + } + } + + g_strfreev(split); + + koto_indexed_file_set_parsed_name(self, file_without_ext); + g_free(file_without_ext); +} + void koto_indexed_file_set_file_name(KotoIndexedFile *self, gchar *new_file_name) { if (new_file_name == NULL) { return; @@ -206,64 +266,45 @@ void koto_indexed_file_set_parsed_name(KotoIndexedFile *self, gchar *new_parsed_ g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PARSED_NAME]); } -void koto_indexed_file_parse_name(KotoIndexedFile *self) { - gchar *copied_file_name = g_strdelimit(g_strdup(self->file_name), "_", ' '); // Replace _ with whitespace for starters - - if (self->artist != NULL && strcmp(self->artist, "") != 0) { // If we have artist set - 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); +void koto_indexed_file_set_position(KotoIndexedFile *self, guint pos) { + if (pos == 0) { // No position change really + return; } - 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 - g_strfreev(split); + self->position = GUINT_TO_POINTER(pos); + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_POSITION]); +} - 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); - } +void koto_indexed_file_update_metadata(KotoIndexedFile *self) { + TagLib_File *t_file = taglib_file_new(self->path); // Get a taglib file for this file - 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 **split = g_strsplit(copied_file_name, ".", -1); // Split every time we see . - g_free(copied_file_name); - guint len_of_extension_split = g_strv_length(split); - - if (len_of_extension_split == 2) { // Only have two elements - copied_file_name = g_strdup(split[0]); // Get the first element - } else { - gchar *new_parsed_name = ""; - for (guint i = 0; i < len_of_extension_split - 1; i++) { // Iterate over everything except the last item - if (g_strcmp0(new_parsed_name, "") == 0) { // Currently empty - new_parsed_name = g_strdup(split[i]); // Just duplicate this string - } 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 - g_free(tmp_copy); // Free our temporary copy - } + if ((t_file != NULL) && taglib_file_is_valid(t_file)) { // If we got the taglib file and it is valid + self->acquired_metadata_from_id3 = TRUE; + TagLib_Tag *tag = taglib_file_tag(t_file); // Get our tag + koto_indexed_file_set_parsed_name(self, taglib_tag_title(tag)); // Set the title of the file + 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 } - copied_file_name = g_strdup(new_parsed_name); - g_free(new_parsed_name); + taglib_tag_free_strings(); // Free strings + taglib_file_free(t_file); // Free the file +} + +void koto_indexed_file_update_path(KotoIndexedFile *self, const gchar *new_path) { + if (new_path == NULL) { + return; } - g_strfreev(split); + if (self->path != NULL) { // Already have a path + g_free(self->path); // Free it + } - koto_indexed_file_set_parsed_name(self, copied_file_name); - g_free(copied_file_name); + 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 + + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PATH]); } KotoIndexedFile* koto_indexed_file_new(const gchar *path) { diff --git a/src/indexer/file.h b/src/indexer/file.h index 669d9f4..4913a68 100644 --- a/src/indexer/file.h +++ b/src/indexer/file.h @@ -25,8 +25,11 @@ G_DECLARE_FINAL_TYPE(KotoIndexedFile, koto_indexed_file, KOTO, INDEXED_FILE, GOb KotoIndexedFile* koto_indexed_file_new(const gchar *path); +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_parsed_name(KotoIndexedFile *self, gchar *new_parsed_name); -void koto_indexed_file_parse_name(KotoIndexedFile *self); +void koto_indexed_file_set_position(KotoIndexedFile *self, guint pos); +void koto_indexed_file_update_metadata(KotoIndexedFile *self); +void koto_indexed_file_update_path(KotoIndexedFile *self, const gchar *new_path); G_END_DECLS diff --git a/src/koto-utils.c b/src/koto-utils.c new file mode 100644 index 0000000..b0239c7 --- /dev/null +++ b/src/koto-utils.c @@ -0,0 +1,48 @@ +/* koto-utils.c + * + * 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. + */ + +#include + +gchar* koto_utils_get_filename_without_extension(gchar *filename) { + gchar *trimmed_file_name = g_strdup(filename); + gchar **split = g_strsplit(filename, ".", -1); // Split every time we see . + g_free(trimmed_file_name); + guint len_of_extension_split = g_strv_length(split); + + if (len_of_extension_split == 2) { // Only have two elements + trimmed_file_name = g_strdup(split[0]); // Get the first element + } else { + gchar *new_parsed_name = ""; + for (guint i = 0; i < len_of_extension_split - 1; i++) { // Iterate over everything except the last item + if (g_strcmp0(new_parsed_name, "") == 0) { // Currently empty + new_parsed_name = g_strdup(split[i]); // Just duplicate this string + } 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 + g_free(tmp_copy); // Free our temporary copy + } + } + + trimmed_file_name = g_strdup(new_parsed_name); + g_free(new_parsed_name); + } + + gchar *stripped_file_name = g_strstrip(trimmed_file_name); // Strip leading and trailing whitespace + g_free(trimmed_file_name); + return stripped_file_name; +} diff --git a/src/koto-utils.h b/src/koto-utils.h new file mode 100644 index 0000000..5d25b30 --- /dev/null +++ b/src/koto-utils.h @@ -0,0 +1,25 @@ +/* koto-utils.h + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include + +G_BEGIN_DECLS + +gchar* koto_utils_get_filename_without_extension(gchar *filename); + +G_END_DECLS diff --git a/src/meson.build b/src/meson.build index b0b252b..a85ee0e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,4 +1,6 @@ koto_sources = [ + 'indexer/album.c', + 'indexer/artist.c', 'indexer/file.c', 'indexer/file-indexer.c', 'main.c', @@ -7,6 +9,7 @@ koto_sources = [ 'koto-headerbar.c', 'koto-nav.c', 'koto-playerbar.c', + 'koto-utils.c', 'koto-window.c', ] @@ -15,6 +18,7 @@ koto_deps = [ dependency('gio-2.0', version: '>= 2.66'), dependency('gtk+-3.0', version: '>= 3.24'), dependency('libmagic', version: '>=5.39'), + dependency('taglib_c', version: '>=1.11'), ] gnome = import('gnome')