Honestly, not going to bother summarizing this massive changeset. You are welcome to look it over in your own free time. Fixes #10. Fixes #11
484 lines
11 KiB
C
484 lines
11 KiB
C
/* 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 <glib-2.0/glib.h>
|
|
#include <sqlite3.h>
|
|
#include "../db/db.h"
|
|
#include "../db/cartographer.h"
|
|
#include "../koto-utils.h"
|
|
#include "structs.h"
|
|
#include "track-helpers.h"
|
|
|
|
extern KotoCartographer * koto_maps;
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_UUID,
|
|
PROP_ARTIST_NAME,
|
|
N_PROPERTIES
|
|
};
|
|
|
|
static GParamSpec * props[N_PROPERTIES] = {
|
|
NULL,
|
|
};
|
|
|
|
enum {
|
|
SIGNAL_ALBUM_ADDED,
|
|
SIGNAL_ALBUM_REMOVED,
|
|
SIGNAL_TRACK_ADDED,
|
|
SIGNAL_TRACK_REMOVED,
|
|
N_SIGNALS
|
|
};
|
|
|
|
static guint artist_signals[N_SIGNALS] = {
|
|
0
|
|
};
|
|
|
|
struct _KotoArtist {
|
|
GObject parent_instance;
|
|
gchar * uuid;
|
|
|
|
gboolean has_artist_art;
|
|
gchar * artist_name;
|
|
GList * albums;
|
|
GList * tracks;
|
|
GHashTable * paths;
|
|
KotoLibraryType type;
|
|
};
|
|
|
|
struct _KotoArtistClass {
|
|
GObjectClass parent_class;
|
|
|
|
void (* album_added) (
|
|
KotoArtist * artist,
|
|
KotoAlbum * album
|
|
);
|
|
void (* album_removed) (
|
|
KotoArtist * artist,
|
|
KotoAlbum * album
|
|
);
|
|
void (* track_added) (
|
|
KotoArtist * artist,
|
|
KotoTrack * track
|
|
);
|
|
void (* track_removed) (
|
|
KotoArtist * artist,
|
|
KotoTrack * track
|
|
);
|
|
};
|
|
|
|
G_DEFINE_TYPE(KotoArtist, koto_artist, G_TYPE_OBJECT);
|
|
|
|
static void koto_artist_get_property(
|
|
GObject * obj,
|
|
guint prop_id,
|
|
GValue * val,
|
|
GParamSpec * spec
|
|
);
|
|
|
|
static void koto_artist_set_property(
|
|
GObject * obj,
|
|
guint prop_id,
|
|
const GValue * val,
|
|
GParamSpec * spec
|
|
);
|
|
|
|
static void koto_artist_class_init(KotoArtistClass * c) {
|
|
GObjectClass * gobject_class;
|
|
|
|
gobject_class = G_OBJECT_CLASS(c);
|
|
gobject_class->set_property = koto_artist_set_property;
|
|
gobject_class->get_property = koto_artist_get_property;
|
|
|
|
artist_signals[SIGNAL_ALBUM_ADDED] = g_signal_new(
|
|
"album-added",
|
|
G_TYPE_FROM_CLASS(gobject_class),
|
|
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
|
G_STRUCT_OFFSET(KotoArtistClass, album_added),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
KOTO_TYPE_ALBUM
|
|
);
|
|
|
|
artist_signals[SIGNAL_ALBUM_REMOVED] = g_signal_new(
|
|
"album-removed",
|
|
G_TYPE_FROM_CLASS(gobject_class),
|
|
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
|
G_STRUCT_OFFSET(KotoArtistClass, album_removed),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
KOTO_TYPE_ALBUM
|
|
);
|
|
|
|
artist_signals[SIGNAL_TRACK_ADDED] = g_signal_new(
|
|
"track-added",
|
|
G_TYPE_FROM_CLASS(gobject_class),
|
|
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
|
G_STRUCT_OFFSET(KotoArtistClass, track_added),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
KOTO_TYPE_TRACK
|
|
);
|
|
|
|
artist_signals[SIGNAL_TRACK_REMOVED] = g_signal_new(
|
|
"track-removed",
|
|
G_TYPE_FROM_CLASS(gobject_class),
|
|
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
|
G_STRUCT_OFFSET(KotoArtistClass, track_removed),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
KOTO_TYPE_TRACK
|
|
);
|
|
|
|
props[PROP_UUID] = g_param_spec_string(
|
|
"uuid",
|
|
"UUID to Artist in database",
|
|
"UUID to Artist in database",
|
|
NULL,
|
|
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
|
|
);
|
|
|
|
props[PROP_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);
|
|
}
|
|
|
|
void koto_artist_commit(KotoArtist * self) {
|
|
if ((self->uuid == NULL) || strcmp(self->uuid, "")) { // UUID not set
|
|
self->uuid = g_strdup(g_uuid_string_random());
|
|
}
|
|
|
|
// TODO: Support multiple types instead of just local music artist
|
|
gchar * commit_op = g_strdup_printf(
|
|
"INSERT INTO artists(id , name, art_path)"
|
|
"VALUES ('%s', quote(\"%s\"), NULL)"
|
|
"ON CONFLICT(id) DO UPDATE SET name=excluded.name, art_path=excluded.art_path;",
|
|
self->uuid,
|
|
self->artist_name
|
|
);
|
|
|
|
new_transaction(commit_op, "Failed to write our artist to the database", FALSE);
|
|
|
|
GHashTableIter paths_iter;
|
|
g_hash_table_iter_init(&paths_iter, self->paths); // Create an iterator for our paths
|
|
gpointer lib_uuid_ptr, artist_rel_path_ptr;
|
|
while (g_hash_table_iter_next(&paths_iter, &lib_uuid_ptr, &artist_rel_path_ptr)) {
|
|
gchar * lib_uuid = lib_uuid_ptr;
|
|
gchar * artist_rel_path = artist_rel_path_ptr;
|
|
|
|
gchar * commit_op = g_strdup_printf(
|
|
"INSERT INTO libraries_artists(id, artist_id, path)"
|
|
"VALUES ('%s', '%s', quote(\"%s\"))"
|
|
"ON CONFLICT(id, artist_id) DO UPDATE SET path=excluded.path;",
|
|
lib_uuid,
|
|
self->uuid,
|
|
artist_rel_path
|
|
);
|
|
|
|
new_transaction(commit_op, "Failed to add this path for the artist", FALSE);
|
|
}
|
|
}
|
|
|
|
static void koto_artist_init(KotoArtist * self) {
|
|
self->albums = NULL; // Create a new GList
|
|
self->has_artist_art = FALSE;
|
|
self->paths = g_hash_table_new(g_str_hash, g_str_equal);
|
|
self->tracks = NULL;
|
|
self->type = KOTO_LIBRARY_TYPE_UNKNOWN;
|
|
}
|
|
|
|
static void koto_artist_get_property(
|
|
GObject * obj,
|
|
guint prop_id,
|
|
GValue * val,
|
|
GParamSpec * spec
|
|
) {
|
|
KotoArtist * self = KOTO_ARTIST(obj);
|
|
|
|
switch (prop_id) {
|
|
case PROP_UUID:
|
|
g_value_set_string(val, self->uuid);
|
|
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_artist_set_property(
|
|
GObject * obj,
|
|
guint prop_id,
|
|
const GValue * val,
|
|
GParamSpec * spec
|
|
) {
|
|
KotoArtist * self = KOTO_ARTIST(obj);
|
|
|
|
switch (prop_id) {
|
|
case PROP_UUID:
|
|
self->uuid = g_strdup(g_value_get_string(val));
|
|
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UUID]);
|
|
break;
|
|
case PROP_ARTIST_NAME:
|
|
koto_artist_set_artist_name(self, (gchar*) g_value_get_string(val));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void koto_artist_add_album(
|
|
KotoArtist * self,
|
|
KotoAlbum * album
|
|
) {
|
|
if (!KOTO_IS_ARTIST(self)) { // Not an artist
|
|
return;
|
|
}
|
|
|
|
if (!KOTO_IS_ALBUM(album)) { // Album provided is not an album
|
|
return;
|
|
}
|
|
|
|
gchar * album_uuid = koto_album_get_uuid(album);
|
|
|
|
if (g_list_index(self->albums, album_uuid) != -1) { // If we have already added the album
|
|
return;
|
|
}
|
|
|
|
self->albums = g_list_append(self->albums, album_uuid); // Push to our album's UUID to the end of the list
|
|
|
|
g_signal_emit(
|
|
self,
|
|
artist_signals[SIGNAL_ALBUM_ADDED],
|
|
0,
|
|
album
|
|
);
|
|
}
|
|
|
|
void koto_artist_add_track(
|
|
KotoArtist * self,
|
|
KotoTrack * track
|
|
) {
|
|
if (!KOTO_IS_ARTIST(self)) { // Not an artist
|
|
return;
|
|
}
|
|
|
|
if (!KOTO_IS_TRACK(track)) { // Not a track
|
|
return;
|
|
}
|
|
|
|
gchar * track_uuid = koto_track_get_uuid(track);
|
|
|
|
if (g_list_index(self->tracks, track_uuid) != -1) { // If we have already added the track
|
|
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);
|
|
|
|
g_signal_emit(
|
|
self,
|
|
artist_signals[SIGNAL_TRACK_ADDED],
|
|
0,
|
|
track
|
|
);
|
|
}
|
|
|
|
GList * koto_artist_get_albums(KotoArtist * self) {
|
|
if (!KOTO_IS_ARTIST(self)) { // Not an artist
|
|
return NULL;
|
|
}
|
|
|
|
return g_list_copy(self->albums);
|
|
}
|
|
|
|
KotoAlbum * koto_artist_get_album_by_name(
|
|
KotoArtist * self,
|
|
gchar * album_name
|
|
) {
|
|
if (!KOTO_IS_ARTIST(self)) { // Not an artist
|
|
return NULL;
|
|
}
|
|
|
|
KotoAlbum * album = NULL;
|
|
|
|
GList * cur_list_iter;
|
|
for (cur_list_iter = self->albums; cur_list_iter != NULL; cur_list_iter = cur_list_iter->next) { // Iterate through our albums by their UUIDs
|
|
KotoAlbum * album_for_uuid = koto_cartographer_get_album_by_uuid(koto_maps, cur_list_iter->data); // Get the album
|
|
|
|
if (!KOTO_IS_ALBUM(album_for_uuid)) { // Not an album
|
|
continue;
|
|
}
|
|
|
|
if (g_strcmp0(koto_album_get_name(album_for_uuid), album_name) == 0) { // These album names match
|
|
album = album_for_uuid;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return album;
|
|
}
|
|
|
|
gchar * koto_artist_get_name(KotoArtist * self) {
|
|
if (!KOTO_IS_ARTIST(self)) { // Not an artist
|
|
return g_strdup("");
|
|
}
|
|
|
|
return g_strdup(koto_utils_is_string_valid(self->artist_name) ? self->artist_name : ""); // Return artist name if set
|
|
}
|
|
|
|
GList * koto_artist_get_tracks(KotoArtist * self) {
|
|
return KOTO_IS_ARTIST(self) ? self->tracks : NULL;
|
|
}
|
|
|
|
KotoLibraryType koto_artist_get_lib_type(KotoArtist * self) {
|
|
return KOTO_IS_ARTIST(self) ? self->type : KOTO_LIBRARY_TYPE_UNKNOWN;
|
|
}
|
|
|
|
gchar * koto_artist_get_uuid(KotoArtist * self) {
|
|
return KOTO_IS_ARTIST(self) ? self->uuid : NULL;
|
|
}
|
|
|
|
void koto_artist_remove_album(
|
|
KotoArtist * self,
|
|
KotoAlbum * album
|
|
) {
|
|
if (!KOTO_IS_ARTIST(self)) { // Not an artist
|
|
return;
|
|
}
|
|
|
|
if (!KOTO_ALBUM(album)) { // No album defined
|
|
return;
|
|
}
|
|
|
|
self->albums = g_list_remove(self->albums, koto_album_get_uuid(album));
|
|
|
|
g_signal_emit(
|
|
self,
|
|
artist_signals[SIGNAL_ALBUM_REMOVED],
|
|
0,
|
|
album
|
|
);
|
|
}
|
|
|
|
void koto_artist_remove_track(
|
|
KotoArtist * self,
|
|
KotoTrack * track
|
|
) {
|
|
if (!KOTO_IS_ARTIST(self)) { // Not an artist
|
|
return;
|
|
}
|
|
|
|
if (!KOTO_IS_TRACK(track)) { // Not a track
|
|
return;
|
|
}
|
|
|
|
self->tracks = g_list_remove(self->tracks, koto_track_get_uuid(track));
|
|
|
|
g_signal_emit(
|
|
self,
|
|
artist_signals[SIGNAL_TRACK_ADDED],
|
|
0,
|
|
track
|
|
);
|
|
}
|
|
|
|
void koto_artist_set_artist_name(
|
|
KotoArtist * self,
|
|
gchar * artist_name
|
|
) {
|
|
if (!KOTO_IS_ARTIST(self)) { // Not an artist
|
|
return;
|
|
}
|
|
|
|
if (!koto_utils_is_string_valid(artist_name)) { // No artist name
|
|
return;
|
|
}
|
|
|
|
if (koto_utils_is_string_valid(self->artist_name)) { // 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]);
|
|
}
|
|
|
|
void koto_artist_set_path(
|
|
KotoArtist * self,
|
|
KotoLibrary * lib,
|
|
const gchar * fixed_path,
|
|
gboolean should_commit
|
|
) {
|
|
if (!KOTO_IS_ARTIST(self)) { // Not an artist
|
|
return;
|
|
}
|
|
|
|
gchar * path = g_strdup(fixed_path); // Duplicate our fixed_path
|
|
gchar * relative_path = koto_library_get_relative_path_to_file(lib, path); // Get the relative path to the file for the given library
|
|
|
|
gchar * library_uuid = koto_library_get_uuid(lib); // Get the library for this path
|
|
g_hash_table_replace(self->paths, library_uuid, relative_path); // Replace any existing value or add this one
|
|
|
|
if (should_commit) { // Should commit to the DB
|
|
koto_artist_commit(self); // Save the artist
|
|
}
|
|
|
|
self->type = koto_library_get_lib_type(lib); // Define our artist type as the type from the library
|
|
}
|
|
|
|
KotoArtist * koto_artist_new(gchar * artist_name) {
|
|
KotoArtist * artist = g_object_new(
|
|
KOTO_TYPE_ARTIST,
|
|
"uuid",
|
|
g_uuid_string_random(),
|
|
"name",
|
|
artist_name,
|
|
NULL
|
|
);
|
|
|
|
return artist;
|
|
}
|
|
|
|
KotoArtist * koto_artist_new_with_uuid(const gchar * uuid) {
|
|
return g_object_new(
|
|
KOTO_TYPE_ARTIST,
|
|
"uuid",
|
|
g_strdup(uuid),
|
|
NULL
|
|
);
|
|
}
|